Skip to content

Commit

Permalink
support-enum-member-in-modular (#2092)
Browse files Browse the repository at this point in the history
* support-enum-member-in-modular

* Update packages/typespec-ts/src/modular/modularCodeModel.ts

* add unit test for enum member type

* add ut
  • Loading branch information
qiaozha committed Oct 31, 2023
1 parent 3472e31 commit 9d398e8
Show file tree
Hide file tree
Showing 11 changed files with 235 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export {
ImageGenerationOptions,
ImageSize,
ImageGenerationResponseFormat,
AzureCognitiveSearchIndexFieldMappingOptions,
AzureCognitiveSearchQueryType,
GetEmbeddingsOptions,
GetCompletionsOptions,
GetChatCompletionsOptions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export {
ImageGenerationOptions,
ImageSize,
ImageGenerationResponseFormat,
AzureCognitiveSearchIndexFieldMappingOptions,
AzureCognitiveSearchQueryType,
} from "./models.js";
export {
GetEmbeddingsOptions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
30 changes: 24 additions & 6 deletions packages/typespec-test/test/openai_modular/spec/client.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
35 changes: 22 additions & 13 deletions packages/typespec-ts/src/modular/buildCodeModel.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { getPagedResult, isFixed } from "@azure-tools/typespec-azure-core";
import {
EnumMember,
Enum,
getDoc,
getFriendlyName,
Expand Down Expand Up @@ -66,7 +65,8 @@ import {
getAllModels,
SdkBuiltInType,
getSdkBuiltInType,
getClientType
getClientType,
SdkEnumValueType
} from "@azure-tools/typespec-client-generator-core";
import { getResourceOperation } from "@typespec/rest";
import {
Expand Down Expand Up @@ -1028,12 +1028,13 @@ function emitEnum(program: Program, type: Enum): Record<string, any> {
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<string, any> {
Expand Down Expand Up @@ -1295,7 +1296,7 @@ function emitUnion(context: SdkContext, type: Union): Record<string, any> {
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: {}
};
Expand All @@ -1311,7 +1312,7 @@ function emitUnion(context: SdkContext, type: Union): Record<string, any> {
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: {}
};
Expand Down Expand Up @@ -1340,11 +1341,17 @@ function getNonNullOptions(type: Union) {
.filter((t) => !isNullType(t));
}

function emitEnumMember(type: any): Record<string, any> {
function emitEnumMember(context: SdkContext, member: any): Record<string, any> {
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
};
}

Expand Down Expand Up @@ -1399,6 +1406,8 @@ function emitType(context: SdkContext, type: EmitterType): Record<string, any> {
return {};
case "Enum":
return emitEnum(context.program, type);
case "EnumMember":
return emitEnumMember(context, type);
default:
throw Error(`Not supported ${type.kind}`);
}
Expand Down
20 changes: 20 additions & 0 deletions packages/typespec-ts/test/modularUnit/modelsGenerator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
42 changes: 42 additions & 0 deletions packages/typespec-ts/test/modularUnit/type.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}`
);
});

Expand Down Expand Up @@ -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 {
Expand Down
35 changes: 35 additions & 0 deletions packages/typespec-ts/test/modularUnit/typeHelpers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
23 changes: 23 additions & 0 deletions packages/typespec-ts/test/unit/modelsGenerator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
32 changes: 32 additions & 0 deletions packages/typespec-ts/test/unit/transform/transformSchemas.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
`
Expand Down

0 comments on commit 9d398e8

Please sign in to comment.