genoc 0.1.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.
- package/LICENSE +21 -0
- package/README.md +233 -0
- package/dist/analyzer/naming.d.ts +24 -0
- package/dist/analyzer/naming.js +122 -0
- package/dist/analyzer/path-analyzer.d.ts +53 -0
- package/dist/analyzer/path-analyzer.js +222 -0
- package/dist/analyzer/schema-mapper.d.ts +48 -0
- package/dist/analyzer/schema-mapper.js +435 -0
- package/dist/cli/app.d.ts +9 -0
- package/dist/cli/app.js +60 -0
- package/dist/cli/errors.d.ts +3 -0
- package/dist/cli/errors.js +6 -0
- package/dist/cli/impl.d.ts +3 -0
- package/dist/cli/impl.js +45 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +5 -0
- package/dist/generator/client-generator.d.ts +21 -0
- package/dist/generator/client-generator.js +287 -0
- package/dist/generator/contracts-generator.d.ts +16 -0
- package/dist/generator/contracts-generator.js +525 -0
- package/dist/generator/error-types.d.ts +24 -0
- package/dist/generator/error-types.js +94 -0
- package/dist/generator/method-generator.d.ts +9 -0
- package/dist/generator/method-generator.js +249 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8 -0
- package/dist/parser/ref-resolver.d.ts +24 -0
- package/dist/parser/ref-resolver.js +119 -0
- package/dist/parser/spec-reader.d.ts +4 -0
- package/dist/parser/spec-reader.js +116 -0
- package/dist/parser/validators.d.ts +7 -0
- package/dist/parser/validators.js +79 -0
- package/dist/parser/version/index.d.ts +18 -0
- package/dist/parser/version/index.js +16 -0
- package/dist/parser/version/normalized-spec.d.ts +199 -0
- package/dist/parser/version/normalized-spec.js +1 -0
- package/dist/parser/version/registry.d.ts +28 -0
- package/dist/parser/version/registry.js +44 -0
- package/dist/parser/version/v3.0/index.d.ts +3 -0
- package/dist/parser/version/v3.0/index.js +3 -0
- package/dist/parser/version/v3.0/normalizer.d.ts +15 -0
- package/dist/parser/version/v3.0/normalizer.js +389 -0
- package/dist/parser/version/v3.0/strategy.d.ts +27 -0
- package/dist/parser/version/v3.0/strategy.js +96 -0
- package/dist/parser/version/v3.0/validator.d.ts +13 -0
- package/dist/parser/version/v3.0/validator.js +117 -0
- package/dist/parser/version/v3.1/index.d.ts +1 -0
- package/dist/parser/version/v3.1/index.js +1 -0
- package/dist/parser/version/v3.1/strategy.d.ts +42 -0
- package/dist/parser/version/v3.1/strategy.js +513 -0
- package/dist/parser/version/v3.2/index.d.ts +4 -0
- package/dist/parser/version/v3.2/index.js +4 -0
- package/dist/parser/version/v3.2/strategy.d.ts +39 -0
- package/dist/parser/version/v3.2/strategy.js +57 -0
- package/dist/parser/version/version-detector.d.ts +4 -0
- package/dist/parser/version/version-detector.js +34 -0
- package/dist/parser/version/version-strategy.d.ts +31 -0
- package/dist/parser/version/version-strategy.js +1 -0
- package/dist/types/client.d.ts +25 -0
- package/dist/types/client.js +1 -0
- package/dist/types/contracts.d.ts +13 -0
- package/dist/types/contracts.js +1 -0
- package/dist/types/openapi.d.ts +173 -0
- package/dist/types/openapi.js +1 -0
- package/dist/utils/case.d.ts +5 -0
- package/dist/utils/case.js +51 -0
- package/dist/utils/generator-helpers.d.ts +23 -0
- package/dist/utils/generator-helpers.js +66 -0
- package/dist/utils/string.d.ts +34 -0
- package/dist/utils/string.js +182 -0
- package/dist/utils/url.d.ts +10 -0
- package/dist/utils/url.js +40 -0
- package/package.json +60 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
import { parseJsonPointer } from '../../../utils/url.js';
|
|
2
|
+
const MAX_REF_DEPTH = 10;
|
|
3
|
+
/**
|
|
4
|
+
* Version strategy for OpenAPI 3.1.x specifications.
|
|
5
|
+
*
|
|
6
|
+
* OpenAPI 3.1 is aligned with JSON Schema 2020-12 and supports:
|
|
7
|
+
* - Type arrays (e.g., `type: ["string", "null"]`)
|
|
8
|
+
* - Sibling properties alongside `$ref`
|
|
9
|
+
* - `exclusiveMinimum`/`exclusiveMaximum` as numbers
|
|
10
|
+
* - `webhooks` at the top level
|
|
11
|
+
*/
|
|
12
|
+
export class V3_1_VersionStrategy {
|
|
13
|
+
version() {
|
|
14
|
+
return '3.1';
|
|
15
|
+
}
|
|
16
|
+
matches(spec) {
|
|
17
|
+
if (!spec || typeof spec !== 'object' || Array.isArray(spec)) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const specObj = spec;
|
|
21
|
+
const openapiVersion = specObj.openapi;
|
|
22
|
+
if (typeof openapiVersion === 'string') {
|
|
23
|
+
return openapiVersion.startsWith('3.1');
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Normalize a raw OpenAPI 3.1 specification to a consistent format.
|
|
29
|
+
*
|
|
30
|
+
* Schema normalization:
|
|
31
|
+
* - `type: "string"` → `types: ["string"]`
|
|
32
|
+
* - `type: ["string", "null"]` → `types: ["string"]`, `nullable: true`
|
|
33
|
+
* - `example: value` → `examples: [value]` (defensive)
|
|
34
|
+
* - `exclusiveMinimum`/`exclusiveMaximum` extracted as numbers
|
|
35
|
+
* - `format: "binary"` → `fileUpload: { binary: true, base64: false }`
|
|
36
|
+
* - `format: "byte"` → `fileUpload: { binary: false, base64: true }`
|
|
37
|
+
*/
|
|
38
|
+
normalizeSpec(rawSpec) {
|
|
39
|
+
if (!rawSpec || typeof rawSpec !== 'object' || Array.isArray(rawSpec)) {
|
|
40
|
+
throw new Error('Invalid spec: must be a non-null object');
|
|
41
|
+
}
|
|
42
|
+
const raw = rawSpec;
|
|
43
|
+
const rawInfo = raw.info;
|
|
44
|
+
if (!rawInfo || typeof rawInfo !== 'object' || Array.isArray(rawInfo)) {
|
|
45
|
+
throw new Error("Invalid spec: missing or invalid 'info' field");
|
|
46
|
+
}
|
|
47
|
+
const ri = rawInfo;
|
|
48
|
+
const info = {
|
|
49
|
+
title: typeof ri.title === 'string' ? ri.title : '',
|
|
50
|
+
version: typeof ri.version === 'string' ? ri.version : '',
|
|
51
|
+
description: typeof ri.description === 'string' ? ri.description : undefined,
|
|
52
|
+
termsOfService: typeof ri.termsOfService === 'string' ? ri.termsOfService : undefined,
|
|
53
|
+
contact: ri.contact && typeof ri.contact === 'object' && !Array.isArray(ri.contact)
|
|
54
|
+
? ri.contact
|
|
55
|
+
: undefined,
|
|
56
|
+
license: ri.license && typeof ri.license === 'object' && !Array.isArray(ri.license)
|
|
57
|
+
? ri.license
|
|
58
|
+
: undefined,
|
|
59
|
+
};
|
|
60
|
+
const servers = Array.isArray(raw.servers)
|
|
61
|
+
? raw.servers
|
|
62
|
+
: undefined;
|
|
63
|
+
let paths;
|
|
64
|
+
if (raw.paths && typeof raw.paths === 'object' && !Array.isArray(raw.paths)) {
|
|
65
|
+
paths = {};
|
|
66
|
+
for (const [pathKey, pathItem] of Object.entries(raw.paths)) {
|
|
67
|
+
if (pathItem && typeof pathItem === 'object' && !Array.isArray(pathItem)) {
|
|
68
|
+
paths[pathKey] = this.normalizePathItem(pathItem);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
let components;
|
|
73
|
+
if (raw.components && typeof raw.components === 'object' && !Array.isArray(raw.components)) {
|
|
74
|
+
const rc = raw.components;
|
|
75
|
+
components = {};
|
|
76
|
+
if (rc.schemas && typeof rc.schemas === 'object' && !Array.isArray(rc.schemas)) {
|
|
77
|
+
components.schemas = {};
|
|
78
|
+
for (const [name, schema] of Object.entries(rc.schemas)) {
|
|
79
|
+
components.schemas[name] = this.normalizeSchema(schema);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (rc.responses && typeof rc.responses === 'object')
|
|
83
|
+
components.responses = rc.responses;
|
|
84
|
+
if (rc.parameters && typeof rc.parameters === 'object')
|
|
85
|
+
components.parameters = rc.parameters;
|
|
86
|
+
if (rc.requestBodies && typeof rc.requestBodies === 'object')
|
|
87
|
+
components.requestBodies = rc.requestBodies;
|
|
88
|
+
if (rc.headers && typeof rc.headers === 'object')
|
|
89
|
+
components.headers = rc.headers;
|
|
90
|
+
if (rc.securitySchemes && typeof rc.securitySchemes === 'object')
|
|
91
|
+
components.securitySchemes = rc.securitySchemes;
|
|
92
|
+
if (rc.links && typeof rc.links === 'object')
|
|
93
|
+
components.links = rc.links;
|
|
94
|
+
if (rc.callbacks && typeof rc.callbacks === 'object')
|
|
95
|
+
components.callbacks = rc.callbacks;
|
|
96
|
+
if (rc.examples && typeof rc.examples === 'object')
|
|
97
|
+
components.examples = rc.examples;
|
|
98
|
+
}
|
|
99
|
+
let webhooks;
|
|
100
|
+
if (raw.webhooks && typeof raw.webhooks === 'object' && !Array.isArray(raw.webhooks)) {
|
|
101
|
+
webhooks = {};
|
|
102
|
+
for (const [name, pathItem] of Object.entries(raw.webhooks)) {
|
|
103
|
+
if (pathItem && typeof pathItem === 'object' && !Array.isArray(pathItem)) {
|
|
104
|
+
webhooks[name] = this.normalizePathItem(pathItem);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
openapi: typeof raw.openapi === 'string' ? raw.openapi : '3.1.0',
|
|
110
|
+
info,
|
|
111
|
+
servers,
|
|
112
|
+
paths,
|
|
113
|
+
components,
|
|
114
|
+
security: Array.isArray(raw.security) ? raw.security : undefined,
|
|
115
|
+
tags: Array.isArray(raw.tags) ? raw.tags : undefined,
|
|
116
|
+
externalDocs: raw.externalDocs && typeof raw.externalDocs === 'object' && !Array.isArray(raw.externalDocs)
|
|
117
|
+
? raw.externalDocs
|
|
118
|
+
: undefined,
|
|
119
|
+
webhooks,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
validateSpec(spec) {
|
|
123
|
+
const errors = [];
|
|
124
|
+
if (!spec.openapi || typeof spec.openapi !== 'string') {
|
|
125
|
+
errors.push("OpenAPI specification must have an 'openapi' field with string value");
|
|
126
|
+
}
|
|
127
|
+
else if (!spec.openapi.startsWith('3.1')) {
|
|
128
|
+
errors.push(`OpenAPI version must start with '3.1', got: ${spec.openapi}`);
|
|
129
|
+
}
|
|
130
|
+
if (!spec.info) {
|
|
131
|
+
errors.push("OpenAPI specification must have an 'info' field with object value");
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
if (!spec.info.title) {
|
|
135
|
+
errors.push("Info object must have a 'title' field with string value");
|
|
136
|
+
}
|
|
137
|
+
if (!spec.info.version) {
|
|
138
|
+
errors.push("Info object must have a 'version' field with string value");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (!spec.paths && !spec.components && !spec.webhooks) {
|
|
142
|
+
errors.push("OpenAPI specification must have at least one of 'paths', 'components', or 'webhooks'");
|
|
143
|
+
}
|
|
144
|
+
if (spec.paths) {
|
|
145
|
+
if (typeof spec.paths !== 'object' || Array.isArray(spec.paths)) {
|
|
146
|
+
errors.push("'paths' field must be an object");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (spec.components?.schemas) {
|
|
150
|
+
if (typeof spec.components.schemas !== 'object' || Array.isArray(spec.components.schemas)) {
|
|
151
|
+
errors.push("'components.schemas' must be an object");
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
for (const [key, schema] of Object.entries(spec.components.schemas)) {
|
|
155
|
+
if (!schema || typeof schema !== 'object' || Array.isArray(schema)) {
|
|
156
|
+
errors.push(`Schema '${key}' must be an object`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
valid: errors.length === 0,
|
|
163
|
+
errors,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Resolve a $ref within the document. For OpenAPI 3.1, sibling properties
|
|
168
|
+
* alongside `$ref` are preserved via shallow merge (siblings override target).
|
|
169
|
+
*/
|
|
170
|
+
resolveRef(ref, doc, context) {
|
|
171
|
+
const resolving = new Set();
|
|
172
|
+
const resolved = this.resolveRefInternal(ref, doc, resolving, 0);
|
|
173
|
+
if (context && typeof context === 'object' && !Array.isArray(context)) {
|
|
174
|
+
const ctx = context;
|
|
175
|
+
const siblings = {};
|
|
176
|
+
let hasSiblings = false;
|
|
177
|
+
for (const [key, value] of Object.entries(ctx)) {
|
|
178
|
+
if (key !== '$ref') {
|
|
179
|
+
siblings[key] = value;
|
|
180
|
+
hasSiblings = true;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (hasSiblings &&
|
|
184
|
+
resolved !== null &&
|
|
185
|
+
resolved !== undefined &&
|
|
186
|
+
typeof resolved === 'object' &&
|
|
187
|
+
!Array.isArray(resolved)) {
|
|
188
|
+
return {
|
|
189
|
+
...resolved,
|
|
190
|
+
...siblings,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return resolved;
|
|
195
|
+
}
|
|
196
|
+
getSupportedFeatures() {
|
|
197
|
+
return ['typeArrays', 'webhooks', 'nullable', 'jsonSchema202012'];
|
|
198
|
+
}
|
|
199
|
+
resolveRefInternal(ref, doc, resolving, depth) {
|
|
200
|
+
if (ref.startsWith('http://') || ref.startsWith('https://')) {
|
|
201
|
+
throw new Error(`External $ref resolution is not supported: ${ref}`);
|
|
202
|
+
}
|
|
203
|
+
if (!ref.startsWith('#')) {
|
|
204
|
+
throw new Error(`External $ref resolution is not supported: ${ref}`);
|
|
205
|
+
}
|
|
206
|
+
if (depth >= MAX_REF_DEPTH) {
|
|
207
|
+
throw new Error(`Maximum $ref depth (${MAX_REF_DEPTH}) exceeded: ${ref}`);
|
|
208
|
+
}
|
|
209
|
+
if (resolving.has(ref)) {
|
|
210
|
+
const cyclePath = [...resolving, ref].join(' -> ');
|
|
211
|
+
throw new Error(`Circular $ref detected: ${cyclePath}`);
|
|
212
|
+
}
|
|
213
|
+
resolving.add(ref);
|
|
214
|
+
const pointer = ref.slice(1);
|
|
215
|
+
const segments = parseJsonPointer(pointer);
|
|
216
|
+
let current = doc;
|
|
217
|
+
for (const segment of segments) {
|
|
218
|
+
if (current === null || current === undefined) {
|
|
219
|
+
throw new Error(`$ref "${ref}" could not be resolved: segment "${segment}" not found`);
|
|
220
|
+
}
|
|
221
|
+
if (typeof current !== 'object') {
|
|
222
|
+
throw new Error(`$ref "${ref}" could not be resolved: segment "${segment}" is not an object`);
|
|
223
|
+
}
|
|
224
|
+
if (Array.isArray(current)) {
|
|
225
|
+
const index = Number(segment);
|
|
226
|
+
if (Number.isNaN(index)) {
|
|
227
|
+
throw new Error(`$ref "${ref}" could not be resolved: "${segment}" is not a valid array index`);
|
|
228
|
+
}
|
|
229
|
+
current = current[index];
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
current = current[segment];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (current === undefined) {
|
|
236
|
+
throw new Error(`$ref "${ref}" could not be resolved`);
|
|
237
|
+
}
|
|
238
|
+
if (current !== null &&
|
|
239
|
+
typeof current === 'object' &&
|
|
240
|
+
!Array.isArray(current) &&
|
|
241
|
+
'$ref' in current) {
|
|
242
|
+
const chainedRef = current.$ref;
|
|
243
|
+
return this.resolveRefInternal(chainedRef, doc, new Set(resolving), depth + 1);
|
|
244
|
+
}
|
|
245
|
+
return current;
|
|
246
|
+
}
|
|
247
|
+
normalizeSchema(schema) {
|
|
248
|
+
if (!schema || typeof schema !== 'object' || Array.isArray(schema)) {
|
|
249
|
+
return { types: [], nullable: false, examples: [] };
|
|
250
|
+
}
|
|
251
|
+
const s = schema;
|
|
252
|
+
if (typeof s.$ref === 'string') {
|
|
253
|
+
return {
|
|
254
|
+
$ref: s.$ref,
|
|
255
|
+
types: [],
|
|
256
|
+
nullable: false,
|
|
257
|
+
examples: [],
|
|
258
|
+
description: typeof s.description === 'string' ? s.description : undefined,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
let types = [];
|
|
262
|
+
let nullable = false;
|
|
263
|
+
if (Array.isArray(s.type)) {
|
|
264
|
+
const typeArr = s.type;
|
|
265
|
+
if (typeArr.includes('null')) {
|
|
266
|
+
nullable = true;
|
|
267
|
+
types = typeArr.filter((t) => t !== 'null');
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
types = [...typeArr];
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
else if (typeof s.type === 'string') {
|
|
274
|
+
types = [s.type];
|
|
275
|
+
}
|
|
276
|
+
if (s.nullable === true) {
|
|
277
|
+
nullable = true;
|
|
278
|
+
}
|
|
279
|
+
let examples = [];
|
|
280
|
+
if (Array.isArray(s.examples)) {
|
|
281
|
+
examples = [...s.examples];
|
|
282
|
+
}
|
|
283
|
+
else if (s.example !== undefined) {
|
|
284
|
+
examples = [s.example];
|
|
285
|
+
}
|
|
286
|
+
const exclusiveMinimum = typeof s.exclusiveMinimum === 'number' ? s.exclusiveMinimum : undefined;
|
|
287
|
+
const exclusiveMaximum = typeof s.exclusiveMaximum === 'number' ? s.exclusiveMaximum : undefined;
|
|
288
|
+
let fileUpload;
|
|
289
|
+
if (typeof s.format === 'string') {
|
|
290
|
+
if (s.format === 'binary') {
|
|
291
|
+
fileUpload = { binary: true, base64: false };
|
|
292
|
+
}
|
|
293
|
+
else if (s.format === 'byte') {
|
|
294
|
+
fileUpload = { binary: false, base64: true };
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const properties = this.normalizePropertyMap(s.properties);
|
|
298
|
+
const items = s.items ? this.normalizeSchema(s.items) : undefined;
|
|
299
|
+
const allOf = Array.isArray(s.allOf)
|
|
300
|
+
? s.allOf.map((item) => this.normalizeSchema(item))
|
|
301
|
+
: undefined;
|
|
302
|
+
const oneOf = Array.isArray(s.oneOf)
|
|
303
|
+
? s.oneOf.map((item) => this.normalizeSchema(item))
|
|
304
|
+
: undefined;
|
|
305
|
+
const anyOf = Array.isArray(s.anyOf)
|
|
306
|
+
? s.anyOf.map((item) => this.normalizeSchema(item))
|
|
307
|
+
: undefined;
|
|
308
|
+
let additionalProperties;
|
|
309
|
+
if (s.additionalProperties === true) {
|
|
310
|
+
additionalProperties = true;
|
|
311
|
+
}
|
|
312
|
+
else if (s.additionalProperties === false) {
|
|
313
|
+
additionalProperties = false;
|
|
314
|
+
}
|
|
315
|
+
else if (s.additionalProperties &&
|
|
316
|
+
typeof s.additionalProperties === 'object' &&
|
|
317
|
+
!Array.isArray(s.additionalProperties)) {
|
|
318
|
+
additionalProperties = this.normalizeSchema(s.additionalProperties);
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
types,
|
|
322
|
+
nullable,
|
|
323
|
+
format: typeof s.format === 'string' ? s.format : undefined,
|
|
324
|
+
properties,
|
|
325
|
+
required: Array.isArray(s.required) ? s.required : undefined,
|
|
326
|
+
items,
|
|
327
|
+
additionalProperties,
|
|
328
|
+
allOf,
|
|
329
|
+
oneOf,
|
|
330
|
+
anyOf,
|
|
331
|
+
enum: Array.isArray(s.enum) ? [...s.enum] : undefined,
|
|
332
|
+
const: 'const' in s ? s.const : undefined,
|
|
333
|
+
default: 'default' in s ? s.default : undefined,
|
|
334
|
+
description: typeof s.description === 'string' ? s.description : undefined,
|
|
335
|
+
examples,
|
|
336
|
+
fileUpload,
|
|
337
|
+
exclusiveMinimum,
|
|
338
|
+
exclusiveMaximum,
|
|
339
|
+
minimum: typeof s.minimum === 'number' ? s.minimum : undefined,
|
|
340
|
+
maximum: typeof s.maximum === 'number' ? s.maximum : undefined,
|
|
341
|
+
readOnly: s.readOnly === true ? true : undefined,
|
|
342
|
+
writeOnly: s.writeOnly === true ? true : undefined,
|
|
343
|
+
deprecated: s.deprecated === true ? true : undefined,
|
|
344
|
+
discriminator: s.discriminator && typeof s.discriminator === 'object' && !Array.isArray(s.discriminator)
|
|
345
|
+
? s.discriminator
|
|
346
|
+
: undefined,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
normalizePropertyMap(raw) {
|
|
350
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
351
|
+
return undefined;
|
|
352
|
+
}
|
|
353
|
+
const entries = Object.entries(raw);
|
|
354
|
+
if (entries.length === 0)
|
|
355
|
+
return undefined;
|
|
356
|
+
const result = {};
|
|
357
|
+
for (const [key, val] of entries) {
|
|
358
|
+
result[key] = this.normalizeSchema(val);
|
|
359
|
+
}
|
|
360
|
+
return result;
|
|
361
|
+
}
|
|
362
|
+
normalizePathItem(raw) {
|
|
363
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
364
|
+
return {};
|
|
365
|
+
}
|
|
366
|
+
const pi = raw;
|
|
367
|
+
const httpMethods = [
|
|
368
|
+
'get',
|
|
369
|
+
'put',
|
|
370
|
+
'post',
|
|
371
|
+
'delete',
|
|
372
|
+
'options',
|
|
373
|
+
'head',
|
|
374
|
+
'patch',
|
|
375
|
+
'trace',
|
|
376
|
+
];
|
|
377
|
+
const result = {};
|
|
378
|
+
if (typeof pi.$ref === 'string')
|
|
379
|
+
result.$ref = pi.$ref;
|
|
380
|
+
if (typeof pi.summary === 'string')
|
|
381
|
+
result.summary = pi.summary;
|
|
382
|
+
if (typeof pi.description === 'string')
|
|
383
|
+
result.description = pi.description;
|
|
384
|
+
if (Array.isArray(pi.servers))
|
|
385
|
+
result.servers = pi.servers;
|
|
386
|
+
if (Array.isArray(pi.parameters)) {
|
|
387
|
+
result.parameters = pi.parameters.map((p) => this.normalizeParameter(p));
|
|
388
|
+
}
|
|
389
|
+
for (const method of httpMethods) {
|
|
390
|
+
const op = pi[method];
|
|
391
|
+
if (op && typeof op === 'object' && !Array.isArray(op)) {
|
|
392
|
+
result[method] = this.normalizeOperation(op);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return result;
|
|
396
|
+
}
|
|
397
|
+
normalizeOperation(raw) {
|
|
398
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
399
|
+
return { responses: {} };
|
|
400
|
+
}
|
|
401
|
+
const o = raw;
|
|
402
|
+
const result = {
|
|
403
|
+
responses: {},
|
|
404
|
+
};
|
|
405
|
+
if (Array.isArray(o.tags))
|
|
406
|
+
result.tags = o.tags;
|
|
407
|
+
if (typeof o.summary === 'string')
|
|
408
|
+
result.summary = o.summary;
|
|
409
|
+
if (typeof o.description === 'string')
|
|
410
|
+
result.description = o.description;
|
|
411
|
+
if (typeof o.operationId === 'string')
|
|
412
|
+
result.operationId = o.operationId;
|
|
413
|
+
if (o.deprecated === true)
|
|
414
|
+
result.deprecated = true;
|
|
415
|
+
if (Array.isArray(o.security))
|
|
416
|
+
result.security = o.security;
|
|
417
|
+
if (Array.isArray(o.servers))
|
|
418
|
+
result.servers = o.servers;
|
|
419
|
+
if (Array.isArray(o.parameters)) {
|
|
420
|
+
result.parameters = o.parameters.map((p) => this.normalizeParameter(p));
|
|
421
|
+
}
|
|
422
|
+
if (o.requestBody && typeof o.requestBody === 'object' && !Array.isArray(o.requestBody)) {
|
|
423
|
+
const rb = o.requestBody;
|
|
424
|
+
result.requestBody = {
|
|
425
|
+
content: this.normalizeContentMap(rb.content),
|
|
426
|
+
};
|
|
427
|
+
if (typeof rb.description === 'string')
|
|
428
|
+
result.requestBody.description = rb.description;
|
|
429
|
+
if (rb.required === true)
|
|
430
|
+
result.requestBody.required = true;
|
|
431
|
+
}
|
|
432
|
+
if (o.responses && typeof o.responses === 'object' && !Array.isArray(o.responses)) {
|
|
433
|
+
const responses = {};
|
|
434
|
+
for (const [code, resp] of Object.entries(o.responses)) {
|
|
435
|
+
if (resp && typeof resp === 'object' && !Array.isArray(resp)) {
|
|
436
|
+
const r = resp;
|
|
437
|
+
responses[code] = {
|
|
438
|
+
description: typeof r.description === 'string' ? r.description : '',
|
|
439
|
+
};
|
|
440
|
+
if (r.headers && typeof r.headers === 'object')
|
|
441
|
+
responses[code].headers = r.headers;
|
|
442
|
+
if (r.content && typeof r.content === 'object')
|
|
443
|
+
responses[code].content = this.normalizeContentMap(r.content);
|
|
444
|
+
if (r.links && typeof r.links === 'object')
|
|
445
|
+
responses[code].links = r.links;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
result.responses = responses;
|
|
449
|
+
}
|
|
450
|
+
return result;
|
|
451
|
+
}
|
|
452
|
+
normalizeContentMap(raw) {
|
|
453
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
454
|
+
return {};
|
|
455
|
+
}
|
|
456
|
+
const result = {};
|
|
457
|
+
for (const [mediaType, mediaObj] of Object.entries(raw)) {
|
|
458
|
+
if (mediaObj && typeof mediaObj === 'object' && !Array.isArray(mediaObj)) {
|
|
459
|
+
const m = mediaObj;
|
|
460
|
+
const entry = {};
|
|
461
|
+
if (m.schema) {
|
|
462
|
+
entry.schema = this.normalizeSchema(m.schema);
|
|
463
|
+
}
|
|
464
|
+
if (m.examples && typeof m.examples === 'object')
|
|
465
|
+
entry.examples = m.examples;
|
|
466
|
+
if ('example' in m)
|
|
467
|
+
entry.example = m.example;
|
|
468
|
+
if (m.encoding && typeof m.encoding === 'object')
|
|
469
|
+
entry.encoding = m.encoding;
|
|
470
|
+
result[mediaType] = entry;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return result;
|
|
474
|
+
}
|
|
475
|
+
normalizeParameter(raw) {
|
|
476
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
477
|
+
return { name: '', in: 'query' };
|
|
478
|
+
}
|
|
479
|
+
const p = raw;
|
|
480
|
+
if (typeof p.$ref === 'string') {
|
|
481
|
+
return {
|
|
482
|
+
name: '',
|
|
483
|
+
in: 'query',
|
|
484
|
+
$ref: p.$ref,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
const result = {
|
|
488
|
+
name: typeof p.name === 'string' ? p.name : '',
|
|
489
|
+
in: ['query', 'path', 'header', 'cookie'].includes(p.in)
|
|
490
|
+
? p.in
|
|
491
|
+
: 'query',
|
|
492
|
+
};
|
|
493
|
+
if (typeof p.description === 'string')
|
|
494
|
+
result.description = p.description;
|
|
495
|
+
if (p.required === true)
|
|
496
|
+
result.required = true;
|
|
497
|
+
if (p.deprecated === true)
|
|
498
|
+
result.deprecated = true;
|
|
499
|
+
if (p.schema)
|
|
500
|
+
result.schema = this.normalizeSchema(p.schema);
|
|
501
|
+
if (typeof p.style === 'string')
|
|
502
|
+
result.style = p.style;
|
|
503
|
+
if (p.explode !== undefined)
|
|
504
|
+
result.explode = p.explode;
|
|
505
|
+
if (p.allowEmptyValue === true)
|
|
506
|
+
result.allowEmptyValue = true;
|
|
507
|
+
if ('example' in p)
|
|
508
|
+
result.example = p.example;
|
|
509
|
+
if (p.examples && typeof p.examples === 'object')
|
|
510
|
+
result.examples = p.examples;
|
|
511
|
+
return result;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ValidationResult } from '../../validators.js';
|
|
2
|
+
import type { NormalizedSpec } from '../normalized-spec.js';
|
|
3
|
+
import type { VersionStrategy } from '../version-strategy.js';
|
|
4
|
+
/**
|
|
5
|
+
* Stub strategy for OpenAPI 3.2.x specifications
|
|
6
|
+
*
|
|
7
|
+
* This strategy matches OpenAPI 3.2.x specifications but throws
|
|
8
|
+
* "not yet supported" errors until full implementation is available.
|
|
9
|
+
*/
|
|
10
|
+
export declare class V3_2_VersionStrategy implements VersionStrategy {
|
|
11
|
+
/**
|
|
12
|
+
* Get the supported OpenAPI version
|
|
13
|
+
*/
|
|
14
|
+
version(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Check if this strategy matches the given OpenAPI specification
|
|
17
|
+
*/
|
|
18
|
+
matches(spec: unknown): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Normalize a raw OpenAPI specification to a consistent format
|
|
21
|
+
* Throws "not yet supported" error
|
|
22
|
+
*/
|
|
23
|
+
normalizeSpec(_rawSpec: unknown): NormalizedSpec;
|
|
24
|
+
/**
|
|
25
|
+
* Validate the normalized specification
|
|
26
|
+
* Throws "not yet supported" error
|
|
27
|
+
*/
|
|
28
|
+
validateSpec(_spec: NormalizedSpec): ValidationResult;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve a reference within the document context
|
|
31
|
+
* Throws "not yet supported" error
|
|
32
|
+
*/
|
|
33
|
+
resolveRef(_ref: string, _doc: unknown, _context?: unknown): unknown;
|
|
34
|
+
/**
|
|
35
|
+
* Get supported features for this version
|
|
36
|
+
* Returns empty array since version is not yet supported
|
|
37
|
+
*/
|
|
38
|
+
getSupportedFeatures(): string[];
|
|
39
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stub strategy for OpenAPI 3.2.x specifications
|
|
3
|
+
*
|
|
4
|
+
* This strategy matches OpenAPI 3.2.x specifications but throws
|
|
5
|
+
* "not yet supported" errors until full implementation is available.
|
|
6
|
+
*/
|
|
7
|
+
export class V3_2_VersionStrategy {
|
|
8
|
+
/**
|
|
9
|
+
* Get the supported OpenAPI version
|
|
10
|
+
*/
|
|
11
|
+
version() {
|
|
12
|
+
return '3.2';
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Check if this strategy matches the given OpenAPI specification
|
|
16
|
+
*/
|
|
17
|
+
matches(spec) {
|
|
18
|
+
if (!spec || typeof spec !== 'object' || Array.isArray(spec)) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const specObj = spec;
|
|
22
|
+
const openapiVersion = specObj.openapi;
|
|
23
|
+
if (typeof openapiVersion === 'string') {
|
|
24
|
+
// Match 3.2.x versions (e.g., "3.2.0", "3.2.1", etc.)
|
|
25
|
+
return openapiVersion.startsWith('3.2');
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Normalize a raw OpenAPI specification to a consistent format
|
|
31
|
+
* Throws "not yet supported" error
|
|
32
|
+
*/
|
|
33
|
+
normalizeSpec(_rawSpec) {
|
|
34
|
+
throw new Error('OpenAPI 3.2 is not yet supported. Supported versions: 3.0, 3.1');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Validate the normalized specification
|
|
38
|
+
* Throws "not yet supported" error
|
|
39
|
+
*/
|
|
40
|
+
validateSpec(_spec) {
|
|
41
|
+
throw new Error('OpenAPI 3.2 is not yet supported. Supported versions: 3.0, 3.1');
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Resolve a reference within the document context
|
|
45
|
+
* Throws "not yet supported" error
|
|
46
|
+
*/
|
|
47
|
+
resolveRef(_ref, _doc, _context) {
|
|
48
|
+
throw new Error('OpenAPI 3.2 is not yet supported. Supported versions: 3.0, 3.1');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get supported features for this version
|
|
52
|
+
* Returns empty array since version is not yet supported
|
|
53
|
+
*/
|
|
54
|
+
getSupportedFeatures() {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version detector for multi-version OpenAPI support
|
|
3
|
+
*/
|
|
4
|
+
export function detectSpecVersion(rawSpec) {
|
|
5
|
+
if (typeof rawSpec !== 'object' || rawSpec === null || Array.isArray(rawSpec)) {
|
|
6
|
+
throw new Error('Invalid spec: must be a non-null object');
|
|
7
|
+
}
|
|
8
|
+
const spec = rawSpec;
|
|
9
|
+
// Check for Swagger 2.0
|
|
10
|
+
if ('swagger' in spec && typeof spec.swagger === 'string') {
|
|
11
|
+
if (spec.swagger === '2.0') {
|
|
12
|
+
throw new Error('Swagger 2.0 is not supported. Convert to OpenAPI using swagger2openapi first.');
|
|
13
|
+
}
|
|
14
|
+
throw new Error(`Unsupported Swagger version: ${spec.swagger}`);
|
|
15
|
+
}
|
|
16
|
+
// Check for OpenAPI 3.0+
|
|
17
|
+
if (!('openapi' in spec) || typeof spec.openapi !== 'string') {
|
|
18
|
+
throw new Error("Invalid spec: missing or invalid 'openapi' field");
|
|
19
|
+
}
|
|
20
|
+
const openapiVersion = spec.openapi;
|
|
21
|
+
// Extract major.minor version
|
|
22
|
+
const versionMatch = openapiVersion.match(/^(\d+\.\d+)/);
|
|
23
|
+
if (!versionMatch) {
|
|
24
|
+
throw new Error(`Invalid OpenAPI version format: ${openapiVersion}`);
|
|
25
|
+
}
|
|
26
|
+
const majorMinorVersion = versionMatch[1];
|
|
27
|
+
// Validate supported versions
|
|
28
|
+
const supportedVersions = ['3.0', '3.1', '3.2'];
|
|
29
|
+
if (!supportedVersions.includes(majorMinorVersion)) {
|
|
30
|
+
throw new Error(`Unsupported OpenAPI version: ${openapiVersion}. ` +
|
|
31
|
+
`Supported versions: ${supportedVersions.join(', ')}`);
|
|
32
|
+
}
|
|
33
|
+
return majorMinorVersion;
|
|
34
|
+
}
|