Skip to content

Commit 2ee96a0

Browse files
chrisradekChristopher Radek
authored andcommitted
tsp-openapi3 - handle requestBodies $ref (microsoft#5893)
Fixes microsoft#5485 --------- Co-authored-by: Christopher Radek <Christopher.Radek@microsoft.com>
1 parent 84df1ed commit 2ee96a0

File tree

15 files changed

+304
-31
lines changed

15 files changed

+304
-31
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: fix
3+
packages:
4+
- "@typespec/openapi3"
5+
---
6+
7+
Updates tsp-openapi3 to support $ref in requestBodies

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,11 @@ function generateRequestBodyParameters(
8383
).join(" | ");
8484

8585
if (body) {
86-
definitions.push(`@bodyRoot body: ${body}`);
86+
let doc = "";
87+
if (requestBodies[0].doc) {
88+
doc = generateDocs(requestBodies[0].doc);
89+
}
90+
definitions.push(`${doc}@body body: ${body}`);
8791
}
8892

8993
return definitions;

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

+21-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
TypeSpecOperationParameter,
1111
TypeSpecRequestBody,
1212
} from "../interfaces.js";
13+
import { Context } from "../utils/context.js";
1314
import { getExtensions, getParameterDecorators } from "../utils/decorators.js";
1415
import { getScopeAndName } from "../utils/get-scope-and-name.js";
1516
import { supportedHttpMethods } from "../utils/supported-http-methods.js";
@@ -20,7 +21,10 @@ import { supportedHttpMethods } from "../utils/supported-http-methods.js";
2021
* @param paths
2122
* @returns
2223
*/
23-
export function transformPaths(paths: Record<string, OpenAPI3PathItem>): TypeSpecOperation[] {
24+
export function transformPaths(
25+
paths: Record<string, OpenAPI3PathItem>,
26+
context: Context,
27+
): TypeSpecOperation[] {
2428
const operations: TypeSpecOperation[] = [];
2529

2630
for (const route of Object.keys(paths)) {
@@ -51,7 +55,7 @@ export function transformPaths(paths: Record<string, OpenAPI3PathItem>): TypeSpe
5155
parameters: dedupeParameters([...routeParameters, ...parameters]),
5256
doc: operation.description,
5357
operationId: operation.operationId,
54-
requestBodies: transformRequestBodies(operation.requestBody),
58+
requestBodies: transformRequestBodies(operation.requestBody, context),
5559
responses: operationResponses,
5660
tags: tags,
5761
});
@@ -102,7 +106,20 @@ function transformOperationParameter(
102106
};
103107
}
104108

