Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
152144a
Merge remote-tracking branch 'origin/main' into support-additional-pr…
MaryGao Jan 8, 2025
c272f64
Add the UTs for additionalProperties
MaryGao Jan 8, 2025
6cbbee3
Update the additionalProperties support in serializer
MaryGao Jan 8, 2025
7757a42
Merge branch 'main' into support-additional-properties
MaryGao Jan 9, 2025
0da5b61
Merge branch 'main' into support-additional-properties
MaryGao Mar 17, 2025
2565bfa
Support the additional bag generation
MaryGao Mar 17, 2025
7616479
Format code
MaryGao Mar 17, 2025
e04efd8
Update the deserialization logic
MaryGao Mar 18, 2025
3e16af3
Format codes
MaryGao Mar 18, 2025
b44f640
Enable the testing for unbranded cases
MaryGao Mar 18, 2025
5eaa2ac
format
v-jiaodi Mar 18, 2025
ac96594
Resolve naming conflicts
MaryGao Mar 18, 2025
29442d2
Merge branch 'support-additional-properties' of https://github.com/MA…
MaryGao Mar 18, 2025
9b99853
Fix the UT issue
MaryGao Mar 18, 2025
888820b
regen code
v-jiaodi Mar 18, 2025
f6a941f
Merge branch 'main' into support-additional-properties
MaryGao Mar 20, 2025
492100b
Merge remote-tracking branch 'origin/main' into support-additional-pr…
MaryGao Apr 9, 2025
1ad50e8
Resolve conflicts to the latest main
MaryGao Apr 9, 2025
2b1473b
format
v-jiaodi Apr 9, 2025
6958975
fix ut
v-jiaodi Apr 9, 2025
91c68d5
update case
v-jiaodi Apr 9, 2025
58824b0
merge main
v-jiaodi Apr 9, 2025
560dc17
Merge branch 'main' of https://github.com/Azure/autorest.typescript i…
v-jiaodi Apr 9, 2025
f1ebea1
Resolve the comments and update the value type ser/derser for helper
MaryGao Apr 10, 2025
0ddabbe
Refresh the smoke testing
MaryGao Apr 10, 2025
5504dba
Revert the serializeRecord logic for legacy codes
MaryGao Apr 10, 2025
36bd11c
Update the smoke test
MaryGao Apr 10, 2025
5c87a33
regen code
v-jiaodi Apr 11, 2025
22e9f48
Merge branch 'main' into support-additional-properties
MaryGao Apr 15, 2025
84c270e
Update the helper from let to const
MaryGao Apr 15, 2025
ff751a5
Add one test case
MaryGao Apr 15, 2025
b511d31
Update the test cases
MaryGao Apr 15, 2025
a3f6f4c
Merge branch 'main' into support-additional-properties
MaryGao Apr 18, 2025
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
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { serializeRecord } from "../../../static-helpers/serialization/serialize-record.js";

