diff --git a/.chronus/changes/encode-numeric-as-string-2024-6-25-20-9-18.md b/.chronus/changes/encode-numeric-as-string-2024-6-25-20-9-18.md new file mode 100644 index 0000000000..8ecec9e7c0 --- /dev/null +++ b/.chronus/changes/encode-numeric-as-string-2024-6-25-20-9-18.md @@ -0,0 +1,10 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: feature +packages: + - "@typespec/compiler" + - "@typespec/openapi3" + - "@typespec/xml" +--- + +Add support for encoding numeric types as string diff --git a/docs/libraries/http/encoding.md b/docs/libraries/http/encoding.md index e680334c62..7f665c4fe9 100644 --- a/docs/libraries/http/encoding.md +++ b/docs/libraries/http/encoding.md @@ -134,3 +134,41 @@ model User { + +## Numeric types ( `int64`, `decimal128`, `float64`, etc.) + +By default numeric types are serialized as a JSON number. However for large types like `int64` or `decimal128` that cannot be represented in certain languages like JavaScript it is recommended to serialize them as string over the wire. + + + + + + + +
TypeSpecExample payload
+ +```tsp +model User { + id: int64; // JSON number + + @encode(string) + idAsString: int64; // JSON string + + viaSalar: decimalString; +} + +@encode(string) +scalar decimalString extends decimal128; +``` + + + +```json +{ + "id": 1234567890123456789012345678901234567890, + "idAsString": "1234567890123456789012345678901234567890", + "viaSalar": "1.3" +} +``` + +
diff --git a/docs/standard-library/built-in-decorators.md b/docs/standard-library/built-in-decorators.md index 7317eae744..d30994aa58 100644 --- a/docs/standard-library/built-in-decorators.md +++ b/docs/standard-library/built-in-decorators.md @@ -100,7 +100,7 @@ model Pet {} Specify how to encode the target type. ```typespec -@encode(encoding: string | EnumMember, encodedAs?: Scalar) +@encode(encodingOrEncodeAs: Scalar | valueof string | EnumMember, encodedAs?: Scalar) ``` #### Target @@ -110,7 +110,7 @@ Specify how to encode the target type. #### Parameters | Name | Type | Description | |------|------|-------------| -| encoding | `string \| EnumMember` | Known name of an encoding. | +| encodingOrEncodeAs | `Scalar` \| `valueof string \| EnumMember` | Known name of an encoding or a scalar type to encode as(Only for numeric types to encode as string). | | encodedAs | `Scalar` | What target type is this being encoded as. Default to string. | #### Examples @@ -130,6 +130,15 @@ scalar myDateTime extends offsetDateTime; scalar myDateTime extends unixTimestamp; ``` +##### encode numeric type to string + + +```tsp +model Pet { + @encode(string) id: int64; +} +``` + ### `@encodedName` {#@encodedName} diff --git a/packages/compiler/generated-defs/TypeSpec.ts b/packages/compiler/generated-defs/TypeSpec.ts index 8ff600c510..5d6514db23 100644 --- a/packages/compiler/generated-defs/TypeSpec.ts +++ b/packages/compiler/generated-defs/TypeSpec.ts @@ -1,6 +1,7 @@ import type { DecoratorContext, Enum, + EnumValue, Interface, Model, ModelProperty, @@ -26,7 +27,7 @@ export interface OperationExample { /** * Specify how to encode the target type. * - * @param encoding Known name of an encoding. + * @param encodingOrEncodeAs Known name of an encoding or a scalar type to encode as(Only for numeric types to encode as string). * @param encodedAs What target type is this being encoded as. Default to string. * @example offsetDateTime encoded with rfc7231 * @@ -40,11 +41,18 @@ export interface OperationExample { * @encode("unixTimestamp", int32) * scalar myDateTime extends unixTimestamp; * ``` + * @example encode numeric type to string + * + * ```tsp + * model Pet { + * @encode(string) id: int64; + * } + * ``` */ export type EncodeDecorator = ( context: DecoratorContext, target: Scalar | ModelProperty, - encoding: Type, + encodingOrEncodeAs: Scalar | string | EnumValue, encodedAs?: Scalar ) => void; diff --git a/packages/compiler/lib/std/decorators.tsp b/packages/compiler/lib/std/decorators.tsp index 48e685b779..efd604dcd2 100644 --- a/packages/compiler/lib/std/decorators.tsp +++ b/packages/compiler/lib/std/decorators.tsp @@ -477,7 +477,7 @@ enum BytesKnownEncoding { /** * Specify how to encode the target type. - * @param encoding Known name of an encoding. + * @param encodingOrEncodeAs Known name of an encoding or a scalar type to encode as(Only for numeric types to encode as string). * @param encodedAs What target type is this being encoded as. Default to string. * * @example offsetDateTime encoded with rfc7231 @@ -493,10 +493,18 @@ enum BytesKnownEncoding { * @encode("unixTimestamp", int32) * scalar myDateTime extends unixTimestamp; * ``` + * + * @example encode numeric type to string + * + * ```tsp + * model Pet { + * @encode(string) id: int64; + * } + * ``` */ extern dec encode( target: Scalar | ModelProperty, - encoding: string | EnumMember, + encodingOrEncodeAs: (valueof string | EnumMember) | Scalar, encodedAs?: Scalar ); diff --git a/packages/compiler/src/core/messages.ts b/packages/compiler/src/core/messages.ts index 025d50a5ef..b4e832f950 100644 --- a/packages/compiler/src/core/messages.ts +++ b/packages/compiler/src/core/messages.ts @@ -854,6 +854,7 @@ const diagnostics = { wrongType: paramMessage`Encoding '${"encoding"}' cannot be used on type '${"type"}'. Expected: ${"expected"}.`, wrongEncodingType: paramMessage`Encoding '${"encoding"}' on type '${"type"}' is expected to be serialized as '${"expected"}' but got '${"actual"}'.`, wrongNumericEncodingType: paramMessage`Encoding '${"encoding"}' on type '${"type"}' is expected to be serialized as '${"expected"}' but got '${"actual"}'. Set '@encode' 2nd parameter to be of type ${"expected"}. e.g. '@encode("${"encoding"}", int32)'`, + firstArg: `First argument of "@encode" must be the encoding name or the string type when encoding numeric types.`, }, }, diff --git a/packages/compiler/src/lib/decorators.ts b/packages/compiler/src/lib/decorators.ts index 0535acbc87..90961eca1c 100644 --- a/packages/compiler/src/lib/decorators.ts +++ b/packages/compiler/src/lib/decorators.ts @@ -55,6 +55,7 @@ import { getTypeName, ignoreDiagnostics, isArrayModelType, + isValue, reportDeprecated, validateDecoratorUniqueOnNode, } from "../core/index.js"; @@ -90,6 +91,7 @@ import { DiagnosticTarget, Enum, EnumMember, + EnumValue, Interface, Model, ModelProperty, @@ -682,8 +684,13 @@ export function isSecret(program: Program, target: Type): boolean | undefined { export type DateTimeKnownEncoding = "rfc3339" | "rfc7231" | "unixTimestamp"; export type DurationKnownEncoding = "ISO8601" | "seconds"; export type BytesKnownEncoding = "base64" | "base64url"; + export interface EncodeData { - encoding: DateTimeKnownEncoding | DurationKnownEncoding | BytesKnownEncoding | string; + /** + * Known encoding key. + * Can be undefined when `@encode(string)` is used on a numeric type. In that case it just means using the base10 decimal representation of the number. + */ + encoding?: DateTimeKnownEncoding | DurationKnownEncoding | BytesKnownEncoding | string; type: Scalar; } @@ -691,38 +698,48 @@ const encodeKey = createStateSymbol("encode"); export const $encode: EncodeDecorator = ( context: DecoratorContext, target: Scalar | ModelProperty, - encoding: string | Type, + encoding: string | EnumValue | Scalar, encodeAs?: Scalar ) => { validateDecoratorUniqueOnNode(context, target, $encode); - const encodingStr = computeEncoding(encoding); - if (encodingStr === undefined) { + const encodeData = computeEncoding(context.program, encoding, encodeAs); + if (encodeData === undefined) { return; } - const encodeData: EncodeData = { - encoding: encodingStr, - type: encodeAs ?? context.program.checker.getStdType("string"), - }; const targetType = getPropertyType(target); validateEncodeData(context, targetType, encodeData); context.program.stateMap(encodeKey).set(target, encodeData); }; -function computeEncoding(encoding: string | Type) { - if (typeof encoding === "string") { - return encoding; - } - switch (encoding.kind) { - case "String": - return encoding.value; - case "EnumMember": - if (encoding.value && typeof encoding.value === "string") { - return encoding.value; - } else { - return getTypeName(encoding); - } - default: + +function computeEncoding( + program: Program, + encodingOrEncodeAs: string | EnumValue | Scalar, + encodeAs: Scalar | undefined +): EncodeData | undefined { + const strType = program.checker.getStdType("string"); + const resolvedEncodeAs = encodeAs ?? strType; + if (typeof encodingOrEncodeAs === "string") { + return { encoding: encodingOrEncodeAs, type: resolvedEncodeAs }; + } else if (isValue(encodingOrEncodeAs)) { + const member = encodingOrEncodeAs.value; + if (member.value && typeof member.value === "string") { + return { encoding: member.value, type: resolvedEncodeAs }; + } else { + return { encoding: getTypeName(member), type: resolvedEncodeAs }; + } + } else { + const originalType = encodingOrEncodeAs.projectionBase ?? encodingOrEncodeAs; + if (originalType !== strType) { + reportDiagnostic(program, { + code: "invalid-encode", + messageId: "firstArg", + target: encodingOrEncodeAs, + }); return undefined; + } + + return { type: encodingOrEncodeAs }; } } @@ -742,7 +759,7 @@ function validateEncodeData(context: DecoratorContext, target: Type, encodeData: code: "invalid-encode", messageId: "wrongType", format: { - encoding: encodeData.encoding, + encoding: encodeData.encoding ?? "string", type: getTypeName(target), expected: validTargets.join(", "), }, @@ -763,11 +780,11 @@ function validateEncodeData(context: DecoratorContext, target: Type, encodeData: const typeName = getTypeName(encodeData.type.projectionBase ?? encodeData.type); reportDiagnostic(context.program, { code: "invalid-encode", - messageId: ["unixTimestamp", "seconds"].includes(encodeData.encoding) + messageId: ["unixTimestamp", "seconds"].includes(encodeData.encoding ?? "string") ? "wrongNumericEncodingType" : "wrongEncodingType", format: { - encoding: encodeData.encoding, + encoding: encodeData.encoding!, type: getTypeName(target), expected: validEncodeTypes.join(", "), actual: typeName, @@ -790,6 +807,8 @@ function validateEncodeData(context: DecoratorContext, target: Type, encodeData: return check(["bytes"], ["string"]); case "base64url": return check(["bytes"], ["string"]); + case undefined: + return check(["numeric"], ["string"]); } } diff --git a/packages/compiler/test/decorators/decorators.test.ts b/packages/compiler/test/decorators/decorators.test.ts index c8237af518..ebe846addd 100644 --- a/packages/compiler/test/decorators/decorators.test.ts +++ b/packages/compiler/test/decorators/decorators.test.ts @@ -731,6 +731,19 @@ describe("compiler: built-in decorators", () => { strictEqual(encodeData.type.name, encodeAs ?? "string"); }); }); + + it(`@encode(string) on numeric scalar`, async () => { + const { s } = (await runner.compile(` + @encode(string) + @test + scalar s extends int64; + `)) as { s: Scalar }; + + const encodeData = getEncode(runner.program, s); + ok(encodeData); + strictEqual(encodeData.encoding, undefined); + strictEqual(encodeData.type.name, "string"); + }); }); describe("invalid", () => { invalidCases.forEach(([target, encoding, encodeAs, expectedCode, expectedMessage]) => { @@ -750,6 +763,20 @@ describe("compiler: built-in decorators", () => { }); }); }); + + it(`@encode(string) on non-numeric scalar`, async () => { + const diagnostics = await runner.diagnose(` + @encode(string) + @test + scalar s extends utcDateTime; + `); + + expectDiagnostics(diagnostics, { + code: "invalid-encode", + severity: "error", + message: "Encoding 'string' cannot be used on type 's'. Expected: numeric.", + }); + }); }); }); }); diff --git a/packages/openapi3/src/encoding.ts b/packages/openapi3/src/encoding.ts new file mode 100644 index 0000000000..efad0f0c28 --- /dev/null +++ b/packages/openapi3/src/encoding.ts @@ -0,0 +1,57 @@ +import { type ModelProperty, Program, type Scalar, getEncode } from "@typespec/compiler"; +import type { ResolvedOpenAPI3EmitterOptions } from "./openapi.js"; +import { getSchemaForStdScalars } from "./std-scalar-schemas.js"; +import type { OpenAPI3Schema } from "./types.js"; + +export function applyEncoding( + program: Program, + typespecType: Scalar | ModelProperty, + target: OpenAPI3Schema, + options: ResolvedOpenAPI3EmitterOptions +): OpenAPI3Schema { + const encodeData = getEncode(program, typespecType); + if (encodeData) { + const newTarget = { ...target }; + const newType = getSchemaForStdScalars(encodeData.type as any, options); + newTarget.type = newType.type; + // If the target already has a format it takes priority. (e.g. int32) + newTarget.format = mergeFormatAndEncoding( + newTarget.format, + encodeData.encoding, + newType.format + ); + return newTarget; + } + return target; +} + +function mergeFormatAndEncoding( + format: string | undefined, + encoding: string | undefined, + encodeAsFormat: string | undefined +): string | undefined { + switch (format) { + case undefined: + return encodeAsFormat ?? encoding ?? format; + case "date-time": + switch (encoding) { + case "rfc3339": + return "date-time"; + case "unixTimestamp": + return "unixtime"; + case "rfc7231": + return "http-date"; + default: + return encoding; + } + case "duration": + switch (encoding) { + case "ISO8601": + return "duration"; + default: + return encodeAsFormat ?? encoding; + } + default: + return encodeAsFormat ?? encoding ?? format; + } +} diff --git a/packages/openapi3/src/openapi.ts b/packages/openapi3/src/openapi.ts index 4a0041a075..073e469567 100644 --- a/packages/openapi3/src/openapi.ts +++ b/packages/openapi3/src/openapi.ts @@ -10,7 +10,6 @@ import { getAllTags, getAnyExtensionFromPath, getDoc, - getEncode, getFormat, getKnownValues, getMaxItems, @@ -44,7 +43,6 @@ import { ProjectionApplication, projectProgram, resolvePath, - Scalar, serializeValueAsJson, Service, Type, @@ -97,6 +95,7 @@ import { import { buildVersionProjections, VersionProjections } from "@typespec/versioning"; import { stringify } from "yaml"; import { getRef } from "./decorators.js"; +import { applyEncoding } from "./encoding.js"; import { createDiagnostic, FileType, OpenAPI3EmitterOptions } from "./lib.js"; import { getDefaultValue, isBytesKeptRaw, OpenAPI3SchemaEmitter } from "./schema-emitter.js"; import { @@ -1476,7 +1475,12 @@ function createOAPIEmitter( if (!typeSchema) { return undefined; } - const schema = applyEncoding(param, applyIntrinsicDecorators(param, typeSchema)); + const schema = applyEncoding( + program, + param, + applyIntrinsicDecorators(param, typeSchema), + options + ); if (param.defaultValue) { schema.default = getDefaultValue(program, param.defaultValue); } @@ -1746,61 +1750,6 @@ function createOAPIEmitter( return newTarget; } - function applyEncoding( - typespecType: Scalar | ModelProperty, - target: OpenAPI3Schema - ): OpenAPI3Schema { - const encodeData = getEncode(program, typespecType); - if (encodeData) { - const newTarget = { ...target }; - const newType = callSchemaEmitter( - encodeData.type, - Visibility.Read, - false, - "application/json" - ) as OpenAPI3Schema; - newTarget.type = newType.type; - // If the target already has a format it takes priority. (e.g. int32) - newTarget.format = mergeFormatAndEncoding( - newTarget.format, - encodeData.encoding, - newType.format - ); - return newTarget; - } - return target; - } - function mergeFormatAndEncoding( - format: string | undefined, - encoding: string, - encodeAsFormat: string | undefined - ): string { - switch (format) { - case undefined: - return encodeAsFormat ?? encoding; - case "date-time": - switch (encoding) { - case "rfc3339": - return "date-time"; - case "unixTimestamp": - return "unixtime"; - case "rfc7231": - return "http-date"; - default: - return encoding; - } - case "duration": - switch (encoding) { - case "ISO8601": - return "duration"; - default: - return encodeAsFormat ?? encoding; - } - default: - return encodeAsFormat ?? encoding; - } - } - function applyExternalDocs(typespecType: Type, target: Record) { const externalDocs = getExternalDocs(program, typespecType); if (externalDocs) { diff --git a/packages/openapi3/src/schema-emitter.ts b/packages/openapi3/src/schema-emitter.ts index 00a6885dd4..04b276afbd 100644 --- a/packages/openapi3/src/schema-emitter.ts +++ b/packages/openapi3/src/schema-emitter.ts @@ -73,8 +73,10 @@ import { shouldInline, } from "@typespec/openapi"; import { getOneOf, getRef } from "./decorators.js"; +import { applyEncoding } from "./encoding.js"; import { OpenAPI3EmitterOptions, reportDiagnostic } from "./lib.js"; import { ResolvedOpenAPI3EmitterOptions } from "./openapi.js"; +import { getSchemaForStdScalars } from "./std-scalar-schemas.js"; import { OpenAPI3Discriminator, OpenAPI3Schema, OpenAPI3SchemaProperty } from "./types.js"; import { VisibilityUsageTracker } from "./visibility-usage.js"; @@ -718,66 +720,7 @@ export class OpenAPI3SchemaEmitter extends TypeEmitter< } #getSchemaForStdScalars(scalar: Scalar & { name: IntrinsicScalarName }): OpenAPI3Schema { - switch (scalar.name) { - case "bytes": - return { type: "string", format: "byte" }; - case "numeric": - return { type: "number" }; - case "integer": - return { type: "integer" }; - case "int8": - return { type: "integer", format: "int8" }; - case "int16": - return { type: "integer", format: "int16" }; - case "int32": - return { type: "integer", format: "int32" }; - case "int64": - return { type: "integer", format: "int64" }; - case "safeint": - switch (this.#options.safeintStrategy) { - case "double-int": - return { type: "integer", format: "double-int" }; - case "int64": - default: - return { type: "integer", format: "int64" }; - } - case "uint8": - return { type: "integer", format: "uint8" }; - case "uint16": - return { type: "integer", format: "uint16" }; - case "uint32": - return { type: "integer", format: "uint32" }; - case "uint64": - return { type: "integer", format: "uint64" }; - case "float": - return { type: "number" }; - case "float64": - return { type: "number", format: "double" }; - case "float32": - return { type: "number", format: "float" }; - case "decimal": - return { type: "number", format: "decimal" }; - case "decimal128": - return { type: "number", format: "decimal128" }; - case "string": - return { type: "string" }; - case "boolean": - return { type: "boolean" }; - case "plainDate": - return { type: "string", format: "date" }; - case "utcDateTime": - case "offsetDateTime": - return { type: "string", format: "date-time" }; - case "plainTime": - return { type: "string", format: "time" }; - case "duration": - return { type: "string", format: "duration" }; - case "url": - return { type: "string", format: "uri" }; - default: - const _assertNever: never = scalar.name; - return {}; - } + return getSchemaForStdScalars(scalar, this.#options); } #applySchemaExamples( @@ -904,31 +847,16 @@ export class OpenAPI3SchemaEmitter extends TypeEmitter< typespecType: Scalar | ModelProperty, target: OpenAPI3Schema | Placeholder ): OpenAPI3Schema { - const encodeData = getEncode(this.emitter.getProgram(), typespecType); - if (encodeData) { - const newTarget = new ObjectBuilder(target); - const newType = this.#getSchemaForStdScalars(encodeData.type as any); - - newTarget.type = newType.type; - // If the target already has a format it takes priority. (e.g. int32) - newTarget.format = this.#mergeFormatAndEncoding( - newTarget.format, - encodeData.encoding, - newType.format - ); - return newTarget; - } - const result = new ObjectBuilder(target); - return result; + return applyEncoding(this.emitter.getProgram(), typespecType, target as any, this.#options); } #mergeFormatAndEncoding( format: string | undefined, - encoding: string, + encoding: string | undefined, encodeAsFormat: string | undefined - ): string { + ): string | undefined { switch (format) { case undefined: - return encodeAsFormat ?? encoding; + return encodeAsFormat ?? encoding ?? format; case "date-time": switch (encoding) { case "rfc3339": diff --git a/packages/openapi3/src/std-scalar-schemas.ts b/packages/openapi3/src/std-scalar-schemas.ts new file mode 100644 index 0000000000..96075ef6ac --- /dev/null +++ b/packages/openapi3/src/std-scalar-schemas.ts @@ -0,0 +1,69 @@ +import type { IntrinsicScalarName, Scalar } from "@typespec/compiler"; +import type { ResolvedOpenAPI3EmitterOptions } from "./openapi.js"; +import type { OpenAPI3Schema } from "./types.js"; + +export function getSchemaForStdScalars( + scalar: Scalar & { name: IntrinsicScalarName }, + options: ResolvedOpenAPI3EmitterOptions +): OpenAPI3Schema { + switch (scalar.name) { + case "bytes": + return { type: "string", format: "byte" }; + case "numeric": + return { type: "number" }; + case "integer": + return { type: "integer" }; + case "int8": + return { type: "integer", format: "int8" }; + case "int16": + return { type: "integer", format: "int16" }; + case "int32": + return { type: "integer", format: "int32" }; + case "int64": + return { type: "integer", format: "int64" }; + case "safeint": + switch (options.safeintStrategy) { + case "double-int": + return { type: "integer", format: "double-int" }; + case "int64": + default: + return { type: "integer", format: "int64" }; + } + case "uint8": + return { type: "integer", format: "uint8" }; + case "uint16": + return { type: "integer", format: "uint16" }; + case "uint32": + return { type: "integer", format: "uint32" }; + case "uint64": + return { type: "integer", format: "uint64" }; + case "float": + return { type: "number" }; + case "float64": + return { type: "number", format: "double" }; + case "float32": + return { type: "number", format: "float" }; + case "decimal": + return { type: "number", format: "decimal" }; + case "decimal128": + return { type: "number", format: "decimal128" }; + case "string": + return { type: "string" }; + case "boolean": + return { type: "boolean" }; + case "plainDate": + return { type: "string", format: "date" }; + case "utcDateTime": + case "offsetDateTime": + return { type: "string", format: "date-time" }; + case "plainTime": + return { type: "string", format: "time" }; + case "duration": + return { type: "string", format: "duration" }; + case "url": + return { type: "string", format: "uri" }; + default: + const _assertNever: never = scalar.name; + return {}; + } +} diff --git a/packages/openapi3/test/primitive-types.test.ts b/packages/openapi3/test/primitive-types.test.ts index 2a430823c4..9ee9f8e4af 100644 --- a/packages/openapi3/test/primitive-types.test.ts +++ b/packages/openapi3/test/primitive-types.test.ts @@ -259,11 +259,16 @@ describe("openapi3: primitives", () => { async function testEncode( scalar: string, expectedOpenApi: OpenAPI3Schema, - encoding?: string, + encoding?: string | null, encodeAs?: string ) { const encodeAsParam = encodeAs ? `, ${encodeAs}` : ""; - const encodeDecorator = encoding ? `@encode("${encoding}"${encodeAsParam})` : ""; + const encodeDecorator = + encoding === null + ? `@encode(${encodeAs})` + : encoding !== undefined + ? `@encode("${encoding}"${encodeAsParam})` + : ""; const res1 = await oapiForModel("s", `${encodeDecorator} scalar s extends ${scalar};`); deepStrictEqual(res1.schemas.s, expectedOpenApi); const res2 = await oapiForModel("Test", `model Test {${encodeDecorator} prop: ${scalar}};`); @@ -309,5 +314,19 @@ describe("openapi3: primitives", () => { it("set format to base64url when encoding bytes as base64url", () => testEncode("bytes", { type: "string", format: "base64url" }, "base64url")); }); + + describe("int64", () => { + it("set type: integer and format to 'int64' by default", () => + testEncode("int64", { type: "integer", format: "int64" })); + it("set type: string and format to int64 when @encode(string)", () => + testEncode("int64", { type: "string", format: "int64" }, null, "string")); + }); + + describe("decimal128", () => { + it("set type: integer and format to 'int64' by default", () => + testEncode("decimal128", { type: "number", format: "decimal128" })); + it("set type: string and format to int64 when @encode(string)", () => + testEncode("decimal128", { type: "string", format: "decimal128" }, null, "string")); + }); }); }); diff --git a/packages/xml/src/types.ts b/packages/xml/src/types.ts index 9fabad38ca..93e027a248 100644 --- a/packages/xml/src/types.ts +++ b/packages/xml/src/types.ts @@ -21,6 +21,6 @@ export type XmlEncoding = | "TypeSpec.Xml.Encoding.xmlBase64Binary"; export interface XmlEncodeData extends EncodeData { - encoding: XmlEncoding | EncodeData["encoding"]; + encoding?: XmlEncoding | EncodeData["encoding"]; type: Scalar; }