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,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalized schema with consistent types across OpenAPI versions
|
|
3
|
+
*/
|
|
4
|
+
export interface NormalizedSchema {
|
|
5
|
+
/** Normalized types - always an array, even for single types */
|
|
6
|
+
types: string[];
|
|
7
|
+
/** Format information */
|
|
8
|
+
format?: string;
|
|
9
|
+
/** Properties object */
|
|
10
|
+
properties?: Record<string, NormalizedSchema>;
|
|
11
|
+
/** Required properties array */
|
|
12
|
+
required?: string[];
|
|
13
|
+
/** Array items schema */
|
|
14
|
+
items?: NormalizedSchema;
|
|
15
|
+
/** Additional properties flag or schema */
|
|
16
|
+
additionalProperties?: boolean | NormalizedSchema;
|
|
17
|
+
/** Reference pointer */
|
|
18
|
+
$ref?: string;
|
|
19
|
+
/** AllOf composition */
|
|
20
|
+
allOf?: NormalizedSchema[];
|
|
21
|
+
/** OneOf composition */
|
|
22
|
+
oneOf?: NormalizedSchema[];
|
|
23
|
+
/** AnyOf composition */
|
|
24
|
+
anyOf?: NormalizedSchema[];
|
|
25
|
+
/** Enum values */
|
|
26
|
+
enum?: unknown[];
|
|
27
|
+
/** Const value */
|
|
28
|
+
const?: unknown;
|
|
29
|
+
/** Default value */
|
|
30
|
+
default?: unknown;
|
|
31
|
+
/** Description */
|
|
32
|
+
description?: string;
|
|
33
|
+
/** Whether nullable */
|
|
34
|
+
nullable: boolean;
|
|
35
|
+
/** Examples - always normalized to array */
|
|
36
|
+
examples: unknown[];
|
|
37
|
+
/** File upload information */
|
|
38
|
+
fileUpload?: {
|
|
39
|
+
binary: boolean;
|
|
40
|
+
base64: boolean;
|
|
41
|
+
};
|
|
42
|
+
/** Exclusive minimum (always number) */
|
|
43
|
+
exclusiveMinimum?: number;
|
|
44
|
+
/** Exclusive maximum (always number) */
|
|
45
|
+
exclusiveMaximum?: number;
|
|
46
|
+
/** Minimum value */
|
|
47
|
+
minimum?: number;
|
|
48
|
+
/** Maximum value */
|
|
49
|
+
maximum?: number;
|
|
50
|
+
/** Read only */
|
|
51
|
+
readOnly?: boolean;
|
|
52
|
+
/** Write only */
|
|
53
|
+
writeOnly?: boolean;
|
|
54
|
+
/** Deprecated */
|
|
55
|
+
deprecated?: boolean;
|
|
56
|
+
/** Discriminator information */
|
|
57
|
+
discriminator?: {
|
|
58
|
+
propertyName: string;
|
|
59
|
+
mapping?: Record<string, string>;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Normalized OpenAPI specification with consistent structure
|
|
64
|
+
*/
|
|
65
|
+
export interface NormalizedSpec {
|
|
66
|
+
/** Original OpenAPI version string */
|
|
67
|
+
openapi: string;
|
|
68
|
+
/** Info object */
|
|
69
|
+
info: {
|
|
70
|
+
title: string;
|
|
71
|
+
version: string;
|
|
72
|
+
description?: string;
|
|
73
|
+
termsOfService?: string;
|
|
74
|
+
contact?: {
|
|
75
|
+
name?: string;
|
|
76
|
+
url?: string;
|
|
77
|
+
email?: string;
|
|
78
|
+
};
|
|
79
|
+
license?: {
|
|
80
|
+
name: string;
|
|
81
|
+
url?: string;
|
|
82
|
+
identifier?: string;
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
/** Servers array */
|
|
86
|
+
servers?: Array<{
|
|
87
|
+
url: string;
|
|
88
|
+
description?: string;
|
|
89
|
+
variables?: Record<string, {
|
|
90
|
+
default: string;
|
|
91
|
+
description?: string;
|
|
92
|
+
enum?: string[];
|
|
93
|
+
}>;
|
|
94
|
+
}>;
|
|
95
|
+
/** Paths object - simplified version */
|
|
96
|
+
paths?: Record<string, {
|
|
97
|
+
$ref?: string;
|
|
98
|
+
summary?: string;
|
|
99
|
+
description?: string;
|
|
100
|
+
get?: NormalizedOperation;
|
|
101
|
+
put?: NormalizedOperation;
|
|
102
|
+
post?: NormalizedOperation;
|
|
103
|
+
delete?: NormalizedOperation;
|
|
104
|
+
options?: NormalizedOperation;
|
|
105
|
+
head?: NormalizedOperation;
|
|
106
|
+
patch?: NormalizedOperation;
|
|
107
|
+
trace?: NormalizedOperation;
|
|
108
|
+
parameters?: Array<NormalizedParameter>;
|
|
109
|
+
servers?: unknown[];
|
|
110
|
+
}>;
|
|
111
|
+
/** Components object */
|
|
112
|
+
components?: {
|
|
113
|
+
schemas?: Record<string, NormalizedSchema>;
|
|
114
|
+
responses?: Record<string, unknown>;
|
|
115
|
+
parameters?: Record<string, unknown>;
|
|
116
|
+
requestBodies?: Record<string, unknown>;
|
|
117
|
+
headers?: Record<string, unknown>;
|
|
118
|
+
securitySchemes?: Record<string, unknown>;
|
|
119
|
+
links?: Record<string, unknown>;
|
|
120
|
+
callbacks?: Record<string, unknown>;
|
|
121
|
+
examples?: Record<string, unknown>;
|
|
122
|
+
};
|
|
123
|
+
/** Security requirements */
|
|
124
|
+
security?: unknown[];
|
|
125
|
+
/** Tags */
|
|
126
|
+
tags?: unknown[];
|
|
127
|
+
/** External documentation */
|
|
128
|
+
externalDocs?: {
|
|
129
|
+
description?: string;
|
|
130
|
+
url: string;
|
|
131
|
+
};
|
|
132
|
+
/** Webhooks */
|
|
133
|
+
webhooks?: Record<string, {
|
|
134
|
+
$ref?: string;
|
|
135
|
+
summary?: string;
|
|
136
|
+
description?: string;
|
|
137
|
+
get?: NormalizedOperation;
|
|
138
|
+
put?: NormalizedOperation;
|
|
139
|
+
post?: NormalizedOperation;
|
|
140
|
+
delete?: NormalizedOperation;
|
|
141
|
+
options?: NormalizedOperation;
|
|
142
|
+
head?: NormalizedOperation;
|
|
143
|
+
patch?: NormalizedOperation;
|
|
144
|
+
trace?: NormalizedOperation;
|
|
145
|
+
parameters?: Array<NormalizedParameter>;
|
|
146
|
+
servers?: unknown[];
|
|
147
|
+
}>;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Normalized operation object
|
|
151
|
+
*/
|
|
152
|
+
interface NormalizedOperation {
|
|
153
|
+
tags?: string[];
|
|
154
|
+
summary?: string;
|
|
155
|
+
description?: string;
|
|
156
|
+
operationId?: string;
|
|
157
|
+
parameters?: Array<NormalizedParameter>;
|
|
158
|
+
requestBody?: {
|
|
159
|
+
description?: string;
|
|
160
|
+
content: Record<string, {
|
|
161
|
+
schema?: NormalizedSchema;
|
|
162
|
+
examples?: Record<string, unknown>;
|
|
163
|
+
example?: unknown;
|
|
164
|
+
encoding?: Record<string, unknown>;
|
|
165
|
+
}>;
|
|
166
|
+
required?: boolean;
|
|
167
|
+
};
|
|
168
|
+
responses: Record<string, {
|
|
169
|
+
description: string;
|
|
170
|
+
headers?: Record<string, unknown>;
|
|
171
|
+
content?: Record<string, {
|
|
172
|
+
schema?: NormalizedSchema;
|
|
173
|
+
examples?: Record<string, unknown>;
|
|
174
|
+
example?: unknown;
|
|
175
|
+
encoding?: Record<string, unknown>;
|
|
176
|
+
}>;
|
|
177
|
+
links?: Record<string, unknown>;
|
|
178
|
+
}>;
|
|
179
|
+
deprecated?: boolean;
|
|
180
|
+
security?: unknown[];
|
|
181
|
+
servers?: unknown[];
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Normalized parameter object
|
|
185
|
+
*/
|
|
186
|
+
interface NormalizedParameter {
|
|
187
|
+
name: string;
|
|
188
|
+
in: 'query' | 'path' | 'header' | 'cookie';
|
|
189
|
+
description?: string;
|
|
190
|
+
required?: boolean;
|
|
191
|
+
deprecated?: boolean;
|
|
192
|
+
schema?: NormalizedSchema;
|
|
193
|
+
style?: string;
|
|
194
|
+
explode?: boolean;
|
|
195
|
+
allowEmptyValue?: boolean;
|
|
196
|
+
example?: unknown;
|
|
197
|
+
examples?: Record<string, unknown>;
|
|
198
|
+
}
|
|
199
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { VersionStrategy } from './version-strategy.js';
|
|
2
|
+
/**
|
|
3
|
+
* Registry for managing version strategies
|
|
4
|
+
*/
|
|
5
|
+
export declare class VersionStrategyRegistry {
|
|
6
|
+
private strategies;
|
|
7
|
+
/**
|
|
8
|
+
* Register a version strategy for a specific OpenAPI version
|
|
9
|
+
*/
|
|
10
|
+
register(strategy: VersionStrategy): void;
|
|
11
|
+
/**
|
|
12
|
+
* Get a version strategy by version string
|
|
13
|
+
* @throws Error if strategy is not registered
|
|
14
|
+
*/
|
|
15
|
+
get(version: string): VersionStrategy;
|
|
16
|
+
/**
|
|
17
|
+
* Detect the version from a raw OpenAPI specification and return the corresponding strategy
|
|
18
|
+
*/
|
|
19
|
+
detectAndResolve(rawSpec: unknown): VersionStrategy;
|
|
20
|
+
/**
|
|
21
|
+
* Get list of supported version strings
|
|
22
|
+
*/
|
|
23
|
+
listSupported(): string[];
|
|
24
|
+
/**
|
|
25
|
+
* Check if a version is supported
|
|
26
|
+
*/
|
|
27
|
+
isSupported(version: string): boolean;
|
|
28
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { detectSpecVersion } from './version-detector.js';
|
|
2
|
+
/**
|
|
3
|
+
* Registry for managing version strategies
|
|
4
|
+
*/
|
|
5
|
+
export class VersionStrategyRegistry {
|
|
6
|
+
strategies = new Map();
|
|
7
|
+
/**
|
|
8
|
+
* Register a version strategy for a specific OpenAPI version
|
|
9
|
+
*/
|
|
10
|
+
register(strategy) {
|
|
11
|
+
const version = strategy.version();
|
|
12
|
+
this.strategies.set(version, strategy);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get a version strategy by version string
|
|
16
|
+
* @throws Error if strategy is not registered
|
|
17
|
+
*/
|
|
18
|
+
get(version) {
|
|
19
|
+
const strategy = this.strategies.get(version);
|
|
20
|
+
if (!strategy) {
|
|
21
|
+
throw new Error(`No strategy registered for version: ${version}`);
|
|
22
|
+
}
|
|
23
|
+
return strategy;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Detect the version from a raw OpenAPI specification and return the corresponding strategy
|
|
27
|
+
*/
|
|
28
|
+
detectAndResolve(rawSpec) {
|
|
29
|
+
const version = detectSpecVersion(rawSpec);
|
|
30
|
+
return this.get(version);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Get list of supported version strings
|
|
34
|
+
*/
|
|
35
|
+
listSupported() {
|
|
36
|
+
return Array.from(this.strategies.keys());
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Check if a version is supported
|
|
40
|
+
*/
|
|
41
|
+
isSupported(version) {
|
|
42
|
+
return this.strategies.has(version);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { NormalizedSpec } from '../normalized-spec.js';
|
|
2
|
+
/**
|
|
3
|
+
* Normalize a raw OpenAPI 3.0.x specification to a consistent format.
|
|
4
|
+
*
|
|
5
|
+
* 3.0-specific transformations applied during normalization:
|
|
6
|
+
* - `nullable: true` → `nullable: true` on the NormalizedSchema
|
|
7
|
+
* - `exclusiveMinimum: true` + `minimum: 5` → `exclusiveMinimum: 5`
|
|
8
|
+
* - `exclusiveMinimum: false` → removed (not included in output)
|
|
9
|
+
* - `example: value` → `examples: [value]`
|
|
10
|
+
* - `format: "binary"` → `fileUpload: { binary: true, base64: false }`
|
|
11
|
+
* - `format: "byte"` → `fileUpload: { binary: false, base64: true }`
|
|
12
|
+
* - `$ref` siblings are stripped (3.0 compliant behavior)
|
|
13
|
+
* - Error if `items` is an array (3.1-only tuple syntax)
|
|
14
|
+
*/
|
|
15
|
+
export declare function normalizeSpec30(rawSpec: unknown): NormalizedSpec;
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a raw OpenAPI 3.0.x specification to a consistent format.
|
|
3
|
+
*
|
|
4
|
+
* 3.0-specific transformations applied during normalization:
|
|
5
|
+
* - `nullable: true` → `nullable: true` on the NormalizedSchema
|
|
6
|
+
* - `exclusiveMinimum: true` + `minimum: 5` → `exclusiveMinimum: 5`
|
|
7
|
+
* - `exclusiveMinimum: false` → removed (not included in output)
|
|
8
|
+
* - `example: value` → `examples: [value]`
|
|
9
|
+
* - `format: "binary"` → `fileUpload: { binary: true, base64: false }`
|
|
10
|
+
* - `format: "byte"` → `fileUpload: { binary: false, base64: true }`
|
|
11
|
+
* - `$ref` siblings are stripped (3.0 compliant behavior)
|
|
12
|
+
* - Error if `items` is an array (3.1-only tuple syntax)
|
|
13
|
+
*/
|
|
14
|
+
export function normalizeSpec30(rawSpec) {
|
|
15
|
+
if (!rawSpec || typeof rawSpec !== 'object' || Array.isArray(rawSpec)) {
|
|
16
|
+
throw new Error('Invalid spec: must be a non-null object');
|
|
17
|
+
}
|
|
18
|
+
const raw = rawSpec;
|
|
19
|
+
const rawInfo = raw.info;
|
|
20
|
+
if (!rawInfo || typeof rawInfo !== 'object' || Array.isArray(rawInfo)) {
|
|
21
|
+
throw new Error("Invalid spec: missing or invalid 'info' field");
|
|
22
|
+
}
|
|
23
|
+
const ri = rawInfo;
|
|
24
|
+
const info = {
|
|
25
|
+
title: typeof ri.title === 'string' ? ri.title : '',
|
|
26
|
+
version: typeof ri.version === 'string' ? ri.version : '',
|
|
27
|
+
description: typeof ri.description === 'string' ? ri.description : undefined,
|
|
28
|
+
termsOfService: typeof ri.termsOfService === 'string' ? ri.termsOfService : undefined,
|
|
29
|
+
contact: ri.contact && typeof ri.contact === 'object' && !Array.isArray(ri.contact)
|
|
30
|
+
? ri.contact
|
|
31
|
+
: undefined,
|
|
32
|
+
license: ri.license && typeof ri.license === 'object' && !Array.isArray(ri.license)
|
|
33
|
+
? ri.license
|
|
34
|
+
: undefined,
|
|
35
|
+
};
|
|
36
|
+
const servers = Array.isArray(raw.servers)
|
|
37
|
+
? raw.servers
|
|
38
|
+
: undefined;
|
|
39
|
+
let paths;
|
|
40
|
+
if (raw.paths && typeof raw.paths === 'object' && !Array.isArray(raw.paths)) {
|
|
41
|
+
paths = {};
|
|
42
|
+
for (const [pathKey, pathItem] of Object.entries(raw.paths)) {
|
|
43
|
+
if (pathItem && typeof pathItem === 'object' && !Array.isArray(pathItem)) {
|
|
44
|
+
paths[pathKey] = normalizePathItem(pathItem);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
let components;
|
|
49
|
+
if (raw.components && typeof raw.components === 'object' && !Array.isArray(raw.components)) {
|
|
50
|
+
const rc = raw.components;
|
|
51
|
+
components = {};
|
|
52
|
+
if (rc.schemas && typeof rc.schemas === 'object' && !Array.isArray(rc.schemas)) {
|
|
53
|
+
components.schemas = {};
|
|
54
|
+
for (const [name, schema] of Object.entries(rc.schemas)) {
|
|
55
|
+
components.schemas[name] = normalizeSchema(schema);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (rc.responses && typeof rc.responses === 'object')
|
|
59
|
+
components.responses = rc.responses;
|
|
60
|
+
if (rc.parameters && typeof rc.parameters === 'object')
|
|
61
|
+
components.parameters = rc.parameters;
|
|
62
|
+
if (rc.requestBodies && typeof rc.requestBodies === 'object')
|
|
63
|
+
components.requestBodies = rc.requestBodies;
|
|
64
|
+
if (rc.headers && typeof rc.headers === 'object')
|
|
65
|
+
components.headers = rc.headers;
|
|
66
|
+
if (rc.securitySchemes && typeof rc.securitySchemes === 'object')
|
|
67
|
+
components.securitySchemes = rc.securitySchemes;
|
|
68
|
+
if (rc.links && typeof rc.links === 'object')
|
|
69
|
+
components.links = rc.links;
|
|
70
|
+
if (rc.callbacks && typeof rc.callbacks === 'object')
|
|
71
|
+
components.callbacks = rc.callbacks;
|
|
72
|
+
if (rc.examples && typeof rc.examples === 'object')
|
|
73
|
+
components.examples = rc.examples;
|
|
74
|
+
}
|
|
75
|
+
let webhooks;
|
|
76
|
+
if (raw.webhooks && typeof raw.webhooks === 'object' && !Array.isArray(raw.webhooks)) {
|
|
77
|
+
webhooks = {};
|
|
78
|
+
for (const [name, pathItem] of Object.entries(raw.webhooks)) {
|
|
79
|
+
if (pathItem && typeof pathItem === 'object' && !Array.isArray(pathItem)) {
|
|
80
|
+
webhooks[name] = normalizePathItem(pathItem);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
openapi: typeof raw.openapi === 'string' ? raw.openapi : '3.0.0',
|
|
86
|
+
info,
|
|
87
|
+
servers,
|
|
88
|
+
paths,
|
|
89
|
+
components,
|
|
90
|
+
security: Array.isArray(raw.security) ? raw.security : undefined,
|
|
91
|
+
tags: Array.isArray(raw.tags) ? raw.tags : undefined,
|
|
92
|
+
externalDocs: raw.externalDocs && typeof raw.externalDocs === 'object' && !Array.isArray(raw.externalDocs)
|
|
93
|
+
? raw.externalDocs
|
|
94
|
+
: undefined,
|
|
95
|
+
webhooks,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Normalize a schema object with 3.0-specific transformations.
|
|
100
|
+
*
|
|
101
|
+
* Key differences from 3.1 normalization:
|
|
102
|
+
* - `nullable` keyword is the primary null indicator (not type arrays with "null")
|
|
103
|
+
* - `exclusiveMinimum`/`exclusiveMaximum` are boolean modifiers of `minimum`/`maximum`
|
|
104
|
+
* - `$ref` siblings are stripped (3.0 compliant behavior)
|
|
105
|
+
* - `items` as array is an error (3.1-only tuple syntax)
|
|
106
|
+
*/
|
|
107
|
+
function normalizeSchema(schema) {
|
|
108
|
+
if (!schema || typeof schema !== 'object' || Array.isArray(schema)) {
|
|
109
|
+
return { types: [], nullable: false, examples: [] };
|
|
110
|
+
}
|
|
111
|
+
const s = schema;
|
|
112
|
+
if (typeof s.$ref === 'string') {
|
|
113
|
+
return {
|
|
114
|
+
$ref: s.$ref,
|
|
115
|
+
types: [],
|
|
116
|
+
nullable: false,
|
|
117
|
+
examples: [],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
let types = [];
|
|
121
|
+
let nullable = false;
|
|
122
|
+
if (typeof s.type === 'string') {
|
|
123
|
+
types = [s.type];
|
|
124
|
+
}
|
|
125
|
+
else if (Array.isArray(s.type)) {
|
|
126
|
+
const typeArr = s.type;
|
|
127
|
+
if (typeArr.includes('null')) {
|
|
128
|
+
nullable = true;
|
|
129
|
+
types = typeArr.filter((t) => t !== 'null');
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
types = [...typeArr];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (s.nullable === true) {
|
|
136
|
+
nullable = true;
|
|
137
|
+
}
|
|
138
|
+
let examples = [];
|
|
139
|
+
if (Array.isArray(s.examples)) {
|
|
140
|
+
examples = [...s.examples];
|
|
141
|
+
}
|
|
142
|
+
else if (s.example !== undefined) {
|
|
143
|
+
examples = [s.example];
|
|
144
|
+
}
|
|
145
|
+
let exclusiveMinimum;
|
|
146
|
+
if (s.exclusiveMinimum === true && typeof s.minimum === 'number') {
|
|
147
|
+
exclusiveMinimum = s.minimum;
|
|
148
|
+
}
|
|
149
|
+
else if (typeof s.exclusiveMinimum === 'number') {
|
|
150
|
+
exclusiveMinimum = s.exclusiveMinimum;
|
|
151
|
+
}
|
|
152
|
+
let exclusiveMaximum;
|
|
153
|
+
if (s.exclusiveMaximum === true && typeof s.maximum === 'number') {
|
|
154
|
+
exclusiveMaximum = s.maximum;
|
|
155
|
+
}
|
|
156
|
+
else if (typeof s.exclusiveMaximum === 'number') {
|
|
157
|
+
exclusiveMaximum = s.exclusiveMaximum;
|
|
158
|
+
}
|
|
159
|
+
let fileUpload;
|
|
160
|
+
if (typeof s.format === 'string') {
|
|
161
|
+
if (s.format === 'binary') {
|
|
162
|
+
fileUpload = { binary: true, base64: false };
|
|
163
|
+
}
|
|
164
|
+
else if (s.format === 'byte') {
|
|
165
|
+
fileUpload = { binary: false, base64: true };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const properties = normalizePropertyMap(s.properties);
|
|
169
|
+
let items;
|
|
170
|
+
if (s.items !== undefined) {
|
|
171
|
+
if (Array.isArray(s.items)) {
|
|
172
|
+
throw new Error("Schema 'items' as an array is not supported in OpenAPI 3.0 (tuple syntax is 3.1-only). Use prefixItems in 3.1.");
|
|
173
|
+
}
|
|
174
|
+
items = normalizeSchema(s.items);
|
|
175
|
+
}
|
|
176
|
+
const allOf = Array.isArray(s.allOf)
|
|
177
|
+
? s.allOf.map((item) => normalizeSchema(item))
|
|
178
|
+
: undefined;
|
|
179
|
+
const oneOf = Array.isArray(s.oneOf)
|
|
180
|
+
? s.oneOf.map((item) => normalizeSchema(item))
|
|
181
|
+
: undefined;
|
|
182
|
+
const anyOf = Array.isArray(s.anyOf)
|
|
183
|
+
? s.anyOf.map((item) => normalizeSchema(item))
|
|
184
|
+
: undefined;
|
|
185
|
+
let additionalProperties;
|
|
186
|
+
if (s.additionalProperties === true) {
|
|
187
|
+
additionalProperties = true;
|
|
188
|
+
}
|
|
189
|
+
else if (s.additionalProperties === false) {
|
|
190
|
+
additionalProperties = false;
|
|
191
|
+
}
|
|
192
|
+
else if (s.additionalProperties &&
|
|
193
|
+
typeof s.additionalProperties === 'object' &&
|
|
194
|
+
!Array.isArray(s.additionalProperties)) {
|
|
195
|
+
additionalProperties = normalizeSchema(s.additionalProperties);
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
types,
|
|
199
|
+
nullable,
|
|
200
|
+
format: typeof s.format === 'string' ? s.format : undefined,
|
|
201
|
+
properties,
|
|
202
|
+
required: Array.isArray(s.required) ? s.required : undefined,
|
|
203
|
+
items,
|
|
204
|
+
additionalProperties,
|
|
205
|
+
allOf,
|
|
206
|
+
oneOf,
|
|
207
|
+
anyOf,
|
|
208
|
+
enum: Array.isArray(s.enum) ? [...s.enum] : undefined,
|
|
209
|
+
const: 'const' in s ? s.const : undefined,
|
|
210
|
+
default: 'default' in s ? s.default : undefined,
|
|
211
|
+
description: typeof s.description === 'string' ? s.description : undefined,
|
|
212
|
+
examples,
|
|
213
|
+
fileUpload,
|
|
214
|
+
exclusiveMinimum,
|
|
215
|
+
exclusiveMaximum,
|
|
216
|
+
minimum: typeof s.minimum === 'number' ? s.minimum : undefined,
|
|
217
|
+
maximum: typeof s.maximum === 'number' ? s.maximum : undefined,
|
|
218
|
+
readOnly: s.readOnly === true ? true : undefined,
|
|
219
|
+
writeOnly: s.writeOnly === true ? true : undefined,
|
|
220
|
+
deprecated: s.deprecated === true ? true : undefined,
|
|
221
|
+
discriminator: s.discriminator && typeof s.discriminator === 'object' && !Array.isArray(s.discriminator)
|
|
222
|
+
? s.discriminator
|
|
223
|
+
: undefined,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function normalizePropertyMap(raw) {
|
|
227
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
const entries = Object.entries(raw);
|
|
231
|
+
if (entries.length === 0)
|
|
232
|
+
return undefined;
|
|
233
|
+
const result = {};
|
|
234
|
+
for (const [key, val] of entries) {
|
|
235
|
+
result[key] = normalizeSchema(val);
|
|
236
|
+
}
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
function normalizePathItem(raw) {
|
|
240
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
241
|
+
return {};
|
|
242
|
+
}
|
|
243
|
+
const pi = raw;
|
|
244
|
+
const httpMethods = [
|
|
245
|
+
'get',
|
|
246
|
+
'put',
|
|
247
|
+
'post',
|
|
248
|
+
'delete',
|
|
249
|
+
'options',
|
|
250
|
+
'head',
|
|
251
|
+
'patch',
|
|
252
|
+
'trace',
|
|
253
|
+
];
|
|
254
|
+
const result = {};
|
|
255
|
+
if (typeof pi.$ref === 'string')
|
|
256
|
+
result.$ref = pi.$ref;
|
|
257
|
+
if (typeof pi.summary === 'string')
|
|
258
|
+
result.summary = pi.summary;
|
|
259
|
+
if (typeof pi.description === 'string')
|
|
260
|
+
result.description = pi.description;
|
|
261
|
+
if (Array.isArray(pi.servers))
|
|
262
|
+
result.servers = pi.servers;
|
|
263
|
+
if (Array.isArray(pi.parameters)) {
|
|
264
|
+
result.parameters = pi.parameters.map((p) => normalizeParameter(p));
|
|
265
|
+
}
|
|
266
|
+
for (const method of httpMethods) {
|
|
267
|
+
const op = pi[method];
|
|
268
|
+
if (op && typeof op === 'object' && !Array.isArray(op)) {
|
|
269
|
+
result[method] = normalizeOperation(op);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return result;
|
|
273
|
+
}
|
|
274
|
+
function normalizeOperation(raw) {
|
|
275
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
276
|
+
return { responses: {} };
|
|
277
|
+
}
|
|
278
|
+
const o = raw;
|
|
279
|
+
const result = {
|
|
280
|
+
responses: {},
|
|
281
|
+
};
|
|
282
|
+
if (Array.isArray(o.tags))
|
|
283
|
+
result.tags = o.tags;
|
|
284
|
+
if (typeof o.summary === 'string')
|
|
285
|
+
result.summary = o.summary;
|
|
286
|
+
if (typeof o.description === 'string')
|
|
287
|
+
result.description = o.description;
|
|
288
|
+
if (typeof o.operationId === 'string')
|
|
289
|
+
result.operationId = o.operationId;
|
|
290
|
+
if (o.deprecated === true)
|
|
291
|
+
result.deprecated = true;
|
|
292
|
+
if (Array.isArray(o.security))
|
|
293
|
+
result.security = o.security;
|
|
294
|
+
if (Array.isArray(o.servers))
|
|
295
|
+
result.servers = o.servers;
|
|
296
|
+
if (Array.isArray(o.parameters)) {
|
|
297
|
+
result.parameters = o.parameters.map((p) => normalizeParameter(p));
|
|
298
|
+
}
|
|
299
|
+
if (o.requestBody && typeof o.requestBody === 'object' && !Array.isArray(o.requestBody)) {
|
|
300
|
+
const rb = o.requestBody;
|
|
301
|
+
result.requestBody = {
|
|
302
|
+
content: normalizeContentMap(rb.content),
|
|
303
|
+
};
|
|
304
|
+
if (typeof rb.description === 'string')
|
|
305
|
+
result.requestBody.description = rb.description;
|
|
306
|
+
if (rb.required === true)
|
|
307
|
+
result.requestBody.required = true;
|
|
308
|
+
}
|
|
309
|
+
if (o.responses && typeof o.responses === 'object' && !Array.isArray(o.responses)) {
|
|
310
|
+
const responses = {};
|
|
311
|
+
for (const [code, resp] of Object.entries(o.responses)) {
|
|
312
|
+
if (resp && typeof resp === 'object' && !Array.isArray(resp)) {
|
|
313
|
+
const r = resp;
|
|
314
|
+
responses[code] = {
|
|
315
|
+
description: typeof r.description === 'string' ? r.description : '',
|
|
316
|
+
};
|
|
317
|
+
if (r.headers && typeof r.headers === 'object')
|
|
318
|
+
responses[code].headers = r.headers;
|
|
319
|
+
if (r.content && typeof r.content === 'object')
|
|
320
|
+
responses[code].content = normalizeContentMap(r.content);
|
|
321
|
+
if (r.links && typeof r.links === 'object')
|
|
322
|
+
responses[code].links = r.links;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
result.responses = responses;
|
|
326
|
+
}
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
329
|
+
function normalizeContentMap(raw) {
|
|
330
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
331
|
+
return {};
|
|
332
|
+
}
|
|
333
|
+
const result = {};
|
|
334
|
+
for (const [mediaType, mediaObj] of Object.entries(raw)) {
|
|
335
|
+
if (mediaObj && typeof mediaObj === 'object' && !Array.isArray(mediaObj)) {
|
|
336
|
+
const m = mediaObj;
|
|
337
|
+
const entry = {};
|
|
338
|
+
if (m.schema) {
|
|
339
|
+
entry.schema = normalizeSchema(m.schema);
|
|
340
|
+
}
|
|
341
|
+
if (m.examples && typeof m.examples === 'object')
|
|
342
|
+
entry.examples = m.examples;
|
|
343
|
+
if ('example' in m)
|
|
344
|
+
entry.example = m.example;
|
|
345
|
+
if (m.encoding && typeof m.encoding === 'object')
|
|
346
|
+
entry.encoding = m.encoding;
|
|
347
|
+
result[mediaType] = entry;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return result;
|
|
351
|
+
}
|
|
352
|
+
function normalizeParameter(raw) {
|
|
353
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
354
|
+
return { name: '', in: 'query' };
|
|
355
|
+
}
|
|
356
|
+
const p = raw;
|
|
357
|
+
if (typeof p.$ref === 'string') {
|
|
358
|
+
return {
|
|
359
|
+
name: '',
|
|
360
|
+
in: 'query',
|
|
361
|
+
$ref: p.$ref,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
const result = {
|
|
365
|
+
name: typeof p.name === 'string' ? p.name : '',
|
|
366
|
+
in: ['query', 'path', 'header', 'cookie'].includes(p.in)
|
|
367
|
+
? p.in
|
|
368
|
+
: 'query',
|
|
369
|
+
};
|
|
370
|
+
if (typeof p.description === 'string')
|
|
371
|
+
result.description = p.description;
|
|
372
|
+
if (p.required === true)
|
|
373
|
+
result.required = true;
|
|
374
|
+
if (p.deprecated === true)
|
|
375
|
+
result.deprecated = true;
|
|
376
|
+
if (p.schema)
|
|
377
|
+
result.schema = normalizeSchema(p.schema);
|
|
378
|
+
if (typeof p.style === 'string')
|
|
379
|
+
result.style = p.style;
|
|
380
|
+
if (p.explode !== undefined)
|
|
381
|
+
result.explode = p.explode;
|
|
382
|
+
if (p.allowEmptyValue === true)
|
|
383
|
+
result.allowEmptyValue = true;
|
|
384
|
+
if ('example' in p)
|
|
385
|
+
result.example = p.example;
|
|
386
|
+
if (p.examples && typeof p.examples === 'object')
|
|
387
|
+
result.examples = p.examples;
|
|
388
|
+
return result;
|
|
389
|
+
}
|