Skip to content

Commit 7ca12cc

Browse files
committed
feat: axios supports
1 parent 0449e27 commit 7ca12cc

File tree

4 files changed

+378
-1
lines changed

4 files changed

+378
-1
lines changed

packages/openapi-code-generator/src/typescript/client/typescript-axios/typescript-axios-client-builder.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {isDefined} from "../../../core/utils"
12
import type {ImportBuilder} from "../../common/import-builder"
23
import {union} from "../../common/type-utils"
34
import {
@@ -14,6 +15,7 @@ export class TypescriptAxiosClientBuilder extends AbstractClientBuilder {
1415
"application/json",
1516
"application/scim+json",
1617
"application/merge-patch+json",
18+
"application/x-www-form-urlencoded",
1719
"text/json",
1820
"text/plain",
1921
"text/x-markdown",
@@ -184,9 +186,26 @@ ${this.legacyExports(clientName)}
184186
return `${param} !== undefined ? ${serialize} : null`
185187
}
186188

189+
case "URLSearchParams": {
190+
const serialize = `this._requestBodyToUrlSearchParams(${[
191+
param,
192+
requestBody.encoding
193+
? JSON.stringify(requestBody.encoding)
194+
: undefined,
195+
]
196+
.filter(isDefined)
197+
.join(", ")})`
198+
199+
if (requestBody.parameter.required) {
200+
return serialize
201+
}
202+
203+
return `${param} !== undefined ? ${serialize} : null`
204+
}
205+
187206
default: {
188207
throw new Error(
189-
`typescript-axios does not support request bodies of content-type '${requestBody.contentType}' using serializer '${requestBody.serializer satisfies "URLSearchParams"}'`,
208+
`typescript-axios does not support request bodies of content-type '${requestBody.contentType}' using serializer '${requestBody.serializer satisfies never}'`,
190209
)
191210
}
192211
}

packages/typescript-axios-runtime/src/main.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import axios, {
66
type RawAxiosRequestHeaders,
77
} from "axios"
88
import qs from "qs"
9+
import {
10+
type Encoding,
11+
requestBodyToUrlSearchParams,
12+
} from "./request-bodies/url-search-params"
913

