Skip to content

Commit 249186b

Browse files
committed
Continue implementing OpenAPI 3.2.0
1 parent 441cf7a commit 249186b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1121
-544
lines changed

src/core/parser.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@
44
* parsing, and providing a unified interface to OpenAPI (3.x) and Swagger (2.x) specifications.
55
*/
66

7-
import { SwaggerDefinition, SwaggerSpec, GeneratorConfig, SecurityScheme, PathInfo } from './types.js';
7+
import { GeneratorConfig, PathInfo, SecurityScheme, ServerObject, SwaggerDefinition, SwaggerSpec } from './types.js';
88
import * as fs from 'node:fs';
99
import * as path from 'node:path';
1010
import { pathToFileURL } from 'node:url';
1111
import yaml from 'js-yaml';
1212
import { extractPaths, isUrl, pascalCase } from './utils.js';
1313

1414
/** Represents a `$ref` object in a JSON Schema. */
15-
interface RefObject { $ref: string; }
15+
interface RefObject {
16+
$ref: string;
17+
}
1618

1719
/**
1820
* A type guard to safely check if an object is a `$ref` object.
@@ -49,6 +51,8 @@ export class SwaggerParser {
4951

5052
/** A normalized array of all top-level schemas (definitions) found in the entry specification. */
5153
public readonly schemas: { name: string; definition: SwaggerDefinition; }[];
54+
/** A normalized array of all servers defined in the entry specification. */
55+
public readonly servers: ServerObject[];
5256
/** A flattened and processed list of all API operations (paths) from the entry specification. */
5357
public readonly operations: PathInfo[];
5458
/** A flattened and processed list of all Webhooks defined in the entry specification. */
@@ -84,6 +88,8 @@ export class SwaggerParser {
8488
name: pascalCase(name),
8589
definition
8690
}));
91+
92+
this.servers = this.spec.servers || [];
8793
this.operations = extractPaths(this.spec.paths);
8894
this.webhooks = extractPaths(this.spec.webhooks);
8995
this.security = this.getSecuritySchemes();

src/core/types.ts

Lines changed: 94 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
*/
99

1010
import { ModuleKind, ScriptTarget } from "ts-morph";
11-
import { Path } from 'swagger-schema-official';
1211

1312
// ===================================================================================
1413
// SECTION: OpenAPI / Swagger Specification Types
@@ -63,6 +62,34 @@ export interface InfoObject {
6362
version: string;
6463
}
6564

