diff --git a/common/changes/@typespec/openapi3/title_2023-10-20-17-49.json b/common/changes/@typespec/openapi3/title_2023-10-20-17-49.json new file mode 100644 index 0000000000..ade2b54b01 --- /dev/null +++ b/common/changes/@typespec/openapi3/title_2023-10-20-17-49.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@typespec/openapi3", + "comment": "Support `@summary` on data types which emits the JSON Schema `title` property.", + "type": "none" + } + ], + "packageName": "@typespec/openapi3" +} \ No newline at end of file diff --git a/packages/openapi3/src/openapi.ts b/packages/openapi3/src/openapi.ts index d7d500cfa8..84c6667912 100644 --- a/packages/openapi3/src/openapi.ts +++ b/packages/openapi3/src/openapi.ts @@ -1470,6 +1470,11 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt schema.enum = values; } + const title = getSummary(program, e); + if (title) { + schema.title = title; + } + return schema; function enumMemberType(member: EnumMember) { if (typeof member.value === "number") { @@ -1537,6 +1542,11 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt if (schemaMembers.length === 1) { // we can just return the single schema member after applying nullable const schema = schemaMembers[0].schema; + applyIntrinsicDecorators(union, schema); + const title = getSummary(program, union); + if (title) { + schema.title = title; + } const type = schemaMembers[0].type; if (nullable) { @@ -1707,6 +1717,11 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt if (Object.keys(properties).length > 0) { modelSchema.properties = properties; } + const title = getSummary(program, model); + if (title) { + modelSchema.title = title; + } + // Attach any OpenAPI extensions attachExtensions(program, model, modelSchema); return modelSchema; @@ -1833,6 +1848,11 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt newTarget.format = "password"; } + const title = getSummary(program, typespecType); + if (title) { + newTarget.title = title; + } + const values = getKnownValues(program, typespecType as any); if (values) { return { diff --git a/packages/openapi3/test/array.test.ts b/packages/openapi3/test/array.test.ts index 19388255e8..1faaa1d7a8 100644 --- a/packages/openapi3/test/array.test.ts +++ b/packages/openapi3/test/array.test.ts @@ -1,4 +1,4 @@ -import { deepStrictEqual, ok } from "assert"; +import { deepStrictEqual, ok, strictEqual } from "assert"; import { oapiForModel } from "./test-host.js"; describe("openapi3: Array", () => { @@ -149,6 +149,18 @@ describe("openapi3: Array", () => { }); }); + it("supports summary", async () => { + const res = await oapiForModel( + "Foo", + ` + @summary("FooArray") + model Foo is string[]; + ` + ); + + strictEqual(res.schemas.Foo.title, "FooArray"); + }); + it("can specify tuple defaults using tuple syntax", async () => { const res = await oapiForModel( "Pet", diff --git a/packages/openapi3/test/enums.test.ts b/packages/openapi3/test/enums.test.ts index d1cfe8b2d1..c0aa1f66a8 100644 --- a/packages/openapi3/test/enums.test.ts +++ b/packages/openapi3/test/enums.test.ts @@ -1,7 +1,8 @@ import { expectDiagnostics } from "@typespec/compiler/testing"; -import { diagnoseOpenApiFor } from "./test-host.js"; +import { strictEqual } from "assert"; +import { diagnoseOpenApiFor, oapiForModel } from "./test-host.js"; -describe("openapi3: models", () => { +describe("openapi3: enums", () => { it("throws diagnostics for empty enum definitions", async () => { const diagnostics = await diagnoseOpenApiFor(`enum PetType {}`); @@ -19,4 +20,17 @@ describe("openapi3: models", () => { message: "Enums are not supported unless all options are literals of the same type.", }); }); + + it("supports summary on enums", async () => { + const res = await oapiForModel( + "Foo", + ` + @summary("FooEnum") + enum Foo { + y: 0; + }; + ` + ); + strictEqual(res.schemas.Foo.title, "FooEnum"); + }); }); diff --git a/packages/openapi3/test/models.test.ts b/packages/openapi3/test/models.test.ts index b7cc3e83bf..65d35a6f03 100644 --- a/packages/openapi3/test/models.test.ts +++ b/packages/openapi3/test/models.test.ts @@ -816,6 +816,21 @@ describe("openapi3: models", () => { }); }); + it("supports summary on models and model properties", async () => { + const res = await oapiForModel( + "Foo", + ` + @summary("FooModel") + model Foo { + @summary("YProp") + y: int32; + }; + ` + ); + strictEqual(res.schemas.Foo.title, "FooModel"); + strictEqual(res.schemas.Foo.properties.y.title, "YProp"); + }); + describe("referencing another property as type", () => { it("use the type of the other property", async () => { const res = await oapiForModel( diff --git a/packages/openapi3/test/primitive-types.test.ts b/packages/openapi3/test/primitive-types.test.ts index 8c86f11247..263d964967 100644 --- a/packages/openapi3/test/primitive-types.test.ts +++ b/packages/openapi3/test/primitive-types.test.ts @@ -1,4 +1,4 @@ -import { deepStrictEqual, ok } from "assert"; +import { deepStrictEqual, ok, strictEqual } from "assert"; import { OpenAPI3Schema } from "../src/types.js"; import { oapiForModel } from "./test-host.js"; @@ -215,6 +215,16 @@ describe("openapi3: primitives", () => { }); }); + it("supports summary on custom scalars", async () => { + const res = await oapiForModel( + "Foo", + ` + @summary("FooScalar") scalar Foo extends string; + ` + ); + strictEqual(res.schemas.Foo.title, "FooScalar"); + }); + describe("using @encode decorator", () => { async function testEncode( scalar: string, diff --git a/packages/openapi3/test/union-schema.test.ts b/packages/openapi3/test/union-schema.test.ts index 1eb0e07136..411108a712 100644 --- a/packages/openapi3/test/union-schema.test.ts +++ b/packages/openapi3/test/union-schema.test.ts @@ -1,5 +1,5 @@ import { expectDiagnostics } from "@typespec/compiler/testing"; -import { deepStrictEqual, ok } from "assert"; +import { deepStrictEqual, ok, strictEqual } from "assert"; import { diagnoseOpenApiFor, oapiForModel, openApiFor } from "./test-host.js"; describe("openapi3: union type", () => { @@ -375,4 +375,25 @@ describe("openapi3: union type", () => { "Empty unions are not supported for OpenAPI v3 - enums must have at least one value.", }); }); + + it("supports summary on unions and union variants", async () => { + const res = await oapiForModel( + "Foo", + ` + @summary("FooUnion") + union Foo { + int32; + + Bar; + } + + @summary("BarUnion") + union Bar { + string; + } + ` + ); + strictEqual(res.schemas.Foo.title, "FooUnion"); + strictEqual(res.schemas.Bar.title, "BarUnion"); + }); });