Skip to content

Commit 16c25c6

Browse files
drwpowMrLeebo
andauthored
Alphabetically sort transforms (#966)
* Alphabetically sort output from transforms * Refactor sort into an --alphabetize flag and add test cases * Fix typo * Rewrite tests to test for alphabetization of specific object types * Add test for alphabetized operations and clean up operation tests * fix TS error Co-authored-by: Jeremy Liberman <[email protected]>
1 parent d32cadd commit 16c25c6

17 files changed

+536
-27
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ npx openapi-typescript schema.yaml
155155
| `--support-array-length` | | `false` | (optional) Generate tuples using array minItems / maxItems |
156156
| `--make-paths-enum` | `-pe` | `false` | (optional) Generate an enum of endpoint paths |
157157
| `--path-params-as-types` | | `false` | (optional) Substitute path parameter names with their respective types |
158+
| `--alphabetize` | | `false` | (optional) Sort types alphabetically |
158159
| `--raw-schema` | | `false` | Generate TS types from partial schema (e.g. having `components.schema` at the top level) |
159160

160161
### 🐢 Node

bin/cli.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Options
3030
--export-type (optional) Export type instead of interface
3131
--support-array-length (optional) Generate tuples using array minItems / maxItems
3232
--path-params-as-types (optional) Substitute path parameter names with their respective types
33+
--alphabetize (optional) Sort types alphabetically
3334
--version (optional) Force schema parsing version
3435
`;
3536

@@ -57,6 +58,7 @@ const flags = parser(args, {
5758
"supportArrayLength",
5859
"makePathsEnum",
5960
"pathParamsAsTypes",
61+
"alphabetize",
6062
],
6163
number: ["version"],
6264
string: ["auth", "header", "headersObject", "httpMethod", "prettierConfig"],
@@ -112,6 +114,7 @@ async function generateSchema(pathToSpec) {
112114
exportType: flags.exportType,
113115
supportArrayLength: flags.supportArrayLength,
114116
pathParamsAsTypes: flags.pathParamsAsTypes,
117+
alphabetize: flags.alphabetize,
115118
});
116119

117120
// output

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ async function openapiTS(
4646
contentNever: options.contentNever || false,
4747
makePathsEnum: options.makePathsEnum || false,
4848
pathParamsAsTypes: options.pathParamsAsTypes,
49+
alphabetize: options.alphabetize || false,
4950
rawSchema: options.rawSchema || false,
5051
supportArrayLength: options.supportArrayLength,
5152
version: options.version || 3,

src/transform/headers.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { GlobalContext, HeaderObject } from "../types.js";
2-
import { comment, tsReadonly } from "../utils.js";
2+
import { comment, getEntries, tsReadonly } from "../utils.js";
33
import { transformSchemaObj } from "./schema.js";
44

55
interface TransformHeadersOptions extends GlobalContext {
@@ -12,8 +12,7 @@ export function transformHeaderObjMap(
1212
): string {
1313
let output = "";
1414

15-
for (const k of Object.keys(headerMap)) {
16-
const v = headerMap[k];
15+
for (const [k, v] of getEntries(headerMap, options)) {
1716
if (!v.schema) continue;
1817

1918
if (v.description) output += comment(v.description);

src/transform/parameters.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { GlobalContext, ParameterObject, ReferenceObject } from "../types.js";
2-
import { comment, tsReadonly } from "../utils.js";
2+
import { comment, getEntries, tsReadonly } from "../utils.js";
33
import { transformSchemaObj } from "./schema.js";
44

55
interface TransformParametersOptions extends GlobalContext {
@@ -19,11 +19,16 @@ export function transformParametersArray(
1919

2020
// sort into map
2121
const mappedParams: Record<string, Record<string, ParameterObject>> = {};
22-
for (const paramObj of parameters as any[]) {
23-
if (paramObj.$ref && globalParameters) {
24-
const paramName = paramObj.$ref.split('["').pop().replace(PARAM_END_RE, ""); // take last segment
22+
for (const paramObj of parameters) {
23+
if ("$ref" in paramObj && paramObj.$ref && globalParameters) {
24+
// take last segment
25+
let paramName = paramObj.$ref.split('["').pop();
26+
paramName = String(paramName).replace(PARAM_END_RE, "");
27+
2528
if (globalParameters[paramName]) {
26-
const reference = globalParameters[paramName] as any;
29+
const reference = globalParameters[paramName];
30+
if (!reference.in) continue;
31+
2732
if (!mappedParams[reference.in]) mappedParams[reference.in] = {};
2833
switch (ctx.version) {
2934
case 3: {
@@ -36,7 +41,7 @@ export function transformParametersArray(
3641
case 2: {
3742
mappedParams[reference.in][reference.name || paramName] = {
3843
...reference,
39-
$ref: paramObj.$ref,
44+
...("$ref" in paramObj ? { $ref: paramObj.$ref } : null),
4045
};
4146
break;
4247
}
@@ -45,15 +50,16 @@ export function transformParametersArray(
4550
continue;
4651
}
4752

53+
if (!("in" in paramObj)) continue;
4854
if (!paramObj.in || !paramObj.name) continue;
4955
if (!mappedParams[paramObj.in]) mappedParams[paramObj.in] = {};
5056
mappedParams[paramObj.in][paramObj.name] = paramObj;
5157
}
5258

5359
// transform output
54-
for (const [paramIn, paramGroup] of Object.entries(mappedParams)) {
60+
for (const [paramIn, paramGroup] of getEntries(mappedParams, ctx)) {
5561
output += ` ${readonly}${paramIn}: {\n`; // open in
56-
for (const [paramName, paramObj] of Object.entries(paramGroup)) {
62+
for (const [paramName, paramObj] of getEntries(paramGroup, ctx)) {
5763
let paramComment = "";
5864
if (paramObj.deprecated) paramComment += `@deprecated `;
5965
if (paramObj.description) paramComment += paramObj.description;

src/transform/paths.ts

Lines changed: 2 additions & 2 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, nodeType } from "../utils.js";
2+
import { comment, tsReadonly, nodeType, getEntries } from "../utils.js";
33
import { transformOperationObj } from "./operation.js";
44
import { transformParametersArray } from "./parameters.js";
55

@@ -33,7 +33,7 @@ export function transformPathsObj(paths: Record<string, PathItemObject>, options
3333

3434
let output = "";
3535

36-
for (const [url, pathItem] of Object.entries(paths)) {
36+
for (const [url, pathItem] of getEntries(paths, options)) {
3737
if (pathItem.description) output += comment(pathItem.description); // add comment
3838

3939
if (pathItem.$ref) {

src/transform/request.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import type { GlobalContext, RequestBody } from "../types.js";
2-
import { comment, tsReadonly } from "../utils.js";
2+
import { comment, getEntries, tsReadonly } from "../utils.js";
33
import { transformSchemaObj } from "./schema.js";
44

55
export function transformRequestBodies(requestBodies: Record<string, RequestBody>, ctx: GlobalContext) {
66
let output = "";
77

8-
for (const [name, requestBody] of Object.entries(requestBodies)) {
8+
for (const [name, requestBody] of getEntries(requestBodies, ctx)) {
99
if (requestBody && requestBody.description) output += ` ${comment(requestBody.description)}`;
1010
output += ` "${name}": {\n ${transformRequestBodyObj(requestBody, ctx)}\n }\n`;
1111
}
@@ -20,7 +20,7 @@ export function transformRequestBodyObj(requestBody: RequestBody, ctx: GlobalCon
2020

2121
if (requestBody.content && Object.keys(requestBody.content).length) {
2222
output += ` ${readonly}content: {\n`; // open content
23-
for (const [k, v] of Object.entries(requestBody.content)) {
23+
for (const [k, v] of getEntries(requestBody.content, ctx)) {
2424
output += ` ${readonly}"${k}": ${transformSchemaObj(v.schema, { ...ctx, required: new Set<string>() })};\n`;
2525
}
2626
output += ` }\n`; // close content

src/transform/responses.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { GlobalContext } from "../types.js";
2-
import { comment, tsReadonly } from "../utils.js";
2+
import { comment, getEntries, tsReadonly } from "../utils.js";
33
import { transformHeaderObjMap } from "./headers.js";
44
import { transformSchemaObj } from "./schema.js";
55

@@ -15,9 +15,8 @@ export function transformResponsesObj(responsesObj: Record<string, any>, ctx: Gl
1515

1616
let output = "";
1717

18-
for (const httpStatusCode of Object.keys(responsesObj)) {
18+
for (const [httpStatusCode, response] of getEntries(responsesObj, ctx)) {
1919
const statusCode = Number(httpStatusCode) || `"${httpStatusCode}"`; // don’t surround w/ quotes if numeric status code
20-
const response = responsesObj[httpStatusCode];
2120
if (response.description) output += comment(response.description);
2221

2322
if (response.$ref) {
@@ -48,10 +47,10 @@ export function transformResponsesObj(responsesObj: Record<string, any>, ctx: Gl
4847
switch (ctx.version) {
4948
case 3: {
5049
output += ` ${readonly}content: {\n`; // open content
51-
for (const contentType of Object.keys(response.content)) {
52-
const contentResponse = response.content[contentType] as any;
50+
// TODO: proper type definitions for this
51+
for (const [contentType, contentResponse] of getEntries<any>(response.content, ctx)) {
5352
const responseType =
54-
contentResponse && contentResponse?.schema
53+
"schema" in contentResponse
5554
? transformSchemaObj(contentResponse.schema, { ...ctx, required: new Set<string>() })
5655
: "unknown";
5756
output += ` ${readonly}"${contentType}": ${responseType};\n`;

src/transform/schema.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
tsUnionOf,
1010
parseSingleSimpleValue,
1111
ParsedSimpleValue,
12+
getEntries,
1213
} from "../utils.js";
1314

1415
interface TransformSchemaObjOptions extends GlobalContext {
@@ -27,9 +28,7 @@ function hasDefaultValue(node: any): boolean {
2728
export function transformSchemaObjMap(obj: Record<string, any>, options: TransformSchemaObjOptions): string {
2829
let output = "";
2930

30-
for (const k of Object.keys(obj)) {
31-
const v = obj[k];
32-
31+
for (const [k, v] of getEntries(obj, options)) {
3332
// 1. Add comment in jsdoc notation
3433
const comment = prepareComment(v);
3534
if (comment) output += comment;

src/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ export interface SwaggerToTSOptions {
136136
rawSchema?: boolean;
137137
/** (optional) Generate an enum containing all API paths. **/
138138
makePathsEnum?: boolean;
139+
/** (optional) Sort types alphabetically. */
140+
alphabetize?: boolean;
139141
/** (optional) Should logging be suppressed? (necessary for STDOUT) */
140142
silent?: boolean;
141143
/** (optional) OpenAPI version. Must be present if parsing raw schema */
@@ -186,6 +188,7 @@ export interface GlobalContext {
186188
makePathsEnum: boolean;
187189
namespace?: string;
188190
pathParamsAsTypes?: boolean;
191+
alphabetize?: boolean;
189192
rawSchema: boolean;
190193
silent?: boolean;
191194
supportArrayLength?: boolean;

0 commit comments

Comments
 (0)