diff --git a/packages/typespec-test/test/openai_modular/generated/typespec-ts/review/openai_modular.api.md b/packages/typespec-test/test/openai_modular/generated/typespec-ts/review/openai_modular.api.md index 2261d01972..2d95f45d2c 100644 --- a/packages/typespec-test/test/openai_modular/generated/typespec-ts/review/openai_modular.api.md +++ b/packages/typespec-test/test/openai_modular/generated/typespec-ts/review/openai_modular.api.md @@ -25,6 +25,19 @@ export interface AzureChatExtensionsMessageContext { // @public export type AzureChatExtensionType = string; +// @public +export interface AzureCognitiveSearchIndexFieldMappingOptions { + contentFieldNames?: string[]; + contentFieldSeparator?: string; + filepathField?: string; + titleField?: string; + urlField?: string; + vectorFields?: string[]; +} + +// @public +export type AzureCognitiveSearchQueryType = string; + // @public export type AzureOpenAIOperationState = string; diff --git a/packages/typespec-test/test/openai_modular/generated/typespec-ts/src/index.ts b/packages/typespec-test/test/openai_modular/generated/typespec-ts/src/index.ts index e96df752a9..279f8050cc 100644 --- a/packages/typespec-test/test/openai_modular/generated/typespec-ts/src/index.ts +++ b/packages/typespec-test/test/openai_modular/generated/typespec-ts/src/index.ts @@ -37,6 +37,8 @@ export { ImageGenerationOptions, ImageSize, ImageGenerationResponseFormat, + AzureCognitiveSearchIndexFieldMappingOptions, + AzureCognitiveSearchQueryType, GetEmbeddingsOptions, GetCompletionsOptions, GetChatCompletionsOptions, diff --git a/packages/typespec-test/test/openai_modular/generated/typespec-ts/src/models/index.ts b/packages/typespec-test/test/openai_modular/generated/typespec-ts/src/models/index.ts index 644fd24446..e9afbfa73e 100644 --- a/packages/typespec-test/test/openai_modular/generated/typespec-ts/src/models/index.ts +++ b/packages/typespec-test/test/openai_modular/generated/typespec-ts/src/models/index.ts @@ -36,6 +36,8 @@ export { ImageGenerationOptions, ImageSize, ImageGenerationResponseFormat, + AzureCognitiveSearchIndexFieldMappingOptions, + AzureCognitiveSearchQueryType, } from "./models.js"; export { GetEmbeddingsOptions, diff --git a/packages/typespec-test/test/openai_modular/generated/typespec-ts/src/models/models.ts b/packages/typespec-test/test/openai_modular/generated/typespec-ts/src/models/models.ts index b21a24de5f..2079ba4bc8 100644 --- a/packages/typespec-test/test/openai_modular/generated/typespec-ts/src/models/models.ts +++ b/packages/typespec-test/test/openai_modular/generated/typespec-ts/src/models/models.ts @@ -610,3 +610,23 @@ export type ImageSize = string; /** The format in which the generated images are returned. */ /** "url", "b64_json" */ export type ImageGenerationResponseFormat = string; + +/** Optional settings to control how fields are processed when using a configured Azure Cognitive Search resource. */ +export interface AzureCognitiveSearchIndexFieldMappingOptions { + /** The name of the index field to use as a title. */ + titleField?: string; + /** The name of the index field to use as a URL. */ + urlField?: string; + /** The name of the index field to use as a filepath. */ + filepathField?: string; + /** The names of index fields that should be treated as content. */ + contentFieldNames?: string[]; + /** The separator pattern that content fields should use. */ + contentFieldSeparator?: string; + /** The names of fields that represent vector data. */ + vectorFields?: string[]; +} + +/** The type of Azure Cognitive Search retrieval query that should be executed when using it as an Azure OpenAI chat extension. */ +/** "simple", "semantic", "vector", "vectorSimpleHybrid", "vectorSemanticHybrid" */ +export type AzureCognitiveSearchQueryType = string; diff --git a/packages/typespec-test/test/openai_modular/spec/client.tsp b/packages/typespec-test/test/openai_modular/spec/client.tsp index 6a68b5b38d..3e4fbd2e0c 100644 --- a/packages/typespec-test/test/openai_modular/spec/client.tsp +++ b/packages/typespec-test/test/openai_modular/spec/client.tsp @@ -5,15 +5,33 @@ using Azure.ClientGenerator.Core; // Azure-specific long-running operations should be treated as implementation details that are wrapped into // appropriately merged public surface. -@@internal(Azure.OpenAI.beginAzureBatchImageGeneration); -@@internal(Azure.OpenAI.getAzureBatchImageGenerationOperationStatus); +@@access(Azure.OpenAI.beginAzureBatchImageGeneration, Access.internal); +@@access(Azure.OpenAI.getAzureBatchImageGenerationOperationStatus, + Access.internal +); // Azure-specific Chat Completions with extensions should be handled by clients as a conditional selection within the // shared Chat Completions route, with the selection gated by the presence or non-presence of additional child // configuration options on the request payload options model. -@@internal(Azure.OpenAI.getChatCompletionsWithAzureExtensions); +@@access(Azure.OpenAI.getChatCompletionsWithAzureExtensions, Access.internal); // Some models from routes with suppressed visibility are still desired for custom public surface. -@@include(Azure.OpenAI.ImageGenerationOptions); -@@include(Azure.OpenAI.ImageLocation); -@@include(Azure.OpenAI.ImageGenerations); +@@access(Azure.OpenAI.ImageGenerationOptions, Access.public); +@@access(Azure.OpenAI.ImageLocation, Access.public); +@@access(Azure.OpenAI.ImageGenerations, Access.public); +@@access(Azure.OpenAI.ImageSize, Access.public); + +@@access(Azure.OpenAI.AzureCognitiveSearchIndexFieldMappingOptions, + Access.public +); +@@usage(Azure.OpenAI.AzureCognitiveSearchIndexFieldMappingOptions, Usage.input); + +@@access(Azure.OpenAI.AzureCognitiveSearchQueryType, Access.public); +@@usage(Azure.OpenAI.AzureCognitiveSearchQueryType, Usage.input); + +@@access(Azure.OpenAI.AzureCognitiveSearchChatExtensionConfiguration, + Access.public +); +@@usage(Azure.OpenAI.AzureCognitiveSearchChatExtensionConfiguration, + Usage.input +); \ No newline at end of file diff --git a/packages/typespec-ts/src/modular/buildCodeModel.ts b/packages/typespec-ts/src/modular/buildCodeModel.ts index 87e843b28c..79483ca692 100644 --- a/packages/typespec-ts/src/modular/buildCodeModel.ts +++ b/packages/typespec-ts/src/modular/buildCodeModel.ts @@ -1,6 +1,5 @@ import { getPagedResult, isFixed } from "@azure-tools/typespec-azure-core"; import { - EnumMember, Enum, getDoc, getFriendlyName, @@ -66,7 +65,8 @@ import { getAllModels, SdkBuiltInType, getSdkBuiltInType, - getClientType + getClientType, + SdkEnumValueType } from "@azure-tools/typespec-client-generator-core"; import { getResourceOperation } from "@typespec/rest"; import { @@ -1028,12 +1028,13 @@ function emitEnum(program: Program, type: Enum): Record { values: enumValues, isFixed: isFixed(program, type) }; - function enumMemberType(member: EnumMember) { - if (typeof member.value === "number") { - return intOrFloat(member.value); - } - return "string"; +} + +function enumMemberType(member: SdkEnumValueType) { + if (typeof member.value === "number") { + return "number"; } + return "string"; } function constantType(value: any, valueType: string): Record { @@ -1295,7 +1296,7 @@ function emitUnion(context: SdkContext, type: Union): Record { internal: true, type: sdkType.kind, valueType: emitSimpleType(context, sdkType.valueType as SdkBuiltInType), - values: sdkType.values.map((x) => emitEnumMember(x)), + values: sdkType.values.map((x) => emitEnumMember(context, x)), isFixed: sdkType.isFixed, xmlMetadata: {} }; @@ -1311,7 +1312,7 @@ function emitUnion(context: SdkContext, type: Union): Record { valueType: emitSimpleType(context, sdkType as SdkBuiltInType), values: nonNullOptions .map((x) => getClientType(context, x)) - .map((x) => emitEnumMember(x)), + .map((x) => emitEnumMember(context, x)), isFixed: true, xmlMetadata: {} }; @@ -1340,11 +1341,17 @@ function getNonNullOptions(type: Union) { .filter((t) => !isNullType(t)); } -function emitEnumMember(type: any): Record { +function emitEnumMember(context: SdkContext, member: any): Record { + const value = member.value ?? member.name; return { - name: type.name ? enumName(type.name) : undefined, - value: type.value, - description: type.doc + type: "constant", + valueType: { + type: enumMemberType(member) + }, + value, + name: member.name ? enumName(member.name) : undefined, + description: getDoc(context.program, member), + isConstant: true }; } @@ -1399,6 +1406,8 @@ function emitType(context: SdkContext, type: EmitterType): Record { return {}; case "Enum": return emitEnum(context.program, type); + case "EnumMember": + return emitEnumMember(context, type); default: throw Error(`Not supported ${type.kind}`); } diff --git a/packages/typespec-ts/test/modularUnit/modelsGenerator.spec.ts b/packages/typespec-ts/test/modularUnit/modelsGenerator.spec.ts index 7cc1ebe44d..0220e4b622 100644 --- a/packages/typespec-ts/test/modularUnit/modelsGenerator.spec.ts +++ b/packages/typespec-ts/test/modularUnit/modelsGenerator.spec.ts @@ -79,6 +79,26 @@ describe("model property type", () => { const typeScriptType = `"foo"`; await verifyModularPropertyType(tspType, typeScriptType); }); + + it("should handle enum member", async () => { + const tspTypeDefinition = ` + @doc("Translation Language Values") + enum TranslationLanguageValues { + @doc("English descriptions") + English: "English", + @doc("Chinese descriptions") + Chinese: "Chinese", + }`; + const tspType = "TranslationLanguageValues.English"; + const typeScriptType = `"English"`; + await verifyModularPropertyType( + tspType, + typeScriptType, + { + additionalTypeSpecDefinition: tspTypeDefinition + } + ); + }); }); describe("modular encode test for property type datetime", () => { diff --git a/packages/typespec-ts/test/modularUnit/type.spec.ts b/packages/typespec-ts/test/modularUnit/type.spec.ts index a23faca59a..9eb5e40137 100644 --- a/packages/typespec-ts/test/modularUnit/type.spec.ts +++ b/packages/typespec-ts/test/modularUnit/type.spec.ts @@ -18,6 +18,27 @@ describe("model type", () => { export interface Test { color: "red" | "blue"; }` + ); + }); + + it("string enum member", async () => { + const modelFile = await emitModularModelsFromTypeSpec(` + enum Color { + Red: "red", + Blue: "blue" + } + model Test { + color: Color.Red; + } + op read(@body body: Test): void; + `); + assert.ok(modelFile); + assertEqualContent( + modelFile!.getFullText()!, + ` + export interface Test { + color: "red"; + }` ); }); @@ -74,6 +95,27 @@ describe("model type", () => { ); }); + it("number enum member", async () => { + const modelFile = await emitModularModelsFromTypeSpec(` + enum Color { + Color1: 1, + Color2: 2 + } + model Test { + color: Color.Color1; + } + op read(@body body: Test): void; + `); + assert.ok(modelFile); + assertEqualContent( + modelFile!.getFullText()!, + ` + export interface Test { + color: 1; + }` + ); + }); + it("nullable numeric literal", async () => { const modelFile = await emitModularModelsFromTypeSpec(` model Test { diff --git a/packages/typespec-ts/test/modularUnit/typeHelpers.spec.ts b/packages/typespec-ts/test/modularUnit/typeHelpers.spec.ts index 5f2cd36a61..50196b2965 100644 --- a/packages/typespec-ts/test/modularUnit/typeHelpers.spec.ts +++ b/packages/typespec-ts/test/modularUnit/typeHelpers.spec.ts @@ -453,6 +453,41 @@ describe("typeHelpers", () => { expect(result.originModule).to.equal("models.js"); }); + it("should handle enum member type as string literal", () => { + const type: Type = { + type: "constant", + name: "A_VAL", + valueType: { type: "string" }, + value: "A_VAL" + }; + const result = getType(type); + expect(result.name).to.equal('"A_VAL"'); + }); + + + it("should handle enum member type as number literal", () => { + const type: Type = { + type: "constant", + name: "1", + valueType: { type: "integer" }, + value: "1" + }; + const result = getType(type); + expect(result.name).to.equal('1'); + }); + + + it("should handle enum member type as number literal", () => { + const type: Type = { + type: "constant", + name: "true", + valueType: { type: "boolean" }, + value: "true" + }; + const result = getType(type); + expect(result.name).to.equal('true'); + }); + it("should handle float type", () => { const type: Type = { type: "float" }; const result = getType(type); diff --git a/packages/typespec-ts/test/unit/modelsGenerator.spec.ts b/packages/typespec-ts/test/unit/modelsGenerator.spec.ts index 3ae3e6194b..7df08918ba 100644 --- a/packages/typespec-ts/test/unit/modelsGenerator.spec.ts +++ b/packages/typespec-ts/test/unit/modelsGenerator.spec.ts @@ -283,6 +283,29 @@ describe("Input/output model type", () => { true ); }); + + it("should handle enum member", async () => { + const tspTypeDefinition = ` + #suppress "@azure-tools/typespec-azure-core/use-extensible-enum" "for test" + @fixed + @doc("Translation Language Values") + enum TranslationLanguageValues { + @doc("English descriptions") + English, + @doc("Chinese descriptions") + Chinese, + }`; + const tspType = "TranslationLanguageValues.English"; + const typeScriptType = `"English"`; + await verifyPropertyType( + tspType, + typeScriptType, + { + additionalTypeSpecDefinition: tspTypeDefinition + }, + true + ); + }); }); it("should handle type_literals:string -> string_literals", async () => { diff --git a/packages/typespec-ts/test/unit/transform/transformSchemas.spec.ts b/packages/typespec-ts/test/unit/transform/transformSchemas.spec.ts index b5b7934723..edf33cd5fe 100644 --- a/packages/typespec-ts/test/unit/transform/transformSchemas.spec.ts +++ b/packages/typespec-ts/test/unit/transform/transformSchemas.spec.ts @@ -267,6 +267,38 @@ describe("#transformSchemas", () => { } as any); }); + it("generate enum member", async () => { + const schemaOutput = await emitSchemasFromTypeSpec( + ` + @doc("Translation Language Values") + enum TranslationLanguageValues { + @doc("English descriptions") + English, + @doc("Chinese descriptions") + Chinese, + } + model Test { + prop: TranslationLanguageValues.English; + } + @route("/models") + @get + op getModel(@body input: Test): Test; + `, + true + ); + assert.isNotNull(schemaOutput); + const first = schemaOutput?.[0] as ObjectSchema; + const property = first.properties![`"prop"`]; + assert.isNotNull(property); + assert.deepEqual(property, { + type: '"English"', + description: undefined, + isConstant: true, + required: true, + usage: ["input", "output"] + } as any); + }); + it("generate union model", async () => { const schemaOutput = await emitSchemasFromTypeSpec( `