@mittwald/api-code-generator 0.0.0-development-04b7288-20240610 → 0.0.0-development-7c0a25b-20240611

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/esm/commands/generate/index.js +70 -0
  2. package/dist/esm/commands/validate/index.js +21 -0
  3. package/dist/esm/generation/asyncStringJoin.js +4 -0
  4. package/dist/esm/generation/asyncStringJoin.test.js +5 -0
  5. package/dist/esm/generation/compileJsonSchema.js +10 -0
  6. package/dist/esm/generation/format.js +17 -0
  7. package/dist/esm/generation/model/CodeGenerationModel.js +43 -0
  8. package/dist/esm/generation/model/components/Components.js +61 -0
  9. package/dist/esm/generation/model/components/Parameters.js +30 -0
  10. package/dist/esm/generation/model/components/RequestBodies.js +25 -0
  11. package/dist/esm/generation/model/components/Response.js +27 -0
  12. package/dist/esm/generation/model/components/ResponseContent.js +12 -0
  13. package/dist/esm/generation/model/components/Responses.js +29 -0
  14. package/dist/esm/generation/model/components/Schemas.js +25 -0
  15. package/dist/esm/generation/model/components/SecurityScheme.js +21 -0
  16. package/dist/esm/generation/model/components/SecuritySchemes.js +43 -0
  17. package/dist/esm/generation/model/global/JSONSchema.js +44 -0
  18. package/dist/esm/generation/model/global/Name.js +20 -0
  19. package/dist/esm/generation/model/global/Name.test.js +10 -0
  20. package/dist/esm/generation/model/paths/Path.js +35 -0
  21. package/dist/esm/generation/model/paths/Paths.js +192 -0
  22. package/dist/esm/generation/model/paths/operation/Operation.js +108 -0
  23. package/dist/esm/generation/model/paths/operation/RequestParameters.js +98 -0
  24. package/dist/esm/generation/model/paths/operation/responses/Response.js +25 -0
  25. package/dist/esm/generation/model/paths/operation/responses/ResponseContent.js +31 -0
  26. package/dist/esm/generation/model/paths/operation/responses/ResponseContentTypes.js +47 -0
  27. package/dist/esm/generation/model/paths/operation/responses/Responses.js +31 -0
  28. package/dist/esm/generation/model/tags/Tag.js +12 -0
  29. package/dist/esm/generation/prepareTypeScriptOutput.js +10 -0
  30. package/dist/esm/generation/refs/assertNoRefs.js +6 -0
  31. package/dist/esm/generation/refs/componentRefsToCustomTypes.js +33 -0
  32. package/dist/esm/generation/refs/componentRefsToCustomTypes.test.js +44 -0
  33. package/dist/esm/generation/refs/extractComponentRefName.js +8 -0
  34. package/dist/esm/generation/refs/isRef.js +3 -0
  35. package/dist/esm/generation/refs/refNameToTSName.js +3 -0
  36. package/dist/esm/generation/refs/refNameToTSName.test.js +9 -0
  37. package/dist/esm/generation/refs/splitRefNamespaces.js +1 -0
  38. package/dist/esm/generation/refs/splitRefNamespaces.test.js +10 -0
  39. package/dist/esm/generation/tsNamespaceName.js +2 -0
  40. package/dist/esm/generation/tsTypeName.js +6 -0
  41. package/dist/esm/generation/tsTypeName.test.js +12 -0
  42. package/dist/esm/index.js +7 -0
  43. package/dist/esm/lib/makeError.js +7 -0
  44. package/dist/esm/lib/relativePath.js +3 -0
  45. package/dist/esm/lib/writeIfChangedAsync.js +8 -0
  46. package/dist/esm/loading/UniversalContentLoader.js +65 -0
  47. package/dist/esm/loading/UniversalContentLoader.test.js +40 -0
  48. package/dist/esm/loading/UniversalFileLoader.js +42 -0
  49. package/dist/esm/loading/types.js +1 -0
  50. package/dist/esm/openapi/OpenAPISchemaValidationError.js +17 -0
  51. package/dist/esm/openapi/OpenApiSpec.js +44 -0
  52. package/dist/types/commands/generate/index.d.ts +18 -0
  53. package/dist/types/commands/validate/index.d.ts +8 -0
  54. package/dist/types/generation/asyncStringJoin.d.ts +1 -0
  55. package/dist/types/generation/asyncStringJoin.test.d.ts +1 -0
  56. package/dist/types/generation/compileJsonSchema.d.ts +2 -0
  57. package/dist/types/generation/format.d.ts +1 -0
  58. package/dist/types/generation/model/CodeGenerationModel.d.ts +19 -0
  59. package/dist/types/generation/model/components/Components.d.ts +24 -0
  60. package/dist/types/generation/model/components/Parameters.d.ts +11 -0
  61. package/dist/types/generation/model/components/RequestBodies.d.ts +13 -0
  62. package/dist/types/generation/model/components/Response.d.ts +12 -0
  63. package/dist/types/generation/model/components/ResponseContent.d.ts +10 -0
  64. package/dist/types/generation/model/components/Responses.d.ts +13 -0
  65. package/dist/types/generation/model/components/Schemas.d.ts +13 -0
  66. package/dist/types/generation/model/components/SecurityScheme.d.ts +10 -0
  67. package/dist/types/generation/model/components/SecuritySchemes.d.ts +14 -0
  68. package/dist/types/generation/model/global/JSONSchema.d.ts +13 -0
  69. package/dist/types/generation/model/global/Name.d.ts +8 -0
  70. package/dist/types/generation/model/global/Name.test.d.ts +1 -0
  71. package/dist/types/generation/model/paths/Path.d.ts +13 -0
  72. package/dist/types/generation/model/paths/Paths.d.ts +25 -0
  73. package/dist/types/generation/model/paths/operation/Operation.d.ts +24 -0
  74. package/dist/types/generation/model/paths/operation/RequestParameters.d.ts +17 -0
  75. package/dist/types/generation/model/paths/operation/responses/Response.d.ts +16 -0
  76. package/dist/types/generation/model/paths/operation/responses/ResponseContent.d.ts +12 -0
  77. package/dist/types/generation/model/paths/operation/responses/ResponseContentTypes.d.ts +16 -0
  78. package/dist/types/generation/model/paths/operation/responses/Responses.d.ts +14 -0
  79. package/dist/types/generation/model/tags/Tag.d.ts +8 -0
  80. package/dist/types/generation/prepareTypeScriptOutput.d.ts +1 -0
  81. package/dist/types/generation/refs/assertNoRefs.d.ts +2 -0
  82. package/dist/types/generation/refs/componentRefsToCustomTypes.d.ts +1 -0
  83. package/dist/types/generation/refs/componentRefsToCustomTypes.test.d.ts +1 -0
  84. package/dist/types/generation/refs/extractComponentRefName.d.ts +2 -0
  85. package/dist/types/generation/refs/isRef.d.ts +2 -0
  86. package/dist/types/generation/refs/refNameToTSName.d.ts +1 -0
  87. package/dist/types/generation/refs/refNameToTSName.test.d.ts +1 -0
  88. package/dist/types/generation/refs/splitRefNamespaces.d.ts +1 -0
  89. package/dist/types/generation/refs/splitRefNamespaces.test.d.ts +1 -0
  90. package/dist/types/generation/tsNamespaceName.d.ts +1 -0
  91. package/dist/types/generation/tsTypeName.d.ts +1 -0
  92. package/dist/types/generation/tsTypeName.test.d.ts +1 -0
  93. package/dist/types/index.d.ts +7 -0
  94. package/dist/types/lib/makeError.d.ts +1 -0
  95. package/dist/types/lib/relativePath.d.ts +1 -0
  96. package/dist/types/lib/writeIfChangedAsync.d.ts +1 -0
  97. package/dist/types/loading/UniversalContentLoader.d.ts +10 -0
  98. package/dist/types/loading/UniversalContentLoader.test.d.ts +1 -0
  99. package/dist/types/loading/UniversalFileLoader.d.ts +9 -0
  100. package/dist/types/loading/types.d.ts +4 -0
  101. package/dist/types/openapi/OpenAPISchemaValidationError.d.ts +7 -0
  102. package/dist/types/openapi/OpenApiSpec.d.ts +11 -0
  103. package/package.json +2 -2
