Skip to content

Commit 2993f7d

Browse files
authored
feat: adds support for OpenAPI 3.2.0 emission (#8828)
- **feat: adds new discriminator default mapping field** - **feat: add new OAS version in emitter options fix: default version for OAS emitter do not match valid values** - **docs: adds the chronus entry for OAI 3.2.0 support** - **feat: adds support for $self property in oai 3.2.0** - **feat: adds support for server name in OAS 3.2.0** - **feat: adds support for media type components in 3.2 feat: adds support for item/prefix encoding and encoding fields for media type and encoding in 3.2.0** --------- Signed-off-by: Vincent Biret <vincentbiret@hotmail.com> Signed-off-by: Vincent Biret <vibiret@microsoft.com>
1 parent 7c762b8 commit 2993f7d

Some content is hidden

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

56 files changed

+1008
-459
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: feature
3+
packages:
4+
- "@typespec/openapi3"
5+
---
6+
7+
added support for OpenAPI 3.2.0 emission

packages/openapi3/src/cli/actions/convert/convert.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AnyObject, dereference } from "@scalar/openapi-parser";
22
import { formatTypeSpec } from "@typespec/compiler";
3-
import { OpenAPI3Document } from "../../../types.js";
3+
import { SupportedOpenAPIDocuments } from "../../../types.js";
44
import { generateMain } from "./generators/generate-main.js";
55
import { transform } from "./transforms/transforms.js";
66
import { createContext } from "./utils/context.js";
@@ -17,7 +17,7 @@ export interface ConvertOpenAPI3DocumentOptions {
1717
}
1818

