Skip to content

Commit f5ceafd

Browse files
skywing918Kyle Zhangtimotheeguerinwanlwanl
authored
Add xml support to openapi3 (#4214)
1. add test cases based on [Examples](https://tspwebsitepr.z22.web.core.windows.net/prs/2982/docs/next/release-notes/xml-support.html#examples) 2. add xml in OpenAPI3Schema type 3. update schema-emitter logic for support xml object --------- Co-authored-by: Kyle Zhang <[email protected]> Co-authored-by: Timothee Guerin <[email protected]> Co-authored-by: Wanpeng Li <[email protected]>
1 parent 34be9c8 commit f5ceafd

11 files changed

+1201
-10
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+
Add XML support using `@typespec/xml` library

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ coverage.xml
4848
.pytest_cache/
4949
test-results.xml
5050
test-results/
51+
__snapshots__/
5152

5253
# Translations
5354
*.mo
@@ -224,4 +225,3 @@ BenchmarkDotnet.Artifacts/
224225
packages/http-client-python/generator/test/**/generated/
225226
packages/http-client-python/generator/test/**/cadl-ranch-coverage.json
226227
!packages/http-client-python/package-lock.json
227-

packages/openapi3/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@
6767
"@typespec/openapi": "workspace:~",
6868
"@typespec/versioning": "workspace:~"
6969
},
70+
"peerDependenciesMeta": {
71+
"@typespec/xml": {
72+
"optional": true
73+
}
74+
},
7075
"devDependencies": {
7176
"@types/node": "~22.7.5",
7277
"@types/yargs": "~17.0.33",
@@ -77,6 +82,7 @@
7782
"@typespec/rest": "workspace:~",
7883
"@typespec/tspd": "workspace:~",
7984
"@typespec/versioning": "workspace:~",
85+
"@typespec/xml": "workspace:~",
8086
"@vitest/coverage-v8": "^2.1.2",
8187
"@vitest/ui": "^2.1.2",
8288
"c8": "^10.1.2",

packages/openapi3/src/lib.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,18 @@ export const libDef = {
263263
default: paramMessage`Authentication "${"authType"}" is not a known authentication by the openapi3 emitter, it will be ignored.`,
264264
},
265265
},
266+
"xml-attribute-invalid-property-type": {
267+
severity: "warning",
268+
messages: {
269+
default: paramMessage`XML \`@attribute\` can only be primitive types in the OpenAPI 3 emitter, Property '${"name"}' type will be changed to type: string.`,
270+
},
271+
},
272+
"xml-unwrapped-invalid-property-type": {
273+
severity: "warning",
274+
messages: {
275+
default: paramMessage`XML \`@unwrapped\` can only used on array properties or primitive ones in the OpenAPI 3 emitter, Property '${"name"}' will be ignored.`,
276+
},
277+
},
266278
},
267279
emitter: {
268280
options: EmitterOptionsSchema as JSONSchemaType<OpenAPI3EmitterOptions>,

packages/openapi3/src/openapi.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ import {
113113
} from "./types.js";
114114
import { deepEquals, isSharedHttpOperation, SharedHttpOperation } from "./util.js";
115115
import { resolveVisibilityUsage, VisibilityUsageTracker } from "./visibility-usage.js";
116+
import { resolveXmlModule, XmlModule } from "./xml-module.js";
116117

117118
const defaultFileType: FileType = "yaml";
118119
const defaultOptions = {
@@ -284,6 +285,7 @@ function createOAPIEmitter(
284285
service: Service,
285286
allHttpAuthentications: HttpAuth[],
286287
defaultAuth: AuthenticationReference,
288+
xmlModule: XmlModule | undefined,
287289
version?: string,
288290
) {
289291
diagnostics = createDiagnosticCollector();
@@ -298,11 +300,12 @@ function createOAPIEmitter(
298300
service.type,
299301
options.omitUnreachableTypes,
300302
);
303+
301304
schemaEmitter = createAssetEmitter(
302305
program,
303306
class extends OpenAPI3SchemaEmitter {
304307
constructor(emitter: AssetEmitter<Record<string, any>, OpenAPI3EmitterOptions>) {
305-
super(emitter, metadataInfo, visibilityUsage, options);
308+
super(emitter, metadataInfo, visibilityUsage, options, xmlModule);
306309
}
307310
} as any,
308311
context,
@@ -615,7 +618,8 @@ function createOAPIEmitter(
615618
const httpService = ignoreDiagnostics(getHttpService(program, service.type));
616619
const auth = (serviceAuth = resolveAuthentication(httpService));
617620

618-
initializeEmitter(service, auth.schemes, auth.defaultAuth, version);
621+
const xmlModule = await resolveXmlModule();
622+
initializeEmitter(service, auth.schemes, auth.defaultAuth, xmlModule, version);
619623
reportIfNoRoutes(program, httpService.operations);
620624

621625
for (const op of resolveOperations(httpService.operations)) {

packages/openapi3/src/schema-emitter.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ import {
8484
OpenAPI3SchemaProperty,
8585
} from "./types.js";
8686
import { VisibilityUsageTracker } from "./visibility-usage.js";
87+
import { XmlModule } from "./xml-module.js";
8788

8889
/**
8990
* OpenAPI3 schema emitter. Deals with emitting content of `components/schemas` section.
@@ -95,16 +96,19 @@ export class OpenAPI3SchemaEmitter extends TypeEmitter<
9596
#metadataInfo: MetadataInfo;
9697
#visibilityUsage: VisibilityUsageTracker;
9798
#options: ResolvedOpenAPI3EmitterOptions;
99+
#xmlModule: XmlModule | undefined;
98100
constructor(
99101
emitter: AssetEmitter<Record<string, any>, OpenAPI3EmitterOptions>,
100102
metadataInfo: MetadataInfo,
101103
visibilityUsage: VisibilityUsageTracker,
102104
options: ResolvedOpenAPI3EmitterOptions,
105+
xmlModule: XmlModule | undefined,
103106
) {
104107
super(emitter);
105108
this.#metadataInfo = metadataInfo;
106109
this.#visibilityUsage = visibilityUsage;
107110
this.#options = options;
111+
this.#xmlModule = xmlModule;
108112
}
109113

110114
modelDeclarationReferenceContext(model: Model, name: string): Context {
@@ -382,7 +386,7 @@ export class OpenAPI3SchemaEmitter extends TypeEmitter<
382386
const schema = this.#applyEncoding(prop, refSchema.value as any);
383387

384388
// Apply decorators on the property to the type's schema
385-
const additionalProps: Partial<OpenAPI3Schema> = this.#applyConstraints(prop, {});
389+
const additionalProps: Partial<OpenAPI3Schema> = this.#applyConstraints(prop, {}, schema);
386390
if (prop.defaultValue) {
387391
additionalProps.default = getDefaultValue(program, prop.defaultValue, prop);
388392
}
@@ -398,10 +402,14 @@ export class OpenAPI3SchemaEmitter extends TypeEmitter<
398402
if (Object.keys(additionalProps).length === 0) {
399403
return schema;
400404
} else {
401-
return {
402-
allOf: [schema],
403-
...additionalProps,
404-
};
405+
if (additionalProps.xml?.attribute) {
406+
return additionalProps;
407+
} else {
408+
return {
409+
allOf: [schema],
410+
...additionalProps,
411+
};
412+
}
405413
}
406414
} else {
407415
if (getOneOf(program, prop) && schema.anyOf) {
@@ -765,6 +773,7 @@ export class OpenAPI3SchemaEmitter extends TypeEmitter<
765773
#applyConstraints(
766774
type: Scalar | Model | ModelProperty | Union | Enum,
767775
original: OpenAPI3Schema,
776+
refSchema?: OpenAPI3Schema,
768777
): ObjectBuilder<OpenAPI3Schema> {
769778
const schema = new ObjectBuilder(original);
770779
const program = this.emitter.getProgram();
@@ -813,6 +822,24 @@ export class OpenAPI3SchemaEmitter extends TypeEmitter<
813822
"deprecated",
814823
);
815824

825+
if (this.#xmlModule) {
826+
switch (type.kind) {
827+
case "Scalar":
828+
case "Model":
829+
this.#xmlModule.attachXmlObjectForScalarOrModel(program, type, schema);
830+
break;
831+
case "ModelProperty":
832+
this.#xmlModule.attachXmlObjectForModelProperty(
833+
program,
834+
this.#options,
835+
type,
836+
schema,
837+
refSchema,
838+
);
839+
break;
840+
}
841+
}
842+
816843
this.#attachExtensions(program, type, schema);
817844

818845
const values = getKnownValues(program, type as any);

packages/openapi3/src/types.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,14 @@ export type JsonType = "array" | "boolean" | "integer" | "number" | "object" | "
376376
*/
377377
export type OpenAPI3SchemaProperty = Ref<OpenAPI3Schema> | OpenAPI3Schema;
378378

379+
export type OpenAPI3XmlSchema = Extensions & {
380+
name?: string;
381+
namespace?: string;
382+
prefix?: string;
383+
attribute?: boolean;
384+
wrapped?: boolean;
385+
};
386+
379387
export type OpenAPI3Schema = Extensions & {
380388
/**
381389
* This attribute is a string that provides a short description of the instance property.
@@ -587,6 +595,9 @@ export type OpenAPI3Schema = Extensions & {
587595

588596
/** Specifies that a schema is deprecated and SHOULD be transitioned out of usage.Default value is false. */
589597
deprecated?: boolean;
598+
599+
/** This MAY be used only on properties schemas. It has no effect on root schemas. Adds additional metadata to describe the XML representation of this property. */
600+
xml?: OpenAPI3XmlSchema;
590601
};
591602

592603
export type OpenAPI3ParameterBase = Extensions & {

0 commit comments

Comments
 (0)