export function resourceArraySerializer(result: Array<Resource>): any[] {
return result.map((item) => {
return resourceSerializer(item);
Expand Down Expand Up @@ -34,7 +36,7 @@ export interface Resource {

export function resourceSerializer(item: Resource): any {
return {
...item,
...item.additionalProperties,
Comment thread
MaryGao marked this conversation as resolved.
Outdated
resourceType: item["resourceType"],
id: item["id"],
meta: !item["meta"] ? item["meta"] : metaSerializer(item["meta"]),
Expand All @@ -45,7 +47,13 @@ export function resourceSerializer(item: Resource): any {

export function resourceDeserializer(item: any): Resource {
return {
...item,
additionalProperties: serializeRecord(item, [
"resourceType",
"id",
"meta",
"implicitRules",
"language",
]),
resourceType: item["resourceType"],
id: item["id"],
meta: !item["meta"] ? item["meta"] : metaDeserializer(item["meta"]),
Expand Down Expand Up @@ -761,7 +769,47 @@ export interface Observation extends DomainResource {

export function observationDeserializer(item: any): Observation {
return {
...item,
additionalProperties: serializeRecord(item, [
"resourceType",
"text",
"contained",
"extension",
"modifierExtension",
"id",
"meta",
"implicitRules",
"language",
"identifier",
"status",
"category",
"code",
"subject",
"encounter",
"effectiveDateTime",
"effectivePeriod",
"effectiveInstant",
"issued",
"valueQuantity",
"valueCodeableConcept",
"valueString",
"valueBoolean",
"valueInteger",
"valueRange",
"valueRatio",
"valueSampledData",
"valueTime",
"valueDateTime",
"valuePeriod",
"dataAbsentReason",
"interpretation",
"note",
"bodySite",
"method",
"referenceRange",
"hasMember",
"derivedFrom",
"component",
]),
resourceType: item["resourceType"],
text: !item["text"] ? item["text"] : narrativeDeserializer(item["text"]),
contained: !item["contained"]
Expand Down Expand Up @@ -1077,7 +1125,17 @@ export interface DomainResource extends Resource {

export function domainResourceDeserializer(item: any): DomainResource {
return {
...item,
additionalProperties: serializeRecord(item, [
Comment thread
MaryGao marked this conversation as resolved.
"resourceType",
"id",
"meta",
"implicitRules",
"language",
"text",
"contained",
"extension",
"modifierExtension",
]),
resourceType: item["resourceType"],
id: item["id"],
meta: !item["meta"] ? item["meta"] : metaDeserializer(item["meta"]),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

export function serializeRecord(item: any, excludeProperties: string[] = []) {
let res: any = {};
Comment thread
MaryGao marked this conversation as resolved.
Outdated
for (let key of Object.keys(item)) {
if (!excludeProperties.includes(key)) {
res[key] = item[key] as any;
}
}
return Object.keys(res).length === 0 ? undefined : res;
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export interface ChatCompletionFunctionCallOption {
}

// @public
export interface ChatCompletionFunctionParameters extends Record<string, any> {
export interface ChatCompletionFunctionParameters {
Comment thread
MaryGao marked this conversation as resolved.
additionalProperties?: Record<string, any>;
Comment thread
MaryGao marked this conversation as resolved.
}

// @public
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1923,12 +1923,15 @@ export function chatCompletionFunctionsSerializer(
}

/** model interface ChatCompletionFunctionParameters */
export interface ChatCompletionFunctionParameters extends Record<string, any> {}
export interface ChatCompletionFunctionParameters {
/** Additional properties */
additionalProperties?: Record<string, any>;
}

export function chatCompletionFunctionParametersSerializer(
item: ChatCompletionFunctionParameters,
): any {
return { ...item };
return { ...item.additionalProperties };
}

/** Alias for _CreateChatCompletionRequestFunctionCall1 */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export interface ChatCompletionFunctionCallOption {
}

// @public
export interface ChatCompletionFunctionParameters extends Record<string, any> {
export interface ChatCompletionFunctionParameters {
additionalProperties?: Record<string, any>;
}

// @public
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1922,12 +1922,15 @@ export function chatCompletionFunctionsSerializer(
}

/** model interface ChatCompletionFunctionParameters */
export interface ChatCompletionFunctionParameters extends Record<string, any> {}
export interface ChatCompletionFunctionParameters {
/** Additional properties */
additionalProperties?: Record<string, any>;
}

export function chatCompletionFunctionParametersSerializer(
item: ChatCompletionFunctionParameters,
): any {
return { ...item };
return { ...item.additionalProperties };
}

/** Alias for _CreateChatCompletionRequestFunctionCall1 */
Expand Down
12 changes: 6 additions & 6 deletions packages/typespec-ts/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,12 +394,6 @@ const libDef = {
default: paramMessage`Please note the header ${"type"} is not serializable.`
}
},
"compatible-additional-properties": {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't see we enable compatibilityMode option in mgmt plane, will it cause problem?

Copy link
Copy Markdown
Member Author

@MaryGao MaryGao Apr 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem for us, I searched in current spec there is no any real additional properties cases yet, that means no extends Record<..> or ...Record<...>.

The case we received for ImpactReporting is a fake case where the payload has the extra layer of additionalProperties explicitly - https://github.com/Azure/azure-rest-api-specs/pull/33352/files#r2011565369.

For long term I would prefer to never turn on compatibilityMode if possible, so finally we could remove this logic and only support for new style which better handles the type check stuff.

severity: "warning",
messages: {
default: paramMessage`Please note that only compatible additional properties is supported for now. You can enable compatibilityMode to generate compatible additional properties for the model - ${"modelName"}.`
}
},
"default-response-body-type": {
severity: "warning",
messages: {
Expand Down Expand Up @@ -453,6 +447,12 @@ const libDef = {
messages: {
default: paramMessage`Enum member name ${"memberName"} is normalized to ${"normalizedName"} with "_" prefix.`
}
},
"property-name-conflict": {
severity: "warning",
messages: {
default: paramMessage`The property name ${"propertyName"} has conflicts with others and please use @clientName to rename it.`
}
}
},
emitter: {
Expand Down
71 changes: 48 additions & 23 deletions packages/typespec-ts/src/modular/emitModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ import {
import { emitQueue } from "../framework/hooks/sdkTypes.js";
import { resolveReference } from "../framework/reference.js";
import { MultipartHelpers } from "./static-helpers-metadata.js";
import { getAllAncestors } from "./helpers/operationHelpers.js";
import { getAllProperties } from "./helpers/operationHelpers.js";

type InterfaceStructure = OptionalKind<InterfaceDeclarationStructure> & {
extends?: string[];
Expand Down Expand Up @@ -492,36 +494,50 @@ function addExtendedDictInfo(
const additionalPropertiesType = model.additionalProperties
? getTypeExpression(context, model.additionalProperties)
: undefined;
if (
(model.properties &&
model.properties.length > 0 &&
model.additionalProperties &&
model.properties?.every((p) => {
if (context.rlcOptions?.compatibilityMode) {
const ancestors = getAllAncestors(model);
const properties = getAllProperties(model, ancestors);
let anyType = true;
if (!additionalPropertiesType) {
// case 1: if additionalProperties is not defined, we should use any type
anyType = true;
} else if (properties.length === 0) {
// case 2: if additionalProperties is defined and model.properties is empty, we should use additionalProperties type
anyType = false;
} else {
// case 3: if additionalProperties is defined and model.properties is not empty, we should check if all properties are compatible with additionalProperties type
anyType = !properties.every((p) => {
Comment thread
MaryGao marked this conversation as resolved.
return additionalPropertiesType?.includes(
getTypeExpression(context, p.type)
);
})) ||
(model.properties?.length === 0 && model.additionalProperties)
) {
modelInterface.extends = [
...(modelInterface.extends ?? []),
`Record<string, ${additionalPropertiesType ?? "any"}>`
];
} else if (context.rlcOptions?.compatibilityMode) {
});
}
if (!modelInterface.extends) {
modelInterface.extends = [];
}
modelInterface.extends.push(`Record<string, any>`);
modelInterface.extends.push(
`Record<string, ${anyType ? "any" : additionalPropertiesType}>`
);
} else {
reportDiagnostic(context.program, {
code: "compatible-additional-properties",
format: {
modelName: modelInterface?.name ?? ""
},
target: NoTarget
});
modelInterface.properties?.push({
name: "additionalProperties",
const additionalPropertiesType = model.additionalProperties
? getTypeExpression(context, model.additionalProperties)
: undefined;
const name = getAdditionalPropertiesName(model);
if (name !== "additionalProperties") {
// report diagnostic for additionalProperties
reportDiagnostic(context.program, {
code: "property-name-conflict",
format: {
propertyName: "additionalProperties"
},
target: NoTarget
});
}
if (!modelInterface.properties) {
modelInterface.properties = [];
}
modelInterface.properties.push({
name,
docs: ["Additional properties"],
hasQuestionToken: true,
isReadonly: false,
Expand All @@ -530,6 +546,15 @@ function addExtendedDictInfo(
}
}

export function getAdditionalPropertiesName(model: SdkModelType): string {
const ancestors = getAllAncestors(model);
const properties = getAllProperties(model, ancestors);
const nameConflict = properties.find(
(p) => p.name === "additionalProperties"
);
return nameConflict ? "additionalPropertiesBag" : "additionalProperties";
}

export function normalizeModelName(
context: SdkContext,
type:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,24 @@ import {
UsageFlags
} from "@azure-tools/typespec-client-generator-core";
import { SdkContext } from "../../utils/interfaces.js";
import { getResponseMapping } from "../helpers/operationHelpers.js";
import { normalizeModelName } from "../emitModels.js";
import {
getAllAncestors,
getAllProperties,
getResponseMapping
} from "../helpers/operationHelpers.js";
import {
getAdditionalPropertiesName,
normalizeModelName
} from "../emitModels.js";
import { NameType, normalizeName } from "@azure-tools/rlc-common";
import { isAzureCoreErrorType } from "../../utils/modelUtils.js";
import {
isDiscriminatedUnion,
isSupportedSerializeType,
ModelSerializeOptions
} from "./serializeUtils.js";
import { resolveReference } from "../../framework/reference.js";
import { SerializationHelpers } from "../static-helpers-metadata.js";

export function buildModelDeserializer(
context: SdkContext,
Expand Down Expand Up @@ -347,10 +356,8 @@ function buildModelTypeDeserializer(
};
const nullabilityPrefix = "";

// This is only handling the compatibility mode, will need to update when we handle additionalProperties property.
const additionalPropertiesSpread = hasAdditionalProperties(type)
? "...item,"
: "";
const additionalPropertiesSpread =
getAdditionalPropertiesStatement(context, type) ?? "";

const propertiesStr = getResponseMapping(context, type, "item");
const propertiesDeserialization = propertiesStr.filter((p) => p.trim());
Expand All @@ -375,6 +382,25 @@ function buildModelTypeDeserializer(
return deserializerFunction;
}

function getAdditionalPropertiesStatement(
context: SdkContext,
type: SdkModelType
): string | undefined {
const allParents = getAllAncestors(type);
const properties = getAllProperties(type, allParents);
const excludeProperties = properties
.filter((p) => !!p.name)
.map((p) => `"${p.name}"`);
const excludePropertiesStr =
excludeProperties.length > 0 ? `[${excludeProperties.join(",")}]` : "";
const additionalPropertiesName = getAdditionalPropertiesName(type);
return hasAdditionalProperties(type)
? context.rlcOptions?.compatibilityMode === true
? "...item,"
: `${additionalPropertiesName}: ${resolveReference(SerializationHelpers.serializeRecord)}(item, ${excludePropertiesStr}),`
: undefined;
}

function buildDictTypeDeserializer(
context: SdkContext,
type: SdkDictionaryType,
Expand Down
Loading
Loading