105-
function transformRequestBodies(requestBodies?: OpenAPI3RequestBody): TypeSpecRequestBody[] {
109+
function transformRequestBodies(
110+
requestBodies: Refable<OpenAPI3RequestBody> | undefined,
111+
context: Context,
112+
): TypeSpecRequestBody[] {
113+
if (!requestBodies) {
114+
return [];
115+
}
116+
117+
const description = requestBodies.description;
118+
119+
if ("$ref" in requestBodies) {
120+
requestBodies = context.getByRef<OpenAPI3RequestBody>(requestBodies.$ref);
121+
}
122+
106123
if (!requestBodies) {
107124
return [];
108125
}
@@ -113,7 +130,7 @@ function transformRequestBodies(requestBodies?: OpenAPI3RequestBody): TypeSpecRe
113130
typespecBodies.push({
114131
contentType,
115132
isOptional: !requestBodies.required,
116-
doc: requestBodies.description,
133+
doc: description ?? requestBodies.description,
117134
encoding: contentBody.encoding,
118135
schema: contentBody.schema,
119136
});

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { transformServiceInfo } from "./transform-service-info.js";
99
export function transform(context: Context): TypeSpecProgram {
1010
const openapi = context.openApi3Doc;
1111
const models = collectDataTypes(context);
12-
const operations = transformPaths(openapi.paths);
12+
const operations = transformPaths(openapi.paths, context);
1313

1414
return {
1515
serviceInfo: transformServiceInfo(openapi.info),

packages/openapi3/src/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ export type OpenAPI3Operation = Extensions & {
685685
responses?: any;
686686
tags?: string[];
687687
operationId?: string;
688-
requestBody?: OpenAPI3RequestBody;
688+
requestBody?: Refable<OpenAPI3RequestBody>;
689689
parameters: Refable<OpenAPI3Parameter>[];
690690
deprecated?: boolean;
691691
security?: Record<string, string[]>[];

packages/openapi3/test/examples.test.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, it } from "vitest";
2-
import { OpenAPI3Document } from "../src/types.js";
2+
import { OpenAPI3Document, OpenAPI3RequestBody } from "../src/types.js";
33
import { openApiFor } from "./test-host.js";
44
import { worksFor } from "./works-for.js";
55

@@ -67,7 +67,9 @@ worksFor(["3.0.0", "3.1.0"], ({ openApiFor }) => {
6767
6868
`,
6969
);
70-
expect(res.paths["/"].post?.requestBody?.content["application/json"].example).toEqual({
70+
expect(
71+
(res.paths["/"].post?.requestBody as OpenAPI3RequestBody).content["application/json"].example,
72+
).toEqual({
7173
name: "Fluffy",
7274
age: 2,
7375
});
@@ -87,7 +89,10 @@ worksFor(["3.0.0", "3.1.0"], ({ openApiFor }) => {
8789
8890
`,
8991
);
90-
expect(res.paths["/"].post?.requestBody?.content["application/json"].examples).toEqual({
92+
expect(
93+
(res.paths["/"].post?.requestBody as OpenAPI3RequestBody).content["application/json"]
94+
.examples,
95+
).toEqual({
9196
MyExample: {
9297
summary: "MyExample",
9398
value: {

packages/openapi3/test/overloads.test.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { deepStrictEqual, ok, strictEqual } from "assert";
22
import { beforeEach, describe, it } from "vitest";
3-
import { OpenAPI3Document } from "../src/types.js";
3+
import { OpenAPI3Document, OpenAPI3RequestBody } from "../src/types.js";
44
import { worksFor } from "./works-for.js";
55

66
worksFor(["3.0.0", "3.1.0"], ({ openApiFor }) => {
@@ -24,7 +24,7 @@ worksFor(["3.0.0", "3.1.0"], ({ openApiFor }) => {
2424
const operation = res.paths["/upload"].post;
2525
ok(operation);
2626
strictEqual(operation.operationId, "upload");
27-
deepStrictEqual(Object.keys(operation.requestBody!.content), [
27+
deepStrictEqual(Object.keys((operation.requestBody as OpenAPI3RequestBody).content), [
2828
"text/plain",
2929
"application/octet-stream",
3030
]);
@@ -56,8 +56,10 @@ worksFor(["3.0.0", "3.1.0"], ({ openApiFor }) => {
5656
strictEqual(stringOperation.operationId, "uploadString");
5757
strictEqual(bytesOperation.operationId, "uploadBytes");
5858

59-
deepStrictEqual(Object.keys(stringOperation.requestBody!.content), ["text/plain"]);
60-
deepStrictEqual(Object.keys(bytesOperation.requestBody!.content), [
59+
deepStrictEqual(Object.keys((stringOperation.requestBody as OpenAPI3RequestBody).content), [
60+
"text/plain",
61+
]);
62+
deepStrictEqual(Object.keys((bytesOperation.requestBody as OpenAPI3RequestBody).content), [
6163
"application/octet-stream",
6264
]);
6365
});
@@ -67,7 +69,7 @@ worksFor(["3.0.0", "3.1.0"], ({ openApiFor }) => {
6769
ok(baseOperation);
6870
strictEqual(baseOperation.operationId, "upload");
6971

70-
deepStrictEqual(Object.keys(baseOperation.requestBody!.content), [
72+
deepStrictEqual(Object.keys((baseOperation.requestBody as OpenAPI3RequestBody).content), [
7173
"text/plain",
7274
"application/octet-stream",
7375
]);

packages/openapi3/test/tsp-openapi3/output/escaped-identifiers/main.tsp

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ model `Escaped-Model` {
2323
@route("/{escaped-property}") @get op `get-thing`(
2424
@query(#{ explode: true }) `weird@param`?: `Foo-Bar`,
2525
...Parameters.`Escaped-Model`.`escaped-property`,
26-
@bodyRoot body: `Escaped-Model`,
26+
@body body: `Escaped-Model`,
2727
): GeneratedHelpers.DefaultResponse<Description = "Success">;
2828

2929
namespace Parameters {

packages/openapi3/test/tsp-openapi3/output/nested/main.tsp

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,20 @@ namespace SubA {
2121
model Thing {
2222
name: string;
2323
}
24-
@route("/sub/a/subsub") @post op doSomething(@bodyRoot body: SubA.SubSubA.Thing): Body<string>;
24+
@route("/sub/a/subsub") @post op doSomething(@body body: SubA.SubSubA.Thing): Body<string>;
2525
}
2626
}
2727

2828
namespace SubB {
2929
model Thing {
3030
id: int64;
3131
}
32-
@route("/sub/b") @post op doSomething(@bodyRoot body: SubB.Thing): Body<string>;
32+
@route("/sub/b") @post op doSomething(@body body: SubB.Thing): Body<string>;
3333
}
3434

3535
namespace SubC {
3636
@route("/") @post op anotherOp(
37-
@bodyRoot body: {
37+
@body body: {
3838
thing: SubA.Thing;
3939
thing2: SubA.Thing;
4040
},

packages/openapi3/test/tsp-openapi3/output/one-any-all/main.tsp

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ model Pet {
2929
}
3030

3131
@route("/any") @post op putAny(
32-
@bodyRoot body: {
32+
@body body: {
3333
pet: Dog | Cat;
3434
},
3535
): NoContentResponse;
3636

3737
@route("/one") @post op putOne(
38-
@bodyRoot body: {
38+
@body body: {
3939
@oneOf
4040
pet: Dog | Cat;
4141
},

packages/openapi3/test/tsp-openapi3/output/param-decorators/main.tsp

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ model Thing {
3232

3333
@route("/thing/{name}") @put op Operations_putThing(
3434
...Parameters.NameParameter,
35-
@bodyRoot body: Thing,
35+
@body body: Thing,
3636
): Thing;
3737

3838
namespace Parameters {

packages/openapi3/test/tsp-openapi3/output/petstore-swagger/main.tsp

+23-7
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,11 @@ model ApiResponse {
116116
@summary("Add a new pet to the store")
117117
op addPet(
118118
@header contentType: "application/json" | "application/xml" | "application/x-www-form-urlencoded",
119-
@bodyRoot body: Pet,
119+
120+
/**
121+
* Create a new pet in the store
122+
*/
123+
@body body: Pet,
120124
): {
121125
@header contentType: "application/xml";
122126
@body body: Pet;
@@ -133,7 +137,11 @@ op addPet(
133137
@summary("Update an existing pet")
134138
op updatePet(
135139
@header contentType: "application/json" | "application/xml" | "application/x-www-form-urlencoded",
136-
@bodyRoot body: Pet,
140+
141+
/**
142+
* Update an existent pet in the store
143+
*/
144+
@body body: Pet,
137145
):
138146
| {
139147
@header contentType: "application/xml";
@@ -253,7 +261,7 @@ op uploadFile(
253261
@query(#{ explode: true }) additionalMetadata?: string,
254262

255263
@header contentType: "application/octet-stream",
256-
@bodyRoot body: bytes,
264+
@body body: bytes,
257265
): ApiResponse;
258266

259267
/**
@@ -276,7 +284,7 @@ op getInventory(): Body<Record<int32>>;
276284
@summary("Place an order for a pet")
277285
op placeOrder(
278286
@header contentType: "application/json" | "application/xml" | "application/x-www-form-urlencoded",
279-
@bodyRoot body: Order,
287+
@body body: Order,
280288
): Order | {
281289
@statusCode statusCode: 405;
282290
};
@@ -327,7 +335,11 @@ op getOrderById(
327335
@summary("Create user")
328336
op createUser(
329337
@header contentType: "application/json" | "application/xml" | "application/x-www-form-urlencoded",
330-
@bodyRoot body: User,
338+
339+
/**
340+
* Created user object
341+
*/
342+
@body body: User,
331343
): GeneratedHelpers.DefaultResponse<
332344
Description = "successful operation",
333345
Body = User
@@ -347,7 +359,7 @@ op createUser(
347359
@route("/user/createWithList")
348360
@post
349361
@summary("Creates list of users with given input array")
350-
op createUsersWithListInput(@bodyRoot body: User[]): {
362+
op createUsersWithListInput(@body body: User[]): {
351363
@header contentType: "application/xml";
352364
@body body: User;
353365
} | User | GeneratedHelpers.DefaultResponse<Description = "successful operation">;
@@ -446,7 +458,11 @@ op updateUser(
446458
@path username: string,
447459

448460
@header contentType: "application/json" | "application/xml" | "application/x-www-form-urlencoded",
449-
@bodyRoot body: User,
461+
462+
/**
463+
* Update an existent user in the store
464+
*/
465+
@body body: User,
450466
): GeneratedHelpers.DefaultResponse<Description = "successful operation">;
451467

452468
namespace GeneratedHelpers {

packages/openapi3/test/tsp-openapi3/output/playground-http-service/main.tsp

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ op Widgets_list(): Body<Widget[]> | GeneratedHelpers.DefaultResponse<
4545
@tag("Widgets")
4646
@route("/widgets")
4747
@post
48-
op Widgets_create(@bodyRoot body: WidgetCreate): Widget | GeneratedHelpers.DefaultResponse<
48+
op Widgets_create(@body body: WidgetCreate): Widget | GeneratedHelpers.DefaultResponse<
4949
Description = "An unexpected error response.",
5050
Body = Error
5151
>;
@@ -71,7 +71,7 @@ op Widgets_read(@path id: string): Widget | GeneratedHelpers.DefaultResponse<
7171
@patch
7272
op Widgets_update(
7373
...Parameters.Widget.id,
74-
@bodyRoot body: WidgetUpdate,
74+
@body body: WidgetUpdate,
7575
): Widget | GeneratedHelpers.DefaultResponse<
7676
Description = "An unexpected error response.",
7777
Body = Error

0 commit comments

Comments
 (0)