Skip to content

Commit 00ea319

Browse files
committed
Continue implementing OpenAPI 3.2.0
1 parent 382a4b4 commit 00ea319

39 files changed

+1200
-157
lines changed

src/core/types.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export interface LicenseObject {
2626
url?: string;
2727
/** An SPDX license identifier for the API. (OAS 3.1+) */
2828
identifier?: string;
29+
/** Specification Extensions */
30+
[key: string]: any;
2931
}
3032

3133
/**
@@ -39,6 +41,8 @@ export interface ContactObject {
3941
url?: string;
4042
/** The email address of the contact person/organization. */
4143
email?: string;
44+
/** Specification Extensions */
45+
[key: string]: any;
4246
}
4347

4448
/**
@@ -60,6 +64,8 @@ export interface InfoObject {
6064
license?: LicenseObject;
6165
/** The version of the OpenAPI document. */
6266
version: string;
67+
/** Specification Extensions */
68+
[key: string]: any;
6369
}
6470

6571
/**
@@ -71,6 +77,8 @@ export interface ExternalDocumentationObject {
7177
description?: string;
7278
/** The URL for the target documentation. */
7379
url: string;
80+
/** Specification Extensions */
81+
[key: string]: any;
7482
}
7583

7684
/**
@@ -90,6 +98,8 @@ export interface TagObject {
9098
parent?: string;
9199
/** A machine-readable string to categorize what sort of tag it is. (OAS 3.2+) */
92100
kind?: string;
101+
/** Specification Extensions */
102+
[key: string]: any;
93103
}
94104

95105
/**
@@ -103,6 +113,8 @@ export interface ServerVariableObject {
103113
default: string;
104114
/** An optional description for the server variable. */
105115
description?: string;
116+
/** Specification Extensions */
117+
[key: string]: any;
106118
}
107119

108120
/**
@@ -118,6 +130,8 @@ export interface ServerObject {
118130
name?: string;
119131
/** A map between a variable name and its value. */
120132
variables?: { [variable: string]: ServerVariableObject };
133+
/** Specification Extensions */
134+
[key: string]: any;
121135
}
122136

123137
/** Represents the `discriminator` object used for polymorphism in OpenAPI schemas. */
@@ -126,6 +140,8 @@ export interface DiscriminatorObject {
126140
propertyName: string;
127141
/** An optional map from a value to a schema reference. */
128142
mapping?: { [key: string]: string };
143+
/** Specification Extensions */
144+
[key: string]: any;
129145
}
130146

131147
/**
@@ -154,6 +170,8 @@ export interface XmlObject {
154170
* Values: 'element' | 'attribute' | 'text' | 'cdata' | 'none'.
155171
*/
156172
nodeType?: 'element' | 'attribute' | 'text' | 'cdata' | 'none' | string;
173+
/** Specification Extensions */
174+
[key: string]: any;
157175
}
158176

159177
/**
@@ -173,6 +191,8 @@ export interface LinkObject {
173191
description?: string;
174192
/** A server object to be used by the target operation. */
175193
server?: ServerObject;
194+
/** Specification Extensions */
195+
[key: string]: any;
176196
}
177197

178198
/**
@@ -195,6 +215,8 @@ export interface HeaderObject {
195215
content?: Record<string, { schema?: SwaggerDefinition | { $ref: string } }>;
196216
example?: any;
197217
examples?: Record<string, any>;
218+
/** Specification Extensions */
219+
[key: string]: any;
198220
}
199221

200222
/** A simplified, normalized representation of an operation parameter. */
@@ -228,6 +250,8 @@ export interface Parameter {
228250
content?: Record<string, { schema?: SwaggerDefinition | { $ref: string } }>;
229251
/** Specifies that a parameter is deprecated and SHOULD be transitioned out of usage. */
230252
deprecated?: boolean;
253+
/** Specification Extensions */
254+
[key: string]: any;
231255
}
232256

233257
/** A processed, unified representation of a single API operation (e.g., GET /users/{id}). */
@@ -267,6 +291,8 @@ export interface PathInfo {
267291
servers?: ServerObject[] | undefined;
268292
/** A map of possible out-of band callbacks related to the parent operation. (OAS 3+) */
269293
callbacks?: Record<string, PathItem | { $ref: string }>;
294+
/** Specification Extensions (e.g. x-codegen-extra) handled during extraction/normalization */
295+
[key: string]: any;
270296
}
271297

272298
/** A single encoding definition for a multipart property. */
@@ -281,6 +307,8 @@ export interface EncodingProperty {
281307
explode?: boolean;
282308
/** allow reserved characters */
283309
allowReserved?: boolean;
310+
/** Specification Extensions */
311+
[key: string]: any;
284312
}
285313

