Skip to content

Commit 22ac62d

Browse files
Powell-v2drwpow
andauthored
feat(flags): add ability to substitute path parameter names with their respective types (#891)
Issue #889 Co-authored-by: Drew Powers <1369770+drwpow@users.noreply.github.com>
1 parent a5641e3 commit 22ac62d

File tree

6 files changed

+89
-4
lines changed

6 files changed

+89
-4
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ npx openapi-typescript schema.yaml
161161
| `--export-type` | | `false` | (optional) Export `type` instead of `interface` |
162162
| `--support-array-length` | | `false` | (optional) Generate tuples using array minItems / maxItems |
163163
| `--make-paths-enum` | `-pe` | `false` | (optional) Generate an enum of endpoint paths |
164+
| `--path-params-as-types` | | `false` | (optional) Substitute path parameter names with their respective types |
164165
| `--raw-schema` | | `false` | Generate TS types from partial schema (e.g. having `components.schema` at the top level) |
165166
| `--version` | | | Force OpenAPI version with `--version 3` or `--version 2` (required for `--raw-schema` when version is unknown) |
166167

bin/cli.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Options
2929
--paths-enum, -pe (optional) Generate an enum containing all API paths.
3030
--export-type (optional) Export type instead of interface
3131
--support-array-length (optional) Generate tuples using array minItems / maxItems
32+
--path-params-as-types (optional) Substitute path parameter names with their respective types
3233
--version (optional) Force schema parsing version
3334
`;
3435

@@ -45,7 +46,7 @@ function errorAndExit(errorMessage) {
4546
const [, , input, ...args] = process.argv;
4647
const flags = parser(args, {
4748
array: ["header"],
48-
boolean: ["defaultNonNullable", "immutableTypes", "rawSchema", "makePathsEnum", "exportType", "supportArrayLength"],
49+
boolean: ["defaultNonNullable", "immutableTypes", "rawSchema", "exportType", "supportArrayLength", "makePathsEnum", "pathParamsAsTypes"],
4950
number: ["version"],
5051
string: ["auth", "header", "headersObject", "httpMethod", "prettierConfig"],
5152
alias: {
@@ -97,6 +98,7 @@ async function generateSchema(pathToSpec) {
9798
httpMethod: flags.httpMethod,
9899
exportType: flags.exportType,
99100
supportArrayLength: flags.supportArrayLength,
101+
pathParamsAsTypes: flags.pathParamsAsTypes,
100102
});
101103

102104
// output

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ async function openapiTS(
4545
makePathsEnum: options.makePathsEnum || false,
4646
version: options.version || 3,
4747
supportArrayLength: options.supportArrayLength,
48+
pathParamsAsTypes: options.pathParamsAsTypes,
4849
};
4950

5051
// note: we may be loading many large schemas into memory at once; take care to reuse references without cloning

src/transform/paths.ts

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { GlobalContext, OperationObject, ParameterObject, PathItemObject } from "../types.js";
2-
import { comment, tsReadonly } from "../utils.js";
2+
import { comment, tsReadonly, nodeType } from "../utils.js";
33
import { transformOperationObj } from "./operation.js";
44
import { transformParametersArray } from "./parameters.js";
55

@@ -8,6 +8,24 @@ interface TransformPathsObjOption extends GlobalContext {
88
operations: Record<string, { operation: OperationObject; pathItem: PathItemObject }>;
99
}
1010

11+
const httpMethods = ["get", "put", "post", "delete", "options", "head", "patch", "trace"] as const;
12+
13+
function replacePathParamsWithTypes(url: string, params: NonNullable<PathItemObject["parameters"]>) {
14+
let result = url;
15+
16+
params.forEach((param) => {
17+
if ("in" in param && param.in === "path") {
18+
if (param.schema && "type" in param.schema) {
19+
result = result.replace(`{${param.name}}`, `\${${nodeType(param.schema)}}`);
20+
} else if (param.type) {
21+
result = result.replace(`{${param.name}}`, `\${${nodeType({ type: param.type })}}`);
22+
}
23+
}
24+
});
25+
26+
return result;
27+
}
28+
1129
/** Note: this needs to mutate objects passed in */
1230
export function transformPathsObj(paths: Record<string, PathItemObject>, options: TransformPathsObjOption): string {
1331
const { globalParameters, operations, ...ctx } = options;
@@ -23,10 +41,30 @@ export function transformPathsObj(paths: Record<string, PathItemObject>, options
2341
continue;
2442
}
2543

26-
output += ` ${readonly}"${url}": {\n`; // open PathItem
44+
let key = `"${url}"`;
45+
46+
if (url.includes("{") && url.includes("}") && ctx.pathParamsAsTypes) {
47+
let params;
48+
49+
if (pathItem.parameters) {
50+
params = pathItem.parameters;
51+
} else {
52+
const firstMethodParams = Object.values(pathItem)
53+
.map((props) => typeof props === "object" && props.parameters)
54+
.filter(Boolean)[0];
55+
56+
if (firstMethodParams) {
57+
params = firstMethodParams;
58+
}
59+
}
60+
61+
key = `[key: \`${replacePathParamsWithTypes(url, params)}\`]`;
62+
}
63+
64+
output += ` ${readonly}${key}: {\n`; // open PathItem
2765

2866
// methods
29-
for (const method of ["get", "put", "post", "delete", "options", "head", "patch", "trace"]) {
67+
for (const method of httpMethods) {
3068
const operation = (pathItem as any)[method];
3169
if (!operation) continue; // only allow valid methods
3270
if (operation.description) output += comment(operation.description); // add comment

src/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ export interface SwaggerToTSOptions {
161161
* (optional) Generate tuples using array minItems / maxItems
162162
*/
163163
supportArrayLength?: boolean;
164+
/**
165+
* (optional) Substitute path parameter names with their respective types.
166+
*/
167+
pathParamsAsTypes?: boolean;
164168
}
165169

166170
/** Context passed to all submodules */
@@ -177,4 +181,5 @@ export interface GlobalContext {
177181
makePathsEnum: boolean;
178182
version: number;
179183
supportArrayLength?: boolean;
184+
pathParamsAsTypes?: boolean;
180185
}

test/core/paths.test.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,4 +487,42 @@ describe("transformPathsObj", () => {
487487
};`)
488488
);
489489
});
490+
491+
it("transforms path params into explicit types (#889)", () => {
492+
expect(
493+
format(
494+
transform(
495+
{
496+
"/{XXX}/nested/{YYY}": {
497+
get: { responses: {} },
498+
parameters: [
499+
{ name: "XXX", in: "path", required: true, schema: { type: "string" } },
500+
{ name: "YYY", in: "path", required: true, schema: { type: "integer" } },
501+
],
502+
},
503+
},
504+
{ ...defaults, pathParamsAsTypes: true }
505+
)
506+
)
507+
).to.equal(
508+
format(`
509+
[key: \`/\${string}/nested/\${number}\`]: {
510+
get: {
511+
parameters: {
512+
path: {
513+
XXX: string;
514+
YYY: number;
515+
};
516+
};
517+
responses: {};
518+
};
519+
parameters: {
520+
path: {
521+
XXX: string;
522+
YYY: number;
523+
};
524+
};
525+
};`)
526+
);
527+
});
490528
});

0 commit comments

Comments
 (0)