@goast/core 0.6.0-beta.1 → 0.6.0

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 (88) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +67 -67
  3. package/esm/src/codegen/config.d.ts +11 -2
  4. package/esm/src/codegen/config.d.ts.map +1 -1
  5. package/esm/src/parse/openapi-types.d.ts +4 -0
  6. package/esm/src/parse/openapi-types.d.ts.map +1 -1
  7. package/esm/src/transform/api-types.d.ts +14 -0
  8. package/esm/src/transform/api-types.d.ts.map +1 -1
  9. package/esm/src/transform/helpers.d.ts +6 -0
  10. package/esm/src/transform/helpers.d.ts.map +1 -1
  11. package/esm/src/transform/helpers.js +34 -5
  12. package/esm/src/transform/transform-endpoint.js +1 -0
  13. package/esm/src/transform/transform-schema.d.ts.map +1 -1
  14. package/esm/src/transform/transform-schema.js +32 -21
  15. package/esm/src/utils/file-system.utils.d.ts.map +1 -1
  16. package/esm/src/utils/file-system.utils.js +36 -4
  17. package/esm/src/utils/schema.utils.d.ts.map +1 -1
  18. package/esm/src/utils/schema.utils.js +91 -11
  19. package/esm/src/utils/string.utils.d.ts +19 -0
  20. package/esm/src/utils/string.utils.d.ts.map +1 -1
  21. package/esm/src/utils/string.utils.js +25 -0
  22. package/package.json +1 -1
  23. package/script/src/codegen/config.d.ts +11 -2
  24. package/script/src/codegen/config.d.ts.map +1 -1
  25. package/script/src/parse/openapi-types.d.ts +4 -0
  26. package/script/src/parse/openapi-types.d.ts.map +1 -1
  27. package/script/src/transform/api-types.d.ts +14 -0
  28. package/script/src/transform/api-types.d.ts.map +1 -1
  29. package/script/src/transform/helpers.d.ts +6 -0
  30. package/script/src/transform/helpers.d.ts.map +1 -1
  31. package/script/src/transform/helpers.js +37 -5
  32. package/script/src/transform/transform-endpoint.js +1 -0
  33. package/script/src/transform/transform-schema.d.ts.map +1 -1
  34. package/script/src/transform/transform-schema.js +31 -20
  35. package/script/src/utils/file-system.utils.d.ts.map +1 -1
  36. package/script/src/utils/file-system.utils.js +35 -3
  37. package/script/src/utils/schema.utils.d.ts.map +1 -1
  38. package/script/src/utils/schema.utils.js +91 -11
  39. package/script/src/utils/string.utils.d.ts +19 -0
  40. package/script/src/utils/string.utils.d.ts.map +1 -1
  41. package/script/src/utils/string.utils.js +26 -0
  42. package/src/_dnt.shims.ts +69 -0
  43. package/src/mod.ts +35 -0
  44. package/src/src/ast/node.ts +46 -0
  45. package/src/src/codegen/config.ts +50 -0
  46. package/src/src/codegen/endpoints-generator.ts +72 -0
  47. package/src/src/codegen/generator.ts +192 -0
  48. package/src/src/codegen/internal-utils.ts +6 -0
  49. package/src/src/codegen/schemas-generator.ts +87 -0
  50. package/src/src/codegen/services-generator.ts +77 -0
  51. package/src/src/codegen/types.ts +37 -0
  52. package/src/src/collect/collector.ts +142 -0
  53. package/src/src/collect/helpers.ts +36 -0
  54. package/src/src/collect/types.ts +21 -0
  55. package/src/src/internal-utils.ts +5 -0
  56. package/src/src/parse/deref-proxy.ts +70 -0
  57. package/src/src/parse/openapi-types.ts +953 -0
  58. package/src/src/parse/parser.ts +206 -0
  59. package/src/src/parse/types.ts +28 -0
  60. package/src/src/transform/api-types.ts +259 -0
  61. package/src/src/transform/helpers.ts +251 -0
  62. package/src/src/transform/transform-document.ts +35 -0
  63. package/src/src/transform/transform-endpoint.ts +332 -0
  64. package/src/src/transform/transform-schema.ts +232 -0
  65. package/src/src/transform/transformer.ts +53 -0
  66. package/src/src/transform/types.ts +52 -0
  67. package/src/src/utils/action-provider.ts +58 -0
  68. package/src/src/utils/api-data.utils.ts +48 -0
  69. package/src/src/utils/array.utils.ts +7 -0
  70. package/src/src/utils/asset-manager.ts +51 -0
  71. package/src/src/utils/class.utils.ts +40 -0
  72. package/src/src/utils/common.utils.ts +86 -0
  73. package/src/src/utils/condition.ts +7 -0
  74. package/src/src/utils/endpoint.utils.ts +10 -0
  75. package/src/src/utils/error.utils.ts +3 -0
  76. package/src/src/utils/factory.ts +42 -0
  77. package/src/src/utils/file-system.utils.ts +97 -0
  78. package/src/src/utils/generator.utils.ts +6 -0
  79. package/src/src/utils/object.utils.ts +32 -0
  80. package/src/src/utils/schema.utils.ts +240 -0
  81. package/src/src/utils/source-builder.ts +597 -0
  82. package/src/src/utils/string-builder/options.ts +19 -0
  83. package/src/src/utils/string-builder/string-builder.ts +165 -0
  84. package/src/src/utils/string-builder/utils.ts +69 -0
  85. package/src/src/utils/string.utils.ts +394 -0
  86. package/src/src/utils/string.utils.types.ts +179 -0
  87. package/src/src/utils/type.utils.ts +93 -0
  88. package/src/src/utils/yaml-info.ts +48 -0