@@ -0,0 +1,192 @@
1
+ import { Name } from "../global/Name.js";
2
+ import { Path } from "./Path.js";
3
+ import { asyncStringJoin } from "../../asyncStringJoin.js";
4
+ import camelcase from "camelcase";
5
+ export class Paths {
6
+ static ns = "Paths";
7
+ paths;
8
+ model;
9
+ name;
10
+ constructor(model, paths) {
11
+ this.model = model;
12
+ this.name = new Name(Paths.ns, model.rootNamespace);
13
+ this.paths = Object.entries(paths ?? {})
14
+ .map(([name, pathDoc]) => {
15
+ if (pathDoc) {
16
+ return Path.fromDoc(this, name, pathDoc);
17
+ }
18
+ })
19
+ .filter((p) => !!p);
20
+ }
21
+ async compileTypes(opts) {
22
+ const t = {
23
+ ns: Paths.ns,
24
+ types: await asyncStringJoin(this.paths, (path) => path.compileTypes(opts)),
25
+ };
26
+ return `\
27
+ namespace ${t.ns} {
28
+ ${t.types}
29
+ }
30
+ `;
31
+ }
32
+ async compileOperationTypes() {
33
+ const t = {
34
+ types: this.getFlattenedOperations()
35
+ .map((operation) => operation.compileRequestResponseTypes())
36
+ .join("\r\n"),
37
+ };
38
+ return `
39
+ namespace Operations {
40
+ ${t.types}
41
+ }
42
+ `;
43
+ }
44
+ compileDescriptors() {
45
+ const t = {
46
+ ns: this.model.rootNamespace.tsType,
47
+ descriptors: this.getFlattenedOperations()
48
+ .map((operation) => operation.compileDescriptor())
49
+ .join("\r\n"),
50
+ };
51
+ return `\
52
+ import { Simplify } from "@mittwald/api-client-commons";
53
+ import { RequestType } from "@mittwald/api-client-commons";
54
+ import { Response } from "@mittwald/api-client-commons";
55
+ import { OpenAPIOperation } from "@mittwald/api-client-commons";
56
+ import { ${t.ns} } from "./types.js";
57
+
58
+ ${t.descriptors}
59
+ `;
60
+ }
61
+ getFlattenedOperations(httpMethod) {
62
+ return this.paths
63
+ .flatMap((p) => p.operations)
64
+ .filter((p) => httpMethod === undefined ||
65
+ p.httpMethod.raw.toLowerCase() === httpMethod.toLowerCase());
66
+ }
67
+ getGroupedOperationsByTag(httpMethod) {
68
+ const result = new Map();
69
+ for (const operation of this.getFlattenedOperations(httpMethod)) {
70
+ for (const tag of operation.tags) {
71
+ const operationsOfTag = result.get(tag);
72
+ if (operationsOfTag) {
73
+ operationsOfTag.push(operation);
74
+ }
75
+ else {
76
+ result.set(tag, [operation]);
77
+ }
78
+ }
79
+ }
80
+ return Array.from(result.entries());
81
+ }
82
+ compileClientMethodsOfTag(tag, operations) {
83
+ const t = {
84
+ tag: tag.name.tsVar,
85
+ comment: tag.description,
86
+ methods: operations
87
+ .map((op) => op.compileClientMethod(tag))
88
+ .join(",\r\n"),
89
+ };
90
+ return `\
91
+ /** ${t.comment} */
92
+ public readonly ${t.tag} = {
93
+ ${t.methods}
94
+ }
95
+ `;
96
+ }
97
+ compileClient() {
98
+ const t = {
99
+ clientClassName: `${this.model.rootNamespace.tsType}Client`,
100
+ methods: this.getGroupedOperationsByTag()
101
+ .map(([tag, operations]) => this.compileClientMethodsOfTag(tag, operations))
102
+ .join("\r\n"),
103
+ };
104
+ return `\
105
+ import * as descriptors from "./descriptors.js";
106
+ import { ApiClientBase } from "@mittwald/api-client-commons";
107
+
108
+ export class ${t.clientClassName} extends ApiClientBase {
109
+ ${t.methods}
110
+ }
111
+
112
+ export default ${t.clientClassName}
113
+ `;
114
+ }
115
+ compileReactClientApiName(tag) {
116
+ return `build${camelcase(tag.name.tsVar, {
117
+ pascalCase: true,
118
+ })}Api`;
119
+ }
120
+ compileReactClientApiBuilderOfTag(baseClientClassName, tag, operations) {
121
+ const t = {
122
+ apiName: this.compileReactClientApiName(tag),
123
+ methods: operations
124
+ .map((op) => op.compileReactClientMethod(tag))
125
+ .join(",\r\n"),
126
+ baseClientClassName,
127
+ };
128
+ return `\
129
+ const ${t.apiName} = (baseClient: ${t.baseClientClassName}) => ({
130
+ ${t.methods}
131
+ })
132
+ `;
133
+ }
134
+ compileReactClientPropertyInitializer(tag) {
135
+ const tagName = tag.name.tsVar;
136
+ const t = {
137
+ apiName: this.compileReactClientApiName(tag),
138
+ comment: tag.description,
139
+ };
140
+ return `\
141
+ this.${tagName} = ${t.apiName}(baseClient);
142
+ `;
143
+ }
144
+ compileReactClientProperties(tag) {
145
+ const tagName = tag.name.tsVar;
146
+ const t = {
147
+ apiName: this.compileReactClientApiName(tag),
148
+ comment: tag.description,
149
+ };
150
+ return `\
151
+ /** ${t.comment} */
152
+ public readonly ${tagName}: ReturnType<typeof ${t.apiName}>;
153
+ `;
154
+ }
155
+ compileReactClient() {
156
+ const baseClientClassName = `${this.model.rootNamespace.tsType}Client`;
157
+ const clientClassName = `${this.model.rootNamespace.tsType}ClientReact`;
158
+ const t = {
159
+ baseClientClassName,
160
+ clientClassName,
161
+ apiBuilder: this.getGroupedOperationsByTag("GET")
162
+ .map(([tag, operations]) => this.compileReactClientApiBuilderOfTag(baseClientClassName, tag, operations))
163
+ .join("\r\n"),
164
+ clientProperties: this.getGroupedOperationsByTag("GET")
165
+ .map(([tag]) => this.compileReactClientProperties(tag))
166
+ .join("\r\n"),
167
+ clientPropertyInitializers: this.getGroupedOperationsByTag("GET")
168
+ .map(([tag]) => this.compileReactClientPropertyInitializer(tag))
169
+ .join("\r\n"),
170
+ };
171
+ return `\
172
+ import ${t.baseClientClassName} from "./client.js";
173
+ import { ApiCallAsyncResourceFactory } from "@mittwald/api-client-commons/react";
174
+ import * as descriptors from "./descriptors.js";
175
+ export * from "@mittwald/react-use-promise";
176
+
177
+ ${t.apiBuilder}
178
+
179
+ export class ${t.clientClassName} {
180
+ ${t.clientProperties}
181
+
182
+ private constructor(baseClient: ${t.baseClientClassName}) {
183
+ ${t.clientPropertyInitializers}
184
+ }
185
+
186
+ public static fromBaseClient(baseClient: ${t.baseClientClassName}): ${t.clientClassName} {
187
+ return new ${t.clientClassName}(baseClient);
188
+ }
189
+ }
190
+ `;
191
+ }
192
+ }
@@ -0,0 +1,108 @@
1
+ import { RequestParameters } from "./RequestParameters.js";
2
+ import { Name } from "../../global/Name.js";
3
+ import { Responses } from "./responses/Responses.js";
4
+ export class Operation {
5
+ path;
6
+ id;
7
+ httpMethod;
8
+ parameters;
9
+ responses;
10
+ summary;
11
+ tags;
12
+ constructor(path, httpMethod, doc) {
13
+ const fallbackId = `${httpMethod.raw}-${path.name.raw}`;
14
+ this.id = new Name(doc.operationId ?? fallbackId);
15
+ this.path = path;
16
+ this.httpMethod = httpMethod;
17
+ this.parameters = RequestParameters.fromDoc(this, doc);
18
+ this.responses = new Responses(this, doc.responses);
19
+ this.summary = doc.summary;
20
+ this.tags =
21
+ doc.tags
22
+ ?.map((name) => path.paths.model.tags.find((t) => t.name.raw === name))
23
+ .filter((t) => !!t) ?? [];
24
+ }
25
+ static fromDoc(path, httpMethod, doc) {
26
+ return new Operation(path, new Name(httpMethod, path.name), doc);
27
+ }
28
+ async compileTypes(options) {
29
+ const t = {
30
+ ns: this.httpMethod.tsType,
31
+ parameters: await this.parameters.compileTypes(options),
32
+ responses: await this.responses.compileTypes(options),
33
+ };
34
+ return `\
35
+ namespace ${t.ns} {
36
+ ${t.parameters};
37
+ ${t.responses};
38
+ }
39
+ `;
40
+ }
41
+ compileDescriptor() {
42
+ const t = {
43
+ constName: this.id.tsVar,
44
+ requestType: this.parameters.compileDescriptorRequestType(),
45
+ responseTypes: this.responses.compileDescriptorTypes(),
46
+ path: this.path.name.raw,
47
+ method: this.httpMethod.raw.toUpperCase(),
48
+ operationId: this.id.raw,
49
+ };
50
+ return `\
51
+ /** ${this.summary} */
52
+ export const ${t.constName}: OpenAPIOperation<
53
+ ${t.requestType},
54
+ ${t.responseTypes}
55
+ > = {
56
+ path: "${t.path}",
57
+ method: "${t.method}",
58
+ operationId: "${t.operationId}",
59
+ };
60
+ `;
61
+ }
62
+ compileRequestResponseTypes() {
63
+ const t = {
64
+ ns: this.id.tsType,
65
+ descriptor: this.id.tsVar,
66
+ };
67
+ return `\
68
+ namespace ${t.ns} {
69
+ type RequestData = InferredRequestData<typeof descriptors.${t.descriptor}>;
70
+ type ResponseData<TStatus extends HttpStatus = 200> = InferredResponseData<typeof descriptors.${t.descriptor}, TStatus>;
71
+ }
72
+ `;
73
+ }
74
+ getMethodName(tag) {
75
+ const methodName = this.id.tsVar;
76
+ return new Name(methodName.startsWith(tag.name.tsVar)
77
+ ? methodName.substring(tag.name.tsVar.length)
78
+ : methodName).tsVar;
79
+ }
80
+ compileClientMethod(tag) {
81
+ const methodName = this.getMethodName(tag);
82
+ const t = {
83
+ methodName,
84
+ descriptorName: this.id.tsVar,
85
+ };
86
+ return `\
87
+ /** ${this.summary} */
88
+ ${t.methodName}: this.requestFunctionFactory(
89
+ descriptors.${t.descriptorName}
90
+ )
91
+ `;
92
+ }
93
+ compileReactClientMethod(tag) {
94
+ const methodName = this.getMethodName(tag);
95
+ const t = {
96
+ methodName,
97
+ descriptorName: this.id.tsVar,
98
+ tag: tag.name.tsVar,
99
+ };
100
+ return `\
101
+ /** ${this.summary} */
102
+ ${t.methodName}: new ApiCallAsyncResourceFactory(
103
+ descriptors.${t.descriptorName},
104
+ baseClient.${t.tag}.${t.methodName}
105
+ ).getApiResource
106
+ `;
107
+ }
108
+ }
@@ -0,0 +1,98 @@
1
+ import { JSONSchema } from "../../global/JSONSchema.js";
2
+ import { Name } from "../../global/Name.js";
3
+ import { isRef } from "../../../refs/isRef.js";
4
+ import invariant from "invariant";
5
+ export class RequestParameters {
6
+ static ns = "Parameters";
7
+ path;
8
+ query;
9
+ header;
10
+ body;
11
+ operation;
12
+ constructor(operation, body, path, query, header) {
13
+ this.operation = operation;
14
+ this.path = path;
15
+ this.query = query;
16
+ this.header = header;
17
+ this.body = body;
18
+ }
19
+ static constructParametersSchema(components, name, $in, parameters = [], securitySchemes) {
20
+ const properties = {};
21
+ const required = [];
22
+ for (const parameter of parameters) {
23
+ const resolvedParameter = components.resolveRef("parameters", parameter);
24
+ if (resolvedParameter.in === $in) {
25
+ const jsonSchemaObject = isRef(parameter)
26
+ ? parameter
27
+ : parameter.schema;
28
+ invariant(!!jsonSchemaObject, `Could not find schema for request parameter ${name.raw}`);
29
+ properties[resolvedParameter.name] = jsonSchemaObject;
30
+ if (resolvedParameter.required) {
31
+ required.push(resolvedParameter.name);
32
+ }
33
+ }
34
+ }
35
+ const securitySchemesJSONSchemas = securitySchemes
36
+ .filter((s) => s.in === $in)
37
+ .map((s) => s.jsonSchema.asCustomTypeRef());
38
+ const jsonSchemaObject = {
39
+ allOf: [
40
+ { type: "object", required, properties },
41
+ ...securitySchemesJSONSchemas,
42
+ ],
43
+ };
44
+ return new JSONSchema(new Name($in, name), jsonSchemaObject);
45
+ }
46
+ static fromDoc(operation, doc) {
47
+ const name = new Name(RequestParameters.ns, operation.httpMethod);
48
+ const requestBodySchema = doc.requestBody &&
49
+ "content" in doc.requestBody &&
50
+ "application/json" in doc.requestBody.content
51
+ ? doc.requestBody.content["application/json"].schema
52
+ : doc.requestBody;
53
+ const body = requestBodySchema
54
+ ? new JSONSchema(new Name("requestBody", name), requestBodySchema)
55
+ : undefined;
56
+ const requiredSecuritySchemeNames = doc.security?.flatMap((obj) => Object.keys(obj)) ?? [];
57
+ const requiredSecuritySchemes = requiredSecuritySchemeNames.map((name) => operation.path.paths.model.components.securitySchemes.requireScheme(name));
58
+ const components = operation.path.paths.model.components;
59
+ const path = RequestParameters.constructParametersSchema(components, name, "path", doc.parameters, requiredSecuritySchemes);
60
+ const header = RequestParameters.constructParametersSchema(components, name, "header", doc.parameters, requiredSecuritySchemes);
61
+ const query = RequestParameters.constructParametersSchema(components, name, "query", doc.parameters, requiredSecuritySchemes);
62
+ return new RequestParameters(operation, body, path, query, header);
63
+ }
64
+ async compileTypes(opts) {
65
+ const header = this.header?.cloneWithOptionalProperties(opts.optionalHeaders ?? []);
66
+ const t = {
67
+ ns: RequestParameters.ns,
68
+ path: (await this.path?.compile(opts)) ?? "",
69
+ body: (await this.body?.compile(opts)) ?? "",
70
+ header: (await header?.compile(opts)) ?? "",
71
+ query: (await this.query?.compile(opts)) ?? "",
72
+ };
73
+ return `\
74
+ namespace ${t.ns} {
75
+ ${t.path}
76
+ ${t.body}
77
+ ${t.header}
78
+ ${t.query}
79
+ }
80
+ `;
81
+ }
82
+ compileDescriptorRequestType() {
83
+ const t = {
84
+ body: this.body?.name.tsTypeWithNamespace ?? "null",
85
+ path: this.path?.name.tsTypeWithNamespace ?? "null",
86
+ header: this.header?.name.tsTypeWithNamespace ?? "null",
87
+ query: this.query?.name.tsTypeWithNamespace ?? "null",
88
+ };
89
+ return `\
90
+ RequestType<
91
+ Simplify<${t.body}>,
92
+ Simplify<${t.path}>,
93
+ Simplify<${t.query}>,
94
+ Simplify<${t.header}>
95
+ >
96
+ `;
97
+ }
98
+ }
@@ -0,0 +1,25 @@
1
+ import { ResponseContentTypes } from "./ResponseContentTypes.js";
2
+ export class Response {
3
+ responses;
4
+ contentTypes;
5
+ httpStatus;
6
+ constructor(responses, httpStatus, doc) {
7
+ this.responses = responses;
8
+ this.httpStatus = httpStatus;
9
+ this.contentTypes = new ResponseContentTypes(this, doc);
10
+ }
11
+ static fromDoc(responses, httpStatus, doc) {
12
+ return new Response(responses, httpStatus, doc);
13
+ }
14
+ async compileContentTypes(opts) {
15
+ const t = {
16
+ ns: this.httpStatus.tsType,
17
+ types: await this.contentTypes.compileTypes(opts),
18
+ };
19
+ return `\
20
+ namespace ${this.httpStatus.tsType} {
21
+ ${t.types}
22
+ }
23
+ `;
24
+ }
25
+ }
@@ -0,0 +1,31 @@
1
+ import { JSONSchema } from "../../../global/JSONSchema.js";
2
+ import { Name } from "../../../global/Name.js";
3
+ export class ResponseContent {
4
+ mediaType;
5
+ schema;
6
+ contentTypes;
7
+ constructor(contentTypes, mediaType, schema) {
8
+ this.mediaType = new Name(mediaType, contentTypes.name);
9
+ this.schema = new JSONSchema(this.mediaType, schema);
10
+ this.contentTypes = contentTypes;
11
+ }
12
+ static buildEmpty(contentTypes) {
13
+ return new ResponseContent(contentTypes, "empty");
14
+ }
15
+ compileDescriptorsResponseContentType() {
16
+ const t = {
17
+ type: this.mediaType.tsTypeWithNamespace,
18
+ httpStatus: this.contentTypes.response.httpStatus.raw === "default"
19
+ ? '"default"'
20
+ : this.contentTypes.response.httpStatus.raw,
21
+ mediaType: this.mediaType.raw,
22
+ };
23
+ return `\
24
+ Response<
25
+ Simplify<${t.type}>,
26
+ ${t.httpStatus},
27
+ "${t.mediaType}"
28
+ >
29
+ `;
30
+ }
31
+ }
@@ -0,0 +1,47 @@
1
+ import { Name } from "../../../global/Name.js";
2
+ import { ResponseContent } from "./ResponseContent.js";
3
+ import { asyncStringJoin } from "../../../../asyncStringJoin.js";
4
+ import invariant from "invariant";
5
+ export class ResponseContentTypes {
6
+ static ns = "Content";
7
+ contentTypes;
8
+ name;
9
+ response;
10
+ constructor(response, responseDoc) {
11
+ this.response = response;
12
+ this.name = new Name(ResponseContentTypes.ns, response.httpStatus);
13
+ this.contentTypes =
14
+ "content" in responseDoc
15
+ ? this.buildContentTypesFromResponseObject(responseDoc)
16
+ : "$ref" in responseDoc
17
+ ? this.buildContentTypesFromReferenceObject(responseDoc)
18
+ : [ResponseContent.buildEmpty(this)];
19
+ }
20
+ buildContentTypesFromResponseObject(doc) {
21
+ return Object.entries(doc.content ?? {}).map(([mediaType, mediaTypeObject]) => new ResponseContent(this, mediaType, mediaTypeObject.schema));
22
+ }
23
+ buildContentTypesFromReferenceObject(doc) {
24
+ const responseComponents = this.response.responses.operation.path.paths.model.components.responses
25
+ .responses;
26
+ const referencedResponse = responseComponents.find((r) => `#/components/responses/${r.name.raw}` === doc.$ref);
27
+ invariant(!!referencedResponse, `Referenced response ${doc.$ref} not found`);
28
+ return referencedResponse.contents.map((c) => new ResponseContent(this, c.mediaType.raw, c.schema));
29
+ }
30
+ compileDescriptorTypes() {
31
+ return this.contentTypes
32
+ .flatMap((contentType) => contentType.compileDescriptorsResponseContentType())
33
+ .filter((s) => s !== "")
34
+ .join(" | ");
35
+ }
36
+ async compileTypes(opts) {
37
+ const t = {
38
+ ns: ResponseContentTypes.ns,
39
+ types: await asyncStringJoin(this.contentTypes, (contentType) => contentType.schema.compile(opts)),
40
+ };
41
+ return `\
42
+ namespace ${t.ns} {
43
+ ${t.types}
44
+ }
45
+ `;
46
+ }
47
+ }
@@ -0,0 +1,31 @@
1
+ import { Name } from "../../../global/Name.js";
2
+ import { asyncStringJoin } from "../../../../asyncStringJoin.js";
3
+ import { Response } from "./Response.js";
4
+ export class Responses {
5
+ static ns = "Responses";
6
+ responses;
7
+ operation;
8
+ name;
9
+ constructor(operation, responses) {
10
+ this.operation = operation;
11
+ this.name = new Name(Responses.ns, operation.httpMethod);
12
+ this.responses = Object.entries(responses ?? {}).map(([name, response]) => Response.fromDoc(this, new Name(name, this.name), response));
13
+ }
14
+ async compileTypes(opts) {
15
+ const t = {
16
+ responses: await asyncStringJoin(this.responses, (response) => response.compileContentTypes(opts)),
17
+ ns: Responses.ns,
18
+ };
19
+ return `\
20
+ namespace ${t.ns} {
21
+ ${t.responses}
22
+ }
23
+ `;
24
+ }
25
+ compileDescriptorTypes() {
26
+ return this.responses
27
+ .flatMap((response) => response.contentTypes.compileDescriptorTypes())
28
+ .filter((s) => s !== "")
29
+ .join(" | ");
30
+ }
31
+ }
@@ -0,0 +1,12 @@
1
+ import { Name } from "../global/Name.js";
2
+ export class Tag {
3
+ name;
4
+ description;
5
+ constructor(name, description) {
6
+ this.name = name;
7
+ this.description = description;
8
+ }
9
+ static fromDoc(doc) {
10
+ return new Tag(new Name(doc.name), doc.description);
11
+ }
12
+ }
@@ -0,0 +1,10 @@
1
+ import { format } from "./format.js";
2
+ const header = `\
3
+ /* eslint-disable */
4
+ /* prettier-ignore */
5
+ /* This file is auto-generated with acg (@mittwald/api-code-generator) */
6
+ `;
7
+ export const prepareTypeScriptOutput = async (content) => {
8
+ const formatted = await format(content);
9
+ return header + formatted;
10
+ };
@@ -0,0 +1,6 @@
1
+ import { isRef } from "./isRef.js";
2
+ export function assertNoRefs(obj) {
3
+ if (isRef(obj)) {
4
+ throw new Error(`$ref's are not supported here (ref: ${obj.$ref})`);
5
+ }
6
+ }
@@ -0,0 +1,33 @@
1
+ import { refNameToTSName } from "./refNameToTSName.js";
2
+ import is from "@sindresorhus/is";
3
+ import cloneDeep from "clone-deep";
4
+ const getComponentRef = (something) => {
5
+ if ("$ref" in something &&
6
+ typeof something.$ref === "string" &&
7
+ something.$ref.startsWith("#/components/")) {
8
+ return something.$ref;
9
+ }
10
+ };
11
+ export const componentRefsToCustomTypes = (rootNamespace, something, clone = true) => {
12
+ if (clone) {
13
+ something = cloneDeep(something);
14
+ }
15
+ if (!is.nonEmptyObject(something)) {
16
+ return something;
17
+ }
18
+ if (is.array(something)) {
19
+ return something.map((item) => componentRefsToCustomTypes(rootNamespace, item, false));
20
+ }
21
+ const componentRef = getComponentRef(something);
22
+ if (componentRef !== undefined) {
23
+ // see https://github.com/bcherny/json-schema-to-typescript#custom-schema-properties
24
+ return {
25
+ tsType: refNameToTSName(rootNamespace, componentRef),
26
+ type: "object",
27
+ };
28
+ }
29
+ return Object.fromEntries(Object.entries(something).map(([key, value]) => [
30
+ key,
31
+ componentRefsToCustomTypes(rootNamespace, value, false),
32
+ ]));
33
+ };
@@ -0,0 +1,44 @@
1
+ import { componentRefsToCustomTypes } from "./componentRefsToCustomTypes.js";
2
+ test.each([
3
+ [
4
+ {
5
+ $ref: "#/components/foo/bar",
6
+ },
7
+ {
8
+ type: "object",
9
+ tsType: "Test.Components.Foo.Bar",
10
+ },
11
+ ],
12
+ [
13
+ {
14
+ $ref: "#/no/component",
15
+ },
16
+ {
17
+ $ref: "#/no/component",
18
+ },
19
+ ],
20
+ [
21
+ {
22
+ noRef: "No fun",
23
+ },
24
+ {
25
+ noRef: "No fun",
26
+ },
27
+ ],
28
+ [
29
+ {
30
+ nested: {
31
+ $ref: "#/components/foo/bar",
32
+ },
33
+ },
34
+ {
35
+ nested: {
36
+ type: "object",
37
+ tsType: "Test.Components.Foo.Bar",
38
+ },
39
+ },
40
+ ],
41
+ ])("componentRefsToCustomTypes works for test %#", (input, expected) => {
42
+ const result = componentRefsToCustomTypes("test", input);
43
+ expect(result).toMatchObject(expected);
44
+ });
@@ -0,0 +1,8 @@
1
+ import { splitRefNamespaces } from "./splitRefNamespaces.js";
2
+ export const extractComponentRefName = (ref, component) => {
3
+ const parts = splitRefNamespaces(ref);
4
+ if (parts[parts.length - 2] === component) {
5
+ return parts[parts.length - 1];
6
+ }
7
+ throw new Error(`Could not extract ref name (component: ${component}, ref: ${ref})`);
8
+ };
@@ -0,0 +1,3 @@
1
+ export function isRef(obj) {
2
+ return "$ref" in obj && typeof obj.$ref === "string";
3
+ }
@@ -0,0 +1,3 @@
1
+ import { splitRefNamespaces } from "./splitRefNamespaces.js";
2
+ import { tsNamespaceName } from "../tsNamespaceName.js";
3
+ export const refNameToTSName = (rootNamespace, $ref) => tsNamespaceName(rootNamespace, ...splitRefNamespaces($ref));
@@ -0,0 +1,9 @@
1
+ import { refNameToTSName } from "./refNameToTSName.js";
2
+ test.each([
3
+ ["#/foo/bar", "Test.Foo.Bar"],
4
+ ["foo", "Test.Foo"],
5
+ ["#/", "Test"],
6
+ ])("splitRefNamespaces works for test %#", (input, expected) => {
7
+ const result = refNameToTSName("Test", input);
8
+ expect(result).toEqual(expected);
9
+ });
@@ -0,0 +1 @@
1
+ export const splitRefNamespaces = ($ref) => $ref.split("/").filter((ns) => ns !== "#" && ns !== "");