286314
/** Represents the request body of an operation. */
@@ -293,6 +321,8 @@ export interface RequestBody {
293321
/** Encoding object for multipart/form-data definitions */
294322
encoding?: Record<string, EncodingProperty>;
295323
}>;
324+
/** Specification Extensions */
325+
[key: string]: any;
296326
}
297327

298328
/** Represents a single response from an API Operation. */
@@ -305,6 +335,8 @@ export interface SwaggerResponse {
305335
links?: Record<string, LinkObject | { $ref: string }>;
306336
/** Maps a header name to its definition. */
307337
headers?: Record<string, HeaderObject | { $ref: string }>;
338+
/** Specification Extensions */
339+
[key: string]: any;
308340
}
309341

310342
/**
@@ -384,6 +416,8 @@ export interface SwaggerDefinition {
384416

385417
xml?: XmlObject;
386418
externalDocs?: ExternalDocumentationObject;
419+
/** Specification Extensions */
420+
[key: string]: any;
387421
}
388422

389423
/** Represents a security scheme recognized by the API. */
@@ -394,6 +428,8 @@ export interface SecurityScheme {
394428
scheme?: 'bearer' | string;
395429
flows?: Record<string, unknown>;
396430
openIdConnectUrl?: string;
431+
/** Specification Extensions */
432+
[key: string]: any;
397433
}
398434

399435
/**
@@ -478,6 +514,8 @@ export interface SwaggerSpec {
478514
};
479515
/** Security definitions (Swagger 2.0). */
480516
securityDefinitions?: { [securityDefinitionName: string]: SecurityScheme };
517+
/** Specification Extensions */
518+
[key: string]: any;
481519
}
482520

483521
// ===================================================================================

src/core/utils.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ type UnifiedParameter = SwaggerOfficialParameter & {
373373
allowEmptyValue?: boolean,
374374
content?: Record<string, { schema?: SwaggerDefinition }>,
375375
deprecated?: boolean
376+
[key: string]: any; // Allow extensions on parameter objects
376377
};
377378

