-
-
Notifications
You must be signed in to change notification settings - Fork 381
Agents fix buildurl interceptor ordering #3912
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
50be732
30050dc
3740600
6d224b2
0963c7d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@hey-api/openapi-ts": patch | ||
| --- | ||
|
|
||
| fix(clients): defer URL construction and thread finalError through interceptors |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -45,10 +45,7 @@ export const createClient = (config: Config = {}): Client => { | |||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (opts.security) { | ||||||||||||||||||||||||||||||||||
| await setAuthParams({ | ||||||||||||||||||||||||||||||||||
| ...opts, | ||||||||||||||||||||||||||||||||||
| security: opts.security, | ||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||
| await setAuthParams({ ...opts, security: opts.security }); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (opts.requestValidator) { | ||||||||||||||||||||||||||||||||||
|
|
@@ -59,34 +56,30 @@ export const createClient = (config: Config = {}): Client => { | |||||||||||||||||||||||||||||||||
| opts.serializedBody = opts.bodySerializer(opts.body) as string | undefined; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // remove Content-Type header if body is empty to avoid sending invalid requests | ||||||||||||||||||||||||||||||||||
| if (opts.body === undefined || opts.serializedBody === '') { | ||||||||||||||||||||||||||||||||||
| opts.headers.delete('Content-Type'); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const resolvedOpts = opts as typeof opts & ResolvedRequestOptions<ThrowOnError, Url>; | ||||||||||||||||||||||||||||||||||
| const url = buildUrl(resolvedOpts); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| return { opts: resolvedOpts, url }; | ||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||
| opts: opts as typeof opts & ResolvedRequestOptions<ThrowOnError, Url>, | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // @ts-expect-error | ||||||||||||||||||||||||||||||||||
| const request: Client['request'] = async (options) => { | ||||||||||||||||||||||||||||||||||
| const throwOnError = options.throwOnError ?? _config.throwOnError; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| let response: Response | undefined; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||
| const { opts, url } = await beforeRequest(options); | ||||||||||||||||||||||||||||||||||
| const { opts } = await beforeRequest(options); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // request interceptors | ||||||||||||||||||||||||||||||||||
| for (const fn of interceptors.request.fns) { | ||||||||||||||||||||||||||||||||||
| if (fn) { | ||||||||||||||||||||||||||||||||||
| await fn(opts); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| if (fn) await fn(opts); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // fetch must be assigned here, otherwise it would throw the error: | ||||||||||||||||||||||||||||||||||
| // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation | ||||||||||||||||||||||||||||||||||
| const url = buildUrl(opts); | ||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The deleted comments above this line ( |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const _fetch = opts.fetch!; | ||||||||||||||||||||||||||||||||||
| const requestInit: ReqInit = { | ||||||||||||||||||||||||||||||||||
| ...opts, | ||||||||||||||||||||||||||||||||||
|
|
@@ -95,15 +88,12 @@ export const createClient = (config: Config = {}): Client => { | |||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| response = await _fetch(url, requestInit); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // response interceptors | ||||||||||||||||||||||||||||||||||
| for (const fn of interceptors.response.fns) { | ||||||||||||||||||||||||||||||||||
| if (fn) { | ||||||||||||||||||||||||||||||||||
| response = await fn(response, opts); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| if (fn) response = await fn(response, opts); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const result = { | ||||||||||||||||||||||||||||||||||
| response, | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
| const result = { response }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (response.ok) { | ||||||||||||||||||||||||||||||||||
| const parseAs = | ||||||||||||||||||||||||||||||||||
|
|
@@ -112,7 +102,8 @@ export const createClient = (config: Config = {}): Client => { | |||||||||||||||||||||||||||||||||
| : opts.parseAs) ?? 'json'; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (response.status === 204 || response.headers.get('Content-Length') === '0') { | ||||||||||||||||||||||||||||||||||
| let emptyData: any; | ||||||||||||||||||||||||||||||||||
| let emptyData: any = {}; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| switch (parseAs) { | ||||||||||||||||||||||||||||||||||
| case 'arrayBuffer': | ||||||||||||||||||||||||||||||||||
| case 'blob': | ||||||||||||||||||||||||||||||||||
|
|
@@ -125,53 +116,37 @@ export const createClient = (config: Config = {}): Client => { | |||||||||||||||||||||||||||||||||
| case 'stream': | ||||||||||||||||||||||||||||||||||
| emptyData = response.body; | ||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||
| case 'json': | ||||||||||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||||||||||
| emptyData = {}; | ||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
105
to
119
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor: removing the explicit |
||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||
| data: emptyData, | ||||||||||||||||||||||||||||||||||
| ...result, | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| return { data: emptyData, ...result }; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| let data: any; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| switch (parseAs) { | ||||||||||||||||||||||||||||||||||
| case 'arrayBuffer': | ||||||||||||||||||||||||||||||||||
| case 'blob': | ||||||||||||||||||||||||||||||||||
| case 'formData': | ||||||||||||||||||||||||||||||||||
| case 'text': | ||||||||||||||||||||||||||||||||||
| data = await response[parseAs](); | ||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| case 'json': { | ||||||||||||||||||||||||||||||||||
| // Some servers return 200 with no Content-Length and empty body. | ||||||||||||||||||||||||||||||||||
| // response.json() would throw; read as text and parse if non-empty. | ||||||||||||||||||||||||||||||||||
| const text = await response.text(); | ||||||||||||||||||||||||||||||||||
| data = text ? JSON.parse(text) : {}; | ||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| case 'stream': | ||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||
| data: response.body, | ||||||||||||||||||||||||||||||||||
| ...result, | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
| return { data: response.body, ...result }; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (parseAs === 'json') { | ||||||||||||||||||||||||||||||||||
| if (opts.responseValidator) { | ||||||||||||||||||||||||||||||||||
| await opts.responseValidator(data); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (opts.responseTransformer) { | ||||||||||||||||||||||||||||||||||
| data = await opts.responseTransformer(data); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| if (opts.responseValidator) await opts.responseValidator(data); | ||||||||||||||||||||||||||||||||||
| if (opts.responseTransformer) data = await opts.responseTransformer(data); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||
| data, | ||||||||||||||||||||||||||||||||||
| ...result, | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
| return { data, ...result }; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const textError = await response.text(); | ||||||||||||||||||||||||||||||||||
|
|
@@ -180,7 +155,7 @@ export const createClient = (config: Config = {}): Client => { | |||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||
| jsonError = JSON.parse(textError); | ||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||
| // noop | ||||||||||||||||||||||||||||||||||
| // ignore JSON parse error | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| throw jsonError ?? textError; | ||||||||||||||||||||||||||||||||||
|
|
@@ -193,41 +168,35 @@ export const createClient = (config: Config = {}): Client => { | |||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| finalError = finalError || {}; | ||||||||||||||||||||||||||||||||||
| if (throwOnError) throw finalError; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (throwOnError) { | ||||||||||||||||||||||||||||||||||
| throw finalError; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||
| error: finalError, | ||||||||||||||||||||||||||||||||||
| response, | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
| return { error: finalError || {}, response }; | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+171
to
+173
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Behavior change: previously
Suggested change
|
||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const makeMethodFn = (method: Uppercase<HttpMethod>) => (options: RequestOptions) => | ||||||||||||||||||||||||||||||||||
| request({ ...options, method }); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const makeSseFn = (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => { | ||||||||||||||||||||||||||||||||||
| const { opts, url } = await beforeRequest(options); | ||||||||||||||||||||||||||||||||||
| const { opts } = await beforeRequest(options); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| return createSseClient({ | ||||||||||||||||||||||||||||||||||
| ...opts, | ||||||||||||||||||||||||||||||||||
| body: opts.body as BodyInit | null | undefined, | ||||||||||||||||||||||||||||||||||
| method, | ||||||||||||||||||||||||||||||||||
| onRequest: async (url, init) => { | ||||||||||||||||||||||||||||||||||
| let request = new Request(url, init); | ||||||||||||||||||||||||||||||||||
| const requestInit = { ...init, url }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| onRequest: async (_unusedUrl, init) => { | ||||||||||||||||||||||||||||||||||
| const clonedOpts = { ...opts }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| for (const fn of interceptors.request.fns) { | ||||||||||||||||||||||||||||||||||
| if (fn) { | ||||||||||||||||||||||||||||||||||
| await fn(requestInit as ResolvedRequestOptions); | ||||||||||||||||||||||||||||||||||
| request = new Request(requestInit.url, requestInit); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| if (fn) await fn(clonedOpts); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| return request; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const finalizedUrl = buildUrl(clonedOpts); | ||||||||||||||||||||||||||||||||||
| return new Request(finalizedUrl, init); | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+188
to
+196
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interceptor mutations to headers, body, signal, etc. are dropped here: |
||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| serializedBody: getValidRequestBody(opts) as BodyInit | null | undefined, | ||||||||||||||||||||||||||||||||||
| url, | ||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
|
@@ -236,6 +205,7 @@ export const createClient = (config: Config = {}): Client => { | |||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||
| buildUrl: _buildUrl, | ||||||||||||||||||||||||||||||||||
| connect: makeMethodFn('CONNECT'), | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stray blank line inside the returned object literal — breaks the alphabetical clustering of method keys.
Suggested change
|
||||||||||||||||||||||||||||||||||
| delete: makeMethodFn('DELETE'), | ||||||||||||||||||||||||||||||||||
| get: makeMethodFn('GET'), | ||||||||||||||||||||||||||||||||||
| getConfig, | ||||||||||||||||||||||||||||||||||
|
|
@@ -249,15 +219,15 @@ export const createClient = (config: Config = {}): Client => { | |||||||||||||||||||||||||||||||||
| setConfig, | ||||||||||||||||||||||||||||||||||
| sse: { | ||||||||||||||||||||||||||||||||||
| connect: makeSseFn('CONNECT'), | ||||||||||||||||||||||||||||||||||
| delete: makeSseFn('DELETE'), | ||||||||||||||||||||||||||||||||||
| get: makeSseFn('GET'), | ||||||||||||||||||||||||||||||||||
| head: makeSseFn('HEAD'), | ||||||||||||||||||||||||||||||||||
| options: makeSseFn('OPTIONS'), | ||||||||||||||||||||||||||||||||||
| patch: makeSseFn('PATCH'), | ||||||||||||||||||||||||||||||||||
| post: makeSseFn('POST'), | ||||||||||||||||||||||||||||||||||
| put: makeSseFn('PUT'), | ||||||||||||||||||||||||||||||||||
| trace: makeSseFn('TRACE'), | ||||||||||||||||||||||||||||||||||
| delete: makeMethodFn('DELETE'), | ||||||||||||||||||||||||||||||||||
| get: makeMethodFn('GET'), | ||||||||||||||||||||||||||||||||||
| head: makeMethodFn('HEAD'), | ||||||||||||||||||||||||||||||||||
| options: makeMethodFn('OPTIONS'), | ||||||||||||||||||||||||||||||||||
| patch: makeMethodFn('PATCH'), | ||||||||||||||||||||||||||||||||||
| post: makeMethodFn('POST'), | ||||||||||||||||||||||||||||||||||
| put: makeMethodFn('PUT'), | ||||||||||||||||||||||||||||||||||
| trace: makeMethodFn('TRACE'), | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+222
to
+229
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Regression: every SSE method except
Suggested change
|
||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||
| trace: makeMethodFn('TRACE'), | ||||||||||||||||||||||||||||||||||
| } as Client; | ||||||||||||||||||||||||||||||||||
| } as unknown as Client; | ||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personal tooling file — belongs in
~/.config/git/ignoreor.git/info/exclude, not the repo's.gitignore. Please drop these three lines.