From 1bd68af3f74e69934af2666ae3ae9781d35b9da6 Mon Sep 17 00:00:00 2001 From: Mary Gao Date: Wed, 20 Dec 2023 16:21:37 +0800 Subject: [PATCH] Support decimal type in RLC (#2170) * Support decimal type in RLC * Update the test codes * Re-generate the test and enable decimal cases * Update the test cases for decimal * Update the wordings * update the test cases * Update the UTs for test cases --- common/config/rush/pnpm-lock.yaml | 8 +- packages/typespec-ts/package.json | 2 +- packages/typespec-ts/src/lib.ts | 6 + packages/typespec-ts/src/utils/modelUtils.ts | 50 ++++- .../propertyTypes/src/clientDefinitions.ts | 30 +++ .../models/propertyTypes/src/models.ts | 30 +++ .../models/propertyTypes/src/outputModels.ts | 30 +++ .../models/propertyTypes/src/parameters.ts | 19 ++ .../models/propertyTypes/src/responses.ts | 24 +++ .../generated/scalar/src/clientDefinitions.ts | 104 ++++++++++ .../generated/scalar/src/parameters.ts | 102 ++++++++++ .../generated/scalar/src/responses.ts | 55 +++++ .../integration/modelPropertyType.spec.ts | 8 + .../test/integration/scalar.spec.ts | 107 ++++++++++ .../test/unit/modelsGenerator.spec.ts | 188 +++++++++++++++++- packages/typespec-ts/test/util/emitUtil.ts | 7 +- 16 files changed, 756 insertions(+), 14 deletions(-) diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 9aee7f1b77..c7f3e227af 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -231,7 +231,7 @@ importers: '@azure-rest/core-client': ^1.1.6 '@azure-tools/cadl-ranch': ^0.10.0 '@azure-tools/cadl-ranch-expect': ^0.9.0 - '@azure-tools/cadl-ranch-specs': ^0.26.1 + '@azure-tools/cadl-ranch-specs': ^0.26.2 '@azure-tools/rlc-common': workspace:^0.19.0 '@azure-tools/typespec-azure-core': '>=0.36.0 <1.0.0' '@azure-tools/typespec-client-generator-core': '>=0.36.1 <1.0.0' @@ -283,7 +283,7 @@ importers: '@azure-rest/core-client': 1.1.6 '@azure-tools/cadl-ranch': 0.10.0_3hgbimueyj4cov2ejjvw37nlra '@azure-tools/cadl-ranch-expect': 0.9.0_34oeiyjczsyn4xxwbbzj3xeide - '@azure-tools/cadl-ranch-specs': 0.26.1_d6rreab6h3zzjhymhpmevgdlii + '@azure-tools/cadl-ranch-specs': 0.26.2_d6rreab6h3zzjhymhpmevgdlii '@azure/core-auth': 1.5.0 '@azure/core-lro': 2.5.4 '@azure/core-paging': 1.5.0 @@ -402,8 +402,8 @@ packages: '@typespec/versioning': 0.50.0_@typespec+compiler@0.50.0 dev: true - /@azure-tools/cadl-ranch-specs/0.26.1_d6rreab6h3zzjhymhpmevgdlii: - resolution: {integrity: sha512-BkOp1ZTl8X08Dq1wgzBHQirrge1DNzhhyaugEsnFnsWIPWmidj3yfSgWem6I4qmc4r1+jq2iVdeMyXTYDeMEcg==} + /@azure-tools/cadl-ranch-specs/0.26.2_d6rreab6h3zzjhymhpmevgdlii: + resolution: {integrity: sha512-752/7At1Fs51i4LOLByD6voV4cqf7cgIZuNZkqAROza8mFk+KVqEOTGdRM9JbMspTTUwDM+z5dx6dxN9ARhNnA==} engines: {node: '>=16.0.0'} peerDependencies: '@azure-tools/cadl-ranch-expect': ~0.9.0 diff --git a/packages/typespec-ts/package.json b/packages/typespec-ts/package.json index 438b66b22a..53c8ec9d6d 100644 --- a/packages/typespec-ts/package.json +++ b/packages/typespec-ts/package.json @@ -58,7 +58,7 @@ "ts-node": "~10.9.1", "typescript": "~5.2.0", "prettier": "~2.7.1", - "@azure-tools/cadl-ranch-specs": "^0.26.1", + "@azure-tools/cadl-ranch-specs": "^0.26.2", "@azure-tools/cadl-ranch-expect": "^0.9.0", "@azure-tools/cadl-ranch": "^0.10.0", "chalk": "^4.0.0", diff --git a/packages/typespec-ts/src/lib.ts b/packages/typespec-ts/src/lib.ts index 6708c5b46f..ee390573df 100644 --- a/packages/typespec-ts/src/lib.ts +++ b/packages/typespec-ts/src/lib.ts @@ -198,6 +198,12 @@ const libDef = { messages: { default: paramMessage`Please specify @items property for the paging operation - ${"operationName"}.` } + }, + "decimal-to-number": { + severity: "warning", + messages: { + default: paramMessage`Please note the decimal type will be converted to number. If you strongly care about precision you can use @encode to encode it as a string for the property - ${"propertyName"}.` + } } }, emitter: { diff --git a/packages/typespec-ts/src/utils/modelUtils.ts b/packages/typespec-ts/src/utils/modelUtils.ts index 65a8c3bbcf..dae8ef2d77 100644 --- a/packages/typespec-ts/src/utils/modelUtils.ts +++ b/packages/typespec-ts/src/utils/modelUtils.ts @@ -1052,7 +1052,7 @@ function getSchemaForStdScalar( case "safeint": return applyIntrinsicDecorators(program, type, { type: "number", - format: "int64" + format: "safeint" }); case "uint8": return applyIntrinsicDecorators(program, type, { @@ -1077,18 +1077,44 @@ function getSchemaForStdScalar( case "float64": return applyIntrinsicDecorators(program, type, { type: "number", - format: "double" + format: "float64" }); case "float32": return applyIntrinsicDecorators(program, type, { type: "number", - format: "float" + format: "float32" }); case "float": return applyIntrinsicDecorators(program, type, { type: "number", format: "float" }); + case "decimal": + reportDiagnostic(program, { + code: "decimal-to-number", + format: { + propertyName: relevantProperty?.name ?? "" + }, + target: relevantProperty ?? type + }); + return applyIntrinsicDecorators(program, type, { + type: "number", + format: "decimal", + description: "decimal" + }); + case "decimal128": + reportDiagnostic(program, { + code: "decimal-to-number", + format: { + propertyName: relevantProperty?.name ?? "" + }, + target: relevantProperty ?? type + }); + return applyIntrinsicDecorators(program, type, { + type: "number", + format: "decimal128", + description: "decimal128" + }); case "string": if (format === "binary") { return { @@ -1243,6 +1269,21 @@ function getEnumStringDescription(type: any) { return undefined; } +function getDecimalDescription(type: any) { + if ( + (type.format === "decimal" || type.format === "decimal128") && + type.type === "number" + ) { + return `NOTE: This property is represented as a 'number' in JavaScript, but it corresponds to a 'decimal' type in other languages. +Due to the inherent limitations of floating-point arithmetic in JavaScript, precision issues may arise when performing arithmetic operations. +If your application requires high precision for arithmetic operations or when round-tripping data back to other languages, consider using a library like decimal.js, which provides an arbitrary-precision Decimal type. +For simpler cases, where you need to control the number of decimal places for display purposes, you can use the 'toFixed()' method. However, be aware that 'toFixed()' returns a string and may not be suitable for all arithmetic precision requirements. +Always be cautious with direct arithmetic operations and consider implementing appropriate rounding strategies to maintain accuracy. + `; + } + return undefined; +} + export function getFormattedPropertyDoc( program: Program, type: ModelProperty | Type, @@ -1250,7 +1291,8 @@ export function getFormattedPropertyDoc( sperator: string = "\n\n" ) { const propertyDoc = getDoc(program, type); - const enhancedDocFromType = getEnumStringDescription(schemaType); + const enhancedDocFromType = + getEnumStringDescription(schemaType) ?? getDecimalDescription(schemaType); if (propertyDoc && enhancedDocFromType) { return `${propertyDoc}${sperator}${enhancedDocFromType}`; } diff --git a/packages/typespec-ts/test/integration/generated/models/propertyTypes/src/clientDefinitions.ts b/packages/typespec-ts/test/integration/generated/models/propertyTypes/src/clientDefinitions.ts index f6ad6d5079..5276b9c750 100644 --- a/packages/typespec-ts/test/integration/generated/models/propertyTypes/src/clientDefinitions.ts +++ b/packages/typespec-ts/test/integration/generated/models/propertyTypes/src/clientDefinitions.ts @@ -12,6 +12,10 @@ import { IntPutParameters, FloatGetParameters, FloatPutParameters, + DecimalGetParameters, + DecimalPutParameters, + Decimal128GetParameters, + Decimal128PutParameters, DatetimeGetParameters, DatetimePutParameters, DurationGetParameters, @@ -66,6 +70,10 @@ import { IntPut204Response, FloatGet200Response, FloatPut204Response, + DecimalGet200Response, + DecimalPut204Response, + Decimal128Get200Response, + Decimal128Put204Response, DatetimeGet200Response, DatetimePut204Response, DurationGet200Response, @@ -154,6 +162,24 @@ export interface FloatGet { put(options: FloatPutParameters): StreamableMethod; } +export interface DecimalGet { + /** Get call */ + get(options?: DecimalGetParameters): StreamableMethod; + /** Put operation */ + put(options: DecimalPutParameters): StreamableMethod; +} + +export interface Decimal128Get { + /** Get call */ + get( + options?: Decimal128GetParameters + ): StreamableMethod; + /** Put operation */ + put( + options: Decimal128PutParameters + ): StreamableMethod; +} + export interface DatetimeGet { /** Get call */ get( @@ -380,6 +406,10 @@ export interface Routes { (path: "/type/property/value-types/int"): IntGet; /** Resource for '/type/property/value-types/float' has methods for the following verbs: get, put */ (path: "/type/property/value-types/float"): FloatGet; + /** Resource for '/type/property/value-types/decimal' has methods for the following verbs: get, put */ + (path: "/type/property/value-types/decimal"): DecimalGet; + /** Resource for '/type/property/value-types/decimal128' has methods for the following verbs: get, put */ + (path: "/type/property/value-types/decimal128"): Decimal128Get; /** Resource for '/type/property/value-types/datetime' has methods for the following verbs: get, put */ (path: "/type/property/value-types/datetime"): DatetimeGet; /** Resource for '/type/property/value-types/duration' has methods for the following verbs: get, put */ diff --git a/packages/typespec-ts/test/integration/generated/models/propertyTypes/src/models.ts b/packages/typespec-ts/test/integration/generated/models/propertyTypes/src/models.ts index 97d846b878..f96396a27f 100644 --- a/packages/typespec-ts/test/integration/generated/models/propertyTypes/src/models.ts +++ b/packages/typespec-ts/test/integration/generated/models/propertyTypes/src/models.ts @@ -31,6 +31,36 @@ export interface FloatProperty { property: number; } +/** Model with a decimal property */ +export interface DecimalProperty { + /** + * Property + * + * NOTE: This property is represented as a 'number' in JavaScript, but it corresponds to a 'decimal' type in other languages. + * Due to the inherent limitations of floating-point arithmetic in JavaScript, precision issues may arise when performing arithmetic operations. + * If your application requires high precision for arithmetic operations or when round-tripping data back to other languages, consider using a library like decimal.js, which provides an arbitrary-precision Decimal type. + * For simpler cases, where you need to control the number of decimal places for display purposes, you can use the 'toFixed()' method. However, be aware that 'toFixed()' returns a string and may not be suitable for all arithmetic precision requirements. + * Always be cautious with direct arithmetic operations and consider implementing appropriate rounding strategies to maintain accuracy. + * + */ + property: number; +} + +/** Model with a decimal128 property */ +export interface Decimal128Property { + /** + * Property + * + * NOTE: This property is represented as a 'number' in JavaScript, but it corresponds to a 'decimal' type in other languages. + * Due to the inherent limitations of floating-point arithmetic in JavaScript, precision issues may arise when performing arithmetic operations. + * If your application requires high precision for arithmetic operations or when round-tripping data back to other languages, consider using a library like decimal.js, which provides an arbitrary-precision Decimal type. + * For simpler cases, where you need to control the number of decimal places for display purposes, you can use the 'toFixed()' method. However, be aware that 'toFixed()' returns a string and may not be suitable for all arithmetic precision requirements. + * Always be cautious with direct arithmetic operations and consider implementing appropriate rounding strategies to maintain accuracy. + * + */ + property: number; +} + /** Model with a datetime property */ export interface DatetimeProperty { /** Property */ diff --git a/packages/typespec-ts/test/integration/generated/models/propertyTypes/src/outputModels.ts b/packages/typespec-ts/test/integration/generated/models/propertyTypes/src/outputModels.ts index 948cfc26d5..ac76b3f934 100644 --- a/packages/typespec-ts/test/integration/generated/models/propertyTypes/src/outputModels.ts +++ b/packages/typespec-ts/test/integration/generated/models/propertyTypes/src/outputModels.ts @@ -31,6 +31,36 @@ export interface FloatPropertyOutput { property: number; } +/** Model with a decimal property */ +export interface DecimalPropertyOutput { + /** + * Property + * + * NOTE: This property is represented as a 'number' in JavaScript, but it corresponds to a 'decimal' type in other languages. + * Due to the inherent limitations of floating-point arithmetic in JavaScript, precision issues may arise when performing arithmetic operations. + * If your application requires high precision for arithmetic operations or when round-tripping data back to other languages, consider using a library like decimal.js, which provides an arbitrary-precision Decimal type. + * For simpler cases, where you need to control the number of decimal places for display purposes, you can use the 'toFixed()' method. However, be aware that 'toFixed()' returns a string and may not be suitable for all arithmetic precision requirements. + * Always be cautious with direct arithmetic operations and consider implementing appropriate rounding strategies to maintain accuracy. + * + */ + property: number; +} + +/** Model with a decimal128 property */ +export interface Decimal128PropertyOutput { + /** + * Property + * + * NOTE: This property is represented as a 'number' in JavaScript, but it corresponds to a 'decimal' type in other languages. + * Due to the inherent limitations of floating-point arithmetic in JavaScript, precision issues may arise when performing arithmetic operations. + * If your application requires high precision for arithmetic operations or when round-tripping data back to other languages, consider using a library like decimal.js, which provides an arbitrary-precision Decimal type. + * For simpler cases, where you need to control the number of decimal places for display purposes, you can use the 'toFixed()' method. However, be aware that 'toFixed()' returns a string and may not be suitable for all arithmetic precision requirements. + * Always be cautious with direct arithmetic operations and consider implementing appropriate rounding strategies to maintain accuracy. + * + */ + property: number; +} + /** Model with a datetime property */ export interface DatetimePropertyOutput { /** Property */ diff --git a/packages/typespec-ts/test/integration/generated/models/propertyTypes/src/parameters.ts b/packages/typespec-ts/test/integration/generated/models/propertyTypes/src/parameters.ts index 17af01e447..d486677055 100644 --- a/packages/typespec-ts/test/integration/generated/models/propertyTypes/src/parameters.ts +++ b/packages/typespec-ts/test/integration/generated/models/propertyTypes/src/parameters.ts @@ -8,6 +8,8 @@ import { BytesProperty, IntProperty, FloatProperty, + DecimalProperty, + Decimal128Property, DatetimeProperty, DurationProperty, EnumProperty, @@ -73,6 +75,23 @@ export interface FloatPutBodyParam { } export type FloatPutParameters = FloatPutBodyParam & RequestParameters; +export type DecimalGetParameters = RequestParameters; + +export interface DecimalPutBodyParam { + /** body */ + body: DecimalProperty; +} + +export type DecimalPutParameters = DecimalPutBodyParam & RequestParameters; +export type Decimal128GetParameters = RequestParameters; + +export interface Decimal128PutBodyParam { + /** body */ + body: Decimal128Property; +} + +export type Decimal128PutParameters = Decimal128PutBodyParam & + RequestParameters; export type DatetimeGetParameters = RequestParameters; export interface DatetimePutBodyParam { diff --git a/packages/typespec-ts/test/integration/generated/models/propertyTypes/src/responses.ts b/packages/typespec-ts/test/integration/generated/models/propertyTypes/src/responses.ts index 76201250b4..4b9a5c9df2 100644 --- a/packages/typespec-ts/test/integration/generated/models/propertyTypes/src/responses.ts +++ b/packages/typespec-ts/test/integration/generated/models/propertyTypes/src/responses.ts @@ -8,6 +8,8 @@ import { BytesPropertyOutput, IntPropertyOutput, FloatPropertyOutput, + DecimalPropertyOutput, + Decimal128PropertyOutput, DatetimePropertyOutput, DurationPropertyOutput, EnumPropertyOutput, @@ -86,6 +88,28 @@ export interface FloatPut204Response extends HttpResponse { status: "204"; } +/** The request has succeeded. */ +export interface DecimalGet200Response extends HttpResponse { + status: "200"; + body: DecimalPropertyOutput; +} + +/** There is no content to send for this request, but the headers may be useful. */ +export interface DecimalPut204Response extends HttpResponse { + status: "204"; +} + +/** The request has succeeded. */ +export interface Decimal128Get200Response extends HttpResponse { + status: "200"; + body: Decimal128PropertyOutput; +} + +/** There is no content to send for this request, but the headers may be useful. */ +export interface Decimal128Put204Response extends HttpResponse { + status: "204"; +} + /** The request has succeeded. */ export interface DatetimeGet200Response extends HttpResponse { status: "200"; diff --git a/packages/typespec-ts/test/integration/generated/scalar/src/clientDefinitions.ts b/packages/typespec-ts/test/integration/generated/scalar/src/clientDefinitions.ts index 868407da82..b54ff6ad48 100644 --- a/packages/typespec-ts/test/integration/generated/scalar/src/clientDefinitions.ts +++ b/packages/typespec-ts/test/integration/generated/scalar/src/clientDefinitions.ts @@ -8,6 +8,16 @@ import { BooleanModelPutParameters, UnknownGetParameters, UnknownPutParameters, + DecimalTypeResponseBodyParameters, + DecimalTypeRequestBodyParameters, + DecimalTypeRequestParameterParameters, + Decimal128TypeResponseBodyParameters, + Decimal128TypeRequestBodyParameters, + Decimal128TypeRequestParameterParameters, + DecimalVerifyPrepareVerifyParameters, + DecimalVerifyVerifyParameters, + Decimal128VerifyPrepareVerifyParameters, + Decimal128VerifyVerifyParameters, } from "./parameters"; import { StringModelGet200Response, @@ -16,6 +26,16 @@ import { BooleanModelPut204Response, UnknownGet200Response, UnknownPut204Response, + DecimalTypeResponseBody200Response, + DecimalTypeRequestBody204Response, + DecimalTypeRequestParameter204Response, + Decimal128TypeResponseBody200Response, + Decimal128TypeRequestBody204Response, + Decimal128TypeRequestParameter204Response, + DecimalVerifyPrepareVerify200Response, + DecimalVerifyVerify204Response, + Decimal128VerifyPrepareVerify200Response, + Decimal128VerifyVerify204Response, } from "./responses"; import { Client, StreamableMethod } from "@azure-rest/core-client"; @@ -48,6 +68,66 @@ export interface UnknownGet { put(options: UnknownPutParameters): StreamableMethod; } +export interface DecimalTypeResponseBody { + get( + options?: DecimalTypeResponseBodyParameters + ): StreamableMethod; +} + +export interface DecimalTypeRequestBody { + put( + options: DecimalTypeRequestBodyParameters + ): StreamableMethod; +} + +export interface DecimalTypeRequestParameter { + get( + options: DecimalTypeRequestParameterParameters + ): StreamableMethod; +} + +export interface Decimal128TypeResponseBody { + get( + options?: Decimal128TypeResponseBodyParameters + ): StreamableMethod; +} + +export interface Decimal128TypeRequestBody { + put( + options: Decimal128TypeRequestBodyParameters + ): StreamableMethod; +} + +export interface Decimal128TypeRequestParameter { + get( + options: Decimal128TypeRequestParameterParameters + ): StreamableMethod; +} + +export interface DecimalVerifyPrepareVerify { + get( + options?: DecimalVerifyPrepareVerifyParameters + ): StreamableMethod; +} + +export interface DecimalVerifyVerify { + post( + options: DecimalVerifyVerifyParameters + ): StreamableMethod; +} + +export interface Decimal128VerifyPrepareVerify { + get( + options?: Decimal128VerifyPrepareVerifyParameters + ): StreamableMethod; +} + +export interface Decimal128VerifyVerify { + post( + options: Decimal128VerifyVerifyParameters + ): StreamableMethod; +} + export interface Routes { /** Resource for '/type/scalar/string' has methods for the following verbs: get, put */ (path: "/type/scalar/string"): StringModelGet; @@ -55,6 +135,30 @@ export interface Routes { (path: "/type/scalar/boolean"): BooleanModelGet; /** Resource for '/type/scalar/unknown' has methods for the following verbs: get, put */ (path: "/type/scalar/unknown"): UnknownGet; + /** Resource for '/type/scalar/decimal/response_body' has methods for the following verbs: get */ + (path: "/type/scalar/decimal/response_body"): DecimalTypeResponseBody; + /** Resource for '/type/scalar/decimal/resquest_body' has methods for the following verbs: put */ + (path: "/type/scalar/decimal/resquest_body"): DecimalTypeRequestBody; + /** Resource for '/type/scalar/decimal/request_parameter' has methods for the following verbs: get */ + (path: "/type/scalar/decimal/request_parameter"): DecimalTypeRequestParameter; + /** Resource for '/type/scalar/decimal128/response_body' has methods for the following verbs: get */ + (path: "/type/scalar/decimal128/response_body"): Decimal128TypeResponseBody; + /** Resource for '/type/scalar/decimal128/resquest_body' has methods for the following verbs: put */ + (path: "/type/scalar/decimal128/resquest_body"): Decimal128TypeRequestBody; + /** Resource for '/type/scalar/decimal128/request_parameter' has methods for the following verbs: get */ + ( + path: "/type/scalar/decimal128/request_parameter" + ): Decimal128TypeRequestParameter; + /** Resource for '/type/scalar/decimal/prepare_verify' has methods for the following verbs: get */ + (path: "/type/scalar/decimal/prepare_verify"): DecimalVerifyPrepareVerify; + /** Resource for '/type/scalar/decimal/verify' has methods for the following verbs: post */ + (path: "/type/scalar/decimal/verify"): DecimalVerifyVerify; + /** Resource for '/type/scalar/decimal128/prepare_verify' has methods for the following verbs: get */ + ( + path: "/type/scalar/decimal128/prepare_verify" + ): Decimal128VerifyPrepareVerify; + /** Resource for '/type/scalar/decimal128/verify' has methods for the following verbs: post */ + (path: "/type/scalar/decimal128/verify"): Decimal128VerifyVerify; } export type ScalarClient = Client & { diff --git a/packages/typespec-ts/test/integration/generated/scalar/src/parameters.ts b/packages/typespec-ts/test/integration/generated/scalar/src/parameters.ts index 8b5c216d29..06a0976fbd 100644 --- a/packages/typespec-ts/test/integration/generated/scalar/src/parameters.ts +++ b/packages/typespec-ts/test/integration/generated/scalar/src/parameters.ts @@ -29,3 +29,105 @@ export interface UnknownPutBodyParam { } export type UnknownPutParameters = UnknownPutBodyParam & RequestParameters; +export type DecimalTypeResponseBodyParameters = RequestParameters; + +export interface DecimalTypeRequestBodyBodyParam { + /** + * NOTE: This property is represented as a 'number' in JavaScript, but it corresponds to a 'decimal' type in other languages. + * Due to the inherent limitations of floating-point arithmetic in JavaScript, precision issues may arise when performing arithmetic operations. + * If your application requires high precision for arithmetic operations or when round-tripping data back to other languages, consider using a library like decimal.js, which provides an arbitrary-precision Decimal type. + * For simpler cases, where you need to control the number of decimal places for display purposes, you can use the 'toFixed()' method. However, be aware that 'toFixed()' returns a string and may not be suitable for all arithmetic precision requirements. + * Always be cautious with direct arithmetic operations and consider implementing appropriate rounding strategies to maintain accuracy. + * + */ + body: number; +} + +export type DecimalTypeRequestBodyParameters = DecimalTypeRequestBodyBodyParam & + RequestParameters; + +export interface DecimalTypeRequestParameterQueryParamProperties { + /** + * NOTE: This property is represented as a 'number' in JavaScript, but it corresponds to a 'decimal' type in other languages. + * Due to the inherent limitations of floating-point arithmetic in JavaScript, precision issues may arise when performing arithmetic operations. + * If your application requires high precision for arithmetic operations or when round-tripping data back to other languages, consider using a library like decimal.js, which provides an arbitrary-precision Decimal type. + * For simpler cases, where you need to control the number of decimal places for display purposes, you can use the 'toFixed()' method. However, be aware that 'toFixed()' returns a string and may not be suitable for all arithmetic precision requirements. + * Always be cautious with direct arithmetic operations and consider implementing appropriate rounding strategies to maintain accuracy. + * + */ + value: number; +} + +export interface DecimalTypeRequestParameterQueryParam { + queryParameters: DecimalTypeRequestParameterQueryParamProperties; +} + +export type DecimalTypeRequestParameterParameters = + DecimalTypeRequestParameterQueryParam & RequestParameters; +export type Decimal128TypeResponseBodyParameters = RequestParameters; + +export interface Decimal128TypeRequestBodyBodyParam { + /** + * NOTE: This property is represented as a 'number' in JavaScript, but it corresponds to a 'decimal' type in other languages. + * Due to the inherent limitations of floating-point arithmetic in JavaScript, precision issues may arise when performing arithmetic operations. + * If your application requires high precision for arithmetic operations or when round-tripping data back to other languages, consider using a library like decimal.js, which provides an arbitrary-precision Decimal type. + * For simpler cases, where you need to control the number of decimal places for display purposes, you can use the 'toFixed()' method. However, be aware that 'toFixed()' returns a string and may not be suitable for all arithmetic precision requirements. + * Always be cautious with direct arithmetic operations and consider implementing appropriate rounding strategies to maintain accuracy. + * + */ + body: number; +} + +export type Decimal128TypeRequestBodyParameters = + Decimal128TypeRequestBodyBodyParam & RequestParameters; + +export interface Decimal128TypeRequestParameterQueryParamProperties { + /** + * NOTE: This property is represented as a 'number' in JavaScript, but it corresponds to a 'decimal' type in other languages. + * Due to the inherent limitations of floating-point arithmetic in JavaScript, precision issues may arise when performing arithmetic operations. + * If your application requires high precision for arithmetic operations or when round-tripping data back to other languages, consider using a library like decimal.js, which provides an arbitrary-precision Decimal type. + * For simpler cases, where you need to control the number of decimal places for display purposes, you can use the 'toFixed()' method. However, be aware that 'toFixed()' returns a string and may not be suitable for all arithmetic precision requirements. + * Always be cautious with direct arithmetic operations and consider implementing appropriate rounding strategies to maintain accuracy. + * + */ + value: number; +} + +export interface Decimal128TypeRequestParameterQueryParam { + queryParameters: Decimal128TypeRequestParameterQueryParamProperties; +} + +export type Decimal128TypeRequestParameterParameters = + Decimal128TypeRequestParameterQueryParam & RequestParameters; +export type DecimalVerifyPrepareVerifyParameters = RequestParameters; + +export interface DecimalVerifyVerifyBodyParam { + /** + * NOTE: This property is represented as a 'number' in JavaScript, but it corresponds to a 'decimal' type in other languages. + * Due to the inherent limitations of floating-point arithmetic in JavaScript, precision issues may arise when performing arithmetic operations. + * If your application requires high precision for arithmetic operations or when round-tripping data back to other languages, consider using a library like decimal.js, which provides an arbitrary-precision Decimal type. + * For simpler cases, where you need to control the number of decimal places for display purposes, you can use the 'toFixed()' method. However, be aware that 'toFixed()' returns a string and may not be suitable for all arithmetic precision requirements. + * Always be cautious with direct arithmetic operations and consider implementing appropriate rounding strategies to maintain accuracy. + * + */ + body: number; +} + +export type DecimalVerifyVerifyParameters = DecimalVerifyVerifyBodyParam & + RequestParameters; +export type Decimal128VerifyPrepareVerifyParameters = RequestParameters; + +export interface Decimal128VerifyVerifyBodyParam { + /** + * NOTE: This property is represented as a 'number' in JavaScript, but it corresponds to a 'decimal' type in other languages. + * Due to the inherent limitations of floating-point arithmetic in JavaScript, precision issues may arise when performing arithmetic operations. + * If your application requires high precision for arithmetic operations or when round-tripping data back to other languages, consider using a library like decimal.js, which provides an arbitrary-precision Decimal type. + * For simpler cases, where you need to control the number of decimal places for display purposes, you can use the 'toFixed()' method. However, be aware that 'toFixed()' returns a string and may not be suitable for all arithmetic precision requirements. + * Always be cautious with direct arithmetic operations and consider implementing appropriate rounding strategies to maintain accuracy. + * + */ + body: number; +} + +export type Decimal128VerifyVerifyParameters = Decimal128VerifyVerifyBodyParam & + RequestParameters; diff --git a/packages/typespec-ts/test/integration/generated/scalar/src/responses.ts b/packages/typespec-ts/test/integration/generated/scalar/src/responses.ts index 19508f5e16..0c1b15cb68 100644 --- a/packages/typespec-ts/test/integration/generated/scalar/src/responses.ts +++ b/packages/typespec-ts/test/integration/generated/scalar/src/responses.ts @@ -35,3 +35,58 @@ export interface UnknownGet200Response extends HttpResponse { export interface UnknownPut204Response extends HttpResponse { status: "204"; } + +/** The request has succeeded. */ +export interface DecimalTypeResponseBody200Response extends HttpResponse { + status: "200"; + body: number; +} + +/** There is no content to send for this request, but the headers may be useful. */ +export interface DecimalTypeRequestBody204Response extends HttpResponse { + status: "204"; +} + +/** There is no content to send for this request, but the headers may be useful. */ +export interface DecimalTypeRequestParameter204Response extends HttpResponse { + status: "204"; +} + +/** The request has succeeded. */ +export interface Decimal128TypeResponseBody200Response extends HttpResponse { + status: "200"; + body: number; +} + +/** There is no content to send for this request, but the headers may be useful. */ +export interface Decimal128TypeRequestBody204Response extends HttpResponse { + status: "204"; +} + +/** There is no content to send for this request, but the headers may be useful. */ +export interface Decimal128TypeRequestParameter204Response + extends HttpResponse { + status: "204"; +} + +/** The request has succeeded. */ +export interface DecimalVerifyPrepareVerify200Response extends HttpResponse { + status: "200"; + body: number[]; +} + +/** There is no content to send for this request, but the headers may be useful. */ +export interface DecimalVerifyVerify204Response extends HttpResponse { + status: "204"; +} + +/** The request has succeeded. */ +export interface Decimal128VerifyPrepareVerify200Response extends HttpResponse { + status: "200"; + body: number[]; +} + +/** There is no content to send for this request, but the headers may be useful. */ +export interface Decimal128VerifyVerify204Response extends HttpResponse { + status: "204"; +} diff --git a/packages/typespec-ts/test/integration/modelPropertyType.spec.ts b/packages/typespec-ts/test/integration/modelPropertyType.spec.ts index dd11591d5a..eb7f8a5f10 100644 --- a/packages/typespec-ts/test/integration/modelPropertyType.spec.ts +++ b/packages/typespec-ts/test/integration/modelPropertyType.spec.ts @@ -31,6 +31,14 @@ const testedTypes: TypeDetail[] = [ type: "float", defaultValue: 42.42 }, + { + type: "decimal", + defaultValue: 0.33333 + }, + { + type: "decimal128", + defaultValue: 0.33333 + }, { type: "datetime", defaultValue: "2022-08-26T18:38:00Z", diff --git a/packages/typespec-ts/test/integration/scalar.spec.ts b/packages/typespec-ts/test/integration/scalar.spec.ts index 580bd551b5..fc0f68199d 100644 --- a/packages/typespec-ts/test/integration/scalar.spec.ts +++ b/packages/typespec-ts/test/integration/scalar.spec.ts @@ -77,4 +77,111 @@ describe("Scalar Client", () => { assert.fail(err as string); } }); + + it("should get decimal response body", async () => { + try { + const result = await client + .path("/type/scalar/decimal/response_body") + .get(); + assert.strictEqual(result.status, "200"); + assert.strictEqual(result.body, 0.33333); + } catch (err) { + assert.fail(err as string); + } + }); + + it("should put decimal request body", async () => { + try { + const result = await client + .path("/type/scalar/decimal/resquest_body") + .put({ body: 0.33333 }); + assert.strictEqual(result.status, "204"); + } catch (err) { + assert.fail(err as string); + } + }); + + it("should get decimal request parameter", async () => { + try { + const result = await client + .path("/type/scalar/decimal/request_parameter") + .get({ queryParameters: { value: 0.33333 } }); + assert.strictEqual(result.status, "204"); + } catch (err) { + assert.fail(err as string); + } + }); + + it("should get decimal128 response body", async () => { + try { + const result = await client + .path("/type/scalar/decimal128/response_body") + .get(); + assert.strictEqual(result.status, "200"); + assert.strictEqual(result.body, 0.33333); + } catch (err) { + assert.fail(err as string); + } + }); + + it("should put decimal128 request body", async () => { + try { + const result = await client + .path("/type/scalar/decimal128/resquest_body") + .put({ body: 0.33333 }); + assert.strictEqual(result.status, "204"); + } catch (err) { + assert.fail(err as string); + } + }); + + it("should get decimal128 request parameter", async () => { + try { + const result = await client + .path("/type/scalar/decimal128/request_parameter") + .get({ queryParameters: { value: 0.33333 } }); + assert.strictEqual(result.status, "204"); + } catch (err) { + assert.fail(err as string); + } + }); + + it("should fail to post decimal verify", async () => { + try { + // prepare the verification + const getResult = await client + .path("/type/scalar/decimal/prepare_verify") + .get(); + // do any calculation based on numbers + let total = 0; + getResult.body.forEach((decimal: number) => { + total += decimal; + }); + const result = await client + .path("/type/scalar/decimal/verify") + .post({ body: total }); + assert.strictEqual(result.status, "400"); + } catch (err) { + assert.fail(err as string); + } + }); + + it("should fail to post decimal128 verify", async () => { + try { + const getResult = await client + .path("/type/scalar/decimal128/prepare_verify") + .get(); + // do any calculation based on numbers + let total = 0; + getResult.body.forEach((decimal: number) => { + total += decimal; + }); + const result = await client + .path("/type/scalar/decimal128/verify") + .post({ body: total }); + assert.strictEqual(result.status, "400"); + } catch (err) { + assert.fail(err as string); + } + }); }); diff --git a/packages/typespec-ts/test/unit/modelsGenerator.spec.ts b/packages/typespec-ts/test/unit/modelsGenerator.spec.ts index de23ee49f2..5701043f0e 100644 --- a/packages/typespec-ts/test/unit/modelsGenerator.spec.ts +++ b/packages/typespec-ts/test/unit/modelsGenerator.spec.ts @@ -346,6 +346,178 @@ describe("Input/output model type", () => { }); }); + describe("decimal generation", () => { + it("should handle decimal -> number", async () => { + const schemaOutput = await emitModelsFromTypeSpec( + ` + model SimpleModel { + prop: decimal; + } + @route("/decimal/prop") + @get + op getModel(...SimpleModel): SimpleModel; + `, + false, + true, + false, + false // disable diagnostics + ); + assert.ok(schemaOutput); + const { inputModelFile, outputModelFile } = schemaOutput!; + assertEqualContent( + inputModelFile?.content!, + ` + export interface SimpleModel { + /** + * NOTE: This property is represented as a 'number' in JavaScript, but it corresponds to a 'decimal' type in other languages. + * Due to the inherent limitations of floating-point arithmetic in JavaScript, precision issues may arise when performing arithmetic operations. + * If your application requires high precision for arithmetic operations or when round-tripping data back to other languages, consider using a library like decimal.js, which provides an arbitrary-precision Decimal type. + * For simpler cases, where you need to control the number of decimal places for display purposes, you can use the 'toFixed()' method. However, be aware that 'toFixed()' returns a string and may not be suitable for all arithmetic precision requirements. + * Always be cautious with direct arithmetic operations and consider implementing appropriate rounding strategies to maintain accuracy. + * + */ + prop: number; + } + ` + ); + assertEqualContent( + outputModelFile?.content!, + ` + export interface SimpleModelOutput { + /** + * NOTE: This property is represented as a 'number' in JavaScript, but it corresponds to a 'decimal' type in other languages. + * Due to the inherent limitations of floating-point arithmetic in JavaScript, precision issues may arise when performing arithmetic operations. + * If your application requires high precision for arithmetic operations or when round-tripping data back to other languages, consider using a library like decimal.js, which provides an arbitrary-precision Decimal type. + * For simpler cases, where you need to control the number of decimal places for display purposes, you can use the 'toFixed()' method. However, be aware that 'toFixed()' returns a string and may not be suitable for all arithmetic precision requirements. + * Always be cautious with direct arithmetic operations and consider implementing appropriate rounding strategies to maintain accuracy. + * + */ + prop: number; + } + ` + ); + }); + + it("should report decimal-to-number diagnostic warning for decimal -> number", async () => { + try { + await emitModelsFromTypeSpec( + ` + model SimpleModel { + prop: decimal; + } + @route("/decimal/prop") + @get + op getModel(...SimpleModel): SimpleModel; + `, + false, + true, + false, + true // throw exception for diagnostics + ); + } catch (err: any) { + assert.strictEqual(err.length, 1); + assert.strictEqual( + err[0].code, + "@azure-tools/typespec-ts/decimal-to-number" + ); + assert.strictEqual( + err[0].message, + "Please note the decimal type will be converted to number. If you strongly care about precision you can use @encode to encode it as a string for the property - prop." + ); + } + }); + + it("should handle decimal128 -> number", async () => { + const schemaOutput = await emitModelsFromTypeSpec( + ` + model SimpleModel { + prop: decimal128; + } + @route("/decimal128/prop") + @get + op getModel(...SimpleModel): SimpleModel; + `, + false, + true, + false, + false // disable diagnostics + ); + assert.ok(schemaOutput); + const { inputModelFile, outputModelFile } = schemaOutput!; + assertEqualContent( + inputModelFile?.content!, + ` + export interface SimpleModel { + /** + * NOTE: This property is represented as a 'number' in JavaScript, but it corresponds to a 'decimal' type in other languages. + * Due to the inherent limitations of floating-point arithmetic in JavaScript, precision issues may arise when performing arithmetic operations. + * If your application requires high precision for arithmetic operations or when round-tripping data back to other languages, consider using a library like decimal.js, which provides an arbitrary-precision Decimal type. + * For simpler cases, where you need to control the number of decimal places for display purposes, you can use the 'toFixed()' method. However, be aware that 'toFixed()' returns a string and may not be suitable for all arithmetic precision requirements. + * Always be cautious with direct arithmetic operations and consider implementing appropriate rounding strategies to maintain accuracy. + * + */ + prop: number; + } + ` + ); + assertEqualContent( + outputModelFile?.content!, + ` + export interface SimpleModelOutput { + /** + * NOTE: This property is represented as a 'number' in JavaScript, but it corresponds to a 'decimal' type in other languages. + * Due to the inherent limitations of floating-point arithmetic in JavaScript, precision issues may arise when performing arithmetic operations. + * If your application requires high precision for arithmetic operations or when round-tripping data back to other languages, consider using a library like decimal.js, which provides an arbitrary-precision Decimal type. + * For simpler cases, where you need to control the number of decimal places for display purposes, you can use the 'toFixed()' method. However, be aware that 'toFixed()' returns a string and may not be suitable for all arithmetic precision requirements. + * Always be cautious with direct arithmetic operations and consider implementing appropriate rounding strategies to maintain accuracy. + * + */ + prop: number; + } + ` + ); + }); + + // TODO: pending with typespec definition https://github.com/microsoft/typespec/issues/2762 + it.skip("should handle decimal/decimal128 with encode `string`", async () => { + const schemaOutput = await emitModelsFromTypeSpec( + ` + model SimpleModel { + @encode("string") + prop1: decimal; + @encode("string") + prop2: decimal128; + } + @route("/decimal/prop/encode") + @get + op getModel(...SimpleModel): SimpleModel; + `, + false, + true + ); + assert.ok(schemaOutput); + const { inputModelFile, outputModelFile } = schemaOutput!; + assertEqualContent( + inputModelFile?.content!, + ` + export interface SimpleModel { + "prop1": string; + "prop2": string; + } + ` + ); + assertEqualContent( + outputModelFile?.content!, + ` + export interface SimpleModelOutput { + "prop1": string; + "prop2": string; + } + ` + ); + }); + }); + describe("array basic generation", () => { it("should handle string[] -> string[]", async () => { const tspType = "string[]"; @@ -2672,7 +2844,12 @@ describe("Input/output model type", () => { >; } `; - const schemaOutput = await emitModelsFromTypeSpec(tspDefinition, true, true, true); + const schemaOutput = await emitModelsFromTypeSpec( + tspDefinition, + true, + true, + true + ); assert.ok(schemaOutput); const { inputModelFile, outputModelFile } = schemaOutput!; // console.log(inputModelFile?.content); @@ -2858,7 +3035,12 @@ describe("Input/output model type", () => { >; } `; - const schemaOutput = await emitModelsFromTypeSpec(tspDefinition, true, true, true); + const schemaOutput = await emitModelsFromTypeSpec( + tspDefinition, + true, + true, + true + ); assert.ok(schemaOutput); const { inputModelFile, outputModelFile } = schemaOutput!; // console.log(inputModelFile?.content); @@ -2946,5 +3128,5 @@ describe("Input/output model type", () => { true ); }); - }) + }); }); diff --git a/packages/typespec-ts/test/util/emitUtil.ts b/packages/typespec-ts/test/util/emitUtil.ts index ed522cc073..089fd4689c 100644 --- a/packages/typespec-ts/test/util/emitUtil.ts +++ b/packages/typespec-ts/test/util/emitUtil.ts @@ -93,7 +93,8 @@ export async function emitModelsFromTypeSpec( tspContent: string, needAzureCore: boolean = false, needTCGC: boolean = false, - withRawContent: boolean = false + withRawContent: boolean = false, + mustEmptyDiagnostic: boolean = true ) { const context = await rlcEmitterFor( tspContent, @@ -110,7 +111,9 @@ export async function emitModelsFromTypeSpec( if (clients && clients[0]) { rlcSchemas = transformSchemas(program, clients[0], dpgContext); } - expectDiagnosticEmpty(program.diagnostics); + if (mustEmptyDiagnostic && dpgContext.program.diagnostics.length > 0) { + throw dpgContext.program.diagnostics; + } return buildSchemaTypes({ schemas: rlcSchemas, srcPath: "",