@vertz/openapi 0.1.1 → 0.1.2
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/README.md +19 -3
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +40 -0
- package/dist/index.js +1 -1
- package/dist/shared/{chunk-er3k8t3a.js → chunk-h8tb765a.js} +169 -96
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,15 +104,31 @@ console.log(`${result.written} files written, ${result.skipped} unchanged`);
|
|
|
104
104
|
|
|
105
105
|
## Using the Generated SDK
|
|
106
106
|
|
|
107
|
+
The generated SDK uses `@vertz/fetch` under the hood. Install it in the project that consumes the SDK:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
bun add @vertz/fetch
|
|
111
|
+
```
|
|
112
|
+
|
|
107
113
|
```ts
|
|
108
114
|
import { createClient } from './generated/client';
|
|
115
|
+
import { isOk } from '@vertz/fetch';
|
|
109
116
|
|
|
110
117
|
const api = createClient({ baseURL: 'https://api.example.com' });
|
|
111
118
|
|
|
112
119
|
// Fully typed — params, body, and response types are inferred
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
120
|
+
// Returns FetchResponse<T> (Result type) — use isOk/isErr to handle
|
|
121
|
+
const result = await api.tasks.list();
|
|
122
|
+
if (isOk(result)) {
|
|
123
|
+
console.log(result.data); // typed as Task[]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// All FetchClient features available: auth strategies, retries, hooks
|
|
127
|
+
const api = createClient({
|
|
128
|
+
baseURL: 'https://api.example.com',
|
|
129
|
+
authStrategies: [{ type: 'bearer', token: 'my-token' }],
|
|
130
|
+
retry: { retries: 3 },
|
|
131
|
+
});
|
|
116
132
|
```
|
|
117
133
|
|
|
118
134
|
## Custom Operation ID Normalization
|
package/dist/cli.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ interface ParsedSpec {
|
|
|
7
7
|
};
|
|
8
8
|
resources: ParsedResource[];
|
|
9
9
|
schemas: ParsedSchema[];
|
|
10
|
+
securitySchemes: ParsedSecurityScheme[];
|
|
10
11
|
}
|
|
11
12
|
interface ParsedResource {
|
|
12
13
|
name: string;
|
|
@@ -24,6 +25,11 @@ interface ParsedOperation {
|
|
|
24
25
|
response?: ParsedSchema;
|
|
25
26
|
responseStatus: number;
|
|
26
27
|
tags: string[];
|
|
28
|
+
security?: OperationSecurity;
|
|
29
|
+
}
|
|
30
|
+
interface OperationSecurity {
|
|
31
|
+
required: boolean;
|
|
32
|
+
schemes: string[];
|
|
27
33
|
}
|
|
28
34
|
interface ParsedParameter {
|
|
29
35
|
name: string;
|
|
@@ -34,6 +40,37 @@ interface ParsedSchema {
|
|
|
34
40
|
name?: string;
|
|
35
41
|
jsonSchema: Record<string, unknown>;
|
|
36
42
|
}
|
|
43
|
+
type ParsedSecurityScheme = {
|
|
44
|
+
type: "bearer";
|
|
45
|
+
name: string;
|
|
46
|
+
description?: string;
|
|
47
|
+
} | {
|
|
48
|
+
type: "basic";
|
|
49
|
+
name: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
} | {
|
|
52
|
+
type: "apiKey";
|
|
53
|
+
name: string;
|
|
54
|
+
in: "header" | "query" | "cookie";
|
|
55
|
+
paramName: string;
|
|
56
|
+
description?: string;
|
|
57
|
+
} | {
|
|
58
|
+
type: "oauth2";
|
|
59
|
+
name: string;
|
|
60
|
+
flows: ParsedOAuthFlows;
|
|
61
|
+
description?: string;
|
|
62
|
+
};
|
|
63
|
+
interface ParsedOAuthFlows {
|
|
64
|
+
authorizationCode?: {
|
|
65
|
+
authorizationUrl: string;
|
|
66
|
+
tokenUrl: string;
|
|
67
|
+
scopes: Record<string, string>;
|
|
68
|
+
};
|
|
69
|
+
clientCredentials?: {
|
|
70
|
+
tokenUrl: string;
|
|
71
|
+
scopes: Record<string, string>;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
37
74
|
interface OperationContext {
|
|
38
75
|
/** Raw operationId from the spec */
|
|
39
76
|
operationId: string;
|
|
@@ -117,9 +154,11 @@ declare function sanitizeIdentifier(name: string): string;
|
|
|
117
154
|
declare function generateAll(spec: ParsedSpec, options?: GenerateOptions): GeneratedFile[];
|
|
118
155
|
/**
|
|
119
156
|
* Generate the main client.ts file.
|
|
157
|
+
* Uses @vertz/fetch FetchClient instead of hand-rolling HTTP methods.
|
|
120
158
|
*/
|
|
121
159
|
declare function generateClient(resources: ParsedResource[], config: {
|
|
122
160
|
baseURL?: string;
|
|
161
|
+
securitySchemes?: ParsedSecurityScheme[];
|
|
123
162
|
}): GeneratedFile;
|
|
124
163
|
/**
|
|
125
164
|
* Convert a JSON Schema object to a TypeScript type expression string.
|
|
@@ -154,6 +193,7 @@ declare function generateTypes(resources: ParsedResource[], schemas: ParsedSchem
|
|
|
154
193
|
declare function parseOpenAPI(spec: Record<string, unknown>): {
|
|
155
194
|
operations: ParsedOperation[];
|
|
156
195
|
schemas: ParsedSchema[];
|
|
196
|
+
securitySchemes: ParsedSecurityScheme[];
|
|
157
197
|
version: "3.0" | "3.1";
|
|
158
198
|
};
|
|
159
199
|
interface ResolveOptions {
|
package/dist/index.js
CHANGED
|
@@ -41,86 +41,93 @@ function groupOperations(operations, strategy, options) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
// src/generators/client-generator.ts
|
|
44
|
+
function camelCase(name) {
|
|
45
|
+
return name.charAt(0).toLowerCase() + name.slice(1);
|
|
46
|
+
}
|
|
47
|
+
function generateAuthField(scheme) {
|
|
48
|
+
switch (scheme.type) {
|
|
49
|
+
case "bearer":
|
|
50
|
+
return ` ${camelCase(scheme.name)}?: string | (() => string | Promise<string>);`;
|
|
51
|
+
case "basic":
|
|
52
|
+
return ` ${camelCase(scheme.name)}?: { username: string; password: string };`;
|
|
53
|
+
case "apiKey":
|
|
54
|
+
return ` ${camelCase(scheme.name)}?: string | (() => string | Promise<string>);`;
|
|
55
|
+
case "oauth2":
|
|
56
|
+
return ` ${camelCase(scheme.name)}?: string | (() => string | Promise<string>);`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function generateAuthStrategy(scheme) {
|
|
60
|
+
const fieldName = camelCase(scheme.name);
|
|
61
|
+
const lines = [];
|
|
62
|
+
switch (scheme.type) {
|
|
63
|
+
case "bearer":
|
|
64
|
+
lines.push(` ...(options.auth?.${fieldName} ? [{`);
|
|
65
|
+
lines.push(` type: 'bearer' as const,`);
|
|
66
|
+
lines.push(` token: options.auth?.${fieldName},`);
|
|
67
|
+
lines.push(" }] : []),");
|
|
68
|
+
break;
|
|
69
|
+
case "basic":
|
|
70
|
+
lines.push(` ...(options.auth?.${fieldName} ? [{`);
|
|
71
|
+
lines.push(` type: 'basic' as const,`);
|
|
72
|
+
lines.push(` ...options.auth?.${fieldName},`);
|
|
73
|
+
lines.push(" }] : []),");
|
|
74
|
+
break;
|
|
75
|
+
case "apiKey":
|
|
76
|
+
lines.push(` ...(options.auth?.${fieldName} ? [{`);
|
|
77
|
+
lines.push(` type: 'apiKey' as const,`);
|
|
78
|
+
lines.push(` key: options.auth?.${fieldName},`);
|
|
79
|
+
lines.push(` location: '${scheme.in}' as const,`);
|
|
80
|
+
lines.push(` name: '${scheme.paramName}',`);
|
|
81
|
+
lines.push(" }] : []),");
|
|
82
|
+
break;
|
|
83
|
+
case "oauth2":
|
|
84
|
+
lines.push(` ...(options.auth?.${fieldName} ? [{`);
|
|
85
|
+
lines.push(` type: 'bearer' as const,`);
|
|
86
|
+
lines.push(` token: options.auth?.${fieldName},`);
|
|
87
|
+
lines.push(" }] : []),");
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
return lines;
|
|
91
|
+
}
|
|
44
92
|
function generateClient(resources, config) {
|
|
45
93
|
const defaultBaseURL = config.baseURL ? `'${config.baseURL.replace(/'/g, "\\'")}'` : "''";
|
|
94
|
+
const schemes = config.securitySchemes ?? [];
|
|
95
|
+
const hasAuth = schemes.length > 0;
|
|
46
96
|
const lines = [];
|
|
47
97
|
lines.push("// Generated by @vertz/openapi — do not edit");
|
|
48
98
|
lines.push("");
|
|
99
|
+
lines.push("import { FetchClient } from '@vertz/fetch';");
|
|
100
|
+
lines.push("import type { FetchClientConfig } from '@vertz/fetch';");
|
|
49
101
|
for (const r of resources) {
|
|
50
102
|
lines.push(`import { create${r.name}Resource } from './resources/${r.identifier}';`);
|
|
51
103
|
}
|
|
52
104
|
lines.push("");
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
105
|
+
if (hasAuth) {
|
|
106
|
+
lines.push("export interface ClientAuth {");
|
|
107
|
+
for (const scheme of schemes) {
|
|
108
|
+
lines.push(generateAuthField(scheme));
|
|
109
|
+
}
|
|
110
|
+
lines.push("}");
|
|
111
|
+
lines.push("");
|
|
112
|
+
}
|
|
113
|
+
if (hasAuth) {
|
|
114
|
+
lines.push("export type ClientOptions = FetchClientConfig & { auth?: ClientAuth };");
|
|
115
|
+
} else {
|
|
116
|
+
lines.push("export type ClientOptions = FetchClientConfig;");
|
|
117
|
+
}
|
|
66
118
|
lines.push("");
|
|
67
119
|
lines.push("export function createClient(options: ClientOptions = {}) {");
|
|
68
|
-
lines.push(` const
|
|
69
|
-
lines.push(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
lines.push(" const params = new URLSearchParams();");
|
|
77
|
-
lines.push(" for (const [k, v] of Object.entries(opts.query)) { if (v != null) params.set(k, String(v)); }");
|
|
78
|
-
lines.push(" const qs = params.toString();");
|
|
79
|
-
lines.push(" if (qs) url += `?${qs}`;");
|
|
80
|
-
lines.push(" }");
|
|
81
|
-
lines.push(" const res = await customFetch(url, { method: 'GET', headers: defaultHeaders });");
|
|
82
|
-
lines.push(" if (!res.ok) throw await ApiError.from(res);");
|
|
83
|
-
lines.push(" if (res.status === 204) return undefined as T;");
|
|
84
|
-
lines.push(" return res.json();");
|
|
85
|
-
lines.push(" },");
|
|
86
|
-
lines.push("");
|
|
87
|
-
for (const method of ["post", "put", "patch"]) {
|
|
88
|
-
lines.push(` async ${method}<T>(path: string, body?: unknown, opts?: { query?: Record<string, unknown> }): Promise<T> {`);
|
|
89
|
-
lines.push(` let url = \`\${baseURL}\${path}\`;`);
|
|
90
|
-
lines.push(" if (opts?.query) {");
|
|
91
|
-
lines.push(" const params = new URLSearchParams();");
|
|
92
|
-
lines.push(" for (const [k, v] of Object.entries(opts.query)) { if (v != null) params.set(k, String(v)); }");
|
|
93
|
-
lines.push(" const qs = params.toString();");
|
|
94
|
-
lines.push(" if (qs) url += `?${qs}`;");
|
|
95
|
-
lines.push(" }");
|
|
96
|
-
lines.push(` const res = await customFetch(url, {`);
|
|
97
|
-
lines.push(` method: '${method.toUpperCase()}',`);
|
|
98
|
-
lines.push(" headers: { ...defaultHeaders, 'Content-Type': 'application/json' },");
|
|
99
|
-
lines.push(" body: body !== undefined ? JSON.stringify(body) : undefined,");
|
|
100
|
-
lines.push(" });");
|
|
101
|
-
lines.push(" if (!res.ok) throw await ApiError.from(res);");
|
|
102
|
-
lines.push(" if (res.status === 204) return undefined as T;");
|
|
103
|
-
lines.push(" return res.json();");
|
|
104
|
-
lines.push(" },");
|
|
105
|
-
lines.push("");
|
|
120
|
+
lines.push(` const client = new FetchClient({`);
|
|
121
|
+
lines.push(` baseURL: ${defaultBaseURL},`);
|
|
122
|
+
if (hasAuth) {
|
|
123
|
+
lines.push(" authStrategies: [");
|
|
124
|
+
for (const scheme of schemes) {
|
|
125
|
+
lines.push(...generateAuthStrategy(scheme));
|
|
126
|
+
}
|
|
127
|
+
lines.push(" ],");
|
|
106
128
|
}
|
|
107
|
-
lines.push("
|
|
108
|
-
lines.push("
|
|
109
|
-
lines.push(" if (opts?.query) {");
|
|
110
|
-
lines.push(" const params = new URLSearchParams();");
|
|
111
|
-
lines.push(" for (const [k, v] of Object.entries(opts.query)) { if (v != null) params.set(k, String(v)); }");
|
|
112
|
-
lines.push(" const qs = params.toString();");
|
|
113
|
-
lines.push(" if (qs) url += `?${qs}`;");
|
|
114
|
-
lines.push(" }");
|
|
115
|
-
lines.push(" const res = await customFetch(url, {");
|
|
116
|
-
lines.push(" method: 'DELETE',");
|
|
117
|
-
lines.push(" headers: defaultHeaders,");
|
|
118
|
-
lines.push(" });");
|
|
119
|
-
lines.push(" if (!res.ok) throw await ApiError.from(res);");
|
|
120
|
-
lines.push(" if (res.status === 204) return undefined as T;");
|
|
121
|
-
lines.push(" return res.json();");
|
|
122
|
-
lines.push(" },");
|
|
123
|
-
lines.push(" };");
|
|
129
|
+
lines.push(" ...options,");
|
|
130
|
+
lines.push(" });");
|
|
124
131
|
lines.push("");
|
|
125
132
|
lines.push(" return {");
|
|
126
133
|
for (const r of resources) {
|
|
@@ -129,21 +136,6 @@ function generateClient(resources, config) {
|
|
|
129
136
|
lines.push(" };");
|
|
130
137
|
lines.push("}");
|
|
131
138
|
lines.push("");
|
|
132
|
-
lines.push("export class ApiError extends Error {");
|
|
133
|
-
lines.push(" override name = 'ApiError';");
|
|
134
|
-
lines.push(" public data: unknown;");
|
|
135
|
-
lines.push("");
|
|
136
|
-
lines.push(" constructor(public status: number, body: string) {");
|
|
137
|
-
lines.push(" super(`API error ${status}: ${body}`);");
|
|
138
|
-
lines.push(" try { this.data = JSON.parse(body); } catch { this.data = body; }");
|
|
139
|
-
lines.push(" }");
|
|
140
|
-
lines.push("");
|
|
141
|
-
lines.push(" static async from(res: Response): Promise<ApiError> {");
|
|
142
|
-
lines.push(" const body = await res.text();");
|
|
143
|
-
lines.push(" return new ApiError(res.status, body);");
|
|
144
|
-
lines.push(" }");
|
|
145
|
-
lines.push("}");
|
|
146
|
-
lines.push("");
|
|
147
139
|
lines.push("export type Client = ReturnType<typeof createClient>;");
|
|
148
140
|
lines.push("");
|
|
149
141
|
return { path: "client.ts", content: lines.join(`
|
|
@@ -268,13 +260,13 @@ function generateResources(resources) {
|
|
|
268
260
|
function generateResourceFile(resource) {
|
|
269
261
|
const lines = [];
|
|
270
262
|
const typeImports = collectTypeImports(resource);
|
|
271
|
-
lines.push("import type {
|
|
263
|
+
lines.push("import type { FetchClient, FetchResponse } from '@vertz/fetch';");
|
|
272
264
|
if (typeImports.size > 0) {
|
|
273
265
|
const sorted = [...typeImports].sort();
|
|
274
266
|
lines.push(`import type { ${sorted.join(", ")} } from '../types/${resource.identifier}';`);
|
|
275
267
|
}
|
|
276
268
|
lines.push("");
|
|
277
|
-
lines.push(`export function create${resource.name}Resource(client:
|
|
269
|
+
lines.push(`export function create${resource.name}Resource(client: FetchClient) {`);
|
|
278
270
|
lines.push(" return {");
|
|
279
271
|
validateUniqueMethodNames(resource);
|
|
280
272
|
for (const op of resource.operations) {
|
|
@@ -330,22 +322,22 @@ function buildParams(op) {
|
|
|
330
322
|
}
|
|
331
323
|
function buildReturnType(op) {
|
|
332
324
|
if (op.responseStatus === 204)
|
|
333
|
-
return "Promise<void
|
|
325
|
+
return "Promise<FetchResponse<void>>";
|
|
334
326
|
if (op.response?.name) {
|
|
335
327
|
const safeName = sanitizeTypeName(op.response.name);
|
|
336
328
|
if (op.response.jsonSchema.type === "array") {
|
|
337
|
-
return `Promise<${safeName}[]
|
|
329
|
+
return `Promise<FetchResponse<${safeName}[]>>`;
|
|
338
330
|
}
|
|
339
|
-
return `Promise<${safeName}
|
|
331
|
+
return `Promise<FetchResponse<${safeName}>>`;
|
|
340
332
|
}
|
|
341
333
|
if (op.response?.jsonSchema.type === "array") {
|
|
342
|
-
return "Promise<unknown[]
|
|
334
|
+
return "Promise<FetchResponse<unknown[]>>";
|
|
343
335
|
}
|
|
344
336
|
if (op.response) {
|
|
345
337
|
const name = capitalize(op.operationId) + "Response";
|
|
346
|
-
return `Promise<${name}
|
|
338
|
+
return `Promise<FetchResponse<${name}>>`;
|
|
347
339
|
}
|
|
348
|
-
return "Promise<void
|
|
340
|
+
return "Promise<FetchResponse<void>>";
|
|
349
341
|
}
|
|
350
342
|
function buildCall(op) {
|
|
351
343
|
const method = op.method.toLowerCase();
|
|
@@ -721,7 +713,10 @@ function generateAll(spec, options) {
|
|
|
721
713
|
const files = [];
|
|
722
714
|
files.push(...generateTypes(resources, schemas));
|
|
723
715
|
files.push(...generateResources(resources));
|
|
724
|
-
files.push(generateClient(resources, {
|
|
716
|
+
files.push(generateClient(resources, {
|
|
717
|
+
baseURL: opts.baseURL,
|
|
718
|
+
securitySchemes: spec.securitySchemes
|
|
719
|
+
}));
|
|
725
720
|
if (opts.schemas) {
|
|
726
721
|
files.push(...generateSchemas(resources, schemas));
|
|
727
722
|
}
|
|
@@ -734,10 +729,19 @@ function generateReadme(spec, options) {
|
|
|
734
729
|
lines.push("");
|
|
735
730
|
lines.push(`> Auto-generated from OpenAPI ${spec.version} spec (v${spec.info.version})`);
|
|
736
731
|
lines.push("");
|
|
732
|
+
lines.push("## Prerequisites");
|
|
733
|
+
lines.push("");
|
|
734
|
+
lines.push("This SDK requires `@vertz/fetch` as a peer dependency:");
|
|
735
|
+
lines.push("");
|
|
736
|
+
lines.push("```bash");
|
|
737
|
+
lines.push("bun add @vertz/fetch");
|
|
738
|
+
lines.push("```");
|
|
739
|
+
lines.push("");
|
|
737
740
|
lines.push("## Usage");
|
|
738
741
|
lines.push("");
|
|
739
742
|
lines.push("```typescript");
|
|
740
743
|
lines.push("import { createClient } from './client';");
|
|
744
|
+
lines.push("import { isOk } from '@vertz/fetch';");
|
|
741
745
|
lines.push("");
|
|
742
746
|
lines.push(`const api = createClient(${options.baseURL ? `{ baseURL: '${options.baseURL}' }` : ""});`);
|
|
743
747
|
lines.push("```");
|
|
@@ -1212,6 +1216,64 @@ function collectComponentSchemas(spec, version) {
|
|
|
1212
1216
|
name
|
|
1213
1217
|
}));
|
|
1214
1218
|
}
|
|
1219
|
+
function extractSecuritySchemes(spec) {
|
|
1220
|
+
const components = isRecord2(spec.components) ? spec.components : undefined;
|
|
1221
|
+
const schemes = components && isRecord2(components.securitySchemes) ? components.securitySchemes : undefined;
|
|
1222
|
+
if (!schemes)
|
|
1223
|
+
return [];
|
|
1224
|
+
const result = [];
|
|
1225
|
+
for (const [name, scheme] of Object.entries(schemes)) {
|
|
1226
|
+
if (!isRecord2(scheme))
|
|
1227
|
+
continue;
|
|
1228
|
+
const description = typeof scheme.description === "string" ? scheme.description : undefined;
|
|
1229
|
+
if (scheme.type === "http") {
|
|
1230
|
+
if (scheme.scheme === "bearer") {
|
|
1231
|
+
result.push({ type: "bearer", name, description });
|
|
1232
|
+
} else if (scheme.scheme === "basic") {
|
|
1233
|
+
result.push({ type: "basic", name, description });
|
|
1234
|
+
}
|
|
1235
|
+
} else if (scheme.type === "apiKey") {
|
|
1236
|
+
const location = scheme.in;
|
|
1237
|
+
const paramName = typeof scheme.name === "string" ? scheme.name : name;
|
|
1238
|
+
result.push({ type: "apiKey", name, in: location, paramName, description });
|
|
1239
|
+
} else if (scheme.type === "oauth2" && isRecord2(scheme.flows)) {
|
|
1240
|
+
const flows = {};
|
|
1241
|
+
const rawFlows = scheme.flows;
|
|
1242
|
+
if (isRecord2(rawFlows.authorizationCode)) {
|
|
1243
|
+
const ac = rawFlows.authorizationCode;
|
|
1244
|
+
flows.authorizationCode = {
|
|
1245
|
+
authorizationUrl: String(ac.authorizationUrl ?? ""),
|
|
1246
|
+
tokenUrl: String(ac.tokenUrl ?? ""),
|
|
1247
|
+
scopes: isRecord2(ac.scopes) ? ac.scopes : {}
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
if (isRecord2(rawFlows.clientCredentials)) {
|
|
1251
|
+
const cc = rawFlows.clientCredentials;
|
|
1252
|
+
flows.clientCredentials = {
|
|
1253
|
+
tokenUrl: String(cc.tokenUrl ?? ""),
|
|
1254
|
+
scopes: isRecord2(cc.scopes) ? cc.scopes : {}
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
result.push({ type: "oauth2", name, flows, description });
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
return result;
|
|
1261
|
+
}
|
|
1262
|
+
function extractOperationSecurity(operation, globalSecurity) {
|
|
1263
|
+
const security = Array.isArray(operation.security) ? operation.security : globalSecurity;
|
|
1264
|
+
if (security.length === 0 && !Array.isArray(operation.security))
|
|
1265
|
+
return;
|
|
1266
|
+
const schemes = [];
|
|
1267
|
+
for (const requirement of security) {
|
|
1268
|
+
if (isRecord2(requirement)) {
|
|
1269
|
+
schemes.push(...Object.keys(requirement));
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
return {
|
|
1273
|
+
required: schemes.length > 0,
|
|
1274
|
+
schemes
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1215
1277
|
function parseOpenAPI(spec) {
|
|
1216
1278
|
const version = getVersion(spec);
|
|
1217
1279
|
if (!isRecord2(spec.info)) {
|
|
@@ -1220,6 +1282,7 @@ function parseOpenAPI(spec) {
|
|
|
1220
1282
|
if (!isRecord2(spec.paths)) {
|
|
1221
1283
|
throw new OpenAPIParserError2("OpenAPI spec is missing required field: paths");
|
|
1222
1284
|
}
|
|
1285
|
+
const globalSecurity = Array.isArray(spec.security) ? spec.security : [];
|
|
1223
1286
|
const operations = [];
|
|
1224
1287
|
for (const [path, pathItem] of Object.entries(spec.paths)) {
|
|
1225
1288
|
if (!isRecord2(pathItem)) {
|
|
@@ -1233,7 +1296,8 @@ function parseOpenAPI(spec) {
|
|
|
1233
1296
|
const operationId = typeof operation.operationId === "string" ? operation.operationId : `${method}_${path}`;
|
|
1234
1297
|
const { pathParams, queryParams } = extractParameters(path, pathItem, operation, spec, version);
|
|
1235
1298
|
const successResponse = pickSuccessResponse(operation, spec);
|
|
1236
|
-
|
|
1299
|
+
const security = extractOperationSecurity(operation, globalSecurity);
|
|
1300
|
+
const parsed = {
|
|
1237
1301
|
operationId,
|
|
1238
1302
|
methodName: normalizeOperationId(operationId, method.toUpperCase(), path),
|
|
1239
1303
|
method: method.toUpperCase(),
|
|
@@ -1244,10 +1308,18 @@ function parseOpenAPI(spec) {
|
|
|
1244
1308
|
response: successResponse.schema ? resolveSchemaForOutput(successResponse.schema, spec, version) : undefined,
|
|
1245
1309
|
responseStatus: successResponse.status,
|
|
1246
1310
|
tags: Array.isArray(operation.tags) ? operation.tags.filter((tag) => typeof tag === "string") : []
|
|
1247
|
-
}
|
|
1311
|
+
};
|
|
1312
|
+
if (security)
|
|
1313
|
+
parsed.security = security;
|
|
1314
|
+
operations.push(parsed);
|
|
1248
1315
|
}
|
|
1249
1316
|
}
|
|
1250
|
-
return {
|
|
1317
|
+
return {
|
|
1318
|
+
operations,
|
|
1319
|
+
schemas: collectComponentSchemas(spec, version),
|
|
1320
|
+
securitySchemes: extractSecuritySchemes(spec),
|
|
1321
|
+
version
|
|
1322
|
+
};
|
|
1251
1323
|
}
|
|
1252
1324
|
|
|
1253
1325
|
// src/writer/incremental.ts
|
|
@@ -1358,7 +1430,8 @@ async function generateFromOpenAPI(config) {
|
|
|
1358
1430
|
version: typeof info?.version === "string" ? info.version : "0.0.0"
|
|
1359
1431
|
},
|
|
1360
1432
|
resources,
|
|
1361
|
-
schemas: parsed.schemas
|
|
1433
|
+
schemas: parsed.schemas,
|
|
1434
|
+
securitySchemes: parsed.securitySchemes
|
|
1362
1435
|
};
|
|
1363
1436
|
const files = generateAll(spec, {
|
|
1364
1437
|
schemas: config.schemas,
|