1014
export type QueryParams = {
1115
[name: string]:
@@ -110,6 +114,13 @@ export abstract class AbstractAxiosClient {
110114
return headers
111115
}
112116

117+
protected _requestBodyToUrlSearchParams(
118+
obj: Record<string, unknown>,
119+
encoding: Record<string, Encoding> = {},
120+
): URLSearchParams {
121+
return requestBodyToUrlSearchParams(obj, encoding)
122+
}
123+
113124
private setHeaders(
114125
headers: Pick<Headers, "set" | "delete">,
115126
headersInit: HeaderParams | AxiosRequestConfig["headers"],
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
import {describe, expect, it} from "@jest/globals"
2+
import {requestBodyToUrlSearchParams} from "./url-search-params"
3+
4+
describe("typescript-fetch-runtime/request-bodies/requestBodyToUrlSearchParams", () => {
5+
it("encodes the basic example from the specification correctly", async () => {
6+
const actual = requestBodyToUrlSearchParams(
7+
{
8+
id: "f81d4fae-7dec-11d0-a765-00a0c91e6bf6",
9+
address: {
10+
streetAddress: "123 Example Dr.",
11+
city: "Somewhere",
12+
state: "CA",
13+
zip: "99999+1234",
14+
},
15+
},
16+
{address: {explode: false, style: "deepObject"}},
17+
)
18+
19+
expect(actual.toString()).toStrictEqual(
20+
// todo: original example from oas differs, https://github.com/OAI/OpenAPI-Specification/issues/4813
21+
"id=f81d4fae-7dec-11d0-a765-00a0c91e6bf6&address=%7B%22streetAddress%22%3A%22123+Example+Dr.%22%2C%22city%22%3A%22Somewhere%22%2C%22state%22%3A%22CA%22%2C%22zip%22%3A%2299999%2B1234%22%7D",
22+
)
23+
})
24+
25+
describe.each([
26+
{
27+
explode: true,
28+
style: "form",
29+
string: "color=blue",
30+
array: "color=blue&color=black&color=brown",
31+
object: "R=100&G=200&B=150",
32+
},
33+
{
34+
explode: false,
35+
style: "form",
36+
string: "color=blue",
37+
array: "color=blue%2Cblack%2Cbrown",
38+
// todo: original example from oas differs, https://github.com/OAI/OpenAPI-Specification/issues/4813
39+
// array: "color=blue,black,brown",
40+
object: "color=R%2C100%2CG%2C200%2CB%2C150",
41+
// todo: original example from oas differs, https://github.com/OAI/OpenAPI-Specification/issues/4813
42+
// object: "color=R,100,G,200,B,150",
43+
},
44+
{
45+
explode: true,
46+
style: "spaceDelimited",
47+
// note: undefined by spec
48+
string: "color=blue",
49+
// note: undefined by spec
50+
array: "color=blue&color=black&color=brown",
51+
// note: undefined by spec
52+
object: "R=100&G=200&B=150",
53+
},
54+
{
55+
explode: false,
56+
style: "spaceDelimited",
57+
// note: undefined by spec
58+
string: "color=blue",
59+
array: "color=blue+black+brown",
60+
// todo: original example from oas differs, https://github.com/OAI/OpenAPI-Specification/issues/4813
61+
// array: "color=blue%20black%20brown",
62+
object: "color=R+100+G+200+B+150",
63+
// todo: original example from oas differs, https://github.com/OAI/OpenAPI-Specification/issues/4813
64+
// object: "color=R%20100%20G%20200%20B%20150",
65+
},
66+
{
67+
explode: true,
68+
style: "pipeDelimited",
69+
// note: undefined by spec
70+
string: "color=blue",
71+
// note: undefined by spec
72+
array: "color=blue&color=black&color=brown",
73+
// note: undefined by spec
74+
object: "R=100&G=200&B=150",
75+
},
76+
{
77+
explode: false,
78+
style: "pipeDelimited",
79+
// note: undefined by spec
80+
string: "color=blue",
81+
array: "color=blue%7Cblack%7Cbrown",
82+
object: "color=R%7C100%7CG%7C200%7CB%7C150",
83+
},
84+
{
85+
explode: true,
86+
style: "deepObject",
87+
// note: undefined by spec
88+
string: "color=blue",
89+
// note: undefined by spec; using stripe expectation of `color[0]=blue&color[1]=black&color[2]=brown`
90+
array: "color%5B0%5D=blue&color%5B1%5D=black&color%5B2%5D=brown",
91+
object: "color%5BR%5D=100&color%5BG%5D=200&color%5BB%5D=150",
92+
},
93+
{
94+
explode: false,
95+
style: "deepObject",
96+
// note: undefined by spec
97+
string: "color=blue",
98+
// note: undefined by spec
99+
array: "color=blue%2Cblack%2Cbrown",
100+
// note: undefined by spec, using JSON.stringify representation
101+
object: "color=%7B%22R%22%3A100%2C%22G%22%3A200%2C%22B%22%3A150%7D",
102+
},
103+
] as const)(
104+
"oas 4.8.12.4 style examples - when explode: $explode and style: $style",
105+
({explode, style, string, array, object}) => {
106+
it("serializes a string correctly", () => {
107+
const actual = requestBodyToUrlSearchParams(
108+
{color: "blue"},
109+
{color: {explode, style}},
110+
)
111+
expect(actual.toString()).toStrictEqual(string)
112+
})
113+
114+
it("serializes a array correctly", () => {
115+
const actual = requestBodyToUrlSearchParams(
116+
{color: ["blue", "black", "brown"]},
117+
{color: {explode, style}},
118+
)
119+
expect(actual.toString()).toStrictEqual(array)
120+
})
121+
122+
it("serializes a object correctly", () => {
123+
const actual = requestBodyToUrlSearchParams(
124+
{color: {R: 100, G: 200, B: 150}},
125+
{
126+
color: {
127+
explode,
128+
style,
129+
},
130+
},
131+
)
132+
expect(actual.toString()).toStrictEqual(object)
133+
})
134+
},
135+
)
136+
137+
describe("stripe /v1 api conventions; style: 'deepObject' explode: true", () => {
138+
/**
139+
* conventions based on the way the stripe nodejs sdk is implemented at time of writing,
140+
* https://github.com/stripe/stripe-node/blob/67b2f17c813bef59635baa6d8b3f246a8c355431/src/utils.ts#L53-L69
141+
* excluding their use of raw `[` / `]` characters rather than percent-encoded brackets.
142+
*/
143+
144+
it("serializes a nested object correctly", () => {
145+
const actual = requestBodyToUrlSearchParams(
146+
{
147+
components: {
148+
account: {
149+
enabled: true,
150+
features: {
151+
some_flag: false,
152+
some_other_flag: true,
153+
},
154+
},
155+
},
156+
},
157+
{
158+
components: {
159+
explode: true,
160+
style: "deepObject",
161+
},
162+
},
163+
)
164+
expect(actual.toString()).toStrictEqual(
165+
"components%5Baccount%5D%5Benabled%5D=true&components%5Baccount%5D%5Bfeatures%5D%5Bsome_flag%5D=false&components%5Baccount%5D%5Bfeatures%5D%5Bsome_other_flag%5D=true",
166+
)
167+
})
168+
169+
it("serializes a nested array correctly", () => {
170+
const actual = requestBodyToUrlSearchParams(
171+
{
172+
arr: ["red", "blue"],
173+
},
174+
{
175+
arr: {
176+
explode: true,
177+
style: "deepObject",
178+
},
179+
},
180+
)
181+
182+
expect(actual.toString()).toStrictEqual("arr%5B0%5D=red&arr%5B1%5D=blue")
183+
})
184+
})
185+
186+
describe("undefined / null handling", () => {
187+
it("omits undefined values", () => {
188+
const actual = requestBodyToUrlSearchParams({
189+
id: "123",
190+
description: null,
191+
})
192+
expect(actual.toString()).toStrictEqual("id=123")
193+
})
194+
it("omits null values", () => {
195+
const actual = requestBodyToUrlSearchParams({
196+
id: "123",
197+
description: undefined,
198+
})
199+
expect(actual.toString()).toStrictEqual("id=123")
200+
})
201+
it("includes empty string values", () => {
202+
const actual = requestBodyToUrlSearchParams({id: "123", description: ""})
203+
expect(actual.toString()).toStrictEqual("id=123&description=")
204+
})
205+
it("includes 0 number values", () => {
206+
const actual = requestBodyToUrlSearchParams({id: "123", description: 0})
207+
expect(actual.toString()).toStrictEqual("id=123&description=0")
208+
})
209+
})
210+
211+
describe("explode: false, nested objects/arrays", () => {
212+
it("uses a JSON serialization when no defined alternative exists", () => {
213+
const actual = requestBodyToUrlSearchParams(
214+
{
215+
foo: {bar: {baz: true}},
216+
},
217+
{foo: {explode: false, style: "form"}},
218+
)
219+
220+
expect(actual.toString()).toStrictEqual(
221+
"foo=bar%2C%7B%22baz%22%3Atrue%7D",
222+
)
223+
})
224+
})
225+
})

0 commit comments

Comments
 (0)