diff --git a/.chronus/changes/copilot-fix-emit-additional-properties-2026-3-4-0-58-57.md b/.chronus/changes/copilot-fix-emit-additional-properties-2026-3-4-0-58-57.md new file mode 100644 index 00000000000..c748ba1d38b --- /dev/null +++ b/.chronus/changes/copilot-fix-emit-additional-properties-2026-3-4-0-58-57.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/emitter-framework" +--- + +Use `Record` extends instead of `additionalProperties` property for models that are or extend record types diff --git a/packages/emitter-framework/src/typescript/components/interface-declaration.tsx b/packages/emitter-framework/src/typescript/components/interface-declaration.tsx index cb1cc107919..f5d936c1944 100644 --- a/packages/emitter-framework/src/typescript/components/interface-declaration.tsx +++ b/packages/emitter-framework/src/typescript/components/interface-declaration.tsx @@ -14,6 +14,7 @@ import { useTsp } from "../../core/context/tsp-context.js"; import { reportDiagnostic } from "../../lib.js"; import { declarationRefkeys, efRefkey } from "../utils/refkey.js"; import { InterfaceMember } from "./interface-member.js"; +import { RecordExpression } from "./record-expression.js"; import { TypeExpression } from "./type-expression.jsx"; export interface TypedInterfaceDeclarationProps extends Omit { type: Model | Interface; @@ -90,9 +91,7 @@ function getExtendsType($: Typekit, type: Model | Interface): Children | undefin if ($.array.is(type.baseModel)) { extending.push(); } else if ($.record.is(type.baseModel)) { - // Here we are in the additional properties land. - // Instead of extending we need to create an envelope property - // do nothing here. + extending.push(); } else { extending.push(efRefkey(type.baseModel)); } @@ -100,11 +99,18 @@ function getExtendsType($: Typekit, type: Model | Interface): Children | undefin const indexType = $.model.getIndexType(type); if (indexType) { - // When extending a record we need to override the element type to be unknown to avoid type errors if ($.record.is(indexType)) { - // Here we are in the additional properties land. - // Instead of extending we need to create an envelope property - // do nothing here. + const elementType = indexType.indexer!.value; + // Only use extends Record if all known properties are assignable to T. + // When properties have incompatible types (e.g., id: number with Record), + // skip the extends clause to avoid TypeScript index signature conflicts. + const properties = $.model.getProperties(type); + const allCompatible = Array.from(properties.values()).every((prop) => + $.type.isAssignableTo(prop.type, elementType), + ); + if (allCompatible) { + extending.push(); + } } else { extending.push(); } @@ -126,19 +132,31 @@ function getExtendsType($: Typekit, type: Model | Interface): Children | undefin */ function InterfaceBody(props: TypedInterfaceDeclarationProps): Children { const { $ } = useTsp(); + const additionalPropertiesKey = "additionalProperties"; let typeMembers: RekeyableMap | undefined; if ($.model.is(props.type)) { typeMembers = $.model.getProperties(props.type); - const additionalProperties = $.model.getAdditionalPropertiesRecord(props.type); - if (additionalProperties) { - typeMembers.set( - "additionalProperties", - $.modelProperty.create({ - name: "additionalProperties", - optional: true, - type: additionalProperties, - }), + + // When the model has a record indexer (from ...Record spreads) but the named + // properties are not assignable to the element type, TypeScript cannot represent + // this with `extends Record` (index signature compatibility constraint). + // Instead, add an `additionalProperties` named property to represent the extra properties. + const indexType = $.model.getIndexType(props.type); + if (indexType && $.record.is(indexType)) { + const elementType = indexType.indexer!.value; + const allCompatible = Array.from(typeMembers.values()).every((prop) => + $.modelProperty.is(prop) ? $.type.isAssignableTo(prop.type, elementType) : true, ); + if (!allCompatible) { + typeMembers.set( + additionalPropertiesKey, + $.modelProperty.create({ + name: additionalPropertiesKey, + optional: true, + type: indexType, + }), + ); + } } } else { typeMembers = createRekeyableMap(props.type.operations); diff --git a/packages/emitter-framework/test/typescript/components/interface-declaration.test.tsx b/packages/emitter-framework/test/typescript/components/interface-declaration.test.tsx index 7d350ed434d..15d8c6e43c0 100644 --- a/packages/emitter-framework/test/typescript/components/interface-declaration.test.tsx +++ b/packages/emitter-framework/test/typescript/components/interface-declaration.test.tsx @@ -187,9 +187,8 @@ describe("Typescript Interface", () => { , ).toRenderTo(` - export interface DifferentSpreadModelRecord { + export interface DifferentSpreadModelRecord extends Record { knownProp: string; - additionalProperties?: Record; } `); }); @@ -235,9 +234,7 @@ describe("Typescript Interface", () => { , ).toRenderTo(` - export interface Foo { - additionalProperties?: Record; - }`); + export interface Foo extends Record {}`); }); it("creates an interface of a model that spreads a Record", async () => { @@ -261,9 +258,7 @@ describe("Typescript Interface", () => { , ).toRenderTo(` - export interface Foo { - additionalProperties?: Record; - } + export interface Foo extends Record {} `); }); @@ -306,7 +301,6 @@ describe("Typescript Interface", () => { } export interface DifferentSpreadModelDerived extends DifferentSpreadModelRecord { derivedProp: ModelForRecord; - additionalProperties?: Record; }`); }); @@ -332,11 +326,65 @@ describe("Typescript Interface", () => { , ).toRenderTo(` - export interface Widget { + export interface Widget extends Record { id: string; weight: number; color: "blue" | "red"; - additionalProperties?: Record; + }`); + }); + + it("adds additionalProperties when known properties are incompatible with the record element type", async () => { + const program = await getProgram(` + namespace DemoService; + model Widget { + id: int32; + ...Record; + } + `); + + const [namespace] = program.resolveTypeReference("DemoService"); + const models = Array.from((namespace as Namespace).models.values()); + + expect( + + + {models.map((model) => ( + + ))} + + , + ).toRenderTo(` + export interface Widget { + id: number; + additionalProperties?: Record; + }`); + }); + + it("adds additionalProperties for multiple incompatible record spreads as a union type", async () => { + const program = await getProgram(` + namespace DemoService; + model Foo { + id: int32; + ...Record; + ...Record; + } + `); + + const [namespace] = program.resolveTypeReference("DemoService"); + const models = Array.from((namespace as Namespace).models.values()); + + expect( + + + {models.map((model) => ( + + ))} + + , + ).toRenderTo(` + export interface Foo { + id: number; + additionalProperties?: Record; }`); }); diff --git a/packages/http-client-js/src/components/transforms/json/json-model-additional-properties-transform.tsx b/packages/http-client-js/src/components/transforms/json/json-model-additional-properties-transform.tsx index a458d1b49a9..31544aecd65 100644 --- a/packages/http-client-js/src/components/transforms/json/json-model-additional-properties-transform.tsx +++ b/packages/http-client-js/src/components/transforms/json/json-model-additional-properties-transform.tsx @@ -10,6 +10,36 @@ export interface JsonAdditionalPropertiesTransformProps { target: "transport" | "application"; } +/** + * Determines whether a model uses the `additionalProperties` wrapper property + * (instead of flat spreading). This is true when the model has a record index type + * from spreads but the named properties are not all assignable to the element type, + * or when an ancestor model uses the wrapper approach. + */ +function usesWrappedAdditionalProperties($: ReturnType["$"], type: Model): boolean { + const indexType = $.model.getIndexType(type); + if (indexType && $.record.is(indexType)) { + const elementType = indexType.indexer!.value; + const properties = $.model.getProperties(type); + const allCompatible = Array.from(properties.values()).every((prop) => + $.modelProperty.is(prop) ? $.type.isAssignableTo(prop.type, elementType) : true, + ); + return !allCompatible; + } + + // Direct base is a record (extends Record) → always flat + if (type.baseModel && $.record.is(type.baseModel)) { + return false; + } + + // Recurse into base model to check if the wrapper comes from inheritance + if (type.baseModel) { + return usesWrappedAdditionalProperties($, type.baseModel); + } + + return false; +} + export function JsonAdditionalPropertiesTransform(props: JsonAdditionalPropertiesTransformProps) { const { $ } = useTsp(); const additionalProperties = $.model.getAdditionalPropertiesRecord(props.type); @@ -18,36 +48,47 @@ export function JsonAdditionalPropertiesTransform(props: JsonAdditionalPropertie return null; } - if (props.target === "application") { - const properties = $.model.getProperties(props.type, { includeExtended: true }); - const destructuredProperties = mapJoin( - () => properties, - (name) => name, - { - joiner: ",", - ender: ",", - }, - ); + const properties = $.model.getProperties(props.type, { includeExtended: true }); + const destructuredProperties = mapJoin( + () => properties, + (name) => name, + { + joiner: ",", + ender: ",", + }, + ); + + if (usesWrappedAdditionalProperties($, props.type)) { + // Incompatible case: use additionalProperties wrapper property + if (props.target === "application") { + const inlineDestructure = code` + ${getJsonRecordTransformRefkey(additionalProperties, props.target)}( + (({ ${destructuredProperties} ...rest }) => rest)(${props.itemRef}) + ), + `; + + return ( + <> + {inlineDestructure} + + ); + } - // Inline destructuring that extracts the properties and passes the rest to jsonRecordUnknownToApplicationTransform_2 - const inlineDestructure = code` - ${getJsonRecordTransformRefkey(additionalProperties, props.target)}( - (({ ${destructuredProperties} ...rest }) => rest)(${props.itemRef}) - ), - `; + const itemRef = code`${props.itemRef}.additionalProperties`; return ( <> - {inlineDestructure} + ...({getJsonRecordTransformRefkey(additionalProperties, props.target)}({itemRef}) ), ); } - const itemRef = code`${props.itemRef}.additionalProperties`; + // Compatible case: spread additional properties directly onto the object + const restExpr = code`(({ ${destructuredProperties} ...rest }) => rest)(${props.itemRef})`; return ( <> - ...({getJsonRecordTransformRefkey(additionalProperties, props.target)}({itemRef}) ), + ...({getJsonRecordTransformRefkey(additionalProperties, props.target)}({restExpr}) ), ); } diff --git a/packages/http-client-js/test/e2e/http/type/property/additional-properties/main.test.ts b/packages/http-client-js/test/e2e/http/type/property/additional-properties/main.test.ts index f7cf77f74b7..e8574e31c30 100644 --- a/packages/http-client-js/test/e2e/http/type/property/additional-properties/main.test.ts +++ b/packages/http-client-js/test/e2e/http/type/property/additional-properties/main.test.ts @@ -20,18 +20,18 @@ describe("Type.Property.AdditionalProperties", () => { it("Expected response body: {'name': 'ExtendsUnknownAdditionalProperties', 'prop1': 32, 'prop2': true, 'prop3': 'abc'}", async () => { const response = await client.get(); expect(response).toEqual({ - additionalProperties: { - prop1: 32, - prop2: true, - prop3: "abc", - }, name: "ExtendsUnknownAdditionalProperties", + prop1: 32, + prop2: true, + prop3: "abc", }); }); it("Expected input body: {'name': 'ExtendsUnknownAdditionalProperties', 'prop1': 32, 'prop2': true, 'prop3': 'abc'}", async () => { await client.put({ - additionalProperties: { prop1: 32, prop2: true, prop3: "abc" }, name: "ExtendsUnknownAdditionalProperties", + prop1: 32, + prop2: true, + prop3: "abc", }); }); }); @@ -46,14 +46,12 @@ describe("Type.Property.AdditionalProperties", () => { it("Expected response body: {'name': 'ExtendsUnknownAdditionalProperties', 'index': 314, 'age': 2.71875, 'prop1': 32, 'prop2': true, 'prop3': 'abc'}", async () => { const response = await client.get(); expect(response).toEqual({ - additionalProperties: { - prop1: 32, - prop2: true, - prop3: "abc", - }, name: "ExtendsUnknownAdditionalProperties", index: 314, age: 2.71875, + prop1: 32, + prop2: true, + prop3: "abc", }); }); it("Expected input body: {'name': 'ExtendsUnknownAdditionalProperties', 'index': 314, 'age': 2.71875, 'prop1': 32, 'prop2': true, 'prop3': 'abc'}", async () => { @@ -61,7 +59,9 @@ describe("Type.Property.AdditionalProperties", () => { name: "ExtendsUnknownAdditionalProperties", index: 314, age: 2.71875, - additionalProperties: { prop1: 32, prop2: true, prop3: "abc" }, + prop1: 32, + prop2: true, + prop3: "abc", }); }); }); @@ -76,11 +76,13 @@ describe("Type.Property.AdditionalProperties", () => { it("Expected response body: {'kind': 'derived', 'name': 'Derived', 'index': 314, 'age': 2.71875, 'prop1': 32, 'prop2': true, 'prop3': 'abc'}", async () => { const response = await client.get(); expect(response).toEqual({ - additionalProperties: { prop1: 32, prop2: true, prop3: "abc" }, kind: "derived", name: "Derived", index: 314, age: 2.71875, + prop1: 32, + prop2: true, + prop3: "abc", }); }); it("Expected input body: {'kind': 'derived', 'name': 'Derived', 'index': 314, 'age': 2.71875, 'prop1': 32, 'prop2': true, 'prop3': 'abc'}", async () => { @@ -89,11 +91,9 @@ describe("Type.Property.AdditionalProperties", () => { age: 2.71875, kind: "derived", name: "Derived", - additionalProperties: { - prop1: 32, - prop2: true, - prop3: "abc", - }, + prop1: 32, + prop2: true, + prop3: "abc", }; await client.put(input); }); @@ -105,18 +105,18 @@ describe("Type.Property.AdditionalProperties", () => { const response = await client.get(); expect(response).toEqual({ name: "IsUnknownAdditionalProperties", - additionalProperties: { prop1: 32, prop2: true, prop3: "abc" }, + prop1: 32, + prop2: true, + prop3: "abc", }); }); it("Expected input body: {'name': 'IsUnknownAdditionalProperties', 'prop1': 32, 'prop2': true, 'prop3': 'abc'}", async () => { await client.put({ name: "IsUnknownAdditionalProperties", - additionalProperties: { - prop1: 32, - prop2: true, - prop3: "abc", - }, + prop1: 32, + prop2: true, + prop3: "abc", }); }); }); @@ -134,7 +134,9 @@ describe("Type.Property.AdditionalProperties", () => { name: "IsUnknownAdditionalProperties", index: 314, age: 2.71875, - additionalProperties: { prop1: 32, prop2: true, prop3: "abc" }, + prop1: 32, + prop2: true, + prop3: "abc", }); }); it("Expected input body: {'name': 'IsUnknownAdditionalProperties', 'index': 314, 'age': 2.71875, 'prop1': 32, 'prop2': true, 'prop3': 'abc'}", async () => { @@ -142,11 +144,9 @@ describe("Type.Property.AdditionalProperties", () => { name: "IsUnknownAdditionalProperties", index: 314, age: 2.71875, - additionalProperties: { - prop1: 32, - prop2: true, - prop3: "abc", - }, + prop1: 32, + prop2: true, + prop3: "abc", }); }); }); @@ -165,7 +165,9 @@ describe("Type.Property.AdditionalProperties", () => { name: "Derived", index: 314, age: 2.71875, - additionalProperties: { prop1: 32, prop2: true, prop3: "abc" }, + prop1: 32, + prop2: true, + prop3: "abc", }); }); it("Expected input body: {'kind': 'derived', 'name': 'Derived', 'index': 314, 'age': 2.71875, 'prop1': 32, 'prop2': true, 'prop3': 'abc'}", async () => { @@ -174,11 +176,9 @@ describe("Type.Property.AdditionalProperties", () => { name: "Derived", index: 314, age: 2.71875, - additionalProperties: { - prop1: 32, - prop2: true, - prop3: "abc", - }, + prop1: 32, + prop2: true, + prop3: "abc", } as any); }); }); diff --git a/packages/http-client-js/test/e2e/http/type/property/additional-properties/spreads.test.ts b/packages/http-client-js/test/e2e/http/type/property/additional-properties/spreads.test.ts index a7c742a6c76..a9ba339f4e2 100644 --- a/packages/http-client-js/test/e2e/http/type/property/additional-properties/spreads.test.ts +++ b/packages/http-client-js/test/e2e/http/type/property/additional-properties/spreads.test.ts @@ -4,13 +4,21 @@ import { ExtendsDifferentSpreadModelArrayClient, ExtendsDifferentSpreadModelClient, ExtendsDifferentSpreadStringClient, + ExtendsFloatAdditionalProperties, ExtendsFloatClient, + ExtendsModelAdditionalProperties, + ExtendsModelArrayAdditionalProperties, ExtendsModelArrayClient, ExtendsModelClient, + ExtendsStringAdditionalProperties, ExtendsStringClient, + IsFloatAdditionalProperties, IsFloatClient, + IsModelAdditionalProperties, + IsModelArrayAdditionalProperties, IsModelArrayClient, IsModelClient, + IsStringAdditionalProperties, IsStringClient, MultipleSpreadClient, SpreadDifferentFloatClient, @@ -18,16 +26,17 @@ import { SpreadDifferentModelClient, SpreadDifferentStringClient, SpreadFloatClient, + SpreadFloatRecord, SpreadModelArrayClient, + SpreadModelArrayRecord, SpreadModelClient, - SpreadRecordForNonDiscriminatedUnion, - SpreadRecordForNonDiscriminatedUnion2, - SpreadRecordForNonDiscriminatedUnion3, + SpreadModelRecord, SpreadRecordNonDiscriminatedUnion2Client, SpreadRecordNonDiscriminatedUnion3Client, SpreadRecordNonDiscriminatedUnionClient, SpreadRecordUnionClient, SpreadStringClient, + SpreadStringRecord, } from "../../../../generated/type/property/additional-properties/src/index.js"; // Helper to create a client instance with common options. @@ -39,9 +48,9 @@ const clientOptions = { describe("Missing AdditionalProperties Endpoints", () => { describe("ExtendsString", () => { const client = new ExtendsStringClient(clientOptions); - const expected = { - additionalProperties: { prop: "abc" }, + const expected: ExtendsStringAdditionalProperties = { name: "ExtendsStringAdditionalProperties", + prop: "abc", }; it("GET returns the expected response", async () => { const response = await client.get(); @@ -54,9 +63,9 @@ describe("Missing AdditionalProperties Endpoints", () => { describe("IsString", () => { const client = new IsStringClient({ allowInsecureConnection: true }); - const expected = { - additionalProperties: { prop: "abc" }, + const expected: IsStringAdditionalProperties = { name: "IsStringAdditionalProperties", + prop: "abc", }; it("GET returns the expected response", async () => { const response = await client.get(); @@ -71,9 +80,9 @@ describe("Missing AdditionalProperties Endpoints", () => { const client = new SpreadStringClient({ allowInsecureConnection: true, }); - const expected = { - additionalProperties: { prop: "abc" }, + const expected: SpreadStringRecord = { name: "SpreadSpringRecord", + prop: "abc", }; it("GET returns the expected response", async () => { const response = await client.get(); @@ -86,9 +95,9 @@ describe("Missing AdditionalProperties Endpoints", () => { describe("ExtendsFloat", () => { const client = new ExtendsFloatClient(clientOptions); - const expected = { - additionalProperties: { prop: 43.125 }, + const expected: ExtendsFloatAdditionalProperties = { id: 43.125, + prop: 43.125, }; it("GET returns the expected response", async () => { const response = await client.get(); @@ -101,9 +110,9 @@ describe("Missing AdditionalProperties Endpoints", () => { describe("IsFloat", () => { const client = new IsFloatClient(clientOptions); - const expected = { - additionalProperties: { prop: 43.125 }, + const expected: IsFloatAdditionalProperties = { id: 43.125, + prop: 43.125, }; it("GET returns the expected response", async () => { const response = await client.get(); @@ -116,9 +125,9 @@ describe("Missing AdditionalProperties Endpoints", () => { describe("SpreadFloat", () => { const client = new SpreadFloatClient(clientOptions); - const expected = { - additionalProperties: { prop: 43.125 }, + const expected: SpreadFloatRecord = { id: 43.125, + prop: 43.125, }; it("GET returns the expected response", async () => { const response = await client.get(); @@ -131,9 +140,9 @@ describe("Missing AdditionalProperties Endpoints", () => { describe("ExtendsModel", () => { const client = new ExtendsModelClient(clientOptions); - const expected = { + const expected: ExtendsModelAdditionalProperties = { knownProp: { state: "ok" }, - additionalProperties: { prop: { state: "ok" } }, + prop: { state: "ok" }, }; it("GET returns the expected response", async () => { const response = await client.get(); @@ -146,9 +155,9 @@ describe("Missing AdditionalProperties Endpoints", () => { describe("IsModel", () => { const client = new IsModelClient(clientOptions); - const expected = { + const expected: IsModelAdditionalProperties = { knownProp: { state: "ok" }, - additionalProperties: { prop: { state: "ok" } }, + prop: { state: "ok" }, }; it("GET returns the expected response", async () => { const response = await client.get(); @@ -161,9 +170,9 @@ describe("Missing AdditionalProperties Endpoints", () => { describe("SpreadModel", () => { const client = new SpreadModelClient(clientOptions); - const expected = { + const expected: SpreadModelRecord = { knownProp: { state: "ok" }, - additionalProperties: { prop: { state: "ok" } }, + prop: { state: "ok" }, }; it("GET returns the expected response", async () => { const response = await client.get(); @@ -176,9 +185,9 @@ describe("Missing AdditionalProperties Endpoints", () => { describe("ExtendsModelArray", () => { const client = new ExtendsModelArrayClient(clientOptions); - const expected = { + const expected: ExtendsModelArrayAdditionalProperties = { knownProp: [{ state: "ok" }, { state: "ok" }], - additionalProperties: { prop: [{ state: "ok" }, { state: "ok" }] }, + prop: [{ state: "ok" }, { state: "ok" }], }; it("GET returns the expected response", async () => { const response = await client.get(); @@ -191,9 +200,9 @@ describe("Missing AdditionalProperties Endpoints", () => { describe("IsModelArray", () => { const client = new IsModelArrayClient(clientOptions); - const expected = { + const expected: IsModelArrayAdditionalProperties = { knownProp: [{ state: "ok" }, { state: "ok" }], - additionalProperties: { prop: [{ state: "ok" }, { state: "ok" }] }, + prop: [{ state: "ok" }, { state: "ok" }], }; it("GET returns the expected response", async () => { const response = await client.get(); @@ -206,9 +215,9 @@ describe("Missing AdditionalProperties Endpoints", () => { describe("SpreadModelArray", () => { const client = new SpreadModelArrayClient(clientOptions); - const expected = { + const expected: SpreadModelArrayRecord = { knownProp: [{ state: "ok" }, { state: "ok" }], - additionalProperties: { prop: [{ state: "ok" }, { state: "ok" }] }, + prop: [{ state: "ok" }, { state: "ok" }], }; it("GET returns the expected response", async () => { const response = await client.get(); @@ -231,7 +240,7 @@ describe("Missing AdditionalProperties Endpoints", () => { expect(response).toEqual(expected); }); it("PUT accepts the expected input", async () => { - await client.put(expected); + await client.put(expected as any); }); }); @@ -246,7 +255,7 @@ describe("Missing AdditionalProperties Endpoints", () => { expect(response).toEqual(expected); }); it("PUT accepts the expected input", async () => { - await client.put(expected); + await client.put(expected as any); }); }); @@ -261,7 +270,7 @@ describe("Missing AdditionalProperties Endpoints", () => { expect(response).toEqual(expected); }); it("PUT accepts the expected input", async () => { - await client.put(expected); + await client.put(expected as any); }); }); @@ -276,7 +285,7 @@ describe("Missing AdditionalProperties Endpoints", () => { expect(response).toEqual(expected); }); it("PUT accepts the expected input", async () => { - await client.put(expected); + await client.put(expected as any); }); }); @@ -284,15 +293,15 @@ describe("Missing AdditionalProperties Endpoints", () => { const client = new ExtendsDifferentSpreadStringClient(clientOptions); const expected = { id: 43.125, - additionalProperties: { prop: "abc" }, derivedProp: "abc", + additionalProperties: { prop: "abc" }, }; it("GET returns the expected response", async () => { const response = await client.get(); expect(response).toEqual(expected); }); it("PUT accepts the expected input", async () => { - await client.put(expected); + await client.put(expected as any); }); }); @@ -300,15 +309,15 @@ describe("Missing AdditionalProperties Endpoints", () => { const client = new ExtendsDifferentSpreadFloatClient(clientOptions); const expected = { name: "abc", - additionalProperties: { prop: 43.125 }, derivedProp: 43.125, + additionalProperties: { prop: 43.125 }, }; it("GET returns the expected response", async () => { const response = await client.get(); expect(response).toEqual(expected); }); it("PUT accepts the expected input", async () => { - await client.put(expected); + await client.put(expected as any); }); }); @@ -316,15 +325,15 @@ describe("Missing AdditionalProperties Endpoints", () => { const client = new ExtendsDifferentSpreadModelClient(clientOptions); const expected = { knownProp: "abc", - additionalProperties: { prop: { state: "ok" } }, derivedProp: { state: "ok" }, + additionalProperties: { prop: { state: "ok" } }, }; it("GET returns the expected response", async () => { const response = await client.get(); expect(response).toEqual(expected); }); it("PUT accepts the expected input", async () => { - await client.put(expected); + await client.put(expected as any); }); }); @@ -332,15 +341,15 @@ describe("Missing AdditionalProperties Endpoints", () => { const client = new ExtendsDifferentSpreadModelArrayClient(clientOptions); const expected = { knownProp: "abc", - additionalProperties: { prop: [{ state: "ok" }, { state: "ok" }] }, derivedProp: [{ state: "ok" }, { state: "ok" }], + additionalProperties: { prop: [{ state: "ok" }, { state: "ok" }] }, }; it("GET returns the expected response", async () => { const response = await client.get(); expect(response).toEqual(expected); }); it("PUT accepts the expected input", async () => { - await client.put(expected); + await client.put(expected as any); }); }); @@ -356,7 +365,7 @@ describe("Missing AdditionalProperties Endpoints", () => { expect(response).toEqual(expected); }); it("PUT accepts the expected input", async () => { - await client.put(expected); + await client.put(expected as any); }); }); @@ -371,13 +380,13 @@ describe("Missing AdditionalProperties Endpoints", () => { expect(response).toEqual(expected); }); it("PUT accepts the expected input", async () => { - await client.put(expected); + await client.put(expected as any); }); }); describe("SpreadRecordNonDiscriminatedUnion", () => { const client = new SpreadRecordNonDiscriminatedUnionClient(clientOptions); - const expected: SpreadRecordForNonDiscriminatedUnion = { + const expected = { name: "abc", additionalProperties: { prop1: { kind: "kind0", fooProp: "abc" }, @@ -385,7 +394,7 @@ describe("Missing AdditionalProperties Endpoints", () => { kind: "kind1", start: "2021-01-01T00:00:00Z", end: "2021-01-02T00:00:00Z", - } as any, + }, }, }; it("GET returns the expected response", async () => { @@ -393,13 +402,13 @@ describe("Missing AdditionalProperties Endpoints", () => { expect(response).toEqual(expected); }); it("PUT accepts the expected input", async () => { - await client.put(expected); + await client.put(expected as any); }); }); describe("SpreadRecordNonDiscriminatedUnion2", () => { const client = new SpreadRecordNonDiscriminatedUnion2Client(clientOptions); - const expected: SpreadRecordForNonDiscriminatedUnion2 = { + const expected = { name: "abc", additionalProperties: { prop1: { kind: "kind1", start: "2021-01-01T00:00:00Z" }, @@ -407,7 +416,7 @@ describe("Missing AdditionalProperties Endpoints", () => { kind: "kind1", start: "2021-01-01T00:00:00Z", end: "2021-01-02T00:00:00Z", - } as any, + }, }, }; it("GET returns the expected response", async () => { @@ -415,13 +424,13 @@ describe("Missing AdditionalProperties Endpoints", () => { expect(response).toEqual(expected); }); it("PUT accepts the expected input", async () => { - await client.put(expected); + await client.put(expected as any); }); }); describe("SpreadRecordNonDiscriminatedUnion3", () => { const client = new SpreadRecordNonDiscriminatedUnion3Client(clientOptions); - const expected: SpreadRecordForNonDiscriminatedUnion3 = { + const expected = { name: "abc", additionalProperties: { prop1: [ @@ -432,7 +441,7 @@ describe("Missing AdditionalProperties Endpoints", () => { kind: "kind1", start: "2021-01-01T00:00:00Z", end: "2021-01-02T00:00:00Z", - } as any, + }, }, }; it("GET returns the expected response", async () => { @@ -440,7 +449,7 @@ describe("Missing AdditionalProperties Endpoints", () => { expect(response).toEqual(expected); }); it("PUT accepts the expected input", async () => { - await client.put(expected); + await client.put(expected as any); }); }); }); diff --git a/packages/http-client-js/test/scenarios/additional-properties/extends.md b/packages/http-client-js/test/scenarios/additional-properties/extends.md index 93ad1e93915..71890fcb603 100644 --- a/packages/http-client-js/test/scenarios/additional-properties/extends.md +++ b/packages/http-client-js/test/scenarios/additional-properties/extends.md @@ -20,11 +20,10 @@ op foo(): Widget; Should generate a model with name `Widget` with the known properties in the root and an evelop property called `additionalProperties` ```ts src/models/models.ts interface Widget -export interface Widget { +export interface Widget extends Record { name: string; age: number; optional?: string; - additionalProperties?: Record; } ``` @@ -38,7 +37,7 @@ export function jsonWidgetToTransportTransform(input_?: Widget | null): any { return input_ as any; } return { - ...jsonRecordUnknownToTransportTransform(input_.additionalProperties), + ...jsonRecordUnknownToTransportTransform((({ name, age, optional, ...rest }) => rest)(input_)), name: input_.name, age: input_.age, optional: input_.optional, @@ -56,7 +55,7 @@ export function jsonWidgetToApplicationTransform(input_?: any): Widget { return input_ as any; } return { - additionalProperties: jsonRecordUnknownToApplicationTransform( + ...jsonRecordUnknownToApplicationTransform( (({ name, age, optional, ...rest }) => rest)(input_), ), name: input_.name,