Skip to content
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
35 changes: 32 additions & 3 deletions packages/typespec-ts/src/modular/helpers/operationHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
SdkBodyParameter,
SdkClientType,
SdkConstantType,
SdkEnumType,
SdkHttpOperation,
SdkHttpParameter,
SdkLroPagingServiceMethod,
Expand All @@ -76,6 +77,7 @@ import {
} from "@azure-tools/typespec-client-generator-core";
import { isMetadata } from "@typespec/http";
import { useContext } from "../../contextManager.js";
import { isExtensibleEnum } from "../type-expressions/get-enum-expression.js";

export function getSendPrivateFunction(
dpgContext: SdkContext,
Expand Down Expand Up @@ -1171,8 +1173,8 @@ function getEncodeForModelProperty(
property: SdkModelPropertyType
): string | undefined {
if (property.encode && property.type.kind === "array") {
// Only arrays of string type can have collectionFormat encoding
if (property.type.valueType.kind !== "string") {
// Only arrays of string type or string-based enum type can have collectionFormat encoding
if (!isStringEncodableArrayValueType(property.type.valueType)) {
reportDiagnostic(context.program, {
code: "un-supported-array-encoding",
format: {
Expand All @@ -1197,6 +1199,25 @@ function getEncodeForModelProperty(
return getEncodeForType(property.type);
}

/**
* Checks if an array value type is string-encodable for collection format encoding.
* This includes both string type and string-based enum types.
*/
function isStringEncodableArrayValueType(valueType: SdkType): boolean {
// Direct string type
if (valueType.kind === "string") {
return true;
}
// String-based enum type
if (
valueType.kind === "enum" &&
(valueType as SdkEnumType).valueType.kind === "string"
) {
return true;
}
Comment thread
JialinHuang803 marked this conversation as resolved.
return false;
}

function getSerializationExpressionForFlatten(
context: SdkContext,
property: SdkModelPropertyType,
Expand Down Expand Up @@ -1589,7 +1610,15 @@ export function deserializeResponseValue(
isTypeNullable(type) || getOptionalForType(type) || !required
? `${restValue} === null || ${restValue} === undefined ? ${restValue}: `
: "";
return `${optionalPrefixForString}${parseHelper}(${restValue})`;
if (
type.valueType.kind === "enum" &&
!isExtensibleEnum(context, type.valueType)
) {
// Special handling for non-extensible enums to cast the result to the correct type
return `${optionalPrefixForString}${parseHelper}(${restValue}) as ${getTypeExpression(context, type)}`;
} else {
return `${optionalPrefixForString}${parseHelper}(${restValue})`;
}
}
}
return `${prefix}.map((${varName}: any) => { return ${elementNullOrUndefinedPrefix}${deserializeResponseValue(context, type.valueType, varName, true, getEncodeForType(type.valueType), recursionDepth + 1)}})`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,137 @@ export function nestedWidgetDeserializer(item: any): NestedWidget {
};
}
```

# Should generate collection format serializers for string-based enum array properties

## TypeSpec

```tsp
enum Color {
Red: "red",
Blue: "blue",
Green: "green"
}

union ColorsUnion {
string,
red: "red";
blue: "blue";
green: "green";
}

alias Type = "x" | "y" | "z";

model Widget {
@encode(ArrayEncoding.commaDelimited)
requiredCsvColors: Color[];

@encode(ArrayEncoding.pipeDelimited)
optionalPipeColors?: ColorsUnion[];

@encode(ArrayEncoding.spaceDelimited)
requiredSpaceTypes: ("a" | "b")[];

@encode(ArrayEncoding.newlineDelimited)
optionalSpaceTypes?: Type[];
}

@route("/widgets")
interface WidgetOperations {
@post
createWidget(@body widget: Widget): Widget;
}
```

This is the tspconfig.yaml.

```yaml
experimental-extensible-enums: true
```

## Models

```ts models
import { buildCsvCollection } from "../static-helpers/serialization/build-csv-collection.js";
import { buildNewlineCollection } from "../static-helpers/serialization/build-newline-collection.js";
import { buildPipeCollection } from "../static-helpers/serialization/build-pipe-collection.js";
import { buildSsvCollection } from "../static-helpers/serialization/build-ssv-collection.js";
import { parseCsvCollection } from "../static-helpers/serialization/parse-csv-collection.js";
import { parseNewlineCollection } from "../static-helpers/serialization/parse-newline-collection.js";
import { parsePipeCollection } from "../static-helpers/serialization/parse-pipe-collection.js";
import { parseSsvCollection } from "../static-helpers/serialization/parse-ssv-collection.js";

/**
* This file contains only generated model types and their (de)serializers.
* Disable the following rules for internal models with '_' prefix and deserializers which require 'any' for raw JSON input.
*/
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/** model interface Widget */
export interface Widget {
requiredCsvColors: Color[];
optionalPipeColors?: ColorsUnion[];
requiredSpaceTypes: ("a" | "b")[];
optionalSpaceTypes?: ("x" | "y" | "z")[];
}

export function widgetSerializer(item: Widget): any {
return {
requiredCsvColors: buildCsvCollection(
item["requiredCsvColors"].map((p: any) => {
return p;
}),
),
optionalPipeColors: !item["optionalPipeColors"]
? item["optionalPipeColors"]
: buildPipeCollection(
item["optionalPipeColors"].map((p: any) => {
return p;
}),
),
requiredSpaceTypes: buildSsvCollection(
item["requiredSpaceTypes"].map((p: any) => {
return p;
}),
),
optionalSpaceTypes: !item["optionalSpaceTypes"]
? item["optionalSpaceTypes"]
: buildNewlineCollection(
item["optionalSpaceTypes"].map((p: any) => {
return p;
}),
),
};
}

export function widgetDeserializer(item: any): Widget {
return {
requiredCsvColors: parseCsvCollection(item["requiredCsvColors"]) as Color[],
optionalPipeColors:
item["optionalPipeColors"] === null || item["optionalPipeColors"] === undefined
? item["optionalPipeColors"]
: parsePipeCollection(item["optionalPipeColors"]),
requiredSpaceTypes: parseSsvCollection(item["requiredSpaceTypes"]) as ("a" | "b")[],
optionalSpaceTypes:
item["optionalSpaceTypes"] === null || item["optionalSpaceTypes"] === undefined
? item["optionalSpaceTypes"]
: (parseNewlineCollection(item["optionalSpaceTypes"]) as ("x" | "y" | "z")[]),
};
}

/** Type of Color */
export type Color = "red" | "blue" | "green";

/** Known values of {@link ColorsUnion} that the service accepts. */
export enum KnownColorsUnion {
/** red */
Red = "red",
/** blue */
Blue = "blue",
/** green */
Green = "green",
}

/** Type of ColorsUnion */
export type ColorsUnion = string;
```
Loading