diff --git a/.chronus/changes/safe-int-openapi3-2024-1-17-0-53-2.md b/.chronus/changes/safe-int-openapi3-2024-1-17-0-53-2.md new file mode 100644 index 000000000..6072a3bc9 --- /dev/null +++ b/.chronus/changes/safe-int-openapi3-2024-1-17-0-53-2.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: feature +packages: + - "@typespec/openapi3" +--- + +Add a new option `safeint-strategy` that can be set to `double-int` to emit `type: integer, format: double-int` instead of `type: integer, format: int64` when using the `safeint` scalar. diff --git a/docs/libraries/openapi3/reference/emitter.md b/docs/libraries/openapi3/reference/emitter.md index bd8979799..b9f4be9c9 100644 --- a/docs/libraries/openapi3/reference/emitter.md +++ b/docs/libraries/openapi3/reference/emitter.md @@ -81,3 +81,14 @@ By default all types declared under the service namespace will be included. With If the generated openapi types should have the `x-typespec-name` extension set with the name of the TypeSpec type that created it. This extension is meant for debugging and should not be depended on. + +### `safeint-strategy` + +**Type:** `"double-int" | "int64"` + +How to handle safeint type. Options are: + +- `double-int`: Will produce `type: integer, format: double-int` +- `int64`: Will produce `type: integer, format: int64` + +Default: `int64` diff --git a/packages/openapi3/README.md b/packages/openapi3/README.md index a41e857f9..d148beb0c 100644 --- a/packages/openapi3/README.md +++ b/packages/openapi3/README.md @@ -86,6 +86,17 @@ By default all types declared under the service namespace will be included. With If the generated openapi types should have the `x-typespec-name` extension set with the name of the TypeSpec type that created it. This extension is meant for debugging and should not be depended on. +#### `safeint-strategy` + +**Type:** `"double-int" | "int64"` + +How to handle safeint type. Options are: + +- `double-int`: Will produce `type: integer, format: double-int` +- `int64`: Will produce `type: integer, format: int64` + +Default: `int64` + ## Decorators ### TypeSpec.OpenAPI diff --git a/packages/openapi3/src/lib.ts b/packages/openapi3/src/lib.ts index ad0549887..335aecedb 100644 --- a/packages/openapi3/src/lib.ts +++ b/packages/openapi3/src/lib.ts @@ -54,6 +54,14 @@ export interface OpenAPI3EmitterOptions { * @default "never" */ "include-x-typespec-name"?: "inline-only" | "never"; + + /** + * How to handle safeint type. Options are: + * - `double-int`: Will produce `type: integer, format: double-int` + * - `int64`: Will produce `type: integer, format: int64` + * @default "int64" + */ + "safeint-strategy"?: "double-int" | "int64"; } const EmitterOptionsSchema: JSONSchemaType = { @@ -117,6 +125,19 @@ const EmitterOptionsSchema: JSONSchemaType = { description: "If the generated openapi types should have the `x-typespec-name` extension set with the name of the TypeSpec type that created it.\nThis extension is meant for debugging and should not be depended on.", }, + "safeint-strategy": { + type: "string", + enum: ["double-int", "int64"], + nullable: true, + default: "int64", + description: [ + "How to handle safeint type. Options are:", + " - `double-int`: Will produce `type: integer, format: double-int`", + " - `int64`: Will produce `type: integer, format: int64`", + "", + "Default: `int64`", + ].join("\n"), + }, }, required: [], }; diff --git a/packages/openapi3/src/openapi.ts b/packages/openapi3/src/openapi.ts index 863970672..21b13418f 100644 --- a/packages/openapi3/src/openapi.ts +++ b/packages/openapi3/src/openapi.ts @@ -118,6 +118,7 @@ const defaultOptions = { "new-line": "lf", "omit-unreachable-types": false, "include-x-typespec-name": "never", + "safeint-strategy": "int64", } as const; export async function $onEmit(context: EmitContext) { @@ -186,6 +187,7 @@ export function resolveOptions( newLine: resolvedOptions["new-line"], omitUnreachableTypes: resolvedOptions["omit-unreachable-types"], includeXTypeSpecName: resolvedOptions["include-x-typespec-name"], + safeintStrategy: resolvedOptions["safeint-strategy"], outputFile: resolvePath(context.emitterOutputDir, outputFile), }; } @@ -196,6 +198,7 @@ export interface ResolvedOpenAPI3EmitterOptions { newLine: NewLine; omitUnreachableTypes: boolean; includeXTypeSpecName: "inline-only" | "never"; + safeintStrategy: "double-int" | "int64"; } function createOAPIEmitter( diff --git a/packages/openapi3/src/schema-emitter.ts b/packages/openapi3/src/schema-emitter.ts index 6456d8b58..5b579c6de 100644 --- a/packages/openapi3/src/schema-emitter.ts +++ b/packages/openapi3/src/schema-emitter.ts @@ -699,7 +699,13 @@ export class OpenAPI3SchemaEmitter extends TypeEmitter< case "int64": return { type: "integer", format: "int64" }; case "safeint": - return { type: "integer", format: "int64" }; + switch (this.#options.safeintStrategy) { + case "double-int": + return { type: "integer", format: "double-int" }; + case "int64": + default: + return { type: "integer", format: "int64" }; + } case "uint8": return { type: "integer", format: "uint8" }; case "uint16": diff --git a/packages/openapi3/test/primitive-types.test.ts b/packages/openapi3/test/primitive-types.test.ts index 03ac8cc58..be6b8e5c5 100644 --- a/packages/openapi3/test/primitive-types.test.ts +++ b/packages/openapi3/test/primitive-types.test.ts @@ -1,7 +1,7 @@ import { deepStrictEqual, ok, strictEqual } from "assert"; import { describe, it } from "vitest"; import { OpenAPI3Schema } from "../src/types.js"; -import { oapiForModel } from "./test-host.js"; +import { oapiForModel, openApiFor } from "./test-host.js"; describe("openapi3: primitives", () => { describe("handle TypeSpec intrinsic types", () => { @@ -48,6 +48,34 @@ describe("openapi3: primitives", () => { } }); + describe("safeint-strategy", () => { + it("produce type: integer, format: double-int for safeint when safeint-strategy is double-int", async () => { + const res = await openApiFor( + ` + model Pet { name: safeint }; + `, + undefined, + { "safeint-strategy": "double-int" } + ); + + const schema = res.components.schemas.Pet.properties.name; + deepStrictEqual(schema, { type: "integer", format: "double-int" }); + }); + + it("produce type: integer, format: int64 for safeint when safeint-strategy is int64", async () => { + const res = await openApiFor( + ` + model Pet { name: safeint }; + `, + undefined, + { "safeint-strategy": "int64" } + ); + + const schema = res.components.schemas.Pet.properties.name; + deepStrictEqual(schema, { type: "integer", format: "int64" }); + }); + }); + it("defines models extended from primitives", async () => { const res = await oapiForModel( "Pet",