@@ -0,0 +1,251 @@
1
+ import { parse as parsePath } from 'node:path';
2
+ import { isOpenApiObjectProperty } from '../internal-utils.js';
3
+ import type { OpenApiHttpMethod, OpenApiObject } from '../parse/openapi-types.js';
4
+ import type { Deref, DerefSource } from '../parse/types.js';
5
+ import { isNullish } from '../utils/common.utils.js';
6
+ import { getDeepProperty } from '../utils/object.utils.js';
7
+ import { getLineInfo } from '../utils/yaml-info.js';
8
+ import type { ApiSchema, ApiSchemaAccessibility, ApiSchemaKind, ApiSchemaProperty } from './api-types.js';
9
+ import type { OpenApiTransformerContext } from './types.js';
10
+
11
+ /** Normalizes a `type` keyword to an array, so a scalar type and a 3.1 type array can be treated alike. */
12
+ export function asTypeArray(type: string | string[] | undefined | null): string[] {
13
+ if (isNullish(type)) return [];
14
+ return Array.isArray(type) ? type : [type];
15
+ }
16
+
17
+ // OpenAPI 3.1 allows `type: [~]`, which YAML parses to `null` rather than to the string `'null'`.
18
+ function isNullType(type: string | null): boolean {
19
+ return type === 'null' || type === null;
20
+ }
21
+
22
+ /** The declared types with every `null` member removed. */
23
+ export function withoutNullType(types: (string | null)[]): string[] {
24
+ return types.filter((t): t is string => !isNullType(t));
25
+ }
26
+
27
+ /** Whether the `type` keyword declares `null`, either as a scalar type or as a member of a 3.1 type array. */
28
+ export function hasNullType(type: string | string[] | undefined | null): boolean {
29
+ return asTypeArray(type).some(isNullType);
30
+ }
31
+
32
+ export function determineSchemaKind<
33
+ T extends {
34
+ oneOf?: unknown;
35
+ allOf?: unknown;
36
+ anyOf?: unknown;
37
+ type?: string | string[];
38
+ properties?: Record<string, unknown>;
39
+ additionalProperties?: unknown;
40
+ },
41
+ >(ctx: OpenApiTransformerContext, schema: T): ApiSchemaKind {
42
+ // An OpenAPI 3.1 type array that reduces to at most one non-null type is normalised away by
43
+ // `transformSchema`, which replaces `type` with the narrowed scalar (or `'null'`) and then asks this
44
+ // function again. Such an array therefore has to reach the `multi-type` branch below *before* the
45
+ // `allOf`/`anyOf` branch can claim it as `combined`, because a type array is 3.1's only way of expressing
46
+ // nullability and it would otherwise never be looked at. The second pass gives the `allOf`/`anyOf` branch
47
+ // its chance, this time with a scalar type it can judge correctly.
48
+ //
49
+ // A type array with two or more non-null members is *not* normalised away: nothing re-asks, so it must keep
50
+ // falling through to the `allOf`/`anyOf` branch exactly as it always has. Its nullability is picked up by
51
+ // `transformSchema` from the type as declared instead.
52
+ const narrowsToSingleType = Array.isArray(schema.type) && withoutNullType(schema.type).length <= 1;
53
+
54
+ if (schema.oneOf) {
55
+ return 'oneOf';
56
+ } else if (!narrowsToSingleType && schema.type !== 'object' && (schema.allOf || schema.anyOf)) {
57
+ const hasProperties = (schema.properties && Object.keys(schema.properties).length > 0) ||
58
+ schema.additionalProperties;
59
+ return hasProperties ? 'object' : 'combined';
60
+ } else if (Array.isArray(schema.type)) {
61
+ return 'multi-type';
62
+ } else if (
63
+ schema.type === 'object' ||
64
+ schema.type === 'string' ||
65
+ schema.type === 'boolean' ||
66
+ schema.type === 'null' ||
67
+ schema.type === 'number' ||
68
+ schema.type === 'integer' ||
69
+ schema.type === 'array'
70
+ ) {
71
+ return schema.type;
72
+ }
73
+
74
+ const treadAsObject = ctx.config.unknownTypeBehavior === 'always-object' ||
75
+ (ctx.config.unknownTypeBehavior === 'object-if-properties' &&
76
+ schema.properties &&
77
+ Object.keys(schema.properties).length > 0);
78
+
79
+ return treadAsObject ? 'object' : 'unknown';
80
+ }
81
+
82
+ export function determineSchemaName(
83
+ schema: {
84
+ title?: string;
85
+ $src: DerefSource<unknown>;
86
+ },
87
+ id: string,
88
+ ): { name: string; isGenerated: boolean } {
89
+ if (schema.title) return { name: schema.title, isGenerated: false };
90
+
91
+ if (schema.$src.path === '/') return { name: parsePath(schema.$src.file).name, isGenerated: true };
92
+
93
+ const schemaNameMatch = schema.$src.path.match(/(?<=\/components\/schemas\/|\/definitions\/)[^/]+$/i);
94
+ if (schemaNameMatch) {
95
+ return { name: schemaNameMatch[0], isGenerated: false };
96
+ }
97
+
98
+ const paramNameMatch = schema.$src.path.match(/\/components\/parameters\/([^/]+)\/schema$/i);
99
+ if (paramNameMatch) {
100
+ return { name: paramNameMatch[1], isGenerated: true };
101
+ }
102
+
103
+ const responseMatch = schema.$src.path.match(/\/paths\/(?<path>.+)\/(?<method>.+)\/responses\/(?<status>\d+)\//);
104
+ if (responseMatch && responseMatch.groups) {
105
+ const { path, method, status } = responseMatch.groups;
106
+ const operation = schema.$src.document.paths?.[path]?.[method as OpenApiHttpMethod] ?? {};
107
+ return {
108
+ name: `${determineEndpointName({ method, path: `/${path}`, operation })}_${status}_Response`,
109
+ isGenerated: true,
110
+ };
111
+ }
112
+
113
+ const responseCompMatch = schema.$src.path.match(/\/components\/responses\/([^/]+)\/content\/.+\/schema/);
114
+ if (responseCompMatch) {
115
+ return {
116
+ name: responseCompMatch[1].toLowerCase().endsWith('response')
117
+ ? responseCompMatch[1]
118
+ : responseCompMatch[1] + 'Response',
119
+ isGenerated: true,
120
+ };
121
+ }
122
+
123
+ const requestBodyMatch = schema.$src.path.match(/\/paths\/(?<path>.+)\/(?<method>.+)\/requestBody\//);
124
+ if (requestBodyMatch && requestBodyMatch.groups) {
125
+ const { path, method } = requestBodyMatch.groups;
126
+ const operation = schema.$src.document.paths?.[path]?.[method as OpenApiHttpMethod] ?? {};
127
+ return { name: `${determineEndpointName({ method, path: `/${path}`, operation })}_Request`, isGenerated: true };
128
+ }
129
+
130
+ const parentSchemaMatch = schema.$src.path.match(/(.*)\/properties\/([^/]*)(\/additionalProperties)?$/);
131
+ if (parentSchemaMatch) {
132
+ const parentSchema = getDeepProperty(schema.$src.document, parentSchemaMatch[1].split('/').filter(Boolean));
133
+ const parentSchemaName = determineSchemaName(
134
+ {
135
+ title: typeof parentSchema === 'object' && parentSchema !== null && 'title' in parentSchema
136
+ ? String(parentSchema.title)
137
+ : undefined,
138
+ $src: {
139
+ file: schema.$src.file,
140
+ pos: getLineInfo(schema.$src.document, parentSchemaMatch[1].split('/').filter(Boolean)),
141
+ path: parentSchemaMatch[1],
142
+ document: schema.$src.document,
143
+ originalComponent: parentSchema,
144
+ },
145
+ },
146
+ id,
147
+ );
148
+ // Continue the chain as long as the parent has a name of its own. Generated parent names are fine to build
149
+ // on — that is what makes a third-level inline object `Parent_middle_inner` instead of an opaque ordinal.
150
+ // Only the `id` fallback below is unusable, because it carries no information about the schema at all.
151
+ if (parentSchemaName.name !== id) {
152
+ return { name: `${parentSchemaName.name}_${parentSchemaMatch[2]}`, isGenerated: true };
153
+ }
154
+ }
155
+
156
+ return { name: id, isGenerated: true };
157
+ }
158
+
159
+ export function determineSchemaAccessibility(schema: {
160
+ readOnly?: boolean;
161
+ writeOnly?: boolean;
162
+ }): ApiSchemaAccessibility {
163
+ if (schema.readOnly === true) {
164
+ return schema.writeOnly === true ? 'none' : 'readOnly';
165
+ } else {
166
+ return schema.writeOnly === true ? 'writeOnly' : 'all';
167
+ }
168
+ }
169
+
170
+ export function updateSchemaAccessibility(
171
+ accessibility: ApiSchemaAccessibility,
172
+ schema: {
173
+ readOnly?: boolean;
174
+ writeOnly?: boolean;
175
+ },
176
+ ): ApiSchemaAccessibility {
177
+ if (accessibility === 'none') return 'none';
178
+ if (accessibility === 'readOnly') return schema.writeOnly === true ? 'none' : 'readOnly';
179
+ if (accessibility === 'writeOnly') return schema.readOnly === true ? 'none' : 'writeOnly';
180
+ return determineSchemaAccessibility(schema);
181
+ }
182
+
183
+ type CustomFields<T extends Record<string, unknown>> = {
184
+ [K in keyof T as K extends `x-${string}` ? K : never]: T[K];
185
+ };
186
+
187
+ export function getCustomFields<T extends Record<string, unknown>>(schema: T): CustomFields<T> {
188
+ const result = {} as CustomFields<T>;
189
+ for (const key in schema) {
190
+ if (key.startsWith('x-')) {
191
+ const name = key.substring(2);
192
+ (result as Record<string, unknown>)[name] = schema[key];
193
+ }
194
+ }
195
+ return result;
196
+ }
197
+
198
+ export function determineEndpointName(endpointInfo: {
199
+ method: string;
200
+ path: string;
201
+ operation: { operationId?: string };
202
+ }): string {
203
+ if (endpointInfo.operation.operationId) return endpointInfo.operation.operationId;
204
+ return endpointInfo.method + endpointInfo.path.replace(/\{([^}]+)\}/g, ':$1').replace(/\//g, '_');
205
+ }
206
+
207
+ export function transformAdditionalProperties<TAdditionalProperties>(
208
+ context: OpenApiTransformerContext,
209
+ schema: {
210
+ additionalProperties?: TAdditionalProperties;
211
+ },
212
+ transformSchema: (
213
+ context: OpenApiTransformerContext,
214
+ schema: Exclude<TAdditionalProperties, undefined | boolean>,
215
+ ) => ApiSchema,
216
+ ): boolean | ApiSchema | undefined {
217
+ if (isNullish(schema.additionalProperties)) return undefined;
218
+ if (typeof schema.additionalProperties === 'boolean') return schema.additionalProperties;
219
+ return transformSchema(context, schema.additionalProperties as Exclude<TAdditionalProperties, undefined | boolean>);
220
+ }
221
+
222
+ export function transformSchemaProperties<TProperties>(
223
+ context: OpenApiTransformerContext,
224
+ schema: { properties?: Record<string, TProperties>; required?: string[] },
225
+ transformSchema: (context: OpenApiTransformerContext, schema: TProperties) => ApiSchema,
226
+ ): Map<string, ApiSchemaProperty> {
227
+ const result = new Map<string, ApiSchemaProperty>();
228
+ if (!schema.properties) return result;
229
+ for (const name of Object.keys(schema.properties)) {
230
+ if (!isOpenApiObjectProperty(name)) continue;
231
+ result.set(name, {
232
+ name,
233
+ schema: transformSchema(context, schema.properties[name]),
234
+ });
235
+ }
236
+ return result;
237
+ }
238
+
239
+ export class IdGenerator {
240
+ private readonly _idMap = new Map<string, number>();
241
+
242
+ public generateId(name: string): string {
243
+ const id = this._idMap.get(name) ?? 1;
244
+ this._idMap.set(name, id + 1);
245
+ return `${name}-${id}`;
246
+ }
247
+ }
248
+
249
+ export function getOpenApiObjectIdentifier(obj: Deref<OpenApiObject<string>>) {
250
+ return obj.$src.file + '#' + obj.$src.path;
251
+ }
@@ -0,0 +1,35 @@
1
+ import type { OpenApiDocument, OpenApiTag } from '../parse/openapi-types.js';
2
+ import type { Deref } from '../parse/types.js';
3
+ import type { ApiService } from './api-types.js';
4
+ import { getOpenApiObjectIdentifier } from './helpers.js';
5
+ import type { OpenApiTransformerContext } from './types.js';
6
+
7
+ export function transformDocument(context: OpenApiTransformerContext, document: Deref<OpenApiDocument>) {
8
+ for (const tag of document.tags ?? []) {
9
+ transformTag(context, tag);
10
+ }
11
+ }
12
+
13
+ function transformTag(context: OpenApiTransformerContext, tag: Deref<OpenApiTag>, isReference = false) {
14
+ const openApiObjectId = getOpenApiObjectIdentifier(tag);
15
+ const existing = context.transformed.services.get(openApiObjectId);
16
+ if (existing) return existing;
17
+
18
+ const ref = tag.$ref ? transformTag(context, tag.$ref, true) : undefined;
19
+ const id = context.idGenerator.generateId('service');
20
+ const service: ApiService = {
21
+ $src: {
22
+ ...tag.$src,
23
+ component: tag,
24
+ },
25
+ $ref: ref,
26
+ id,
27
+ name: tag.name ?? id,
28
+ description: tag.description,
29
+ endpoints: [],
30
+ };
31
+
32
+ context.transformed.services.set(openApiObjectId, service);
33
+ if (!isReference) context.services.set(service.name, service);
34
+ return service;
35
+ }
@@ -0,0 +1,332 @@
1
+ import type { OpenApiCollectorEndpointInfo } from '../collect/types.js';
2
+ import { isOpenApiObjectProperty } from '../internal-utils.js';
3
+ import type {
4
+ OpenApiHeader,
5
+ OpenApiMediaType,
6
+ OpenApiParameter,
7
+ OpenApiPathItem,
8
+ OpenApiRequestBody,
9
+ OpenApiResponse,
10
+ OpenApiSchema,
11
+ } from '../parse/openapi-types.js';
12
+ import type { Deref } from '../parse/types.js';
13
+ import type {
14
+ ApiContent,
15
+ ApiEndpoint,
16
+ ApiHeader,
17
+ ApiParameter,
18
+ ApiParameterTarget,
19
+ ApiPath,
20
+ ApiRequestBody,
21
+ ApiResponse,
22
+ } from './api-types.js';
23
+ import { determineEndpointName, getCustomFields, getOpenApiObjectIdentifier } from './helpers.js';
24
+ import { transformSchema } from './transform-schema.js';
25
+ import type { OpenApiTransformerContext } from './types.js';
26
+
27
+ export function transformEndpoint(context: OpenApiTransformerContext, endpointInfo: OpenApiCollectorEndpointInfo) {
28
+ const apiPath = transformApiPath(context, endpointInfo.path, endpointInfo.pathItem);
29
+ const endpoint: ApiEndpoint = {
30
+ $src: {
31
+ ...endpointInfo.operation.$src,
32
+ component: endpointInfo.operation,
33
+ },
34
+ $ref: undefined,
35
+ id: context.idGenerator.generateId('endpoint'),
36
+ name: determineEndpointName(endpointInfo),
37
+ path: endpointInfo.path,
38
+ pathInfo: apiPath,
39
+ method: endpointInfo.method,
40
+ summary: endpointInfo.operation.summary,
41
+ description: endpointInfo.operation.description,
42
+ parameters: combineParameters(
43
+ apiPath.parameters,
44
+ endpointInfo.operation.parameters?.map((p) => transformParameter(context, p)) ?? [],
45
+ ),
46
+ deprecated: endpointInfo.operation.deprecated ?? false,
47
+ requestBody: endpointInfo.operation.requestBody
48
+ ? transformRequestBody(context, endpointInfo.operation.requestBody)
49
+ : undefined,
50
+ responses: transformResponses(context, endpointInfo.operation.responses),
51
+ tags: endpointInfo.operation.tags ?? [],
52
+ custom: getCustomFields(endpointInfo.operation),
53
+ };
54
+
55
+ const tags = endpoint.tags.length === 0 ? [''] : endpoint.tags;
56
+ for (const tag of tags) {
57
+ let service = context.services.get(tag);
58
+ if (!service) {
59
+ const id = context.idGenerator.generateId('service');
60
+ service = {
61
+ $ref: undefined,
62
+ id,
63
+ name: tag || id,
64
+ description: undefined,
65
+ endpoints: [],
66
+ };
67
+ context.services.set(tag, service);
68
+ }
69
+
70
+ service.endpoints.push(endpoint);
71
+ }
72
+
73
+ apiPath[endpointInfo.method] = endpoint;
74
+ context.endpoints.set(`${endpointInfo.method}:${endpointInfo.path}`, endpoint);
75
+ return endpoint;
76
+ }
77
+
78
+ function transformApiPath(
79
+ context: OpenApiTransformerContext,
80
+ path: string,
81
+ pathItem: Deref<OpenApiPathItem>,
82
+ isReference = false,
83
+ ): ApiPath {
84
+ const existingPath = context.paths.get(path);
85
+ if (existingPath) return existingPath;
86
+
87
+ const openApiObjectId = getOpenApiObjectIdentifier(pathItem);
88
+ const existing = context.transformed.paths.get(openApiObjectId);
89
+ if (existing) return existing;
90
+
91
+ const ref = pathItem.$ref ? transformApiPath(context, path, pathItem.$ref, true) : undefined;
92
+ const apiPath: ApiPath = {
93
+ $src: {
94
+ ...pathItem.$src,
95
+ component: pathItem,
96
+ },
97
+ $ref: ref,
98
+ id: context.idGenerator.generateId('path'),
99
+ summary: pathItem.summary,
100
+ description: pathItem.description,
101
+ path: path,
102
+ parameters: pathItem.parameters?.map((p) => transformParameter(context, p)) ?? [],
103
+
104
+ // HTTP methods are added later (see transformEndpoint)
105
+ get: undefined,
106
+ put: undefined,
107
+ post: undefined,
108
+ delete: undefined,
109
+ options: undefined,
110
+ head: undefined,
111
+ patch: undefined,
112
+ trace: undefined,
113
+ };
114
+
115
+ context.transformed.paths.set(openApiObjectId, apiPath);
116
+ if (!isReference) context.paths.set(path, apiPath);
117
+ return apiPath;
118
+ }
119
+
120
+ function transformParameter(context: OpenApiTransformerContext, parameter: Deref<OpenApiParameter>): ApiParameter {
121
+ const openApiObjectId = getOpenApiObjectIdentifier(parameter);
122
+ const existing = context.transformed.parameters.get(openApiObjectId);
123
+ if (existing) return existing;
124
+
125
+ const ref = parameter.$ref ? transformParameter(context, parameter.$ref) : undefined;
126
+ const id = context.idGenerator.generateId('parameter');
127
+ const p: ApiParameter = {
128
+ $src: {
129
+ ...parameter.$src,
130
+ component: parameter,
131
+ },
132
+ $ref: ref,
133
+ id: id,
134
+ name: parameter.name ?? id,
135
+ target: parameter.in as ApiParameterTarget,
136
+ description: parameter.description,
137
+ required: parameter.required ?? false,
138
+ deprecated: parameter.deprecated ?? false,
139
+ allowEmptyValue: parameter.allowEmptyValue,
140
+ style: parameter.style,
141
+ explode: parameter.explode,
142
+ allowReserved: parameter.allowReserved,
143
+ schema: parameter.schema ? transformSchema(context, parameter.schema) : undefined,
144
+ };
145
+
146
+ context.transformed.parameters.set(openApiObjectId, p);
147
+ return p;
148
+ }
149
+
150
+ function transformRequestBody(
151
+ context: OpenApiTransformerContext,
152
+ requestBody: Deref<OpenApiRequestBody>,
153
+ ): ApiRequestBody {
154
+ const openApiObjectId = getOpenApiObjectIdentifier(requestBody);
155
+ const existing = context.transformed.requestBodies.get(openApiObjectId);
156
+ if (existing) return existing;
157
+
158
+ const ref = requestBody.$ref ? transformRequestBody(context, requestBody.$ref) : undefined;
159
+ const rb: ApiRequestBody = {
160
+ $src: {
161
+ ...requestBody.$src,
162
+ component: requestBody,
163
+ },
164
+ $ref: ref,
165
+ id: context.idGenerator.generateId('requestBody'),
166
+ description: requestBody.description,
167
+ required: requestBody.required ?? false,
168
+ content: transformContent(context, requestBody.content),
169
+ };
170
+
171
+ context.transformed.requestBodies.set(openApiObjectId, rb);
172
+ return rb;
173
+ }
174
+
175
+ function transformResponses(
176
+ context: OpenApiTransformerContext,
177
+ responses?: Record<string, Deref<OpenApiResponse>>,
178
+ ): ApiResponse[] {
179
+ if (!responses) return [];
180
+ const result: ApiResponse[] = [];
181
+ for (const status of Object.keys(responses)) {
182
+ if (!isOpenApiObjectProperty(status)) continue;
183
+ result.push(transformResponse(context, responses[status], status));
184
+ }
185
+ return result;
186
+ }
187
+
188
+ function transformResponse(
189
+ context: OpenApiTransformerContext,
190
+ response: Deref<OpenApiResponse>,
191
+ status: string,
192
+ ): ApiResponse {
193
+ const openApiObjectId = getOpenApiObjectIdentifier(response);
194
+ const existing = context.transformed.responses.get(openApiObjectId);
195
+ if (existing) return existing;
196
+
197
+ const ref = response.$ref ? transformResponse(context, response.$ref, status) : undefined;
198
+ const r: ApiResponse = {
199
+ $src: {
200
+ ...response.$src,
201
+ component: response,
202
+ },
203
+ $ref: ref,
204
+ id: context.idGenerator.generateId('response'),
205
+ statusKey: status,
206
+ statusCode: Number(status) || undefined,
207
+ description: response.description,
208
+ headers: transformHeaders(context, response.headers),
209
+ contentOptions: transformContent(context, response.content),
210
+ };
211
+
212
+ context.transformed.responses.set(openApiObjectId, r);
213
+ return r;
214
+ }
215
+
216
+ function transformContent(
217
+ context: OpenApiTransformerContext,
218
+ content?: Record<string, Deref<OpenApiMediaType>>,
219
+ ): ApiContent[] {
220
+ if (!content) return [];
221
+ const result: ApiContent[] = [];
222
+ for (const media of Object.keys(content)) {
223
+ if (!isOpenApiObjectProperty(media)) continue;
224
+ result.push(transformMediaType(context, content[media], media));
225
+ }
226
+ return result;
227
+ }
228
+
229
+ function transformMediaType(
230
+ context: OpenApiTransformerContext,
231
+ mediaType: Deref<OpenApiMediaType>,
232
+ media: string,
233
+ ): ApiContent {
234
+ const openApiObjectId = getOpenApiObjectIdentifier(mediaType);
235
+ const existing = context.transformed.content.get(openApiObjectId);
236
+ if (existing) return existing;
237
+
238
+ const ref = mediaType.$ref ? transformMediaType(context, mediaType.$ref, media) : undefined;
239
+ const mt: ApiContent = {
240
+ $src: {
241
+ ...mediaType.$src,
242
+ component: mediaType,
243
+ },
244
+ $ref: ref,
245
+ id: context.idGenerator.generateId('content'),
246
+ type: media,
247
+ schema: mediaType.schema ? transformSchema(context, mediaType.schema) : undefined,
248
+ };
249
+
250
+ context.transformed.content.set(openApiObjectId, mt);
251
+ return mt;
252
+ }
253
+
254
+ function combineParameters(pathParams: ApiParameter[], operationParams: ApiParameter[]): ApiParameter[] {
255
+ const result = [...pathParams];
256
+ for (const opParam of operationParams) {
257
+ const existingParam = result.findIndex((p) => p.name === opParam.name);
258
+ if (existingParam >= 0) {
259
+ result[existingParam] = opParam;
260
+ } else {
261
+ result.push(opParam);
262
+ }
263
+ }
264
+ return result;
265
+ }
266
+
267
+ function isSchema(obj: Record<string, unknown>): obj is Deref<OpenApiSchema> {
268
+ return obj && typeof obj === 'object' && obj['type'] !== undefined;
269
+ }
270
+
271
+ function transformHeaders(
272
+ context: OpenApiTransformerContext,
273
+ headers?: Record<string, Deref<OpenApiHeader> | Deref<OpenApiSchema>>,
274
+ ): ApiHeader[] {
275
+ if (!headers) return [];
276
+ const result: ApiHeader[] = [];
277
+ for (const name of Object.keys(headers)) {
278
+ if (!isOpenApiObjectProperty(name)) continue;
279
+ result.push(transformHeader(context, headers[name], name));
280
+ }
281
+ return result;
282
+ }
283
+
284
+ function transformHeader(
285
+ context: OpenApiTransformerContext,
286
+ header: Deref<OpenApiHeader> | Deref<OpenApiSchema>,
287
+ name: string,
288
+ ): ApiHeader {
289
+ const openApiObjectId = getOpenApiObjectIdentifier(header);
290
+ const existing = context.transformed.headers.get(openApiObjectId);
291
+ if (existing) return existing;
292
+
293
+ let h: ApiHeader;
294
+ const ref = header.$ref ? transformHeader(context, header.$ref, name) : undefined;
295
+ const id = context.idGenerator.generateId('header');
296
+ if (isSchema(header)) {
297
+ h = {
298
+ // deno-lint-ignore no-explicit-any
299
+ $src: Object.assign({}, header.$src, { component: header }) as any,
300
+ $ref: ref as ApiHeader['$ref'],
301
+ id,
302
+ name,
303
+ description: header.description,
304
+ required: false,
305
+ deprecated: false,
306
+ allowEmptyValue: undefined,
307
+ style: undefined,
308
+ explode: undefined,
309
+ allowReserved: undefined,
310
+ schema: transformSchema(context, header),
311
+ };
312
+ } else {
313
+ h = {
314
+ // deno-lint-ignore no-explicit-any
315
+ $src: Object.assign({}, header.$src, { component: header }) as any,
316
+ $ref: ref as ApiHeader['$ref'],
317
+ id,
318
+ name,
319
+ description: header.description,
320
+ required: header.required ?? false,
321
+ deprecated: header.deprecated ?? false,
322
+ allowEmptyValue: header.allowEmptyValue,
323
+ style: header.style,
324
+ explode: header.explode,
325
+ allowReserved: header.allowReserved,
326
+ schema: header.schema ? transformSchema(context, header.schema) : undefined,
327
+ };
328
+ }
329
+
330
+ context.transformed.headers.set(openApiObjectId, h);
331
+ return h;
332
+ }