Skip to content

support-enum-member-in-modular #2092

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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