1919
export async function convertOpenAPI3Document(
20-
document: OpenAPI3Document,
20+
document: SupportedOpenAPIDocuments,
2121
{ disableExternalRefs, namespace }: ConvertOpenAPI3DocumentOptions = {},
2222
) {
2323
const dereferenceOptions = disableExternalRefs

packages/openapi3/src/cli/actions/convert/generators/generate-model.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { printIdentifier } from "@typespec/compiler";
2-
import { OpenAPI3Encoding, OpenAPI3Schema, Refable } from "../../../../types.js";
2+
import { OpenAPI3Encoding, Refable, SupportedOpenAPISchema } from "../../../../types.js";
33
import {
44
TypeSpecAlias,
55
TypeSpecDataTypes,
@@ -96,7 +96,7 @@ function generateUnion(union: TypeSpecUnion, context: Context): string {
9696

9797
const schema = union.schema;
9898

99-
const getVariantName = (member: Refable<OpenAPI3Schema>) => {
99+
const getVariantName = (member: Refable<SupportedOpenAPISchema>) => {
100100
if (union.schema.discriminator === undefined) {
101101
return "";
102102
}
@@ -159,7 +159,7 @@ function generateUnion(union: TypeSpecUnion, context: Context): string {
159159
}
160160
}
161161

162-
if (schema.nullable) {
162+
if ("nullable" in schema && schema.nullable) {
163163
definitions.push("null,");
164164
}
165165

packages/openapi3/src/cli/actions/convert/generators/generate-response-expressions.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -359,10 +359,10 @@ function convertHeaderToProperty(
359359

360360
return {
361361
name: normalizedName,
362-
decorators: [headerDecorator, ...getDecoratorsForSchema(header.schema)],
363-
doc: props.header.description ?? header.description ?? header.schema.description,
362+
decorators: [headerDecorator, ...(header.schema ? getDecoratorsForSchema(header.schema) : [])],
363+
doc: props.header.description ?? header.description ?? header.schema?.description,
364364
isOptional: !header.required,
365-
schema: header.schema,
365+
schema: header.schema ?? {},
366366
};
367367
}
368368

packages/openapi3/src/cli/actions/convert/generators/generate-types.ts

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { printIdentifier } from "@typespec/compiler";
22
import {
33
OpenAPI3Encoding,
4-
OpenAPI3Schema,
5-
OpenAPI3SchemaProperty,
4+
OpenAPISchema3_2,
65
Refable,
6+
SupportedOpenAPISchema,
77
} from "../../../../types.js";
88
import { Context } from "../utils/context.js";
99
import {
@@ -17,7 +17,7 @@ export class SchemaToExpressionGenerator {
1717
constructor(public rootNamespace: string) {}
1818

1919
public generateTypeFromRefableSchema(
20-
schema: Refable<OpenAPI3Schema>,
20+
schema: Refable<SupportedOpenAPISchema>,
2121
callingScope: string[],
2222
isHttpPart = false,
2323
encoding?: Record<string, OpenAPI3Encoding>,
@@ -29,7 +29,7 @@ export class SchemaToExpressionGenerator {
2929
: this.getTypeFromSchema(schema, callingScope, isHttpPart, encoding, context);
3030
}
3131

32-
public generateArrayType(schema: OpenAPI3Schema, callingScope: string[]): string {
32+
public generateArrayType(schema: SupportedOpenAPISchema, callingScope: string[]): string {
3333
const items = schema.items;
3434
if (!items) {
3535
return "unknown[]";
@@ -85,7 +85,7 @@ export class SchemaToExpressionGenerator {
8585
}
8686

8787
private getTypeFromSchema(
88-
schema: OpenAPI3Schema,
88+
schema: SupportedOpenAPISchema,
8989
callingScope: string[],
9090
isHttpPart = false,
9191
encoding?: Record<string, OpenAPI3Encoding>,
@@ -103,7 +103,7 @@ export class SchemaToExpressionGenerator {
103103
} else {
104104
// Create a schema with a single type to reuse existing type extraction logic
105105
// Remove type array, nullable, and default to avoid double-processing
106-
const singleTypeSchema: OpenAPI3Schema = {
106+
const singleTypeSchema: SupportedOpenAPISchema = {
107107
...schema,
108108
type: t as any,
109109
nullable: undefined,
@@ -154,7 +154,7 @@ export class SchemaToExpressionGenerator {
154154
type = this.generateArrayType(schema, callingScope);
155155
}
156156

157-
if (schema.nullable) {
157+
if ("nullable" in schema && schema.nullable) {
158158
type += ` | null`;
159159
}
160160

@@ -170,7 +170,7 @@ export class SchemaToExpressionGenerator {
170170
}
171171

172172
private generateDefaultValue(
173-
schema: OpenAPI3Schema,
173+
schema: SupportedOpenAPISchema,
174174
callingScope: string[],
175175
context?: Context,
176176
): string | undefined {
@@ -224,9 +224,9 @@ export class SchemaToExpressionGenerator {
224224
return undefined; // Return undefined to indicate no default found
225225
}
226226

227-
private getAllOfType(schema: OpenAPI3Schema, callingScope: string[]): string {
227+
private getAllOfType(schema: SupportedOpenAPISchema, callingScope: string[]): string {
228228
const requiredProps: string[] = schema.required || [];
229-
let properties: Record<string, Refable<OpenAPI3Schema>> = {};
229+
let properties: Record<string, Refable<SupportedOpenAPISchema>> = {};
230230
const baseTypes: string[] = [];
231231

232232
for (const member of schema.allOf || []) {
@@ -261,7 +261,9 @@ export class SchemaToExpressionGenerator {
261261
}
262262
}
263263

264-
private stripDefaultsFromSchema(schema: Refable<OpenAPI3Schema>): Refable<OpenAPI3Schema> {
264+
private stripDefaultsFromSchema(
265+
schema: Refable<SupportedOpenAPISchema>,
266+
): Refable<SupportedOpenAPISchema> {
265267
if ("$ref" in schema) {
266268
return schema;
267269
}
@@ -274,24 +276,31 @@ export class SchemaToExpressionGenerator {
274276
strippedSchema.items = this.stripDefaultsFromSchema(strippedSchema.items);
275277
}
276278

279+
// in the next blocks the casts are ugly but safe because the stripped schema is going to be the same as the original one
277280
if (strippedSchema.anyOf) {
278-
strippedSchema.anyOf = strippedSchema.anyOf.map((item) => this.stripDefaultsFromSchema(item));
281+
strippedSchema.anyOf = strippedSchema.anyOf.map((item) =>
282+
this.stripDefaultsFromSchema(item),
283+
) as unknown as OpenAPISchema3_2[];
279284
}
280285

281286
if (strippedSchema.oneOf) {
282-
strippedSchema.oneOf = strippedSchema.oneOf.map((item) => this.stripDefaultsFromSchema(item));
287+
strippedSchema.oneOf = strippedSchema.oneOf.map((item) =>
288+
this.stripDefaultsFromSchema(item),
289+
) as unknown as OpenAPISchema3_2[];
283290
}
284291

285292
if (strippedSchema.allOf) {
286-
strippedSchema.allOf = strippedSchema.allOf.map((item) => this.stripDefaultsFromSchema(item));
293+
strippedSchema.allOf = strippedSchema.allOf.map((item) =>
294+
this.stripDefaultsFromSchema(item),
295+
) as unknown as OpenAPISchema3_2[];
287296
}
288297

289298
if (strippedSchema.properties) {
290-
const strippedProperties: Record<string, Refable<OpenAPI3Schema>> = {};
299+
const strippedProperties: Record<string, Refable<SupportedOpenAPISchema>> = {};
291300
for (const [key, prop] of Object.entries(strippedSchema.properties)) {
292301
strippedProperties[key] = this.stripDefaultsFromSchema(prop);
293302
}
294-
strippedSchema.properties = strippedProperties;
303+
strippedSchema.properties = strippedProperties as unknown as Record<string, OpenAPISchema3_2>;
295304
}
296305

297306
if (
@@ -306,7 +315,7 @@ export class SchemaToExpressionGenerator {
306315
return strippedSchema;
307316
}
308317

309-
private getAnyOfType(schema: OpenAPI3Schema, callingScope: string[]): string {
318+
private getAnyOfType(schema: SupportedOpenAPISchema, callingScope: string[]): string {
310319
const definitions: string[] = [];
311320

312321
for (const item of schema.anyOf ?? []) {
@@ -318,7 +327,7 @@ export class SchemaToExpressionGenerator {
318327
return definitions.join(" | ");
319328
}
320329

321-
private getOneOfType(schema: OpenAPI3Schema, callingScope: string[]): string {
330+
private getOneOfType(schema: SupportedOpenAPISchema, callingScope: string[]): string {
322331
const definitions: string[] = [];
323332

324333
for (const item of schema.oneOf ?? []) {
@@ -408,7 +417,7 @@ export class SchemaToExpressionGenerator {
408417
public static readonly decoratorNamesToExcludeForParts: string[] = ["minValue", "maxValue"];
409418

410419
private getObjectType(
411-
schema: OpenAPI3Schema,
420+
schema: SupportedOpenAPISchema,
412421
callingScope: string[],
413422
isHttpPart = false,
414423
encoding?: Record<string, OpenAPI3Encoding>,
@@ -466,7 +475,7 @@ export class SchemaToExpressionGenerator {
466475
}
467476

468477
export function isReferencedEnumType(
469-
propSchema: OpenAPI3SchemaProperty,
478+
propSchema: Refable<SupportedOpenAPISchema>,
470479
context: Context,
471480
): boolean {
472481
let isEnumType = false;
@@ -486,7 +495,7 @@ export function isReferencedEnumType(
486495
}
487496

488497
export function isReferencedUnionType(
489-
propSchema: OpenAPI3SchemaProperty,
498+
propSchema: Refable<SupportedOpenAPISchema>,
490499
context: Context,
491500
): boolean {
492501
let isUnionType = false;
@@ -509,7 +518,7 @@ export function isReferencedUnionType(
509518
return isUnionType;
510519
}
511520

512-
export function getTypeSpecPrimitiveFromSchema(schema: OpenAPI3Schema): string | undefined {
521+
export function getTypeSpecPrimitiveFromSchema(schema: SupportedOpenAPISchema): string | undefined {
513522
if (schema.type === "boolean") {
514523
return "boolean";
515524
} else if ((schema as any).type === "null") {
@@ -524,7 +533,7 @@ export function getTypeSpecPrimitiveFromSchema(schema: OpenAPI3Schema): string |
524533
return;
525534
}
526535

527-
function getIntegerType(schema: OpenAPI3Schema): string {
536+
function getIntegerType(schema: SupportedOpenAPISchema): string {
528537
const format = schema.format ?? "";
529538
switch (format) {
530539
case "int8":
@@ -543,7 +552,7 @@ function getIntegerType(schema: OpenAPI3Schema): string {
543552
}
544553
}
545554

546-
function getNumberType(schema: OpenAPI3Schema): string {
555+
function getNumberType(schema: SupportedOpenAPISchema): string {
547556
const format = schema.format ?? "";
548557
switch (format) {
549558
case "decimal":
@@ -559,7 +568,7 @@ function getNumberType(schema: OpenAPI3Schema): string {
559568
}
560569
}
561570

562-
function getStringType(schema: OpenAPI3Schema): string {
571+
function getStringType(schema: SupportedOpenAPISchema): string {
563572
const format = schema.format ?? "";
564573
let type = "string";
565574
switch (format) {
@@ -588,6 +597,6 @@ function getStringType(schema: OpenAPI3Schema): string {
588597
return type;
589598
}
590599

591-
function getEnum(schemaEnum: (string | number | boolean)[]): string {
600+
function getEnum(schemaEnum: (string | number | boolean | null)[]): string {
592601
return schemaEnum.map((e) => JSON.stringify(e)).join(" | ");
593602
}

packages/openapi3/src/cli/actions/convert/interfaces.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { Contact, License } from "@typespec/openapi";
2-
import { OpenAPI3Encoding, OpenAPI3Responses, OpenAPI3Schema, Refable } from "../../../types.js";
2+
import {
3+
OpenAPI3Encoding,
4+
OpenAPI3Responses,
5+
Refable,
6+
SupportedOpenAPISchema,
7+
} from "../../../types.js";
38

49
export interface TypeSpecProgram {
510
serviceInfo: TypeSpecServiceInfo;
@@ -87,7 +92,7 @@ export interface TypeSpecModel extends TypeSpecDeclaration {
8792
kind: "model";
8893

8994
properties: TypeSpecModelProperty[];
90-
additionalProperties?: Refable<OpenAPI3Schema>;
95+
additionalProperties?: Refable<SupportedOpenAPISchema>;
9196
/**
9297
* Note: Only one of `extends` or `is` should be specified.
9398
*/
@@ -99,7 +104,7 @@ export interface TypeSpecModel extends TypeSpecDeclaration {
99104
/**
100105
* Defaults to 'object'
101106
*/
102-
type?: OpenAPI3Schema["type"];
107+
type?: SupportedOpenAPISchema["type"];
103108

104109
spread?: string[];
105110

@@ -120,17 +125,17 @@ export interface TypeSpecAlias extends Pick<TypeSpecDeclaration, "name" | "doc"
120125

121126
export interface TypeSpecEnum extends TypeSpecDeclaration {
122127
kind: "enum";
123-
schema: OpenAPI3Schema;
128+
schema: SupportedOpenAPISchema;
124129
}
125130

126131
export interface TypeSpecUnion extends TypeSpecDeclaration {
127132
kind: "union";
128-
schema: OpenAPI3Schema;
133+
schema: SupportedOpenAPISchema;
129134
}
130135

131136
export interface TypeSpecScalar extends TypeSpecDeclaration {
132137
kind: "scalar";
133-
schema: OpenAPI3Schema;
138+
schema: SupportedOpenAPISchema;
134139
}
135140

136141
export interface TypeSpecModelProperty {
@@ -143,7 +148,7 @@ export interface TypeSpecModelProperty {
143148
* Example: location decorators for parameters
144149
*/
145150
decorators: TypeSpecDecorator[];
146-
schema: Refable<OpenAPI3Schema>;
151+
schema: Refable<SupportedOpenAPISchema>;
147152
}
148153

149154
export interface TypeSpecOperation extends TypeSpecDeclaration {
@@ -160,13 +165,13 @@ export interface TypeSpecOperationParameter {
160165
doc?: string;
161166
decorators: TypeSpecDecorator[];
162167
isOptional: boolean;
163-
schema: Refable<OpenAPI3Schema>;
168+
schema: Refable<SupportedOpenAPISchema>;
164169
}
165170

166171
export interface TypeSpecRequestBody {
167172
contentType: string;
168173
doc?: string;
169174
isOptional: boolean;
170175
encoding?: Record<string, OpenAPI3Encoding>;
171-
schema?: Refable<OpenAPI3Schema>;
176+
schema?: Refable<SupportedOpenAPISchema>;
172177
}

packages/openapi3/src/cli/actions/convert/transforms/transform-component-parameters.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { printIdentifier } from "@typespec/compiler";
2-
import { OpenAPI3Parameter } from "../../../../types.js";
2+
import { OpenAPI3Parameter, OpenAPIParameter3_2 } from "../../../../types.js";
33
import { TypeSpecDataTypes, TypeSpecModelProperty } from "../interfaces.js";
44
import { Context } from "../utils/context.js";
55
import { getParameterDecorators } from "../utils/decorators.js";
@@ -22,14 +22,15 @@ export function transformComponentParameters(
2222

2323
for (const name of Object.keys(parameters)) {
2424
const parameter = parameters[name];
25+
if ("$ref" in parameter) continue;
2526
transformComponentParameter(dataTypes, name, parameter);
2627
}
2728
}
2829

2930
function transformComponentParameter(
3031
dataTypes: TypeSpecDataTypes[],
3132
key: string,
32-
parameter: OpenAPI3Parameter,
33+
parameter: OpenAPI3Parameter | OpenAPIParameter3_2,
3334
): void {
3435
const { name, scope } = getScopeAndName(key);
3536
// Parameters should live in the root Parameters namespace
@@ -44,12 +45,15 @@ function transformComponentParameter(
4445
});
4546
}
4647

47-
function getModelPropertyFromParameter(parameter: OpenAPI3Parameter): TypeSpecModelProperty {
48+
function getModelPropertyFromParameter(
49+
parameter: OpenAPI3Parameter | OpenAPIParameter3_2,
50+
): TypeSpecModelProperty {
51+
const parameterSchema = "schema" in parameter ? (parameter.schema ?? {}) : {};
4852
return {
4953
name: printIdentifier(parameter.name),
5054
isOptional: !parameter.required,
51-
doc: parameter.description ?? parameter.schema.description,
55+
doc: parameter.description ?? parameterSchema.description,
5256
decorators: getParameterDecorators(parameter),
53-
schema: parameter.schema,
57+
schema: parameterSchema,
5458
};
5559
}

0 commit comments

Comments
 (0)