Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@typespec/openapi3"
---

Fix import of `deprecated: true` on OpenAPI3 operations to generate `#deprecated "deprecated"` directive in converted TypeSpec output.
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,23 @@ import {
} from "../interfaces.js";
import { Context } from "../utils/context.js";
import { generateDocs } from "../utils/docs.js";
import { generateDecorators } from "./generate-decorators.js";
import { generateDecorators, generateDirectives } from "./generate-decorators.js";
import { generateOperationReturnType } from "./generate-response-expressions.js";

export function generateOperation(operation: TypeSpecOperation, context: Context): string {
const definitions: string[] = [];
const preamble: string[] = [];
const operationParts: string[] = [];

if (operation.doc) {
definitions.push(generateDocs(operation.doc));
preamble.push(generateDocs(operation.doc));
}

definitions.push(...operation.tags.map((t) => `@tag("${t}")`));
// Directives (e.g. #deprecated) must each be on their own line before the operation
preamble.push(...generateDirectives(operation.directives));

operationParts.push(...operation.tags.map((t) => `@tag("${t}")`));

definitions.push(generateDecorators(operation.decorators).join(" "));
operationParts.push(generateDecorators(operation.decorators).join(" "));

// generate parameters
const parameters: string[] = [
Expand All @@ -29,12 +33,15 @@ export function generateOperation(operation: TypeSpecOperation, context: Context
const responses = generateOperationReturnType(operation, context);

if (operation.fixmes?.length) {
definitions.push("\n", ...operation.fixmes.map((f) => `// FIXME: ${f}\n`));
operationParts.push("\n", ...operation.fixmes.map((f) => `// FIXME: ${f}\n`));
}

definitions.push(`op ${operation.name}(${parameters.join(", ")}): ${responses};`);
operationParts.push(`op ${operation.name}(${parameters.join(", ")}): ${responses};`);

return definitions.join(" ");
const preambleStr = preamble.join("\n");
const operationStr = operationParts.join(" ");

return preambleStr ? `${preambleStr}\n${operationStr}` : operationStr;
}

function generateOperationParameter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Refable,
} from "../../../../types.js";
import {
TypeSpecDirective,
TypeSpecOperation,
TypeSpecOperationParameter,
TypeSpecRequestBody,
Expand Down Expand Up @@ -75,6 +76,10 @@ export function transformPaths(

const requestBodies = transformRequestBodies(operation.requestBody, context);

const directives: TypeSpecDirective[] = operation.deprecated
? [{ name: "deprecated", message: "deprecated" }]
: [];

// Check if we need to split the operation due to incompatible content types
const splitOperations = splitOperationByContentType(
operationId,
Expand All @@ -85,6 +90,7 @@ export function transformPaths(
operationResponses,
tags,
fixmes,
directives,
usedOperationIds,
);

Expand Down Expand Up @@ -161,6 +167,7 @@ function splitOperationByContentType(
responses: any,
tags: string[],
fixmes: string[],
directives: TypeSpecDirective[],
usedOperationIds: Set<string>,
): TypeSpecOperation[] {
// If no request bodies or only one content type, no splitting needed
Expand All @@ -169,6 +176,7 @@ function splitOperationByContentType(
{
...getScopeAndName(operationId),
decorators,
directives,
parameters,
doc,
operationId,
Expand All @@ -190,6 +198,7 @@ function splitOperationByContentType(
{
...getScopeAndName(operationId),
decorators,
directives,
parameters,
doc,
operationId,
Expand Down Expand Up @@ -248,6 +257,7 @@ function splitOperationByContentType(
operations.push({
...getScopeAndName(newOperationId),
decorators: newDecorators,
directives,
parameters,
doc,
operationId: newOperationId,
Expand Down
110 changes: 110 additions & 0 deletions packages/openapi3/test/tsp-openapi3/convert-openapi3-doc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,116 @@ def grade(sample: dict, item: dict) -> float:
),
);
});

it("should convert deprecated operation without description to #deprecated directive", async () => {
const tsp = await convertOpenAPI3Document({
openapi: version,
info: {
title: "(title)",
version: "0.0.0",
},
tags: [],
paths: {
"/foo": {
get: {
operationId: "Foo_get",
deprecated: true,
tags: ["Foo"],
responses: {
"200": {
description: "OK",
},
},
},
},
},
} as any);

strictEqual(
tsp,
await formatTypeSpec(
`
import "@typespec/http";
import "@typespec/openapi";
import "@typespec/openapi3";

using Http;
using OpenAPI;

@service(#{
title: "(title)",
})
@info(#{
version: "0.0.0",
})
namespace title;

#deprecated "deprecated"
@tag("Foo")
@route("/foo")
@get
op Foo_get(): OkResponse;
`,
{ printWidth: 100, tabWidth: 2 },
),
);
});

it("should convert deprecated operation with description to #deprecated directive", async () => {
const tsp = await convertOpenAPI3Document({
openapi: version,
info: {
title: "(title)",
version: "0.0.0",
},
tags: [],
paths: {
"/foo": {
get: {
operationId: "Foo_get",
description: "Get foo",
deprecated: true,
tags: ["Foo"],
responses: {
"200": {
description: "OK",
},
},
},
},
},
} as any);

strictEqual(
tsp,
await formatTypeSpec(
`
import "@typespec/http";
import "@typespec/openapi";
import "@typespec/openapi3";

using Http;
using OpenAPI;

@service(#{
title: "(title)",
})
@info(#{
version: "0.0.0",
})
namespace title;

/** Get foo */
#deprecated "deprecated"
@tag("Foo")
@route("/foo")
@get
op Foo_get(): OkResponse;
`,
{ printWidth: 100, tabWidth: 2 },
),
);
});
});

describe("convertOpenAPI3Document tag metadata", () => {
Expand Down
Loading