diff --git a/.chronus/changes/fix_enum_default_value-2024-8-30-17-30-54.md b/.chronus/changes/fix_enum_default_value-2024-8-30-17-30-54.md new file mode 100644 index 000000000..725dd9feb --- /dev/null +++ b/.chronus/changes/fix_enum_default_value-2024-8-30-17-30-54.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@azure-tools/typespec-client-generator-core" +--- + +support value type for client default value \ No newline at end of file diff --git a/packages/typespec-client-generator-core/src/interfaces.ts b/packages/typespec-client-generator-core/src/interfaces.ts index c0f03f3f3..cd789fb20 100644 --- a/packages/typespec-client-generator-core/src/interfaces.ts +++ b/packages/typespec-client-generator-core/src/interfaces.ts @@ -390,7 +390,7 @@ export interface SdkModelPropertyTypeBase extends DecoratedType { summary?: string; apiVersions: string[]; onClient: boolean; - clientDefaultValue?: any; + clientDefaultValue?: unknown; isApiVersionParam: boolean; optional: boolean; crossLanguageDefinitionId: string; diff --git a/packages/typespec-client-generator-core/src/internal-utils.ts b/packages/typespec-client-generator-core/src/internal-utils.ts index 5f3094fbc..277b3a875 100644 --- a/packages/typespec-client-generator-core/src/internal-utils.ts +++ b/packages/typespec-client-generator-core/src/internal-utils.ts @@ -115,7 +115,7 @@ export function updateWithApiVersionInformation( namespace?: Namespace | Interface, ): { isApiVersionParam: boolean; - clientDefaultValue?: unknown; + clientDefaultValue?: string; } { const isApiVersionParam = isApiVersion(context, type); return { @@ -569,3 +569,30 @@ export function isOnClient( ) ); } + +export function getValueTypeValue( + value: Value, +): string | boolean | null | number | Array | object | undefined { + switch (value.valueKind) { + case "ArrayValue": + return value.values.map((x) => getValueTypeValue(x)); + case "BooleanValue": + case "StringValue": + case "NullValue": + return value.value; + case "NumericValue": + return value.value.asNumber(); + case "EnumValue": + return value.value.value ?? value.value.name; + case "ObjectValue": + return Object.fromEntries( + [...value.properties.keys()].map((x) => [ + x, + getValueTypeValue(value.properties.get(x)!.value), + ]), + ); + case "ScalarValue": + // TODO: handle scalar value + return undefined; + } +} diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index abf28b5a7..7078a0c84 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -64,6 +64,7 @@ import { getHashForType, getLocationOfOperation, getTypeDecorators, + getValueTypeValue, isNeverOrVoidType, isSubscriptionId, updateWithApiVersionInformation, @@ -531,8 +532,8 @@ function getEndpointTypeFromSingleServer< if (sdkParam.kind === "path") { templateArguments.push(sdkParam); sdkParam.onClient = true; - if (param.defaultValue && "value" in param.defaultValue) { - sdkParam.clientDefaultValue = param.defaultValue.value; + if (param.defaultValue) { + sdkParam.clientDefaultValue = getValueTypeValue(param.defaultValue); } const apiVersionInfo = updateWithApiVersionInformation(context, param, client.__raw.type); sdkParam.isApiVersionParam = apiVersionInfo.isApiVersionParam; diff --git a/packages/typespec-client-generator-core/test/internal-utils.test.ts b/packages/typespec-client-generator-core/test/internal-utils.test.ts index eb58e8528..26aa59e3c 100644 --- a/packages/typespec-client-generator-core/test/internal-utils.test.ts +++ b/packages/typespec-client-generator-core/test/internal-utils.test.ts @@ -1,5 +1,7 @@ -import { ok, strictEqual } from "assert"; +import { Model } from "@typespec/compiler"; +import { deepStrictEqual, ok, strictEqual } from "assert"; import { beforeEach, describe, it } from "vitest"; +import { getValueTypeValue } from "../src/internal-utils.js"; import { listSubClients } from "../src/public-utils.js"; import { SdkTestRunner, createSdkTestRunner } from "./test-host.js"; @@ -156,4 +158,150 @@ describe("typespec-client-generator-core: internal-utils", () => { strictEqual(subClients[6].name, "AABGroup2"); }); }); + + describe("getValueTypeValue", () => { + it("string default value", async () => { + const { Test } = (await runner.compile(` + @service({}) + namespace My.Service; + + @test + model Test { + prop: string = "default"; + } + `)) as { Test: Model }; + + strictEqual(getValueTypeValue(Test.properties.get("prop")?.defaultValue!), "default"); + }); + + it("boolean default value", async () => { + const { Test } = (await runner.compile(` + @service({}) + namespace My.Service; + + @test + model Test { + prop: boolean = false; + } + `)) as { Test: Model }; + + strictEqual(getValueTypeValue(Test.properties.get("prop")?.defaultValue!), false); + }); + + it("null default value", async () => { + const { Test } = (await runner.compile(` + @service({}) + namespace My.Service; + + @test + model Test { + prop: boolean | null = null; + } + `)) as { Test: Model }; + + strictEqual(getValueTypeValue(Test.properties.get("prop")?.defaultValue!), null); + }); + + it("numeric int default value", async () => { + const { Test } = (await runner.compile(` + @service({}) + namespace My.Service; + + @test + model Test { + prop: int32 = 1; + } + `)) as { Test: Model }; + + strictEqual(getValueTypeValue(Test.properties.get("prop")?.defaultValue!), 1); + }); + + it("numeric float default value", async () => { + const { Test } = (await runner.compile(` + @service({}) + namespace My.Service; + + @test + model Test { + prop: float32 = 1.234; + } + `)) as { Test: Model }; + + strictEqual(getValueTypeValue(Test.properties.get("prop")?.defaultValue!), 1.234); + }); + + it("enum member default value", async () => { + const { Test } = (await runner.compile(` + @service({}) + namespace My.Service; + + @test + model Test { + prop: MyEnum = MyEnum.A; + } + + enum MyEnum { + A: "A", + B: "B", + } + `)) as { Test: Model }; + + strictEqual(getValueTypeValue(Test.properties.get("prop")?.defaultValue!), "A"); + }); + + it("enum member without value default value", async () => { + const { Test } = (await runner.compile(` + @service({}) + namespace My.Service; + + @test + model Test { + prop: MyEnum = MyEnum.A; + } + + enum MyEnum { + A, + B, + } + `)) as { Test: Model }; + + strictEqual(getValueTypeValue(Test.properties.get("prop")?.defaultValue!), "A"); + }); + + it("array default value", async () => { + const { Test } = (await runner.compile(` + @service({}) + namespace My.Service; + + @test + model Test { + prop: string[] = #["a", "b"]; + } + `)) as { Test: Model }; + + deepStrictEqual(getValueTypeValue(Test.properties.get("prop")?.defaultValue!), ["a", "b"]); + }); + + it("object default value", async () => { + const { Test } = (await runner.compile(` + @service({}) + namespace My.Service; + + @test + model Test { + prop: Point = #{ x: 0, y: 0 }; + } + + model Point { + x: int32; + y: int32; + } + `)) as { Test: Model }; + + deepStrictEqual(getValueTypeValue(Test.properties.get("prop")?.defaultValue!), { + x: 0, + y: 0, + }); + }); + }); }); diff --git a/packages/typespec-client-generator-core/test/packages/client.test.ts b/packages/typespec-client-generator-core/test/packages/client.test.ts index dd1c9e4a8..d7f214011 100644 --- a/packages/typespec-client-generator-core/test/packages/client.test.ts +++ b/packages/typespec-client-generator-core/test/packages/client.test.ts @@ -942,4 +942,48 @@ describe("typespec-client-generator-core: package", () => { client.initialization.properties.find((x) => x.isApiVersionParam), ); }); + + it("endpoint template argument with default value of enum member", async () => { + await runner.compile(` + @server( + "{endpoint}/client/structure/{client}", + "", + { + @doc("Need to be set as 'http://localhost:3000' in client.") + endpoint: url, + + @doc("Need to be set as 'default', 'multi-client', 'renamed-operation', 'two-operation-group' in client.") + client: ClientType = ClientType.Default, + } + ) + @service({}) + namespace My.Service; + + enum ClientType { + Default: "default", + MultiClient: "multi-client", + RenamedOperation: "renamed-operation", + TwoOperationGroup: "two-operation-group", + ClientOperationGroup: "client-operation-group", + } + `); + const sdkPackage = runner.context.sdkPackage; + strictEqual(sdkPackage.clients.length, 1); + const client = sdkPackage.clients[0]; + + strictEqual(client.initialization.properties.length, 1); + const parameter = client.initialization.properties[0]; + strictEqual(parameter.name, "endpoint"); + strictEqual(parameter.type.kind, "union"); + + const templateArg = parameter.type.variantTypes[0]; + strictEqual(templateArg.kind, "endpoint"); + strictEqual(templateArg.templateArguments.length, 2); + const clientTemplateArg = templateArg.templateArguments[1]; + strictEqual(clientTemplateArg.kind, "path"); + strictEqual(clientTemplateArg.name, "client"); + strictEqual(clientTemplateArg.optional, false); + strictEqual(clientTemplateArg.onClient, true); + strictEqual(clientTemplateArg.clientDefaultValue, "default"); + }); });