-
Notifications
You must be signed in to change notification settings - Fork 480
Expand file tree
/
Copy pathendpoints.go
More file actions
247 lines (217 loc) · 7.49 KB
/
endpoints.go
File metadata and controls
247 lines (217 loc) · 7.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
// Package docs can be used to gather go-ipfs commands and automatically
// generate documentation or tests.
package docs
import (
"fmt"
"sort"
jsondoc "github.com/Stebalien/go-json-doc"
cid "github.com/ipfs/go-cid"
cmds "github.com/ipfs/go-ipfs-cmds"
config "github.com/ipfs/kubo"
corecmds "github.com/ipfs/kubo/core/commands"
peer "github.com/libp2p/go-libp2p/core/peer"
multiaddr "github.com/multiformats/go-multiaddr"
)
var JsondocGlossary = jsondoc.NewGlossary().
WithSchema(new(cid.Cid), jsondoc.Object{"/": "<cid-string>"}).
WithName(new(multiaddr.Multiaddr), "multiaddr-string").
WithName(new(peer.ID), "peer-id").
WithSchema(new(peer.AddrInfo),
jsondoc.Object{"ID": "peer-id", "Addrs": []string{"<multiaddr-string>"}})
var ignoreOptsPerEndpoint = map[string]map[string]struct{}{
"/api/v0/add": {
cmds.RecLong: struct{}{},
cmds.DerefLong: struct{}{},
cmds.StdinName: struct{}{},
cmds.Hidden: struct{}{},
cmds.Ignore: struct{}{},
cmds.IgnoreRules: struct{}{},
},
}
// A map of single endpoints to be skipped (subcommands are processed though).
var IgnoreEndpoints = map[string]bool{}
// How much to indent when generating the response schemas
const IndentLevel = 4
// Failsafe when traversing objects containing objects of the same type
const MaxIndent = 20
// Endpoint defines an IPFS RPC API endpoint.
type Endpoint struct {
Name string
Status cmds.Status
Arguments []*Argument
Options []*Argument
Description string
HTTPDescription string // additional HTTP-specific notes, shown after Description
Response string
ResponseContentType string
Group string
}
// Argument defines an IPFS RPC API endpoint argument.
type Argument struct {
Endpoint string
Name string
Description string
Type string
Required bool
Default string
}
type sorter []*Endpoint
func (a sorter) Len() int { return len(a) }
func (a sorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a sorter) Less(i, j int) bool { return a[i].Name < a[j].Name }
const APIPrefix = "/api/v0"
// AllEndpoints gathers all the endpoints from go-ipfs.
func AllEndpoints() []*Endpoint {
return Endpoints(APIPrefix, corecmds.Root)
}
func InStatus(endpoints []*Endpoint, status cmds.Status) []*Endpoint {
var results []*Endpoint
for _, endpoint := range endpoints {
if endpoint.Status == status {
results = append(results, endpoint)
}
}
return results
}
func IPFSVersion() string {
return config.CurrentVersionNumber
}
// ignoredGlobalOptions lists root-level flags that should not appear in the
// HTTP RPC API reference. This includes:
// - CLI-only flags (--repo-dir, --config-file, --api, --debug, --help)
// that refer to local paths or control CLI behavior
// - --encoding: the HTTP handler defaults to JSON and this is already
// documented in the "Flags" intro section of the reference
// - --stream-channels: parsed by the HTTP handler but has no effect;
// HTTP response streaming is determined by the response type, not this flag
// - --upgrade-cidv0-in-output: deprecated, --cid-base with a non-base58btc
// value now automatically upgrades CIDv0 to CIDv1
var ignoredGlobalOptions = map[string]struct{}{
corecmds.RepoDirOption: {},
corecmds.ConfigFileOption: {},
corecmds.ConfigOption: {},
corecmds.DebugOption: {},
corecmds.LocalOption: {}, // deprecated alias for --offline
corecmds.ApiOption: {},
"help": {}, // cmds.OptLongHelp
"h": {}, // cmds.OptShortHelp
cmds.EncLong: {}, // --encoding: HTTP defaults to JSON, documented in intro
cmds.ChanOpt: {}, // --stream-channels: no-op over HTTP
"upgrade-cidv0-in-output": {}, // deprecated: --cid-base auto-upgrades for non-base58btc
}
// GlobalOptions extracts the options defined on corecmds.Root that are
// relevant to the HTTP RPC API. The Root command itself has Run == nil so
// Endpoints() skips it, but its Options slice contains global flags
// (--offline, --timeout, --cid-base, etc.) that can be passed as query
// parameters to any endpoint. Flags listed in ignoredGlobalOptions are
// filtered out.
//
// The --api-auth flag is a special case: it controls RPC authentication
// but is sent as an HTTP Authorization header, not a query parameter.
// It is returned separately so the formatter can document it differently.
func GlobalOptions() (queryOpts []*Argument, authOpt *Argument) {
for _, opt := range corecmds.Root.Options {
name := opt.Names()[0]
if _, skip := ignoredGlobalOptions[name]; skip {
continue
}
def := fmt.Sprint(opt.Default())
if def == "<nil>" {
def = ""
}
arg := &Argument{
Name: name,
Type: opt.Type().String(),
Description: opt.Description(),
Default: def,
}
// api-auth is sent as an HTTP header, not a query parameter
if name == corecmds.ApiAuthOption {
authOpt = arg
continue
}
queryOpts = append(queryOpts, arg)
}
return queryOpts, authOpt
}
// Endpoints receives a name and a go-ipfs command and returns the endpoints it
// defines] (sorted). It does this by recursively gathering endpoints defined by
// subcommands. Thus, calling it with the core command Root generates all
// the endpoints.
func Endpoints(name string, cmd *cmds.Command) (endpoints []*Endpoint) {
var arguments []*Argument
var options []*Argument
ignore := cmd.Run == nil || IgnoreEndpoints[name] || cmd.NoRemote
if !ignore { // Extract arguments, options...
for _, arg := range cmd.Arguments {
argType := "string"
if arg.Type == cmds.ArgFile {
argType = "file"
}
arguments = append(arguments, &Argument{
Endpoint: name,
Name: arg.Name,
Type: argType,
Required: arg.Required,
Description: arg.Description,
})
}
for _, opt := range cmd.Options {
if ignoreOpts, ok := ignoreOptsPerEndpoint[name]; ok {
if _, ok := ignoreOpts[opt.Names()[0]]; ok {
// skip this option for this endpoint.
continue
}
}
def := fmt.Sprint(opt.Default())
if def == "<nil>" {
def = ""
}
options = append(options, &Argument{
Name: opt.Names()[0],
Type: opt.Type().String(),
Description: opt.Description(),
Default: def,
})
}
// Extract HTTP-specific documentation from Helptext.HTTP if present
var contentType, httpDescription string
if cmd.Helptext.HTTP != nil {
contentType = cmd.Helptext.HTTP.ResponseContentType
httpDescription = cmd.Helptext.HTTP.Description
}
res := buildResponse(cmd.Type, contentType)
endpoints = []*Endpoint{
{
Name: name,
Status: cmd.Status,
Description: cmd.Helptext.Tagline,
HTTPDescription: httpDescription,
Arguments: arguments,
Options: options,
Response: res,
ResponseContentType: contentType,
},
}
}
for n, cmd := range cmd.Subcommands {
endpoints = append(endpoints,
Endpoints(fmt.Sprintf("%s/%s", name, n), cmd)...)
}
sort.Sort(sorter(endpoints))
return endpoints
}
func buildResponse(res any, contentType string) string {
// Commands with a nil type return text or binary content.
if res == nil {
if contentType != "" {
return "This endpoint returns data in the format indicated by the Content-Type header."
}
return "This endpoint returns the same output as the CLI command, or the CLI with --enc=json."
}
desc, err := JsondocGlossary.Describe(res)
if err != nil {
panic(err)
}
return desc
}