65+
/**
66+
* An object representing a Server Variable for server URL template substitution.
67+
* @see https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.2.0.md#server-variable-object
68+
*/
69+
export interface ServerVariableObject {
70+
/** An enumeration of string values to be used if the substitution options are from a limited set. */
71+
enum?: string[];
72+
/** The default value to use for substitution. */
73+
default: string;
74+
/** An optional description for the server variable. */
75+
description?: string;
76+
}
77+
78+
/**
79+
* An object representing a Server.
80+
* @see https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.2.0.md#server-object
81+
*/
82+
export interface ServerObject {
83+
/** A URL to the target host. */
84+
url: string;
85+
/** An optional string describing the host designated by the URL. */
86+
description?: string;
87+
/** An optional unique string to refer to the host designated by the URL. (OAS 3.2) */
88+
name?: string;
89+
/** A map between a variable name and its value. */
90+
variables?: { [variable: string]: ServerVariableObject };
91+
}
92+
6693
/** Represents the `discriminator` object used for polymorphism in OpenAPI schemas. */
6794
export interface DiscriminatorObject {
6895
/** The name of the property in the payload that determines the schema to use. */
@@ -158,14 +185,34 @@ export interface PathInfo {
158185
responses?: Record<string, SwaggerResponse>;
159186
/** The generator-derived, safe method name for this operation. */
160187
methodName?: string;
188+
/** Security requirements specific to this operation. keys are definitions, values are scopes. */
189+
security?: { [key: string]: string[] }[];
190+
}
191+
192+
/** A single encoding definition for a multipart property. */
193+
export interface EncodingProperty {
194+
/** The Content-Type for encoding a specific property. */
195+
contentType?: string;
196+
/** A map of headers that are to be encoded for the property. */
197+
headers?: Record<string, any>;
198+
/** serialization style */
199+
style?: string;
200+
/** whether to explode array/objects */
201+
explode?: boolean;
202+
/** allow reserved characters */
203+
allowReserved?: boolean;
161204
}
162205

163206
/** Represents the request body of an operation. */
164207
export interface RequestBody {
165208
/** Determines if the request body is required. */
166209
required?: boolean;
167210
/** A map of media types to their corresponding schemas for the request body. */
168-
content?: Record<string, { schema?: SwaggerDefinition | { $ref: string } }>;
211+
content?: Record<string, {
212+
schema?: SwaggerDefinition | { $ref: string };
213+
/** Encoding object for multipart/form-data definitions */
214+
encoding?: Record<string, EncodingProperty>;
215+
}>;
169216
}
170217

171218
/** Represents a single response from an API Operation. */
@@ -240,24 +287,66 @@ export interface SecurityScheme {
240287
openIdConnectUrl?: string;
241288
}
242289

290+
/**
291+
* Represents an Operation Object in OpenAPI (v2 or v3).
292+
*/
293+
export interface SpecOperation {
294+
tags?: string[];
295+
summary?: string;
296+
description?: string;
297+
externalDocs?: ExternalDocumentationObject;
298+
operationId?: string;
299+
consumes?: string[];
300+
produces?: string[];
301+
parameters?: any[]; // Raw parameters (Swagger 2 or OAS 3)
302+
requestBody?: any; // OAS 3
303+
responses: Record<string, any>;
304+
schemes?: string[];
305+
deprecated?: boolean;
306+
security?: Record<string, string[]>[];
307+
[key: string]: any;
308+
}
309+
310+
/**
311+
* Represents a Path Item Object in OpenAPI (v2 or v3).
312+
*/
313+
export interface PathItem {
314+
$ref?: string;
315+
summary?: string;
316+
description?: string;
317+
get?: SpecOperation;
318+
put?: SpecOperation;
319+
post?: SpecOperation;
320+
delete?: SpecOperation;
321+
options?: SpecOperation;
322+
head?: SpecOperation;
323+
patch?: SpecOperation;
324+
trace?: SpecOperation;
325+
query?: SpecOperation; // OAS 3.2 draft
326+
parameters?: any[];
327+
[key: string]: any;
328+
}
329+
243330
/** The root object of a parsed OpenAPI/Swagger specification. */
244331
export interface SwaggerSpec {
245332
openapi?: string;
246333
swagger?: string;
247334
$self?: string;
248335
info: InfoObject;
249-
paths: { [pathName: string]: Path };
336+
paths: { [pathName: string]: PathItem };
250337
/** The incoming webhooks that MAY be received as part of this API. */
251-
webhooks?: { [name: string]: Path };
338+
webhooks?: { [name: string]: PathItem };
252339
/** The default value for the $schema keyword within Schema Objects. */
253340
jsonSchemaDialect?: string;
341+
/** An array of Server Objects, which provide connectivity information to a target server. */
342+
servers?: ServerObject[];
254343
/** Schema definitions (Swagger 2.0). */
255344
definitions?: { [definitionsName: string]: SwaggerDefinition };
256345
/** Replaces `definitions` in OpenAPI 3.x. */
257346
components?: {
258347
schemas?: Record<string, SwaggerDefinition>;
259348
securitySchemes?: Record<string, SecurityScheme>;
260-
pathItems?: Record<string, Path>;
349+
pathItems?: Record<string, PathItem>;
261350
};
262351
/** Security definitions (Swagger 2.0). */
263352
securityDefinitions?: { [securityDefinitionName: string]: SecurityScheme };

src/core/utils.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,26 @@
22

33
/**
44
* @fileoverview
5-
* This file contains core utility functions used throughout the OpenAPI Angular generator.
5+
* This file contains core utility functions used throughout the generator.
66
* It includes functions for string manipulation (case conversion), TypeScript type resolution from
77
* OpenAPI schemas, OpenAPI spec parsing helpers, and functions for generating unique DI token names.
88
* These utilities are pure, dependency-free helpers that form the building blocks of the generation logic.
99
*/
1010

1111
import { MethodDeclaration } from 'ts-morph';
12-
import { GeneratorConfig, Parameter, PathInfo, RequestBody, SwaggerDefinition, SwaggerResponse } from './types.js';
12+
import {
13+
GeneratorConfig,
14+
Parameter,
15+
PathInfo,
16+
PathItem,
17+
RequestBody,
18+
SpecOperation,
19+
SwaggerDefinition,
20+
SwaggerResponse
21+
} from './types.js';
1322
import {
1423
BodyParameter,
15-
Operation,
1624
Parameter as SwaggerOfficialParameter,
17-
Path,
1825
Response
1926
} from "swagger-schema-official";
2027

@@ -258,11 +265,6 @@ type UnifiedParameter = SwaggerOfficialParameter & {
258265
content?: Record<string, { schema?: SwaggerDefinition }>
259266
};
260267

261-
// Helper type that adds OpenAPI 3.x properties to the Swagger 2.0 Operation type
262-
type OpenAPIOperation = Operation & {
263-
requestBody?: RequestBody;
264-
};
265-
266268
/**
267269
* Flattens the nested `paths` object from an OpenAPI spec into a linear array of `PathInfo` objects.
268270
* It merges path-level and operation-level parameters and normalizes Swagger 2.0 `body` parameters
@@ -271,18 +273,21 @@ type OpenAPIOperation = Operation & {
271273
* @param swaggerPaths The `paths` object from the OpenAPI specification a.k.a `SwaggerSpec['paths']`.
272274
* @returns An array of processed `PathInfo` objects, or an empty array if `swaggerPaths` is undefined.
273275
*/
274-
export function extractPaths(swaggerPaths: { [p: string]: Path } | undefined): PathInfo[] {
276+
export function extractPaths(swaggerPaths: { [p: string]: PathItem } | undefined): PathInfo[] {
275277
if (!swaggerPaths) {
276278
return [];
277279
}
278280

279281
const paths: PathInfo[] = [];
280-
const methods = ["get", "post", "put", "patch", "delete", "options", "head"];
282+
// Added 'query' to the supported methods list for OAS 3.2 compliance.
283+
const methods = ["get", "post", "put", "patch", "delete", "options", "head", "query"];
281284

282285
for (const [path, pathItem] of Object.entries(swaggerPaths)) {
283286
const pathParameters: UnifiedParameter[] = (pathItem.parameters as UnifiedParameter[]) || [];
284287
for (const method of methods) {
285-
const operation = pathItem[method as keyof Path] as OpenAPIOperation;
288+
// Swagger 2.0 Path object is loosely typed here, and doesn't necessarily have 'query' prop.
289+
// However, for OAS 3.2, if it exists, we want it.
290+
const operation = (pathItem as any)[method] as SpecOperation;
286291
if (operation) {
287292
const paramsMap = new Map<string, UnifiedParameter>();
288293
pathParameters.forEach(p => paramsMap.set(`${p.name}:${p.in}`, p));
@@ -391,6 +396,8 @@ export function extractPaths(swaggerPaths: { [p: string]: Path } | undefined): P
391396
if (operation.consumes) pathInfo.consumes = operation.consumes;
392397
// Add external documentation info from operation
393398
if (operation.externalDocs) pathInfo.externalDocs = operation.externalDocs;
399+
// Capture security if present
400+
if (operation.security) pathInfo.security = operation.security;
394401

395402
paths.push(pathInfo);
396403
}

src/service/emit/orchestrator.ts

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,8 @@ import { BaseInterceptorGenerator } from './utility/base-interceptor.generator.j
1919
import { ProviderGenerator } from './utility/provider.generator.js';
2020
import { MainIndexGenerator, ServiceIndexGenerator } from './utility/index.generator.js';
2121
import { ServiceTestGenerator } from "./test/service-test-generator.js";
22+
import { ServerUrlGenerator } from './utility/server-url.generator.js';
2223

23-
/**
24-
* Orchestrates the entire code generation process for the Angular client library.
25-
* It calls specialized generators in a specific order to create models, services,
26-
* utilities, providers, and optionally an admin UI.
27-
*
28-
* @param outputRoot The root directory where all generated files will be written.
29-
* @param parser An initialized `SwaggerParser` instance containing the API specification.
30-
* @param config The global generator configuration object.
31-
* @param project The `ts-morph` project instance to which source files will be added.
32-
* @returns A promise that resolves when generation is complete.
33-
*/
3424
export async function emitClientLibrary(outputRoot: string, parser: SwaggerParser, config: GeneratorConfig, project: Project): Promise<void> {
3525
new TypeGenerator(parser, project, config).generate(outputRoot);
3626
console.log('✅ Models generated.');
@@ -49,19 +39,18 @@ export async function emitClientLibrary(outputRoot: string, parser: SwaggerParse
4939
new TokenGenerator(project, config.clientName).generate(outputRoot);
5040
new HttpParamsBuilderGenerator(project).generate(outputRoot);
5141
new FileDownloadGenerator(project).generate(outputRoot);
42+
new ServerUrlGenerator(parser, project).generate(outputRoot);
43+
5244
if (config.options.dateType === 'Date') {
5345
new DateTransformerGenerator(project).generate(outputRoot);
5446
}
5547

56-
// Check for security schemes to determine if auth-related utilities are needed.
5748
const securitySchemes = parser.getSecuritySchemes();
58-
let tokenNames: string[] = []; // Default to an empty array
49+
let tokenNames: string[] = [];
5950
if (Object.keys(securitySchemes).length > 0) {
6051
new AuthTokensGenerator(project).generate(outputRoot);
6152

6253
const interceptorGenerator = new AuthInterceptorGenerator(parser, project);
63-
// generate() returns the names of the tokens used (e.g., 'apiKey', 'bearerToken'),
64-
// which the ProviderGenerator needs to create the correct configuration interface.
6554
const interceptorResult = interceptorGenerator.generate(outputRoot);
6655
tokenNames = interceptorResult?.tokenNames || [];
6756

0 commit comments

Comments
 (0)