@redocly/cli 0.0.0-snapshot.1787132334 → 0.0.0-snapshot.1787213166
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/chunks/3DFTNVIZ.js +875 -0
- package/lib/chunks/3GFYSU6P.js +7 -0
- package/lib/chunks/3J7TYMNY.js +12 -0
- package/lib/chunks/3XBPOSEL.js +9 -0
- package/lib/chunks/437JJBFI.js +9 -0
- package/lib/chunks/43GCSHRC.js +2669 -0
- package/lib/chunks/5CNLBXMA.js +7 -0
- package/lib/chunks/{INO3K4ZT.js → 5UA5LBQR.js} +1 -1
- package/lib/chunks/633P7WGD.js +7 -0
- package/lib/chunks/66SRVT5P.js +7 -0
- package/lib/chunks/6XZCMSUQ.js +7 -0
- package/lib/chunks/A66YYMVS.js +519 -0
- package/lib/chunks/ATNTAOVB.js +21 -0
- package/lib/chunks/BDG6IUCF.js +12 -0
- package/lib/chunks/BKWLBNAY.js +8 -0
- package/lib/chunks/CYTKNF4B.js +32 -0
- package/lib/chunks/D63AL5JQ.js +7 -0
- package/lib/chunks/DSV3ACCR.js +9 -0
- package/lib/chunks/DXLDMRME.js +7 -0
- package/lib/chunks/FLFI42AS.js +41 -0
- package/lib/chunks/{GAJNF7OK.js → G7Q7L6SS.js} +1 -1
- package/lib/chunks/GFUSM635.js +7 -0
- package/lib/chunks/GRNUYN6X.js +7 -0
- package/lib/chunks/{X6Q5KQDP.js → GTSJ3BAP.js} +1 -1
- package/lib/chunks/{A7WPX7IN.js → HX2FFDOL.js} +1 -1
- package/lib/chunks/{JWE6N3HA.js → IFGW4ORS.js} +1 -1
- package/lib/chunks/{L2KDP4QO.js → IFQUUR3G.js} +1 -1
- package/lib/chunks/IHICMXLQ.js +7 -0
- package/lib/chunks/IWSGIYUF.js +7 -0
- package/lib/chunks/J5UU3PWO.js +7 -0
- package/lib/chunks/JAEMYDV4.js +18 -0
- package/lib/chunks/{BCMHACSX.js → JIXLIIZV.js} +1 -1
- package/lib/chunks/JKEX4O5F.js +7 -0
- package/lib/chunks/{62EHE3FD.js → JNERJ3VR.js} +2 -2
- package/lib/chunks/{N264HAKF.js → JSYUI5YX.js} +1 -1
- package/lib/chunks/JWWD4Q3V.js +176 -0
- package/lib/chunks/K4VSUIRV.js +7 -0
- package/lib/chunks/L5QMCITI.js +460 -0
- package/lib/chunks/L6CYFTBX.js +7 -0
- package/lib/chunks/M3JDOVCW.js +911 -0
- package/lib/chunks/{5VPVDCTR.js → NBYGLUQF.js} +1 -1
- package/lib/chunks/{FOZ2GXNX.js → NY3X26BD.js} +1 -1
- package/lib/chunks/OSMFPTOV.js +7 -0
- package/lib/chunks/QOHNT5HA.js +7 -0
- package/lib/chunks/R7X55KCC.js +7 -0
- package/lib/chunks/{LSZDC6IW.js → S4I2R55X.js} +1 -1
- package/lib/chunks/{43QLIHPO.js → SKPLCW2A.js} +1 -1
- package/lib/chunks/T6AYNJRF.js +184 -0
- package/lib/chunks/VYAPZWZF.js +13 -0
- package/lib/chunks/{BBUY2DN4.js → WXUTF626.js} +1 -1
- package/lib/chunks/XICBZCC4.js +48 -0
- package/lib/chunks/XOCXUWKN.js +71 -0
- package/lib/chunks/XSPNMYMZ.js +15 -0
- package/lib/chunks/{JTBULWP6.js → YBSUKQHU.js} +1 -1
- package/lib/chunks/YSMUBJCG.js +7 -0
- package/lib/chunks/{VEENOHE7.js → YUFGUVRF.js} +1 -1
- package/lib/chunks/ZTS6JG4V.js +7 -0
- package/lib/eject-assets/AGENTS.md +131 -0
- package/lib/eject-assets/generators/cli.mjs +796 -0
- package/lib/eject-assets/generators/go.mjs +866 -0
- package/lib/eject-assets/generators/mock.mjs +689 -0
- package/lib/eject-assets/generators/php.mjs +840 -0
- package/lib/eject-assets/generators/python.mjs +823 -0
- package/lib/eject-assets/generators/swr.mjs +193 -0
- package/lib/eject-assets/generators/tanstack-query.mjs +626 -0
- package/lib/eject-assets/generators/transformers.mjs +403 -0
- package/lib/eject-assets/generators/typescript.mjs +1821 -0
- package/lib/eject-assets/generators/zod.mjs +520 -0
- package/lib/eject-assets/skills/cli-generator/SKILL.md +112 -0
- package/lib/eject-assets/skills/client-generators/SKILL.md +136 -0
- package/lib/eject-assets/skills/go-generator/SKILL.md +94 -0
- package/lib/eject-assets/skills/mock-generator/SKILL.md +45 -0
- package/lib/eject-assets/skills/php-generator/SKILL.md +110 -0
- package/lib/eject-assets/skills/python-generator/SKILL.md +108 -0
- package/lib/eject-assets/skills/swr-generator/SKILL.md +44 -0
- package/lib/eject-assets/skills/tanstack-query-generator/SKILL.md +48 -0
- package/lib/eject-assets/skills/transformers-generator/SKILL.md +43 -0
- package/lib/eject-assets/skills/typescript-generator/SKILL.md +80 -0
- package/lib/eject-assets/skills/zod-generator/SKILL.md +49 -0
- package/lib/index.js +265 -232
- package/package.json +1 -1
- package/lib/chunks/7RS7GK5N.js +0 -8
- package/lib/chunks/N5E3KSKU.js +0 -2542
- package/lib/chunks/SJZFGAFW.js +0 -187
|
@@ -0,0 +1,875 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from 'node:module';
|
|
2
|
+
import { fileURLToPath as __fileURLToPath } from 'node:url';
|
|
3
|
+
import { dirname as __pathDirname } from 'node:path';
|
|
4
|
+
const require = __createRequire(import.meta.url);
|
|
5
|
+
var __filename = __fileURLToPath(import.meta.url);
|
|
6
|
+
var __dirname = __pathDirname(__filename);
|
|
7
|
+
import{a as C,b as I,c as U}from"./VYAPZWZF.js";import{a as P}from"./WZXA5XPB.js";import{a as D,b as T,c as $,d as E,e as q,f as x,g as L,h as A,i as J,j as B,k as H,l as M}from"./L6CYFTBX.js";var j=`// Package client \u2014 the embedded runtime for generated Go SDKs. Hand-authored
|
|
8
|
+
// once and stitched into every generated client (see
|
|
9
|
+
// scripts/generate-runtime-sources.mjs), semantically in lockstep with the
|
|
10
|
+
// TypeScript runtime: auth OR-alternatives, a retry loop with Retry-After and
|
|
11
|
+
// full-jitter backoff, per-attempt timeouts, idempotency keys, and middleware
|
|
12
|
+
// hooks. Standard library only \u2014 a generated Go SDK has zero dependencies.
|
|
13
|
+
package client
|
|
14
|
+
|
|
15
|
+
import (
|
|
16
|
+
"bytes"
|
|
17
|
+
"context"
|
|
18
|
+
"encoding/base64"
|
|
19
|
+
"encoding/json"
|
|
20
|
+
"errors"
|
|
21
|
+
"fmt"
|
|
22
|
+
"io"
|
|
23
|
+
"math/rand"
|
|
24
|
+
"mime/multipart"
|
|
25
|
+
"net/http"
|
|
26
|
+
"net/url"
|
|
27
|
+
"strconv"
|
|
28
|
+
"strings"
|
|
29
|
+
"time"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
// APIError is returned for a non-2xx response, carrying the decoded error body.
|
|
33
|
+
type APIError struct {
|
|
34
|
+
URL string
|
|
35
|
+
Status int
|
|
36
|
+
StatusText string
|
|
37
|
+
Body any
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func (e *APIError) Error() string {
|
|
41
|
+
return fmt.Sprintf("request failed with status %d", e.Status)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// TimeoutError is returned when a request attempt exceeds the configured
|
|
45
|
+
// timeout \u2014 carrying the context a log line needs.
|
|
46
|
+
type TimeoutError struct {
|
|
47
|
+
OperationID string
|
|
48
|
+
Timeout time.Duration
|
|
49
|
+
Attempt int
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func (e *TimeoutError) Error() string {
|
|
53
|
+
return fmt.Sprintf("request %q timed out after %s (attempt %d)", e.OperationID, e.Timeout, e.Attempt)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// SecuritySpec mirrors the descriptor table's security entries.
|
|
57
|
+
type SecuritySpec struct {
|
|
58
|
+
Scheme string
|
|
59
|
+
Kind string // "bearer" | "basic" | "apiKey"
|
|
60
|
+
Name string // header/query/cookie name for apiKey
|
|
61
|
+
In string // "header" | "query" | "cookie"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Auth holds the client credentials; zero value = anonymous.
|
|
65
|
+
type Auth struct {
|
|
66
|
+
Bearer func() string
|
|
67
|
+
Basic *BasicAuth
|
|
68
|
+
APIKey map[string]func() string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
type BasicAuth struct {
|
|
72
|
+
Username string
|
|
73
|
+
Password string
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// RetryConfig mirrors the TypeScript runtime's retry policy knobs.
|
|
77
|
+
type RetryConfig struct {
|
|
78
|
+
Retries int
|
|
79
|
+
RetryDelay time.Duration // base; default 1s
|
|
80
|
+
RetryStrategy string // "" (exponential) | "fixed"
|
|
81
|
+
NoJitter bool
|
|
82
|
+
// RetryOn fully replaces the default predicate when set.
|
|
83
|
+
RetryOn func(attempt int, resp *http.Response, err error) bool
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Middleware hooks run around every request (OnRequest before serialization order
|
|
87
|
+
// is N/A in Go \u2014 bodies are values; OnResponse runs in reverse registration order).
|
|
88
|
+
type Middleware struct {
|
|
89
|
+
OnRequest func(req *http.Request)
|
|
90
|
+
OnResponse func(resp *http.Response)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Date is an RFC 3339 full-date \u2014 a calendar date with no time component. Fields
|
|
94
|
+
// typed \`date\` under \`dateType: Date\` use it because encoding/json speaks only
|
|
95
|
+
// RFC 3339 date-time for time.Time, which a bare "2006-01-02" fails to satisfy.
|
|
96
|
+
type Date struct {
|
|
97
|
+
time.Time
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const dateLayout = "2006-01-02"
|
|
101
|
+
|
|
102
|
+
// UnmarshalJSON parses a "2006-01-02" string; an empty string leaves the zero value.
|
|
103
|
+
func (d *Date) UnmarshalJSON(data []byte) error {
|
|
104
|
+
var raw string
|
|
105
|
+
if err := json.Unmarshal(data, &raw); err != nil {
|
|
106
|
+
return err
|
|
107
|
+
}
|
|
108
|
+
if raw == "" {
|
|
109
|
+
return nil
|
|
110
|
+
}
|
|
111
|
+
parsed, err := time.Parse(dateLayout, raw)
|
|
112
|
+
if err != nil {
|
|
113
|
+
return err
|
|
114
|
+
}
|
|
115
|
+
d.Time = parsed
|
|
116
|
+
return nil
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// MarshalJSON writes the date back without a time component.
|
|
120
|
+
func (d Date) MarshalJSON() ([]byte, error) {
|
|
121
|
+
return json.Marshal(d.Format(dateLayout))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Config is the per-client configuration shared by every operation method.
|
|
125
|
+
type Config struct {
|
|
126
|
+
ServerURL string
|
|
127
|
+
HTTPClient *http.Client
|
|
128
|
+
Headers map[string]string
|
|
129
|
+
Timeout time.Duration
|
|
130
|
+
Retry RetryConfig
|
|
131
|
+
Middleware []Middleware
|
|
132
|
+
IdempotencyKey func() string
|
|
133
|
+
Auth Auth
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
func resolveToken(provider func() string) string {
|
|
137
|
+
if provider == nil {
|
|
138
|
+
return ""
|
|
139
|
+
}
|
|
140
|
+
return provider()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
func schemeConfigured(spec SecuritySpec, auth Auth) bool {
|
|
144
|
+
switch spec.Kind {
|
|
145
|
+
case "apiKey":
|
|
146
|
+
_, ok := auth.APIKey[spec.Scheme]
|
|
147
|
+
return ok
|
|
148
|
+
case "bearer":
|
|
149
|
+
return auth.Bearer != nil
|
|
150
|
+
default:
|
|
151
|
+
return auth.Basic != nil
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// resolveAuth applies the first fully-configured OR-alternative; when none is,
|
|
156
|
+
// the first alternative's configured schemes are still sent (the server rejects
|
|
157
|
+
// the request \u2014 same behavior as the TypeScript runtime).
|
|
158
|
+
func resolveAuth(security [][]SecuritySpec, auth Auth) (map[string]string, url.Values) {
|
|
159
|
+
headers := map[string]string{}
|
|
160
|
+
query := url.Values{}
|
|
161
|
+
if len(security) == 0 {
|
|
162
|
+
return headers, query
|
|
163
|
+
}
|
|
164
|
+
alternative := security[0]
|
|
165
|
+
for _, candidate := range security {
|
|
166
|
+
all := true
|
|
167
|
+
for _, spec := range candidate {
|
|
168
|
+
if !schemeConfigured(spec, auth) {
|
|
169
|
+
all = false
|
|
170
|
+
break
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if all {
|
|
174
|
+
alternative = candidate
|
|
175
|
+
break
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
var cookies []string
|
|
179
|
+
for _, spec := range alternative {
|
|
180
|
+
switch spec.Kind {
|
|
181
|
+
case "apiKey":
|
|
182
|
+
provider, ok := auth.APIKey[spec.Scheme]
|
|
183
|
+
if !ok {
|
|
184
|
+
continue
|
|
185
|
+
}
|
|
186
|
+
value := resolveToken(provider)
|
|
187
|
+
switch spec.In {
|
|
188
|
+
case "query":
|
|
189
|
+
query.Set(spec.Name, value)
|
|
190
|
+
case "cookie":
|
|
191
|
+
cookies = append(cookies, spec.Name+"="+url.QueryEscape(value))
|
|
192
|
+
default:
|
|
193
|
+
headers[spec.Name] = value
|
|
194
|
+
}
|
|
195
|
+
case "bearer":
|
|
196
|
+
if auth.Bearer != nil {
|
|
197
|
+
headers["Authorization"] = "Bearer " + resolveToken(auth.Bearer)
|
|
198
|
+
}
|
|
199
|
+
default:
|
|
200
|
+
if auth.Basic != nil {
|
|
201
|
+
token := base64.StdEncoding.EncodeToString([]byte(auth.Basic.Username + ":" + auth.Basic.Password))
|
|
202
|
+
headers["Authorization"] = "Basic " + token
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if len(cookies) > 0 {
|
|
207
|
+
headers["Cookie"] = strings.Join(cookies, "; ")
|
|
208
|
+
}
|
|
209
|
+
return headers, query
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// buildURL substitutes {param} path placeholders with percent-encoded values.
|
|
213
|
+
func buildURL(serverURL, path string, pathParams map[string]string) string {
|
|
214
|
+
filled := path
|
|
215
|
+
for name, value := range pathParams {
|
|
216
|
+
filled = strings.ReplaceAll(filled, "{"+name+"}", url.PathEscape(value))
|
|
217
|
+
}
|
|
218
|
+
return strings.TrimRight(serverURL, "/") + filled
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
var transientStatus = map[int]bool{408: true, 429: true, 500: true, 502: true, 503: true, 504: true}
|
|
222
|
+
|
|
223
|
+
func defaultRetryOn(method string, headers map[string]string, resp *http.Response, err error) bool {
|
|
224
|
+
safe := false
|
|
225
|
+
switch strings.ToUpper(method) {
|
|
226
|
+
case "GET", "HEAD", "PUT", "DELETE", "OPTIONS":
|
|
227
|
+
safe = true
|
|
228
|
+
}
|
|
229
|
+
if _, ok := headers["Idempotency-Key"]; ok {
|
|
230
|
+
safe = true
|
|
231
|
+
}
|
|
232
|
+
if !safe {
|
|
233
|
+
return false
|
|
234
|
+
}
|
|
235
|
+
if err != nil {
|
|
236
|
+
return true
|
|
237
|
+
}
|
|
238
|
+
return resp != nil && transientStatus[resp.StatusCode]
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
func retryDelay(retry RetryConfig, attempt int, retryAfter string) time.Duration {
|
|
242
|
+
if retryAfter != "" {
|
|
243
|
+
if seconds, err := strconv.ParseFloat(retryAfter, 64); err == nil {
|
|
244
|
+
return time.Duration(seconds * float64(time.Second))
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
base := retry.RetryDelay
|
|
248
|
+
if base == 0 {
|
|
249
|
+
base = time.Second
|
|
250
|
+
}
|
|
251
|
+
raw := base
|
|
252
|
+
if retry.RetryStrategy != "fixed" {
|
|
253
|
+
raw = base * time.Duration(1<<(attempt-1))
|
|
254
|
+
}
|
|
255
|
+
if retry.NoJitter {
|
|
256
|
+
return raw
|
|
257
|
+
}
|
|
258
|
+
return time.Duration(rand.Int63n(int64(raw) + 1))
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
type requestSpec struct {
|
|
262
|
+
OperationID string
|
|
263
|
+
Method string
|
|
264
|
+
URL string
|
|
265
|
+
Headers map[string]string
|
|
266
|
+
Query url.Values
|
|
267
|
+
Body io.Reader
|
|
268
|
+
ContentType string
|
|
269
|
+
Timeout time.Duration
|
|
270
|
+
Retry *RetryConfig
|
|
271
|
+
IdempotencyKey string
|
|
272
|
+
// bodyBytes is retained so retries can replay the body.
|
|
273
|
+
bodyBytes []byte
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// send is the request core: header merge, idempotency keys, the retry loop
|
|
277
|
+
// (fresh timeout budget per attempt), and the middleware onion.
|
|
278
|
+
func send(ctx context.Context, config *Config, spec requestSpec) (*http.Response, error) {
|
|
279
|
+
retry := config.Retry
|
|
280
|
+
if spec.Retry != nil {
|
|
281
|
+
retry = *spec.Retry
|
|
282
|
+
}
|
|
283
|
+
timeout := config.Timeout
|
|
284
|
+
if spec.Timeout != 0 {
|
|
285
|
+
timeout = spec.Timeout
|
|
286
|
+
}
|
|
287
|
+
headers := map[string]string{}
|
|
288
|
+
for key, value := range config.Headers {
|
|
289
|
+
headers[key] = value
|
|
290
|
+
}
|
|
291
|
+
for key, value := range spec.Headers {
|
|
292
|
+
headers[key] = value
|
|
293
|
+
}
|
|
294
|
+
method := strings.ToUpper(spec.Method)
|
|
295
|
+
if (method == "POST" || method == "PATCH") && headers["Idempotency-Key"] == "" {
|
|
296
|
+
if spec.IdempotencyKey != "" {
|
|
297
|
+
headers["Idempotency-Key"] = spec.IdempotencyKey
|
|
298
|
+
} else if config.IdempotencyKey != nil {
|
|
299
|
+
headers["Idempotency-Key"] = config.IdempotencyKey()
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
httpClient := config.HTTPClient
|
|
303
|
+
if httpClient == nil {
|
|
304
|
+
httpClient = http.DefaultClient
|
|
305
|
+
}
|
|
306
|
+
if spec.Body != nil {
|
|
307
|
+
payload, err := io.ReadAll(spec.Body)
|
|
308
|
+
if err != nil {
|
|
309
|
+
return nil, err
|
|
310
|
+
}
|
|
311
|
+
spec.bodyBytes = payload
|
|
312
|
+
}
|
|
313
|
+
fullURL := spec.URL
|
|
314
|
+
if len(spec.Query) > 0 {
|
|
315
|
+
separator := "?"
|
|
316
|
+
if strings.Contains(fullURL, "?") {
|
|
317
|
+
separator = "&"
|
|
318
|
+
}
|
|
319
|
+
fullURL += separator + spec.Query.Encode()
|
|
320
|
+
}
|
|
321
|
+
maxAttempts := 1 + retry.Retries
|
|
322
|
+
for attempt := 1; ; attempt++ {
|
|
323
|
+
attemptCtx := ctx
|
|
324
|
+
var cancel context.CancelFunc
|
|
325
|
+
if timeout > 0 {
|
|
326
|
+
attemptCtx, cancel = context.WithTimeout(ctx, timeout)
|
|
327
|
+
}
|
|
328
|
+
var bodyReader io.Reader
|
|
329
|
+
if spec.bodyBytes != nil {
|
|
330
|
+
bodyReader = bytes.NewReader(spec.bodyBytes)
|
|
331
|
+
}
|
|
332
|
+
req, err := http.NewRequestWithContext(attemptCtx, method, fullURL, bodyReader)
|
|
333
|
+
if err != nil {
|
|
334
|
+
if cancel != nil {
|
|
335
|
+
cancel()
|
|
336
|
+
}
|
|
337
|
+
return nil, err
|
|
338
|
+
}
|
|
339
|
+
for key, value := range headers {
|
|
340
|
+
req.Header.Set(key, value)
|
|
341
|
+
}
|
|
342
|
+
if spec.ContentType != "" && spec.bodyBytes != nil {
|
|
343
|
+
req.Header.Set("Content-Type", spec.ContentType)
|
|
344
|
+
}
|
|
345
|
+
for _, mw := range config.Middleware {
|
|
346
|
+
if mw.OnRequest != nil {
|
|
347
|
+
mw.OnRequest(req)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
resp, err := httpClient.Do(req)
|
|
351
|
+
shouldRetry := retry.RetryOn
|
|
352
|
+
retryable := false
|
|
353
|
+
if shouldRetry != nil {
|
|
354
|
+
retryable = shouldRetry(attempt, resp, err)
|
|
355
|
+
} else {
|
|
356
|
+
retryable = defaultRetryOn(method, headers, resp, err)
|
|
357
|
+
}
|
|
358
|
+
if err != nil {
|
|
359
|
+
if cancel != nil {
|
|
360
|
+
cancel()
|
|
361
|
+
}
|
|
362
|
+
timedOut := errors.Is(err, context.DeadlineExceeded) && ctx.Err() == nil
|
|
363
|
+
if attempt < maxAttempts && retryable {
|
|
364
|
+
time.Sleep(retryDelay(retry, attempt, ""))
|
|
365
|
+
continue
|
|
366
|
+
}
|
|
367
|
+
if timedOut {
|
|
368
|
+
return nil, &TimeoutError{OperationID: spec.OperationID, Timeout: timeout, Attempt: attempt}
|
|
369
|
+
}
|
|
370
|
+
return nil, err
|
|
371
|
+
}
|
|
372
|
+
for i := len(config.Middleware) - 1; i >= 0; i-- {
|
|
373
|
+
if config.Middleware[i].OnResponse != nil {
|
|
374
|
+
config.Middleware[i].OnResponse(resp)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if resp.StatusCode >= 400 && attempt < maxAttempts && retryable {
|
|
378
|
+
after := resp.Header.Get("Retry-After")
|
|
379
|
+
io.Copy(io.Discard, resp.Body)
|
|
380
|
+
resp.Body.Close()
|
|
381
|
+
if cancel != nil {
|
|
382
|
+
cancel()
|
|
383
|
+
}
|
|
384
|
+
time.Sleep(retryDelay(retry, attempt, after))
|
|
385
|
+
continue
|
|
386
|
+
}
|
|
387
|
+
// The response body outlives this call; tie the attempt context's lifetime to it.
|
|
388
|
+
if cancel != nil {
|
|
389
|
+
resp.Body = &cancelOnClose{ReadCloser: resp.Body, cancel: cancel}
|
|
390
|
+
}
|
|
391
|
+
return resp, nil
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
type cancelOnClose struct {
|
|
396
|
+
io.ReadCloser
|
|
397
|
+
cancel context.CancelFunc
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
func (c *cancelOnClose) Close() error {
|
|
401
|
+
c.cancel()
|
|
402
|
+
return c.ReadCloser.Close()
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// decodeJSON decodes a response body into target; a nil target drains and closes.
|
|
406
|
+
func decodeJSON(resp *http.Response, target any) error {
|
|
407
|
+
defer resp.Body.Close()
|
|
408
|
+
if target == nil {
|
|
409
|
+
_, err := io.Copy(io.Discard, resp.Body)
|
|
410
|
+
return err
|
|
411
|
+
}
|
|
412
|
+
return json.NewDecoder(resp.Body).Decode(target)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// headerString returns the named response header, or nil when absent.
|
|
416
|
+
func headerString(header http.Header, name string) *string {
|
|
417
|
+
value := header.Get(name)
|
|
418
|
+
if value == "" {
|
|
419
|
+
return nil
|
|
420
|
+
}
|
|
421
|
+
return &value
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// headerInt64 parses the named header as an integer; nil when absent or unparsable.
|
|
425
|
+
func headerInt64(header http.Header, name string) *int64 {
|
|
426
|
+
raw := strings.TrimSpace(header.Get(name))
|
|
427
|
+
if raw == "" {
|
|
428
|
+
return nil
|
|
429
|
+
}
|
|
430
|
+
value, err := strconv.ParseInt(raw, 10, 64)
|
|
431
|
+
if err != nil {
|
|
432
|
+
return nil
|
|
433
|
+
}
|
|
434
|
+
return &value
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// headerFloat64 parses the named header as a number; nil when absent or unparsable.
|
|
438
|
+
func headerFloat64(header http.Header, name string) *float64 {
|
|
439
|
+
raw := strings.TrimSpace(header.Get(name))
|
|
440
|
+
if raw == "" {
|
|
441
|
+
return nil
|
|
442
|
+
}
|
|
443
|
+
value, err := strconv.ParseFloat(raw, 64)
|
|
444
|
+
if err != nil {
|
|
445
|
+
return nil
|
|
446
|
+
}
|
|
447
|
+
return &value
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// headerBool parses a \`true\`/\`false\` header; nil when absent or anything else.
|
|
451
|
+
func headerBool(header http.Header, name string) *bool {
|
|
452
|
+
raw := strings.ToLower(strings.TrimSpace(header.Get(name)))
|
|
453
|
+
if raw != "true" && raw != "false" {
|
|
454
|
+
return nil
|
|
455
|
+
}
|
|
456
|
+
value := raw == "true"
|
|
457
|
+
return &value
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// apiErrorFrom builds the structured error for a non-2xx response.
|
|
461
|
+
func apiErrorFrom(resp *http.Response, requestURL string) error {
|
|
462
|
+
defer resp.Body.Close()
|
|
463
|
+
var body any
|
|
464
|
+
data, _ := io.ReadAll(resp.Body)
|
|
465
|
+
if len(data) > 0 {
|
|
466
|
+
if err := json.Unmarshal(data, &body); err != nil {
|
|
467
|
+
body = string(data)
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return &APIError{URL: requestURL, Status: resp.StatusCode, StatusText: resp.Status, Body: body}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// \u2500\u2500\u2500 Pagination \u2500\u2500\u2500
|
|
474
|
+
|
|
475
|
+
// PaginationSpec mirrors the descriptor table's pagination entries.
|
|
476
|
+
type PaginationSpec struct {
|
|
477
|
+
Style string
|
|
478
|
+
Param string
|
|
479
|
+
NextCursor string
|
|
480
|
+
HasMore string
|
|
481
|
+
LimitParam string
|
|
482
|
+
Items string
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// resolvePointer walks an RFC 6901 JSON pointer over decoded JSON; nil on any miss.
|
|
486
|
+
func resolvePointer(data any, pointer string) any {
|
|
487
|
+
if pointer == "" {
|
|
488
|
+
return data
|
|
489
|
+
}
|
|
490
|
+
if !strings.HasPrefix(pointer, "/") {
|
|
491
|
+
return nil
|
|
492
|
+
}
|
|
493
|
+
current := data
|
|
494
|
+
for _, token := range strings.Split(pointer[1:], "/") {
|
|
495
|
+
key := strings.ReplaceAll(strings.ReplaceAll(token, "~1", "/"), "~0", "~")
|
|
496
|
+
switch typed := current.(type) {
|
|
497
|
+
case map[string]any:
|
|
498
|
+
current = typed[key]
|
|
499
|
+
case []any:
|
|
500
|
+
index, err := strconv.Atoi(key)
|
|
501
|
+
if err != nil || index < 0 || index >= len(typed) {
|
|
502
|
+
return nil
|
|
503
|
+
}
|
|
504
|
+
current = typed[index]
|
|
505
|
+
default:
|
|
506
|
+
return nil
|
|
507
|
+
}
|
|
508
|
+
if current == nil {
|
|
509
|
+
return nil
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return current
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// reencode converts decoded JSON (maps/slices) into a typed value via a JSON round-trip.
|
|
516
|
+
func reencode(raw any, target any) error {
|
|
517
|
+
data, err := json.Marshal(raw)
|
|
518
|
+
if err != nil {
|
|
519
|
+
return err
|
|
520
|
+
}
|
|
521
|
+
return json.Unmarshal(data, target)
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
type pageCall func(params url.Values) (any, *http.Response, error)
|
|
525
|
+
|
|
526
|
+
// iterPages yields raw page JSON per the pagination spec \u2014 the same stop
|
|
527
|
+
// conditions and infinite-loop guards as the TypeScript runtime. The returned
|
|
528
|
+
// function is a range-over-func iterator (Go 1.23+) and plainly callable before that.
|
|
529
|
+
func iterPages(call pageCall, spec PaginationSpec, base url.Values) func(yield func(any, error) bool) {
|
|
530
|
+
return func(yield func(any, error) bool) {
|
|
531
|
+
switch spec.Style {
|
|
532
|
+
case "cursor":
|
|
533
|
+
var cursor any
|
|
534
|
+
if values, ok := base[spec.Param]; ok && len(values) > 0 {
|
|
535
|
+
cursor = values[0]
|
|
536
|
+
}
|
|
537
|
+
for {
|
|
538
|
+
params := cloneValues(base)
|
|
539
|
+
if cursor != nil {
|
|
540
|
+
params.Set(spec.Param, fmt.Sprint(cursor))
|
|
541
|
+
}
|
|
542
|
+
page, _, err := call(params)
|
|
543
|
+
if err != nil {
|
|
544
|
+
yield(nil, err)
|
|
545
|
+
return
|
|
546
|
+
}
|
|
547
|
+
if !yield(page, nil) {
|
|
548
|
+
return
|
|
549
|
+
}
|
|
550
|
+
if spec.HasMore != "" {
|
|
551
|
+
if more, ok := resolvePointer(page, spec.HasMore).(bool); ok && !more {
|
|
552
|
+
return
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
next := resolvePointer(page, spec.NextCursor)
|
|
556
|
+
if next == nil || next == "" {
|
|
557
|
+
return
|
|
558
|
+
}
|
|
559
|
+
switch next.(type) {
|
|
560
|
+
case string, float64:
|
|
561
|
+
default:
|
|
562
|
+
yield(nil, fmt.Errorf("pagination cursor at %s is not a string or number", spec.NextCursor))
|
|
563
|
+
return
|
|
564
|
+
}
|
|
565
|
+
if cursor != nil && fmt.Sprint(next) == fmt.Sprint(cursor) {
|
|
566
|
+
yield(nil, errors.New("pagination did not advance: the operation returned the same cursor twice"))
|
|
567
|
+
return
|
|
568
|
+
}
|
|
569
|
+
cursor = next
|
|
570
|
+
}
|
|
571
|
+
case "link":
|
|
572
|
+
params := cloneValues(base)
|
|
573
|
+
previous := ""
|
|
574
|
+
for {
|
|
575
|
+
page, resp, err := call(params)
|
|
576
|
+
if err != nil {
|
|
577
|
+
yield(nil, err)
|
|
578
|
+
return
|
|
579
|
+
}
|
|
580
|
+
if !yield(page, nil) {
|
|
581
|
+
return
|
|
582
|
+
}
|
|
583
|
+
target := linkNext(resp.Header.Get("Link"))
|
|
584
|
+
if target == "" {
|
|
585
|
+
return
|
|
586
|
+
}
|
|
587
|
+
pageURL := ""
|
|
588
|
+
if resp.Request != nil && resp.Request.URL != nil {
|
|
589
|
+
pageURL = resp.Request.URL.String()
|
|
590
|
+
}
|
|
591
|
+
baseURL, err := url.Parse(pageURL)
|
|
592
|
+
if err != nil || pageURL == "" {
|
|
593
|
+
baseURL, _ = url.Parse("http://relative.invalid")
|
|
594
|
+
}
|
|
595
|
+
targetURL, err := baseURL.Parse(target)
|
|
596
|
+
if err != nil {
|
|
597
|
+
yield(nil, err)
|
|
598
|
+
return
|
|
599
|
+
}
|
|
600
|
+
next := targetURL.String()
|
|
601
|
+
if next == previous || next == pageURL {
|
|
602
|
+
yield(nil, errors.New(\`pagination did not advance: the Link rel="next" target repeats\`))
|
|
603
|
+
return
|
|
604
|
+
}
|
|
605
|
+
previous = next
|
|
606
|
+
params = cloneValues(base)
|
|
607
|
+
for key, values := range targetURL.Query() {
|
|
608
|
+
for _, value := range values {
|
|
609
|
+
params.Add(key, value)
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
default: // offset / page
|
|
614
|
+
position := 0
|
|
615
|
+
if spec.Style == "page" {
|
|
616
|
+
position = 1
|
|
617
|
+
}
|
|
618
|
+
if values, ok := base[spec.Param]; ok && len(values) > 0 && values[0] != "" {
|
|
619
|
+
if parsed, err := strconv.Atoi(values[0]); err == nil {
|
|
620
|
+
position = parsed
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
previousItems := ""
|
|
624
|
+
for {
|
|
625
|
+
params := cloneValues(base)
|
|
626
|
+
params.Set(spec.Param, strconv.Itoa(position))
|
|
627
|
+
page, _, err := call(params)
|
|
628
|
+
if err != nil {
|
|
629
|
+
yield(nil, err)
|
|
630
|
+
return
|
|
631
|
+
}
|
|
632
|
+
items, _ := resolvePointer(page, spec.Items).([]any)
|
|
633
|
+
serialized := ""
|
|
634
|
+
if items != nil {
|
|
635
|
+
serialized = fmt.Sprint(items)
|
|
636
|
+
if serialized == previousItems {
|
|
637
|
+
yield(nil, errors.New("pagination did not advance: the operation returned the same page twice"))
|
|
638
|
+
return
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if !yield(page, nil) {
|
|
642
|
+
return
|
|
643
|
+
}
|
|
644
|
+
if len(items) == 0 {
|
|
645
|
+
return
|
|
646
|
+
}
|
|
647
|
+
previousItems = serialized
|
|
648
|
+
if spec.Style == "page" {
|
|
649
|
+
position++
|
|
650
|
+
} else {
|
|
651
|
+
position += len(items)
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
func cloneValues(values url.Values) url.Values {
|
|
659
|
+
out := url.Values{}
|
|
660
|
+
for key, entries := range values {
|
|
661
|
+
for _, entry := range entries {
|
|
662
|
+
out.Add(key, entry)
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
return out
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
func linkNext(header string) string {
|
|
669
|
+
if header == "" {
|
|
670
|
+
return ""
|
|
671
|
+
}
|
|
672
|
+
for _, entry := range strings.Split(header, ",") {
|
|
673
|
+
parts := strings.Split(entry, ";")
|
|
674
|
+
if len(parts) < 2 {
|
|
675
|
+
continue
|
|
676
|
+
}
|
|
677
|
+
target := strings.TrimSpace(parts[0])
|
|
678
|
+
if !strings.HasPrefix(target, "<") || !strings.HasSuffix(target, ">") {
|
|
679
|
+
continue
|
|
680
|
+
}
|
|
681
|
+
for _, param := range parts[1:] {
|
|
682
|
+
trimmed := strings.TrimSpace(param)
|
|
683
|
+
if strings.HasPrefix(trimmed, "rel=") {
|
|
684
|
+
rel := strings.Trim(strings.TrimPrefix(trimmed, "rel="), \`"\`)
|
|
685
|
+
for _, kind := range strings.Fields(rel) {
|
|
686
|
+
if kind == "next" {
|
|
687
|
+
return strings.Trim(target, "<>")
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
return ""
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// \u2500\u2500\u2500 Server-Sent Events \u2500\u2500\u2500
|
|
697
|
+
|
|
698
|
+
// ServerSentEvent is one decoded event; Data is the raw text (or parsed JSON
|
|
699
|
+
// for operations that declare a JSON event stream).
|
|
700
|
+
type ServerSentEvent struct {
|
|
701
|
+
Event string
|
|
702
|
+
Data any
|
|
703
|
+
ID string
|
|
704
|
+
Retry int
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
func parseSSEFrame(raw string, jsonData bool) (ServerSentEvent, bool, error) {
|
|
708
|
+
event := ServerSentEvent{Retry: -1}
|
|
709
|
+
sawField := false
|
|
710
|
+
var dataLines []string
|
|
711
|
+
normalized := strings.ReplaceAll(strings.ReplaceAll(raw, "\\r\\n", "\\n"), "\\r", "\\n")
|
|
712
|
+
for _, line := range strings.Split(normalized, "\\n") {
|
|
713
|
+
if line == "" || strings.HasPrefix(line, ":") {
|
|
714
|
+
continue
|
|
715
|
+
}
|
|
716
|
+
field, value, _ := strings.Cut(line, ":")
|
|
717
|
+
value = strings.TrimPrefix(value, " ")
|
|
718
|
+
sawField = true
|
|
719
|
+
switch field {
|
|
720
|
+
case "event":
|
|
721
|
+
event.Event = value
|
|
722
|
+
case "data":
|
|
723
|
+
dataLines = append(dataLines, value)
|
|
724
|
+
case "id":
|
|
725
|
+
event.ID = value
|
|
726
|
+
case "retry":
|
|
727
|
+
if parsed, err := strconv.Atoi(value); err == nil && parsed >= 0 && value != "" {
|
|
728
|
+
event.Retry = parsed
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
if !sawField {
|
|
733
|
+
return event, false, nil
|
|
734
|
+
}
|
|
735
|
+
text := strings.Join(dataLines, "\\n")
|
|
736
|
+
event.Data = text
|
|
737
|
+
if jsonData && text != "" {
|
|
738
|
+
var parsed any
|
|
739
|
+
if err := json.Unmarshal([]byte(text), &parsed); err != nil {
|
|
740
|
+
return event, false, err
|
|
741
|
+
}
|
|
742
|
+
event.Data = parsed
|
|
743
|
+
}
|
|
744
|
+
return event, true, nil
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// iterSSE streams events, reconnecting on dropped connections with Last-Event-ID
|
|
748
|
+
// (a fresh open call = fresh auth); a 4xx/5xx or a bad JSON payload is definitive.
|
|
749
|
+
func iterSSE(open func(extraHeaders map[string]string) (*http.Response, error), jsonData bool) func(yield func(ServerSentEvent, error) bool) {
|
|
750
|
+
return func(yield func(ServerSentEvent, error) bool) {
|
|
751
|
+
lastEventID := ""
|
|
752
|
+
serverRetry := -1
|
|
753
|
+
failures := 0
|
|
754
|
+
for {
|
|
755
|
+
headers := map[string]string{"Accept": "text/event-stream"}
|
|
756
|
+
if lastEventID != "" {
|
|
757
|
+
headers["Last-Event-ID"] = lastEventID
|
|
758
|
+
}
|
|
759
|
+
resp, err := open(headers)
|
|
760
|
+
if err == nil && resp.StatusCode >= 400 {
|
|
761
|
+
yield(ServerSentEvent{}, apiErrorFrom(resp, ""))
|
|
762
|
+
return
|
|
763
|
+
}
|
|
764
|
+
if err == nil {
|
|
765
|
+
failures = 0
|
|
766
|
+
buffer := ""
|
|
767
|
+
chunk := make([]byte, 4096)
|
|
768
|
+
clean := false
|
|
769
|
+
for {
|
|
770
|
+
n, readErr := resp.Body.Read(chunk)
|
|
771
|
+
buffer += string(chunk[:n])
|
|
772
|
+
for {
|
|
773
|
+
frame, rest, found := strings.Cut(buffer, "\\n\\n")
|
|
774
|
+
if !found {
|
|
775
|
+
break
|
|
776
|
+
}
|
|
777
|
+
buffer = rest
|
|
778
|
+
event, ok, parseErr := parseSSEFrame(frame, jsonData)
|
|
779
|
+
if parseErr != nil {
|
|
780
|
+
resp.Body.Close()
|
|
781
|
+
yield(ServerSentEvent{}, parseErr)
|
|
782
|
+
return
|
|
783
|
+
}
|
|
784
|
+
if ok {
|
|
785
|
+
if event.ID != "" {
|
|
786
|
+
lastEventID = event.ID
|
|
787
|
+
}
|
|
788
|
+
if event.Retry >= 0 {
|
|
789
|
+
serverRetry = event.Retry
|
|
790
|
+
}
|
|
791
|
+
if !yield(event, nil) {
|
|
792
|
+
resp.Body.Close()
|
|
793
|
+
return
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
if readErr == io.EOF {
|
|
798
|
+
clean = true
|
|
799
|
+
break
|
|
800
|
+
}
|
|
801
|
+
if readErr != nil {
|
|
802
|
+
break
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
resp.Body.Close()
|
|
806
|
+
if clean {
|
|
807
|
+
if strings.TrimSpace(buffer) != "" {
|
|
808
|
+
if event, ok, parseErr := parseSSEFrame(buffer, jsonData); parseErr == nil && ok {
|
|
809
|
+
yield(event, nil)
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
failures++
|
|
816
|
+
base := time.Second
|
|
817
|
+
if serverRetry >= 0 {
|
|
818
|
+
base = time.Duration(serverRetry) * time.Millisecond
|
|
819
|
+
}
|
|
820
|
+
delay := base * time.Duration(1<<(failures-1))
|
|
821
|
+
if delay > 30*time.Second {
|
|
822
|
+
delay = 30 * time.Second
|
|
823
|
+
}
|
|
824
|
+
time.Sleep(time.Duration(rand.Int63n(int64(delay) + 1)))
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// \u2500\u2500\u2500 Multipart \u2500\u2500\u2500
|
|
830
|
+
|
|
831
|
+
// toMultipart splits a typed body into a multipart/form-data payload: []byte
|
|
832
|
+
// values upload as file parts, everything else as form fields (nested values
|
|
833
|
+
// JSON-encoded) \u2014 mirroring the TypeScript runtime's FormData serialization.
|
|
834
|
+
func toMultipart(body any) (string, io.Reader, error) {
|
|
835
|
+
var wire map[string]any
|
|
836
|
+
if err := reencode(body, &wire); err != nil {
|
|
837
|
+
return "", nil, err
|
|
838
|
+
}
|
|
839
|
+
buffer := &bytes.Buffer{}
|
|
840
|
+
writer := multipart.NewWriter(buffer)
|
|
841
|
+
for key, value := range wire {
|
|
842
|
+
switch typed := value.(type) {
|
|
843
|
+
case string:
|
|
844
|
+
if err := writer.WriteField(key, typed); err != nil {
|
|
845
|
+
return "", nil, err
|
|
846
|
+
}
|
|
847
|
+
case float64, bool:
|
|
848
|
+
if err := writer.WriteField(key, fmt.Sprint(typed)); err != nil {
|
|
849
|
+
return "", nil, err
|
|
850
|
+
}
|
|
851
|
+
default:
|
|
852
|
+
encoded, err := json.Marshal(typed)
|
|
853
|
+
if err != nil {
|
|
854
|
+
return "", nil, err
|
|
855
|
+
}
|
|
856
|
+
if err := writer.WriteField(key, string(encoded)); err != nil {
|
|
857
|
+
return "", nil, err
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
if err := writer.Close(); err != nil {
|
|
862
|
+
return "", nil, err
|
|
863
|
+
}
|
|
864
|
+
return writer.FormDataContentType(), buffer, nil
|
|
865
|
+
}
|
|
866
|
+
`;var S=T.go;function Z(t){if(t===void 0)return"client";if(!/^[a-z_][a-z0-9_]*$/.test(t)||S.has(t))throw new P(`goPackage "${t}" is not a valid Go package name: use lowercase letters, digits, and underscores, don't start with a digit, and avoid Go keywords.`);return t}function p(t){let r=$(t,{style:"pascal",reserved:S});return r.startsWith("_")?`N${r.slice(1)}`:r}function h(t,r="string"){if(L(t)){let e=h(A(t),r);return e.startsWith("*")||e==="any"?e:`*${e}`}switch(t.kind){case"scalar":if(r==="Date"&&t.scalar==="string"){if(t.metadata?.format==="date-time")return"time.Time";if(t.metadata?.format==="date")return"Date"}return{string:"string",integer:"int64",number:"float64",boolean:"bool"}[t.scalar];case"array":return`[]${h(t.items,r)}`;case"record":return`map[string]${h(t.value,r)}`;case"ref":return p(t.name);case"literal":return typeof t.value=="string"?"string":typeof t.value=="boolean"?"bool":"float64";case"enum":return{string:"string",integer:"int64",number:"float64",boolean:"bool"}[t.scalar];case"omit":return p(t.base);case"union":case"null":case"object":case"intersection":case"unknown":return"any"}}function R(t,r,e){let n=B(e);if(n.length===0)return;t.line(`// ${r} \u2014 ${n[0]}`);let a=!1;for(let i of n.slice(1)){if(i===""){a||t.line("//"),a=!0;continue}t.line(`// ${i}`),a=!1}}function Y(t,r,e,n,a){R(t,p(r),a),t.block(`type ${p(r)} struct {`,()=>{for(let i of e){let l=p(i.name),s=h(i.schema,n),c=`\`json:"${i.name}"\``;i.required||(!s.startsWith("*")&&!s.startsWith("[]")&&!s.startsWith("map[")&&s!=="any"&&(s=`*${s}`),c=`\`json:"${i.name},omitempty"\``),t.line(`${l} ${s} ${c}`)}},"}"),t.blank()}function K(t){return`${t.replace(/\n{3,}/g,`
|
|
867
|
+
|
|
868
|
+
`).trimEnd()}
|
|
869
|
+
`}function X(t,r="string"){let e=new C(" ");e.line("package client"),e.blank(),t.schemas.some(({schema:i})=>x(i,t)!==void 0)&&(e.line('import "encoding/json"'),e.blank());let a=tt(t,r);return r==="Date"&&a.includes("time.Time")&&(e.line('import "time"'),e.blank()),e.line(a),K(W(e.toString()))}function tt(t,r){let e=new C(" ");for(let{name:n,schema:a}of t.schemas){let i=J(a);if(i!==void 0){let s=i.scalar==="string"?"string":"int64";R(e,p(n),a.description),e.line(`type ${p(n)} ${s}`),e.blank(),e.block("const (",()=>{i.values.forEach(c=>{let o=p(n)+D.pascal(String(c));e.line(`${o} ${p(n)} = ${JSON.stringify(c)}`)})},")"),e.blank();continue}if(a.kind==="object"||a.kind==="intersection"){let s=q(a,t);if(s!==void 0){Y(e,n,s.properties,r,s.description??a.description);continue}}let l=x(a,t);if(l!==void 0){let s=p(n),c=l.cases.map(o=>`${o.value} -> ${p(o.schemaName)}`).join(", ");e.line(`// ${s} is a discriminated union ("${l.property}"): ${c}.`),e.line(`type ${s} = any`),e.blank(),e.line(`// Unmarshal${s} decodes into the member selected by "${l.property}".`),e.block(`func Unmarshal${s}(data []byte) (${s}, error) {`,()=>{e.block("var probe struct {",()=>{e.line(`Discriminant string \`json:"${l.property}"\``)},"}"),e.block("if err := json.Unmarshal(data, &probe); err != nil {",()=>{e.line("return nil, err")},"}"),e.line("switch probe.Discriminant {");for(let o of l.cases)e.block(`case ${JSON.stringify(o.value)}:`,()=>{e.line(`var value ${p(o.schemaName)}`),e.line("err := json.Unmarshal(data, &value)"),e.line("return value, err")});e.line("}"),e.line("var fallback any"),e.line("err := json.Unmarshal(data, &fallback)"),e.line("return fallback, err")},"}"),e.blank();continue}R(e,p(n),a.description),e.line(`type ${p(n)} = ${h(a,r)}`),e.blank()}return e.toString()}function G(t){return t.successResponses.find(r=>r.contentType.toLowerCase().includes("json"))?.schema}function et(t,r){let e=t.security.map(n=>n.flatMap(a=>{let i=r.securitySchemes.find(c=>c.key===a);if(i===void 0)return[];if(i.kind==="bearer"||i.kind==="basic")return[`{Scheme: ${JSON.stringify(a)}, Kind: ${JSON.stringify(i.kind)}}`];let l=i.kind==="apiKeyHeader"?i.headerName:i.kind==="apiKeyQuery"?i.paramName:i.cookieName,s=i.kind==="apiKeyHeader"?"header":i.kind==="apiKeyQuery"?"query":"cookie";return[`{Scheme: ${JSON.stringify(a)}, Kind: "apiKey", Name: ${JSON.stringify(l)}, In: ${JSON.stringify(s)}}`]})).filter(n=>n.length>0);if(e.length!==0)return`[][]SecuritySpec{${e.map(n=>`{${n.join(", ")}}`).join(", ")}}`}function N(t){let r=new Set,e=[];for(let n of t.services)for(let a of n.operations){let i=p(a.name),l=2;for(;r.has(i);)i=`${p(a.name)}${l++}`;r.add(i),e.push({op:a,ident:i})}return e}function O(t,r){if(r==="string")return t;let e=t.startsWith("*")?`(${t})`:t;return r==="time.Time"?`${e}.Format(time.RFC3339)`:r==="Date"?`${e}.Format("2006-01-02")`:r==="int64"?`strconv.FormatInt(${t}, 10)`:r==="float64"?`strconv.FormatFloat(${t}, 'f', -1, 64)`:r==="bool"?`strconv.FormatBool(${t})`:`fmt.Sprint(${t})`}function W(t){let r=t.split(`
|
|
870
|
+
`),e=[...r],n=/^(\t+)([A-Za-z_]\w*) (\S+)( `[^`]*`)?$/,a=/^(\t+)([A-Za-z_]\w*) (\S+) = (.+)$/,i=/^(\t+)("(?:[^"\\]|\\.)*":) (.+)$/,l=o=>{if(o.length<2)return;let u=[];for(let{parts:d}of o)d.forEach((f,m)=>{m<d.length-1&&(u[m]=Math.max(u[m]??0,f.length))});for(let{index:d,parts:f,indent:m}of o){let g=f.map((b,y)=>y<f.length-1?b.padEnd(u[y]??0):b);e[d]=m+g.join(" ").trimEnd()}},s=[],c;return r.forEach((o,u)=>{let d=i.exec(o),f=d===null?a.exec(o):null,m=d===null&&f===null?n.exec(o):null,g=m!==null&&!S.has(m[2])?m:null,b=d!==null?"entry":f!==null?"const":g!==null?"field":void 0;if((b===void 0||b!==c)&&(l(s),s=[],c=b),d!==null){s.push({index:u,indent:d[1],parts:[d[2],d[3]]});return}if(f!==null){s.push({index:u,indent:f[1],parts:[f[2],f[3],"=",f[4]]});return}if(g!==null){let y=[g[2],g[3]];g[4]!==void 0&&y.push(g[4].trimStart()),s.push({index:u,indent:g[1],parts:y})}}),l(s),e.join(`
|
|
871
|
+
`)}function F(t){let r=t.split(`
|
|
872
|
+
`),e=[],n=!1;for(let a of r)if(!a.startsWith("package ")){if(a.startsWith("import (")){n=!0;continue}if(n){a.startsWith(")")&&(n=!1);continue}a.startsWith("import ")||e.push(a)}return e.join(`
|
|
873
|
+
`).trim()}function z(t){return t.successResponses.find(r=>r.contentType.toLowerCase().includes("text/event-stream"))}function nt(t){return t.requestBody?.contentType.toLowerCase().includes("multipart")??!1}function rt(t){return`&PaginationSpec{${[`Style: ${JSON.stringify(t.style)}`,...t.param!==void 0?[`Param: ${JSON.stringify(t.param)}`]:[],...t.nextCursor!==void 0?[`NextCursor: ${JSON.stringify(t.nextCursor)}`]:[],...t.hasMore!==void 0?[`HasMore: ${JSON.stringify(t.hasMore)}`]:[],...t.limitParam!==void 0?[`LimitParam: ${JSON.stringify(t.limitParam)}`]:[],...t.items!==void 0?[`Items: ${JSON.stringify(t.items)}`]:[]].join(", ")}}`}var at=["c","ctx","body","params","out","op"];function V(t,r){let e=E(t.pathParams.map(n=>n.name),{style:"camel",reserved:S,taken:at});return t.pathParams.map((n,a)=>({param:n,go:e[a],type:h(n.schema,r)}))}function it(t,r){let e=new Set;return(t.successResponseHeaders??[]).map(n=>{let a=p(n.name),i=a,l=2;for(;e.has(i);)i=`${a}${l++}`;e.add(i);let s=M(n.schema,r),c={integer:{goType:"*int64",helper:"headerInt64"},number:{goType:"*float64",helper:"headerFloat64"},boolean:{goType:"*bool",helper:"headerBool"},string:{goType:"*string",helper:"headerString"}}[s];return{field:i,name:n.name,...c}})}function _(t,r,e,n,a,i=!1){let l=V(r,n),s=r.queryParams.length>0,c=G(r),o=c===void 0?void 0:h(c,n),u=i?it(r,a):[];i&&(t.line(`// ${e}Headers carries the declared response headers of ${e}WithHeaders (nil when absent or unparsable).`),t.block(`type ${e}Headers struct {`,()=>{for(let y of u)t.line(`${y.field} ${y.goType}`)},"}"),t.blank());let d=["ctx context.Context",...l.map(({go:y,type:k})=>`${y} ${k}`),...r.requestBody?[`body ${h(r.requestBody.schema,n)}`]:[],...s?[`params *${e}Params`]:[]],f=z(r),m=i?o===void 0?`(${e}Headers, error)`:`(${o}, ${e}Headers, error)`:f!==void 0?"func(yield func(ServerSentEvent, error) bool)":o===void 0?"error":`(${o}, error)`,g=y=>i?o===void 0?`return headers, ${y}`:`return out, headers, ${y}`:o===void 0?`return ${y}`:`return out, ${y}`,b=i?`${e}WithHeaders`:e;R(t,b,i?`Like ${e}, also returning the declared response headers.`:r.summary),t.block(`func (c *Client) ${b}(${d.join(", ")}) ${m} {`,()=>{f===void 0&&o!==void 0&&t.line(`var out ${o}`),i&&t.line(`var headers ${e}Headers`),t.line(`op := operations[${JSON.stringify(r.specName??r.name)}]`),t.line("authHeaders, query := resolveAuth(op.Security, c.config.Auth)"),s&&t.block("if params != nil {",()=>{for(let v of r.queryParams){let w=p(v.name);t.block(`if params.${w} != nil {`,()=>{t.line(`query.Set(${JSON.stringify(v.name)}, ${O(`*params.${w}`,h(v.schema,n))})`)},"}")}},"}");let y=l.map(({param:v,go:w,type:Q})=>`${JSON.stringify(v.name)}: ${O(w,Q)}`).join(", ");if(t.line(`requestURL := buildURL(c.config.ServerURL, op.Path, map[string]string{${y}})`),f!==void 0){t.block("open := func(extraHeaders map[string]string) (*http.Response, error) {",()=>{t.line("merged := map[string]string{}"),t.block("for key, value := range authHeaders {",()=>{t.line("merged[key] = value")},"}"),t.block("for key, value := range extraHeaders {",()=>{t.line("merged[key] = value")},"}"),t.line("return send(ctx, &c.config, requestSpec{OperationID: op.ID, Method: op.Method, URL: requestURL, Headers: merged, Query: query})")},"}"),t.line(`return iterSSE(open, ${f.schema!==void 0&&f.schema.kind!=="unknown"})`);return}let k=["OperationID: op.ID","Method: op.Method","URL: requestURL","Headers: authHeaders","Query: query"];if(r.requestBody&&nt(r)?(t.line("contentType, reader, err := toMultipart(body)"),t.block("if err != nil {",()=>{t.line(g("err"))},"}"),k.push("Body: reader"),k.push("ContentType: contentType")):r.requestBody&&(t.line("payload, err := json.Marshal(body)"),t.block("if err != nil {",()=>{t.line(g("err"))},"}"),k.push("Body: bytes.NewReader(payload)"),k.push(`ContentType: ${JSON.stringify(r.requestBody.contentType)}`)),t.line(`resp, err := send(ctx, &c.config, requestSpec{${k.join(", ")}})`),t.block("if err != nil {",()=>{t.line(g("err"))},"}"),t.block("if resp.StatusCode >= 400 {",()=>{t.line(g("apiErrorFrom(resp, requestURL)"))},"}"),i){t.block(`if err := decodeJSON(resp, ${o===void 0?"nil":"&out"}); err != nil {`,()=>{t.line(g("err"))},"}");for(let v of u)t.line(`headers.${v.field} = ${v.helper}(resp.Header, ${JSON.stringify(v.name)})`);t.line(o===void 0?"return headers, nil":"return out, headers, nil")}else o===void 0?t.line("return decodeJSON(resp, nil)"):(t.block("if err := decodeJSON(resp, &out); err != nil {",()=>{t.line("return out, err")},"}"),t.line("return out, nil"))},"}"),t.blank()}function st(t,r,e,n,a,i){let l=V(r,n),s=r.queryParams.length>0,c=["ctx context.Context",...l.map(({go:u,type:d})=>`${u} ${d}`),...s?[`params *${e}Params`]:[]].join(", "),o=()=>{t.line(`op := operations[${JSON.stringify(r.specName??r.name)}]`),t.line("base := url.Values{}"),s&&t.block("if params != nil {",()=>{for(let u of r.queryParams){let d=p(u.name);t.block(`if params.${d} != nil {`,()=>{t.line(`base.Set(${JSON.stringify(u.name)}, ${O(`*params.${d}`,h(u.schema,n))})`)},"}")}},"}"),t.block("call := func(pageParams url.Values) (any, *http.Response, error) {",()=>{t.line("authHeaders, query := resolveAuth(op.Security, c.config.Auth)"),t.block("for key, values := range pageParams {",()=>{t.block("for _, value := range values {",()=>{t.line("query.Set(key, value)")},"}")},"}");let u=l.map(({param:d,go:f,type:m})=>`${JSON.stringify(d.name)}: ${O(f,m)}`).join(", ");t.line(`requestURL := buildURL(c.config.ServerURL, op.Path, map[string]string{${u}})`),t.line("resp, err := send(ctx, &c.config, requestSpec{OperationID: op.ID, Method: op.Method, URL: requestURL, Headers: authHeaders, Query: query})"),t.block("if err != nil {",()=>{t.line("return nil, nil, err")},"}"),t.block("if resp.StatusCode >= 400 {",()=>{t.line("return nil, resp, apiErrorFrom(resp, requestURL)")},"}"),t.line("var raw any"),t.block("if err := decodeJSON(resp, &raw); err != nil {",()=>{t.line("return nil, resp, err")},"}"),t.line("return raw, resp, nil")},"}"),t.line("pages := iterPages(call, *op.Pagination, base)")};t.line(`// ${e}Pages iterates ${e} response pages; use with \`for page, err := range\`.`),t.block(`func (c *Client) ${e}Pages(${c}) func(yield func(${a}, error) bool) {`,()=>{o(),t.block(`return func(yield func(${a}, error) bool) {`,()=>{t.block("pages(func(raw any, err error) bool {",()=>{t.line(`var page ${a}`),t.block("if err == nil {",()=>{t.line("err = reencode(raw, &page)")},"}"),t.line("return yield(page, err)")},"})")},"}")},"}"),t.blank(),t.line(`// ${e}Items iterates the items of every ${e} page.`),t.block(`func (c *Client) ${e}Items(${c}) func(yield func(${i}, error) bool) {`,()=>{o(),t.block(`return func(yield func(${i}, error) bool) {`,()=>{t.block("pages(func(raw any, err error) bool {",()=>{t.block("if err != nil {",()=>{t.line(`var zero ${i}`),t.line("return yield(zero, err)")},"}"),t.line("pageItems, _ := resolvePointer(raw, op.Pagination.Items).([]any)"),t.block("for _, item := range pageItems {",()=>{t.line(`var typed ${i}`),t.block("if err := reencode(item, &typed); err != nil {",()=>{t.line("return yield(typed, err)")},"}"),t.block("if !yield(typed, nil) {",()=>{t.line("return false")},"}")},"}"),t.line("return true")},"})")},"}")},"}"),t.blank()}function ot(t){let r=new Set(t.variables.map(l=>l.name)),e=[],n="",a=t.url,i=/\{([^{}]+)\}/;for(let l=i.exec(a);l!==null;l=i.exec(a))n+=a.slice(0,l.index),r.has(l[1])?(n!==""&&e.push(JSON.stringify(n)),n="",e.push($(l[1],{style:"camel",reserved:S}))):n+=l[0],a=a.slice(l.index+l[0].length);return n+=a,(n!==""||e.length===0)&&e.push(JSON.stringify(n)),e.join(" + ")}function lt(t,r){let e=r.servers??[];if(e.length===0)return;let n=new Set;e.forEach((a,i)=>{let l=`${p(a.description??`server${i+1}`)}URL`;n.has(l)&&(l=`${l}${i+1}`),n.add(l);let s=a.variables.map(o=>`${$(o.name,{style:"camel",reserved:S})} string`),c=a.variables.map(o=>`${$(o.name,{style:"camel",reserved:S})} default: ${JSON.stringify(o.default)}`).join(", ");t.line(`// ${l} returns the ${JSON.stringify(a.description??a.url)} base URL${c===""?".":` (${c}).`}`),t.block(`func ${l}(${s.join(", ")}) string {`,()=>{t.line(`return ${ot(a)}`)},"}"),t.blank()})}var pt=({model:t,outputPath:r,emit:e})=>{let n=new C(" "),a=e.dateType??"string",i=Z(e.goPackage),l=new Map;for(let{op:s,ident:c}of N(t)){let o=I(s,e.pagination);o!==void 0&&l.set(c,o)}n.line(`// Code generated by @redocly/client-generator (go) from "${t.title}" ${t.version}. DO NOT EDIT.`),n.line("// Regenerate with `redocly generate-client`. Standard library only \u2014 zero dependencies."),n.line(`package ${i}`),n.blank(),n.block("import (",()=>{for(let s of["bytes","context","encoding/base64","encoding/json","errors","fmt","io","math/rand","mime/multipart","net/http","net/url","strconv","strings","time"])n.line(JSON.stringify(s))},")"),n.blank(),n.line(F(X(t,a))),n.blank(),lt(n,t),n.line("// \u2500\u2500\u2500 Embedded runtime (@redocly/client-generator go runtime) \u2500\u2500\u2500"),n.line(F(j)),n.blank(),n.block("type operationMeta struct {",()=>{n.line("ID string"),n.line("Method string"),n.line("Path string"),n.line("Security [][]SecuritySpec"),n.line("Pagination *PaginationSpec")},"}"),n.blank(),n.block("var operations = map[string]operationMeta{",()=>{for(let{op:s,ident:c}of N(t)){let o=s.specName??s.name,u=et(s,t),d=l.get(c),f=[`ID: ${JSON.stringify(o)}`,`Method: ${JSON.stringify(s.method.toUpperCase())}`,`Path: ${JSON.stringify(s.path)}`,...u!==void 0?[`Security: ${u}`]:[],...d!==void 0?[`Pagination: ${rt(d)}`]:[]];n.line(`${JSON.stringify(o)}: {${f.join(", ")}},`)}},"}"),n.blank();for(let{op:s,ident:c}of N(t))s.queryParams.length!==0&&(n.block(`type ${c}Params struct {`,()=>{for(let o of s.queryParams){let u=h(o.schema,a);n.line(`${p(o.name)} ${u.startsWith("*")?u:`*${u}`}`)}},"}"),n.blank());R(n,"Client",`Client for ${t.title} (${t.version}).`),n.block("type Client struct {",()=>{n.line("config Config")},"}"),n.blank(),n.block("func New(config Config) *Client {",()=>{n.block('if config.ServerURL == "" {',()=>{n.line(`config.ServerURL = ${JSON.stringify(e.serverUrl??t.serverUrl??"")}`)},"}"),n.line("return &Client{config: config}")},"}"),n.blank();for(let{op:s,ident:c}of N(t)){_(n,s,c,a),z(s)===void 0&&(s.successResponseHeaders?.length??0)>0&&_(n,s,c,a,t,!0);let o=l.get(c);if(o===void 0)continue;let u=G(s),d=u===void 0?"any":h(u,a),f=u!==void 0&&o.items!==void 0?H(u,o.items,t):void 0,m=f?.kind==="array"?f.items:void 0;st(n,s,c,a,d,m===void 0?"any":h(m,a))}return[{path:r.replace(/\.[^.\\/]+$/,".go"),content:K(W(n.toString()))}]};function ct(t,r){let e=r.emit.dateType??"string",n=r.emit.goPackage??"client",a=p(t.name),i=["ctx",...t.pathParams.map(l=>`"<${$(l.name,{style:"camel",reserved:S})}>"`),...t.requestBody?[`${h(t.requestBody.schema,e)}{ /* \u2026 */ }`]:[],...t.queryParams.length>0?["nil"]:[]];return{lang:"go",label:"Go SDK",source:`client := ${n}.New(${n}.Config{})
|
|
874
|
+
result, err := client.${a}(${i.join(", ")})
|
|
875
|
+
`}}var yt=({model:t,outputPath:r,emit:e})=>[{path:r.replace(/\.[^.\\/]+$/,".go.md"),content:U(t,{title:`${t.title} Go SDK reference`,frontmatter:e.docsFrontmatter===!0,language:{name:"go",label:"Go",fence:"go",requires:"The SDK needs the standard library only."},sample:n=>ct(n,{model:t,emit:e,outputPath:r}),pagination:e.pagination})}];export{h as a,X as b,pt as c,ct as d,yt as e};
|