From 656ebd436bc5a23db51e86ea2c6c730711b00483 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 9 Nov 2023 10:10:54 -0800 Subject: [PATCH 1/5] Json schema default --- .../json-schema/src/json-schema-emitter.ts | 65 +++++++++++++++++-- packages/json-schema/src/lib.ts | 11 +++- packages/json-schema/test/models.test.ts | 39 ++++++++++- 3 files changed, 106 insertions(+), 9 deletions(-) diff --git a/packages/json-schema/src/json-schema-emitter.ts b/packages/json-schema/src/json-schema-emitter.ts index 7d3a29c2ff..25ec08470a 100644 --- a/packages/json-schema/src/json-schema-emitter.ts +++ b/packages/json-schema/src/json-schema-emitter.ts @@ -1,5 +1,6 @@ import { BooleanLiteral, + compilerAssert, emitFile, Enum, EnumMember, @@ -19,6 +20,8 @@ import { getRelativePathFromDirectory, getSummary, IntrinsicType, + isArrayModelType, + isNullType, Model, ModelProperty, NumericLiteral, @@ -64,7 +67,7 @@ import { isJsonSchemaDeclaration, JsonSchemaDeclaration, } from "./index.js"; -import { JSONSchemaEmitterOptions } from "./lib.js"; +import { JSONSchemaEmitterOptions, reportDiagnostic } from "./lib.js"; export class JsonSchemaEmitter extends TypeEmitter, JSONSchemaEmitterOptions> { #seenIds = new Set(); #typeForSourceFile = new Map, JsonSchemaDeclaration>(); @@ -160,16 +163,66 @@ export class JsonSchemaEmitter extends TypeEmitter, JSONSche } modelPropertyLiteral(property: ModelProperty): EmitterOutput { - const result = this.emitter.emitTypeReference(property.type); + const propertyType = this.emitter.emitTypeReference(property.type); - if (result.kind !== "code") { + if (propertyType.kind !== "code") { throw new Error("Unexpected non-code result from emit reference"); } - const withConstraints = new ObjectBuilder(result.value); - this.#applyConstraints(property, withConstraints); + const result = new ObjectBuilder(propertyType.value); - return withConstraints; + if (property.default) { + result.default = this.#getDefaultValue(property.type, property.default); + } + + this.#applyConstraints(property, result); + + return result; + } + + #getDefaultValue(type: Type, defaultType: Type): any { + const program = this.emitter.getProgram(); + + switch (defaultType.kind) { + case "String": + return defaultType.value; + case "Number": + return defaultType.value; + case "Boolean": + return defaultType.value; + case "Tuple": + compilerAssert( + type.kind === "Tuple" || (type.kind === "Model" && isArrayModelType(program, type)), + "setting tuple default to non-tuple value" + ); + + if (type.kind === "Tuple") { + return defaultType.values.map((defaultTupleValue, index) => + this.#getDefaultValue(type.values[index], defaultTupleValue) + ); + } else { + return defaultType.values.map((defaultTuplevalue) => + this.#getDefaultValue(type.indexer!.value, defaultTuplevalue) + ); + } + + case "Intrinsic": + return isNullType(defaultType) + ? null + : reportDiagnostic(program, { + code: "invalid-default", + format: { type: defaultType.kind }, + target: defaultType, + }); + case "EnumMember": + return defaultType.value ?? defaultType.name; + default: + reportDiagnostic(program, { + code: "invalid-default", + format: { type: defaultType.kind }, + target: defaultType, + }); + } } booleanLiteral(boolean: BooleanLiteral): EmitterOutput { diff --git a/packages/json-schema/src/lib.ts b/packages/json-schema/src/lib.ts index 21a7759484..d8d4455692 100644 --- a/packages/json-schema/src/lib.ts +++ b/packages/json-schema/src/lib.ts @@ -1,4 +1,4 @@ -import { createTypeSpecLibrary, JSONSchemaType } from "@typespec/compiler"; +import { createTypeSpecLibrary, JSONSchemaType, paramMessage } from "@typespec/compiler"; export type FileType = "yaml" | "json"; export type Int64Strategy = "string" | "number"; @@ -82,7 +82,14 @@ export const EmitterOptionsSchema: JSONSchemaType = { export const libDef = { name: "@typespec/json-schema", - diagnostics: {}, + diagnostics: { + "invalid-default": { + severity: "error", + messages: { + default: paramMessage`Invalid type '${"type"}' for a default value`, + }, + }, + }, emitter: { options: EmitterOptionsSchema as JSONSchemaType, }, diff --git a/packages/json-schema/test/models.test.ts b/packages/json-schema/test/models.test.ts index 48ef9ffd68..a057d58cfc 100644 --- a/packages/json-schema/test/models.test.ts +++ b/packages/json-schema/test/models.test.ts @@ -1,4 +1,4 @@ -import assert from "assert"; +import assert, { deepStrictEqual } from "assert"; import { emitSchema } from "./utils.js"; describe("emitting models", () => { @@ -254,4 +254,41 @@ describe("emitting models", () => { $ref: "RecordInt32.json", }); }); + + describe("default values", () => { + it("specify default value on enum property", async () => { + const res = await emitSchema( + ` + model Foo { + optionalEnum?: MyEnum = MyEnum.a; + }; + + enum MyEnum { + a: "a-value", + b, + } + ` + ); + + deepStrictEqual(res["Foo.json"].properties.optionalEnum, { + $ref: "MyEnum.json", + default: "a-value", + }); + }); + + it("specify default value on string property", async () => { + const res = await emitSchema( + ` + model Foo { + optional?: string = "abc"; + } + ` + ); + + deepStrictEqual(res["Foo.json"].properties.optional, { + type: "string", + default: "abc", + }); + }); + }); }); From e95298643ee04c928765239fa669404183fa4c59 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 9 Nov 2023 10:20:23 -0800 Subject: [PATCH 2/5] changelog --- .../feature-json-schema-default_2023-11-09-18-20.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 common/changes/@typespec/json-schema/feature-json-schema-default_2023-11-09-18-20.json diff --git a/common/changes/@typespec/json-schema/feature-json-schema-default_2023-11-09-18-20.json b/common/changes/@typespec/json-schema/feature-json-schema-default_2023-11-09-18-20.json new file mode 100644 index 0000000000..9fd19b19fa --- /dev/null +++ b/common/changes/@typespec/json-schema/feature-json-schema-default_2023-11-09-18-20.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@typespec/json-schema", + "comment": "Add support for simple literal default on model properties", + "type": "none" + } + ], + "packageName": "@typespec/json-schema" +} \ No newline at end of file From 7f437c567a2867c50aba5f8956710c1b81999d71 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Wed, 15 Nov 2023 11:52:28 -0800 Subject: [PATCH 3/5] Add more test cases --- packages/json-schema/test/models.test.ts | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/json-schema/test/models.test.ts b/packages/json-schema/test/models.test.ts index a057d58cfc..c2c564137c 100644 --- a/packages/json-schema/test/models.test.ts +++ b/packages/json-schema/test/models.test.ts @@ -290,5 +290,37 @@ describe("emitting models", () => { default: "abc", }); }); + + it("specify default value on numeric property", async () => { + const res = await emitSchema( + ` + model Foo { + optional?: int32 = 123; + } + ` + ); + + deepStrictEqual(res["Foo.json"].properties.optional, { + type: "number", + minimum: -2147483648, + maximum: 2147483647, + default: 123, + }); + }); + + it("specify default value on boolean property", async () => { + const res = await emitSchema( + ` + model Foo { + optional?: int32 = true; + } + ` + ); + + deepStrictEqual(res["Foo.json"].properties.optional, { + type: "boolean", + default: true, + }); + }); }); }); From 9affd50a78e8b61d6ec9de78ee5167f38125bea2 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Wed, 15 Nov 2023 12:36:12 -0800 Subject: [PATCH 4/5] fix --- packages/json-schema/test/models.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/json-schema/test/models.test.ts b/packages/json-schema/test/models.test.ts index c2c564137c..1f11417f4c 100644 --- a/packages/json-schema/test/models.test.ts +++ b/packages/json-schema/test/models.test.ts @@ -301,7 +301,7 @@ describe("emitting models", () => { ); deepStrictEqual(res["Foo.json"].properties.optional, { - type: "number", + type: "integer", minimum: -2147483648, maximum: 2147483647, default: 123, From e526f7abea315458f74d805748a0036685b59e12 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Wed, 15 Nov 2023 12:51:50 -0800 Subject: [PATCH 5/5] fix --- packages/json-schema/test/models.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/json-schema/test/models.test.ts b/packages/json-schema/test/models.test.ts index 1f11417f4c..3fdb6de85d 100644 --- a/packages/json-schema/test/models.test.ts +++ b/packages/json-schema/test/models.test.ts @@ -312,7 +312,7 @@ describe("emitting models", () => { const res = await emitSchema( ` model Foo { - optional?: int32 = true; + optional?: boolean = true; } ` );