Skip to content

Commit 246f156

Browse files
committed
feat: add sort-route-params flags
1 parent 0d8aded commit 246f156

File tree

8 files changed

+509
-5
lines changed

8 files changed

+509
-5
lines changed

index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,11 @@ const generateCommand = defineCommand({
258258
description: "sort routes in alphabetical order",
259259
default: codeGenBaseConfig.sortRoutes,
260260
},
261+
"sort-route-params": {
262+
type: "boolean",
263+
description: "sort route params from path order",
264+
default: codeGenBaseConfig.sortRouteParams,
265+
},
261266
"sort-types": {
262267
type: "boolean",
263268
description: "sort fields and types",
@@ -324,6 +329,7 @@ const generateCommand = defineCommand({
324329
silent: args.silent,
325330
singleHttpClient: args["single-http-client"],
326331
sortRoutes: args["sort-routes"],
332+
sortRouteParams: args["sort-route-params"],
327333
sortTypes: args["sort-types"],
328334
templates: args.templates,
329335
toJS: args.js,

src/configuration.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ export class CodeGenConfig {
117117
disableThrowOnError = false;
118118
sortTypes = false;
119119
sortRoutes = false;
120+
sortRouteParams = false;
120121
templatePaths = {
121122
/** `templates/base` */
122123
base: "",

templates/default/procedure-call.ejs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,27 @@ const rawWrapperArgs = config.extractRequestParams ?
3939
requestConfigParam,
4040
])
4141
42-
const wrapperArgs = _
43-
// Sort by optionality
44-
.sortBy(rawWrapperArgs, [o => o.optional])
45-
.map(argToTmpl)
46-
.join(', ')
42+
43+
const requiredArgs = rawWrapperArgs.filter((o) => !o.optional)
44+
const optionalArgs = rawWrapperArgs.filter((o) => o.optional)
45+
46+
// sort by params index of params in path
47+
if (config.sortRouteParams) {
48+
requiredArgs.sort(({name}) => {
49+
const idx = path.indexOf(`{${name}}`)
50+
if (idx === -1) {
51+
return Infinity
52+
}
53+
return idx
54+
})
55+
}
56+
57+
const sortedRawWrapperArgs = [
58+
...requiredArgs,
59+
...optionalArgs
60+
]
61+
62+
const wrapperArgs = sortedRawWrapperArgs.map(argToTmpl).join(', ')
4763
4864
// RequestParams["type"]
4965
const requestContentKind = {

tests/extended.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ describe("extended", async () => {
3333
generateClient: true,
3434
generateRouteTypes: true,
3535
sortRoutes: true,
36+
sortRouteParams: true,
3637
sortTypes: true,
3738
});
3839

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`basic > --sort-route-params 1`] = `
4+
"/* eslint-disable */
5+
/* tslint:disable */
6+
// @ts-nocheck
7+
/*
8+
* ---------------------------------------------------------------
9+
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
10+
* ## ##
11+
* ## AUTHOR: acacode ##
12+
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
13+
* ---------------------------------------------------------------
14+
*/
15+
16+
export type QueryParamsType = Record<string | number, any>;
17+
export type ResponseFormat = keyof Omit<Body, "body" | "bodyUsed">;
18+
19+
export interface FullRequestParams extends Omit<RequestInit, "body"> {
20+
/** set parameter to \`true\` for call \`securityWorker\` for this request */
21+
secure?: boolean;
22+
/** request path */
23+
path: string;
24+
/** content type of request body */
25+
type?: ContentType;
26+
/** query params */
27+
query?: QueryParamsType;
28+
/** format of response (i.e. response.json() -> format: "json") */
29+
format?: ResponseFormat;
30+
/** request body */
31+
body?: unknown;
32+
/** base url */
33+
baseUrl?: string;
34+
/** request cancellation token */
35+
cancelToken?: CancelToken;
36+
}
37+
38+
export type RequestParams = Omit<
39+
FullRequestParams,
40+
"body" | "method" | "query" | "path"
41+
>;
42+
43+
export interface ApiConfig<SecurityDataType = unknown> {
44+
baseUrl?: string;
45+
baseApiParams?: Omit<RequestParams, "baseUrl" | "cancelToken" | "signal">;
46+
securityWorker?: (
47+
securityData: SecurityDataType | null,
48+
) => Promise<RequestParams | void> | RequestParams | void;
49+
customFetch?: typeof fetch;
50+
}
51+
52+
export interface HttpResponse<D extends unknown, E extends unknown = unknown>
53+
extends Response {
54+
data: D;
55+
error: E;
56+
}
57+
58+
type CancelToken = Symbol | string | number;
59+
60+
export enum ContentType {
61+
Json = "application/json",
62+
JsonApi = "application/vnd.api+json",
63+
FormData = "multipart/form-data",
64+
UrlEncoded = "application/x-www-form-urlencoded",
65+
Text = "text/plain",
66+
}
67+
68+
export class HttpClient<SecurityDataType = unknown> {
69+
public baseUrl: string = "https://6-dot-authentiqio.appspot.com";
70+
private securityData: SecurityDataType | null = null;
71+
private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"];
72+
private abortControllers = new Map<CancelToken, AbortController>();
73+
private customFetch = (...fetchParams: Parameters<typeof fetch>) =>
74+
fetch(...fetchParams);
75+
76+
private baseApiParams: RequestParams = {
77+
credentials: "same-origin",
78+
headers: {},
79+
redirect: "follow",
80+
referrerPolicy: "no-referrer",
81+
};
82+
83+
constructor(apiConfig: ApiConfig<SecurityDataType> = {}) {
84+
Object.assign(this, apiConfig);
85+
}
86+
87+
public setSecurityData = (data: SecurityDataType | null) => {
88+
this.securityData = data;
89+
};
90+
91+
protected encodeQueryParam(key: string, value: any) {
92+
const encodedKey = encodeURIComponent(key);
93+
return \`\${encodedKey}=\${encodeURIComponent(typeof value === "number" ? value : \`\${value}\`)}\`;
94+
}
95+
96+
protected addQueryParam(query: QueryParamsType, key: string) {
97+
return this.encodeQueryParam(key, query[key]);
98+
}
99+
100+
protected addArrayQueryParam(query: QueryParamsType, key: string) {
101+
const value = query[key];
102+
return value.map((v: any) => this.encodeQueryParam(key, v)).join("&");
103+
}
104+
105+
protected toQueryString(rawQuery?: QueryParamsType): string {
106+
const query = rawQuery || {};
107+
const keys = Object.keys(query).filter(
108+
(key) => "undefined" !== typeof query[key],
109+
);
110+
return keys
111+
.map((key) =>
112+
Array.isArray(query[key])
113+
? this.addArrayQueryParam(query, key)
114+
: this.addQueryParam(query, key),
115+
)
116+
.join("&");
117+
}
118+
119+
protected addQueryParams(rawQuery?: QueryParamsType): string {
120+
const queryString = this.toQueryString(rawQuery);
121+
return queryString ? \`?\${queryString}\` : "";
122+
}
123+
124+
private contentFormatters: Record<ContentType, (input: any) => any> = {
125+
[ContentType.Json]: (input: any) =>
126+
input !== null && (typeof input === "object" || typeof input === "string")
127+
? JSON.stringify(input)
128+
: input,
129+
[ContentType.JsonApi]: (input: any) =>
130+
input !== null && (typeof input === "object" || typeof input === "string")
131+
? JSON.stringify(input)
132+
: input,
133+
[ContentType.Text]: (input: any) =>
134+
input !== null && typeof input !== "string"
135+
? JSON.stringify(input)
136+
: input,
137+
[ContentType.FormData]: (input: any) => {
138+
if (input instanceof FormData) {
139+
return input;
140+
}
141+
142+
return Object.keys(input || {}).reduce((formData, key) => {
143+
const property = input[key];
144+
formData.append(
145+
key,
146+
property instanceof Blob
147+
? property
148+
: typeof property === "object" && property !== null
149+
? JSON.stringify(property)
150+
: \`\${property}\`,
151+
);
152+
return formData;
153+
}, new FormData());
154+
},
155+
[ContentType.UrlEncoded]: (input: any) => this.toQueryString(input),
156+
};
157+
158+
protected mergeRequestParams(
159+
params1: RequestParams,
160+
params2?: RequestParams,
161+
): RequestParams {
162+
return {
163+
...this.baseApiParams,
164+
...params1,
165+
...(params2 || {}),
166+
headers: {
167+
...(this.baseApiParams.headers || {}),
168+
...(params1.headers || {}),
169+
...((params2 && params2.headers) || {}),
170+
},
171+
};
172+
}
173+
174+
protected createAbortSignal = (
175+
cancelToken: CancelToken,
176+
): AbortSignal | undefined => {
177+
if (this.abortControllers.has(cancelToken)) {
178+
const abortController = this.abortControllers.get(cancelToken);
179+
if (abortController) {
180+
return abortController.signal;
181+
}
182+
return void 0;
183+
}
184+
185+
const abortController = new AbortController();
186+
this.abortControllers.set(cancelToken, abortController);
187+
return abortController.signal;
188+
};
189+
190+
public abortRequest = (cancelToken: CancelToken) => {
191+
const abortController = this.abortControllers.get(cancelToken);
192+
193+
if (abortController) {
194+
abortController.abort();
195+
this.abortControllers.delete(cancelToken);
196+
}
197+
};
198+
199+
public request = async <T = any, E = any>({
200+
body,
201+
secure,
202+
path,
203+
type,
204+
query,
205+
format,
206+
baseUrl,
207+
cancelToken,
208+
...params
209+
}: FullRequestParams): Promise<HttpResponse<T, E>> => {
210+
const secureParams =
211+
((typeof secure === "boolean" ? secure : this.baseApiParams.secure) &&
212+
this.securityWorker &&
213+
(await this.securityWorker(this.securityData))) ||
214+
{};
215+
const requestParams = this.mergeRequestParams(params, secureParams);
216+
const queryString = query && this.toQueryString(query);
217+
const payloadFormatter = this.contentFormatters[type || ContentType.Json];
218+
const responseFormat = format || requestParams.format;
219+
220+
return this.customFetch(
221+
\`\${baseUrl || this.baseUrl || ""}\${path}\${queryString ? \`?\${queryString}\` : ""}\`,
222+
{
223+
...requestParams,
224+
headers: {
225+
...(requestParams.headers || {}),
226+
...(type && type !== ContentType.FormData
227+
? { "Content-Type": type }
228+
: {}),
229+
},
230+
signal:
231+
(cancelToken
232+
? this.createAbortSignal(cancelToken)
233+
: requestParams.signal) || null,
234+
body:
235+
typeof body === "undefined" || body === null
236+
? null
237+
: payloadFormatter(body),
238+
},
239+
).then(async (response) => {
240+
const r = response as HttpResponse<T, E>;
241+
r.data = null as unknown as T;
242+
r.error = null as unknown as E;
243+
244+
const responseToParse = responseFormat ? response.clone() : response;
245+
const data = !responseFormat
246+
? r
247+
: await responseToParse[responseFormat]()
248+
.then((data) => {
249+
if (r.ok) {
250+
r.data = data;
251+
} else {
252+
r.error = data;
253+
}
254+
return r;
255+
})
256+
.catch((e) => {
257+
r.error = e;
258+
return r;
259+
});
260+
261+
if (cancelToken) {
262+
this.abortControllers.delete(cancelToken);
263+
}
264+
265+
if (!response.ok) throw data;
266+
return data;
267+
});
268+
};
269+
}
270+
271+
/**
272+
* @title Authentiq
273+
* @version 6
274+
* @license Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0.html)
275+
* @termsOfService http://authentiq.com/terms/
276+
* @baseUrl https://6-dot-authentiqio.appspot.com
277+
* @contact Authentiq team <[email protected]> (http://authentiq.io/support)
278+
*
279+
* Strong authentication, without the passwords.
280+
*/
281+
export class Api<
282+
SecurityDataType extends unknown,
283+
> extends HttpClient<SecurityDataType> {
284+
key = {
285+
/**
286+
* @description Register a new ID \`JWT(sub, devtoken)\` v5: \`JWT(sub, pk, devtoken, ...)\` See: https://github.com/skion/authentiq/wiki/JWT-Examples
287+
*
288+
* @tags key, post
289+
* @name KeyRegister
290+
* @request POST:/key/{PK}/{JobID}/
291+
*/
292+
keyRegister: (
293+
pk: string,
294+
jobId: string,
295+
body: any,
296+
params: RequestParams = {},
297+
) =>
298+
this.request<
299+
{
300+
/** revoke key */
301+
secret?: string;
302+
/** registered */
303+
status?: string;
304+
},
305+
any
306+
>({
307+
path: \`/key/\${pk}/\${jobId}/\`,
308+
method: "POST",
309+
body: body,
310+
format: "json",
311+
...params,
312+
}),
313+
};
314+
}
315+
"
316+
`;

0 commit comments

Comments
 (0)