378379
/**
@@ -536,6 +537,13 @@ export function extractPaths(
536537
param.description = p.description;
537538
}
538539

540+
// Propagate extensions like x-codegen-param-name from parameter object to normalized Parameter
541+
Object.keys(p).forEach(key => {
542+
if (key.startsWith('x-')) {
543+
(param as any)[key] = (p as any)[key];
544+
}
545+
});
546+
539547
return param;
540548
});
541549

@@ -606,6 +614,14 @@ export function extractPaths(
606614
if (operation.externalDocs) pathInfo.externalDocs = operation.externalDocs;
607615
if (effectiveSecurity) pathInfo.security = effectiveSecurity;
608616

617+
// Propagate custom extensions (x-custom-field) from the operation to PathInfo
618+
// This allows generators to access vendor specific metadata on the operation level
619+
Object.keys(operation).forEach(key => {
620+
if (key.startsWith('x-') && !(key in pathInfo)) {
621+
pathInfo[key] = operation[key];
622+
}
623+
});
624+
609625
paths.push(pathInfo);
610626
}
611627
}

src/core/validator.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export class SpecValidationError extends Error {
1818
* - Valid version string ('swagger: "2.x"' or 'openapi: "3.x"')
1919
* - "info" object with "title" and "version"
2020
* - At least one functional root property ("paths", "components", or "webhooks")
21+
* - License Object constraints (mutually exclusive `url` and `identifier`)
2122
*
2223
* @param spec The parsed specification object.
2324
* @throws {SpecValidationError} if the specification is invalid.
@@ -46,7 +47,20 @@ export function validateSpec(spec: SwaggerSpec): void {
4647
throw new SpecValidationError("Specification info object must contain a required string field: 'version'.");
4748
}
4849

49-
// 3. Check Structural Root
50+
// 3. Check License Object Constraints (OAS 3.1+)
51+
// "The `identifier` field is mutually exclusive of the `url` field."
52+
if (spec.info.license) {
53+
// OAS 3.2/3.1 Strictness: checking logical existence rather than falsy values to avoid edge cases with empty strings,
54+
// though empty strings would be invalid URIs anyway.
55+
const hasUrl = spec.info.license.url !== undefined && spec.info.license.url !== null;
56+
const hasIdentifier = spec.info.license.identifier !== undefined && spec.info.license.identifier !== null;
57+
58+
if (hasUrl && hasIdentifier) {
59+
throw new SpecValidationError("License object cannot contain both 'url' and 'identifier' fields. They are mutually exclusive.");
60+
}
61+
}
62+
63+
// 4. Check Structural Root
5064
// Per OAS 3.2: "at least one of the components, paths, or webhooks fields MUST be present."
5165
// For Swagger 2.0: 'paths' is technically required.
5266

src/service/emit/admin/admin.generator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { Project } from 'ts-morph';
22

33
import { posix as path } from 'node:path';
44

5-
import { SwaggerParser } from '../../../core/parser.js';
6-
import { Resource } from '../../../core/types.js';
5+
import { SwaggerParser } from '@src/core/parser.js';
6+
import { Resource } from '@src/core/types.js';
77
import { discoverAdminResources } from './resource-discovery.js';
88
import { FormComponentGenerator } from './form-component.generator.js';
99
import { ListComponentGenerator } from './list-component.generator.js';

src/service/emit/admin/custom-validators.generator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ClassDeclaration, MethodDeclarationStructure, OptionalKind, Project, Scope } from 'ts-morph';
22
import { posix as path } from 'node:path';
3-
import { UTILITY_GENERATOR_HEADER_COMMENT } from '../../../core/constants.js';
3+
import { UTILITY_GENERATOR_HEADER_COMMENT } from '@src/core/constants.js';
44

55
/**
66
* Generates the custom-validators.ts file using ts-morph for robust AST manipulation.

src/service/emit/admin/form-component.generator.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { ClassDeclaration, Project, Scope, SourceFile } from 'ts-morph';
2-
import { FormProperty, Resource, SwaggerDefinition } from '../../../core/types.js';
3-
import { camelCase, getTypeScriptType, pascalCase, singular } from '../../../core/utils.js';
2+
import { FormProperty, Resource, SwaggerDefinition } from '@src/core/types.js';
3+
import { camelCase, getTypeScriptType, pascalCase, singular } from '@src/core/utils.js';
44
import { commonStandaloneImports } from './common-imports.js';
55
import { mapSchemaToFormControl } from './form-control.mapper.js';
66
import { generateFormComponentHtml } from './html/form-component-html.builder.js';
77
import { generateFormComponentScss } from './html/form-component-scss.builder.js';
8-
import { SwaggerParser } from '../../../core/parser.js';
8+
import { SwaggerParser } from '@src/core/parser.js';
99

1010
/**
1111
* Orchestrates the generation of a complete Angular standalone form component,

src/service/emit/admin/html/form-component-html.builder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { Resource, SwaggerDefinition } from "../../../../core/types.js";
1+
import { Resource, SwaggerDefinition } from "@src/core/types.js";
22
import { HtmlElementBuilder as _ } from '../html-element.builder.js';
33
import { buildFormControl } from "./form-controls-html.builder.js";
4-
import { SwaggerParser } from "../../../../core/parser.js";
4+
import { SwaggerParser } from "@src/core/parser.js";
55

66
/**
77
* Generates the complete HTML content for a resource's form component template.

src/service/emit/admin/html/list-component-html.builder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
* with dynamic columns, action buttons, and pagination using the HtmlElementBuilder.
66
*/
77

8-
import { Resource } from '../../../../core/types.js';
9-
import { pascalCase, singular } from '../../../../core/utils.js';
8+
import { Resource } from '@src/core/types.js';
9+
import { pascalCase, singular } from '@src/core/utils.js';
1010
import { HtmlElementBuilder as _ } from '../html-element.builder.js';
1111

1212
/**

src/service/emit/admin/list-component.generator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ClassDeclaration, Project, Scope } from 'ts-morph';
2-
import { FormProperty, Resource } from '../../../core/types.js';
3-
import { camelCase, pascalCase } from '../../../core/utils.js';
2+
import { FormProperty, Resource } from '@src/core/types.js';
3+
import { camelCase, pascalCase } from '@src/core/utils.js';
44
import { commonStandaloneImports } from './common-imports.js';
55
import { generateListComponentHtml } from './html/list-component-html.builder.js';
66
import { generateListComponentScss } from './html/list-component-scss.builder.js';

src/service/emit/admin/resource-discovery.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
// src/service/emit/admin/resource-discovery.ts
22

3-
import { SwaggerParser } from '../../../core/parser.js';
3+
import { SwaggerParser } from '@src/core/parser.js';
44
import {
55
DiscriminatorObject,
66
FormProperty,
77
PathInfo,
88
Resource,
99
ResourceOperation,
1010
SwaggerDefinition,
11-
} from '../../../core/types.js';
12-
import { camelCase, pascalCase, singular } from '../../../core/utils.js';
11+
} from '@src/core/types.js';
12+
import { camelCase, pascalCase, singular } from '@src/core/utils.js';
1313

1414
function getMethodName(op: PathInfo): string {
1515
const pathToMethodName = (path: string): string =>

0 commit comments

Comments
 (0)