Skip to content

Commit 5d5792d

Browse files
committed
fix: problems with path params with dots
1 parent 05023eb commit 5d5792d

File tree

7 files changed

+551
-2
lines changed

7 files changed

+551
-2
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"test:--object-types": "node tests/spec/object-types/test.js",
4545
"test:--axios--single-http-client": "node tests/spec/axiosSingleHttpClient/test.js",
4646
"test:--type-suffix--type-prefix": "node tests/spec/typeSuffixPrefix/test.js",
47+
"test:--dot-path-params": "node tests/spec/dot-path-params/test.js",
4748
"test:--cli": "node index.js -p tests/spec/cli/schema.json -o tests/spec/cli -n schema.ts --extract-response-body --extract-response-error --type-prefix Prefix --api-class-name MySuperApi --no-client",
4849
"test:partialBaseTemplate": "node tests/spec/partialBaseTemplate/test.js",
4950
"test:partialDefaultTemplate": "node tests/spec/partialDefaultTemplate/test.js",

src/routes.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ const isSuccessStatus = (status) =>
116116

117117
const parseRoute = (route) => {
118118
const pathParamMatches = (route || "").match(
119-
/({(([a-zA-Z]-?_?){1,})([0-9]{1,})?})|(:(([a-zA-Z]-?_?){1,})([0-9]{1,})?:?)/g,
119+
/({(([a-zA-Z]-?_?\.?){1,})([0-9]{1,})?})|(:(([a-zA-Z]-?_?\.?){1,})([0-9]{1,})?:?)/g,
120120
);
121121

122122
// used in case when path parameters is not declared in requestInfo.parameters ("in": "path")

tests/helpers/assertGeneratedModule.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ const assertGeneratedModule = (pathToModule1, pathToModule2) => {
1111

1212
const diff = gitDiff(output, expected, {
1313
color: true,
14-
flags: "--diff-algorithm=default --ignore-space-at-eol --ignore-cr-at-eol",
14+
flags:
15+
"--diff-algorithm=default --ignore-space-at-eol --ignore-cr-at-eol --ignore-space-change --ignore-all-space",
1516
});
1617

1718
if (diff && diff.length) {
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
/* eslint-disable */
2+
/* tslint:disable */
3+
/*
4+
* ---------------------------------------------------------------
5+
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
6+
* ## ##
7+
* ## AUTHOR: acacode ##
8+
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
9+
* ---------------------------------------------------------------
10+
*/
11+
12+
export type MyResponse = object;
13+
14+
export type QueryParamsType = Record<string | number, any>;
15+
export type ResponseFormat = keyof Omit<Body, "body" | "bodyUsed">;
16+
17+
export interface FullRequestParams extends Omit<RequestInit, "body"> {
18+
/** set parameter to `true` for call `securityWorker` for this request */
19+
secure?: boolean;
20+
/** request path */
21+
path: string;
22+
/** content type of request body */
23+
type?: ContentType;
24+
/** query params */
25+
query?: QueryParamsType;
26+
/** format of response (i.e. response.json() -> format: "json") */
27+
format?: ResponseFormat;
28+
/** request body */
29+
body?: unknown;
30+
/** base url */
31+
baseUrl?: string;
32+
/** request cancellation token */
33+
cancelToken?: CancelToken;
34+
}
35+
36+
export type RequestParams = Omit<FullRequestParams, "body" | "method" | "query" | "path">;
37+
38+
export interface ApiConfig<SecurityDataType = unknown> {
39+
baseUrl?: string;
40+
baseApiParams?: Omit<RequestParams, "baseUrl" | "cancelToken" | "signal">;
41+
securityWorker?: (securityData: SecurityDataType | null) => Promise<RequestParams | void> | RequestParams | void;
42+
customFetch?: typeof fetch;
43+
}
44+
45+
export interface HttpResponse<D extends unknown, E extends unknown = unknown> extends Response {
46+
data: D;
47+
error: E;
48+
}
49+
50+
type CancelToken = Symbol | string | number;
51+
52+
export enum ContentType {
53+
Json = "application/json",
54+
FormData = "multipart/form-data",
55+
UrlEncoded = "application/x-www-form-urlencoded",
56+
}
57+
58+
export class HttpClient<SecurityDataType = unknown> {
59+
public baseUrl: string = "";
60+
private securityData: SecurityDataType | null = null;
61+
private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"];
62+
private abortControllers = new Map<CancelToken, AbortController>();
63+
private customFetch = (...fetchParams: Parameters<typeof fetch>) => fetch(...fetchParams);
64+
65+
private baseApiParams: RequestParams = {
66+
credentials: "same-origin",
67+
headers: {},
68+
redirect: "follow",
69+
referrerPolicy: "no-referrer",
70+
};
71+
72+
constructor(apiConfig: ApiConfig<SecurityDataType> = {}) {
73+
Object.assign(this, apiConfig);
74+
}
75+
76+
public setSecurityData = (data: SecurityDataType | null) => {
77+
this.securityData = data;
78+
};
79+
80+
protected encodeQueryParam(key: string, value: any) {
81+
const encodedKey = encodeURIComponent(key);
82+
return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`;
83+
}
84+
85+
protected addQueryParam(query: QueryParamsType, key: string) {
86+
return this.encodeQueryParam(key, query[key]);
87+
}
88+
89+
protected addArrayQueryParam(query: QueryParamsType, key: string) {
90+
const value = query[key];
91+
return value.map((v: any) => this.encodeQueryParam(key, v)).join("&");
92+
}
93+
94+
protected toQueryString(rawQuery?: QueryParamsType): string {
95+
const query = rawQuery || {};
96+
const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]);
97+
return keys
98+
.map((key) => (Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key)))
99+
.join("&");
100+
}
101+
102+
protected addQueryParams(rawQuery?: QueryParamsType): string {
103+
const queryString = this.toQueryString(rawQuery);
104+
return queryString ? `?${queryString}` : "";
105+
}
106+
107+
private contentFormatters: Record<ContentType, (input: any) => any> = {
108+
[ContentType.Json]: (input: any) =>
109+
input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input,
110+
[ContentType.FormData]: (input: any) =>
111+
Object.keys(input || {}).reduce((formData, key) => {
112+
const property = input[key];
113+
formData.append(
114+
key,
115+
property instanceof Blob
116+
? property
117+
: typeof property === "object" && property !== null
118+
? JSON.stringify(property)
119+
: `${property}`,
120+
);
121+
return formData;
122+
}, new FormData()),
123+
[ContentType.UrlEncoded]: (input: any) => this.toQueryString(input),
124+
};
125+
126+
protected mergeRequestParams(params1: RequestParams, params2?: RequestParams): RequestParams {
127+
return {
128+
...this.baseApiParams,
129+
...params1,
130+
...(params2 || {}),
131+
headers: {
132+
...(this.baseApiParams.headers || {}),
133+
...(params1.headers || {}),
134+
...((params2 && params2.headers) || {}),
135+
},
136+
};
137+
}
138+
139+
protected createAbortSignal = (cancelToken: CancelToken): AbortSignal | undefined => {
140+
if (this.abortControllers.has(cancelToken)) {
141+
const abortController = this.abortControllers.get(cancelToken);
142+
if (abortController) {
143+
return abortController.signal;
144+
}
145+
return void 0;
146+
}
147+
148+
const abortController = new AbortController();
149+
this.abortControllers.set(cancelToken, abortController);
150+
return abortController.signal;
151+
};
152+
153+
public abortRequest = (cancelToken: CancelToken) => {
154+
const abortController = this.abortControllers.get(cancelToken);
155+
156+
if (abortController) {
157+
abortController.abort();
158+
this.abortControllers.delete(cancelToken);
159+
}
160+
};
161+
162+
public request = async <T = any, E = any>({
163+
body,
164+
secure,
165+
path,
166+
type,
167+
query,
168+
format,
169+
baseUrl,
170+
cancelToken,
171+
...params
172+
}: FullRequestParams): Promise<HttpResponse<T, E>> => {
173+
const secureParams =
174+
((typeof secure === "boolean" ? secure : this.baseApiParams.secure) &&
175+
this.securityWorker &&
176+
(await this.securityWorker(this.securityData))) ||
177+
{};
178+
const requestParams = this.mergeRequestParams(params, secureParams);
179+
const queryString = query && this.toQueryString(query);
180+
const payloadFormatter = this.contentFormatters[type || ContentType.Json];
181+
const responseFormat = format || requestParams.format;
182+
183+
return this.customFetch(`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`, {
184+
...requestParams,
185+
headers: {
186+
...(requestParams.headers || {}),
187+
...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}),
188+
},
189+
signal: cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal,
190+
body: typeof body === "undefined" || body === null ? null : payloadFormatter(body),
191+
}).then(async (response) => {
192+
const r = response as HttpResponse<T, E>;
193+
r.data = null as unknown as T;
194+
r.error = null as unknown as E;
195+
196+
const data = !responseFormat
197+
? r
198+
: await response[responseFormat]()
199+
.then((data) => {
200+
if (r.ok) {
201+
r.data = data;
202+
} else {
203+
r.error = data;
204+
}
205+
return r;
206+
})
207+
.catch((e) => {
208+
r.error = e;
209+
return r;
210+
});
211+
212+
if (cancelToken) {
213+
this.abortControllers.delete(cancelToken);
214+
}
215+
216+
if (!response.ok) throw data;
217+
return data;
218+
});
219+
};
220+
}
221+
222+
/**
223+
* @title unset
224+
* @version unset
225+
*/
226+
export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDataType> {
227+
foobar = {
228+
/**
229+
* No description
230+
*
231+
* @name ReproFunc
232+
* @summary Repros an issue
233+
* @request POST:/foobar/{truck.id}/item
234+
*/
235+
reproFunc: (truckId: string, params: RequestParams = {}) =>
236+
this.request<any, MyResponse>({
237+
path: `/foobar/${truckId}/item`,
238+
method: "POST",
239+
...params,
240+
}),
241+
};
242+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"swagger": "2.0",
3+
"info": {
4+
"title": "unset",
5+
"version": "unset"
6+
},
7+
"consumes": ["application/json"],
8+
"produces": ["application/json"],
9+
"paths": {
10+
"/foobar/{truck.id}/item": {
11+
"post": {
12+
"summary": "Repros an issue",
13+
"operationId": "ReproFunc",
14+
"responses": {
15+
"default": {
16+
"description": "A response.",
17+
"schema": {
18+
"$ref": "#/definitions/myResponse"
19+
}
20+
}
21+
},
22+
"parameters": [
23+
{
24+
"name": "truck.id",
25+
"in": "path",
26+
"required": true,
27+
"type": "string"
28+
}
29+
]
30+
}
31+
}
32+
},
33+
"definitions": {
34+
"myResponse": {
35+
"type": "object"
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)