From 33165e8712ab0fcc509e62e2d35a20afdd3e30ed Mon Sep 17 00:00:00 2001 From: mrafnadeem-apimatic Date: Mon, 2 Jun 2025 08:37:10 +0500 Subject: [PATCH 01/20] build: enforce consistent file names --- tsconfig.base.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsconfig.base.json b/tsconfig.base.json index a1892d536..ae545a7e3 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -19,6 +19,7 @@ "importHelpers": true, "paths": { "tslib": ["./node_modules/tslib/tslib.d.ts"] - } + }, + "forceConsistentCasingInFileNames": true } } From de4d33785bbe07a64758af4476f496650b5c611c Mon Sep 17 00:00:00 2001 From: mrafnadeem-apimatic Date: Mon, 2 Jun 2025 08:39:25 +0500 Subject: [PATCH 02/20] build: enforce strict type checking --- tsconfig.base.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsconfig.base.json b/tsconfig.base.json index ae545a7e3..a5930e40b 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -20,6 +20,7 @@ "paths": { "tslib": ["./node_modules/tslib/tslib.d.ts"] }, - "forceConsistentCasingInFileNames": true + "forceConsistentCasingInFileNames": true, + "strict": true } } From b17e31d43794a0b81e0baaf780fbfbf2aef9256e Mon Sep 17 00:00:00 2001 From: mrafnadeem-apimatic Date: Tue, 3 Jun 2025 10:58:03 +0500 Subject: [PATCH 03/20] refactor: make oneOf discriminators type-safe --- packages/schema/src/types/oneOf.ts | 113 ++++++++++++++++++++--------- 1 file changed, 80 insertions(+), 33 deletions(-) diff --git a/packages/schema/src/types/oneOf.ts b/packages/schema/src/types/oneOf.ts index 858430e2b..f26ce7b86 100644 --- a/packages/schema/src/types/oneOf.ts +++ b/packages/schema/src/types/oneOf.ts @@ -12,6 +12,8 @@ type DiscriminatorMap>> = { [K in ArraySchemaType]?: Schema>; }; +type ValueOf = T[keyof T]; + export function oneOf>>( schemas: [...T], discriminatorMap?: DiscriminatorMap, @@ -36,69 +38,114 @@ function createOneOfWithDiscriminator>>( return { type: () => `OneOf<${schemas.map((schema) => schema.type()).join(' | ')}>`, validateBeforeMap: (value, ctxt) => { - const discriminatorValue = - value && typeof value === 'object' && value[discriminatorField]; - if (discriminatorValue && discriminatorMap[discriminatorValue]) { - return discriminatorMap[discriminatorValue].validateBeforeMap( - value, - ctxt - ); + const discriminatedSchema = getDiscriminatedSchema( + value, + discriminatorMap, + discriminatorField + ); + if (discriminatedSchema) { + return discriminatedSchema.validateBeforeMap(value, ctxt); } return matchAndValidateBeforeMap(schemas, value, ctxt); }, validateBeforeUnmap: (value, ctxt) => { - const discriminatorValue = - value && typeof value === 'object' && value[discriminatorField]; - if (discriminatorValue && discriminatorMap[discriminatorValue]) { - return discriminatorMap[discriminatorValue].validateBeforeUnmap( - value, - ctxt - ); + const discriminatedSchema = getDiscriminatedSchema( + value, + discriminatorMap, + discriminatorField + ); + if (discriminatedSchema) { + return discriminatedSchema.validateBeforeUnmap(value, ctxt); } return matchAndValidateBeforeUnmap(schemas, value, ctxt); }, map: (value, ctxt) => { - const discriminatorValue = value && value[discriminatorField]; - if (discriminatorValue && discriminatorMap[discriminatorValue]) { - return discriminatorMap[discriminatorValue].map(value, ctxt); + const discriminatedSchema = getDiscriminatedSchema( + value, + discriminatorMap, + discriminatorField, + false + ); + if (discriminatedSchema) { + return discriminatedSchema.map(value, ctxt); } return matchAndMap(schemas, value, ctxt); }, unmap: (value, ctxt) => { - const discriminatorValue = value && value[discriminatorField]; - if (discriminatorValue && discriminatorMap[discriminatorValue]) { - return discriminatorMap[discriminatorValue].unmap(value, ctxt); + const discriminatedSchema = getDiscriminatedSchema( + value, + discriminatorMap, + discriminatorField, + false + ); + if (discriminatedSchema) { + return discriminatedSchema.unmap(value, ctxt); } return matchAndUnmap(schemas, value, ctxt); }, validateBeforeMapXml: (value, ctxt) => { - const discriminatorValue = - value && typeof value === 'object' && value[discriminatorField]; - if (discriminatorValue && discriminatorMap[discriminatorValue]) { - return discriminatorMap[discriminatorValue].validateBeforeMapXml( - value, - ctxt - ); + const discriminatedSchema = getDiscriminatedSchema( + value, + discriminatorMap, + discriminatorField + ); + if (discriminatedSchema) { + return discriminatedSchema.validateBeforeMapXml(value, ctxt); } return matchAndValidateBeforeMapXml(schemas, value, ctxt); }, mapXml: (value, ctxt) => { - const discriminatorValue = value && value[discriminatorField]; - if (discriminatorValue && discriminatorMap[discriminatorValue]) { - return discriminatorMap[discriminatorValue].mapXml(value, ctxt); + const discriminatedSchema = getDiscriminatedSchema( + value, + discriminatorMap, + discriminatorField, + false + ); + if (discriminatedSchema) { + return discriminatedSchema.mapXml(value, ctxt); } return matchAndMapXml(schemas, value, ctxt); }, unmapXml: (value, ctxt) => { - const discriminatorValue = value && value[discriminatorField]; - if (discriminatorValue && discriminatorMap[discriminatorValue]) { - return discriminatorMap[discriminatorValue].unmapXml(value, ctxt); + const discriminatedSchema = getDiscriminatedSchema( + value, + discriminatorMap, + discriminatorField, + false + ); + if (discriminatedSchema) { + return discriminatedSchema.unmapXml(value, ctxt); } return matchAndUnmapXml(schemas, value, ctxt); }, }; } +function getDiscriminatedSchema>>( + value: unknown, + discriminatorMap: DiscriminatorMap, + discriminatorField: string, + useTypeOfCheck: boolean = true +): ValueOf> | false { + const discriminatorValue = + value && + (useTypeOfCheck ? typeof value === 'object' : true) && + (value as Record)[discriminatorField]; + + if (!discriminatorValue) { + return false; + } + + const schema = + discriminatorMap[discriminatorValue as keyof DiscriminatorMap]; + + if (schema) { + return schema; + } + + return false; +} + function createOneOfWithoutDiscriminator>>( schemas: T ): Schema> { From 0f0849c4cce2dd3fb8fd1cd99a4641c3f23aef82 Mon Sep 17 00:00:00 2001 From: mrafnadeem-apimatic Date: Tue, 3 Jun 2025 11:41:02 +0500 Subject: [PATCH 04/20] refactor: move common logic to typeUtils --- packages/schema/src/typeUtils.ts | 47 ++++++++++++++++++++++++++++++ packages/schema/src/types/oneOf.ts | 44 ++++------------------------ 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/packages/schema/src/typeUtils.ts b/packages/schema/src/typeUtils.ts index ebde3aaca..640bd681e 100644 --- a/packages/schema/src/typeUtils.ts +++ b/packages/schema/src/typeUtils.ts @@ -4,6 +4,8 @@ * Some of these have been picked up from the superstruct library. */ +import { Schema } from './schema'; + /** * Type helper to Flatten the Union of optional and required properties. */ @@ -30,3 +32,48 @@ type RequiredKeys = { export type OptionalizeObject = Flatten< { [K in RequiredKeys]: T[K] } & { [K in OptionalKeys]?: T[K] } >; + +type SchemaType> = T extends Schema + ? U + : never; + +export type ArraySchemaType< + T extends Array> +> = T[number] extends Schema ? SchemaType : never; + +/** + * Type helper to work with schemas of a discriminated oneOf or anyOf type + */ +export type DiscriminatorMap>> = { + [K in ArraySchemaType]?: Schema>; +}; + +type ValueOf = T[keyof T]; + +/** + * Check a value's discriminator field and get its corresponding schema + */ +export function getDiscriminatedSchema>>( + value: unknown, + discriminatorMap: DiscriminatorMap, + discriminatorField: string, + useTypeOfCheck: boolean = true +): ValueOf> | false { + const discriminatorValue = + value && + (useTypeOfCheck ? typeof value === 'object' : true) && + (value as Record)[discriminatorField]; + + if (!discriminatorValue) { + return false; + } + + const schema = + discriminatorMap[discriminatorValue as keyof DiscriminatorMap]; + + if (schema) { + return schema; + } + + return false; +} diff --git a/packages/schema/src/types/oneOf.ts b/packages/schema/src/types/oneOf.ts index f26ce7b86..a80fcb19c 100644 --- a/packages/schema/src/types/oneOf.ts +++ b/packages/schema/src/types/oneOf.ts @@ -1,18 +1,9 @@ import { Schema, SchemaContextCreator } from '../schema'; - -type SchemaType> = T extends Schema - ? U - : never; - -type ArraySchemaType< - T extends Array> -> = T[number] extends Schema ? SchemaType : never; - -type DiscriminatorMap>> = { - [K in ArraySchemaType]?: Schema>; -}; - -type ValueOf = T[keyof T]; +import { + ArraySchemaType, + DiscriminatorMap, + getDiscriminatedSchema, +} from '../typeUtils'; export function oneOf>>( schemas: [...T], @@ -121,31 +112,6 @@ function createOneOfWithDiscriminator>>( }; } -function getDiscriminatedSchema>>( - value: unknown, - discriminatorMap: DiscriminatorMap, - discriminatorField: string, - useTypeOfCheck: boolean = true -): ValueOf> | false { - const discriminatorValue = - value && - (useTypeOfCheck ? typeof value === 'object' : true) && - (value as Record)[discriminatorField]; - - if (!discriminatorValue) { - return false; - } - - const schema = - discriminatorMap[discriminatorValue as keyof DiscriminatorMap]; - - if (schema) { - return schema; - } - - return false; -} - function createOneOfWithoutDiscriminator>>( schemas: T ): Schema> { From d8dde36e64146382431a4c86d61511d72e3cdd9d Mon Sep 17 00:00:00 2001 From: mrafnadeem-apimatic Date: Tue, 3 Jun 2025 12:19:31 +0500 Subject: [PATCH 05/20] refactor: fix types for anyOf discriminator --- packages/schema/src/types/anyOf.ts | 103 ++++++++++++++++------------- 1 file changed, 58 insertions(+), 45 deletions(-) diff --git a/packages/schema/src/types/anyOf.ts b/packages/schema/src/types/anyOf.ts index 78ca3130c..eb4846217 100644 --- a/packages/schema/src/types/anyOf.ts +++ b/packages/schema/src/types/anyOf.ts @@ -1,16 +1,9 @@ import { Schema, SchemaContextCreator } from '../schema'; - -type SchemaType> = T extends Schema - ? U - : never; - -type ArraySchemaType< - T extends Array> -> = T[number] extends Schema ? SchemaType : never; - -type DiscriminatorMap>> = { - [K in ArraySchemaType]?: Schema>; -}; +import { + ArraySchemaType, + DiscriminatorMap, + getDiscriminatedSchema, +} from '../typeUtils'; export function anyOf>>( schemas: [...T], @@ -36,63 +29,83 @@ function createAnyOfWithDiscriminator>>( return { type: () => `OneOf<${schemas.map((schema) => schema.type()).join(' | ')}>`, validateBeforeMap: (value, ctxt) => { - const discriminatorValue = - value && typeof value === 'object' && value[discriminatorField]; - if (discriminatorValue && discriminatorMap[discriminatorValue]) { - return discriminatorMap[discriminatorValue].validateBeforeMap( - value, - ctxt - ); + const discriminatedSchema = getDiscriminatedSchema( + value, + discriminatorMap, + discriminatorField + ); + if (discriminatedSchema) { + return discriminatedSchema.validateBeforeMap(value, ctxt); } return matchAndValidateBeforeMap(schemas, value, ctxt); }, validateBeforeUnmap: (value, ctxt) => { - const discriminatorValue = - value && typeof value === 'object' && value[discriminatorField]; - if (discriminatorValue && discriminatorMap[discriminatorValue]) { - return discriminatorMap[discriminatorValue].validateBeforeUnmap( - value, - ctxt - ); + const discriminatedSchema = getDiscriminatedSchema( + value, + discriminatorMap, + discriminatorField + ); + if (discriminatedSchema) { + return discriminatedSchema.validateBeforeUnmap(value, ctxt); } return matchAndValidateBeforeUnmap(schemas, value, ctxt); }, map: (value, ctxt) => { - const discriminatorValue = value && value[discriminatorField]; - if (discriminatorValue && discriminatorMap[discriminatorValue]) { - return discriminatorMap[discriminatorValue].map(value, ctxt); + const discriminatedSchema = getDiscriminatedSchema( + value, + discriminatorMap, + discriminatorField, + false + ); + if (discriminatedSchema) { + return discriminatedSchema.map(value, ctxt); } return matchAndMap(schemas, value, ctxt); }, unmap: (value, ctxt) => { - const discriminatorValue = value && value[discriminatorField]; - if (discriminatorValue && discriminatorMap[discriminatorValue]) { - return discriminatorMap[discriminatorValue].unmap(value, ctxt); + const discriminatedSchema = getDiscriminatedSchema( + value, + discriminatorMap, + discriminatorField, + false + ); + if (discriminatedSchema) { + return discriminatedSchema.unmap(value, ctxt); } return matchAndUnmap(schemas, value, ctxt); }, validateBeforeMapXml: (value, ctxt) => { - const discriminatorValue = - value && typeof value === 'object' && value[discriminatorField]; - if (discriminatorValue && discriminatorMap[discriminatorValue]) { - return discriminatorMap[discriminatorValue].validateBeforeMapXml( - value, - ctxt - ); + const discriminatedSchema = getDiscriminatedSchema( + value, + discriminatorMap, + discriminatorField + ); + if (discriminatedSchema) { + return discriminatedSchema.validateBeforeMapXml(value, ctxt); } return matchAndValidateBeforeMapXml(schemas, value, ctxt); }, mapXml: (value, ctxt) => { - const discriminatorValue = value && value[discriminatorField]; - if (discriminatorValue && discriminatorMap[discriminatorValue]) { - return discriminatorMap[discriminatorValue].mapXml(value, ctxt); + const discriminatedSchema = getDiscriminatedSchema( + value, + discriminatorMap, + discriminatorField, + false + ); + if (discriminatedSchema) { + return discriminatedSchema.mapXml(value, ctxt); } return matchAndMapXml(schemas, value, ctxt); }, unmapXml: (value, ctxt) => { - const discriminatorValue = value && value[discriminatorField]; - if (discriminatorValue && discriminatorMap[discriminatorValue]) { - return discriminatorMap[discriminatorValue].unmapXml(value, ctxt); + const discriminatedSchema = getDiscriminatedSchema( + value, + discriminatorMap, + discriminatorField, + false + ); + if (discriminatedSchema) { + return discriminatedSchema.unmapXml(value, ctxt); } return matchAndUnmapXml(schemas, value, ctxt); }, From 004b3dac3377867f7a3ae55707354987541aaab0 Mon Sep 17 00:00:00 2001 From: mrafnadeem-apimatic Date: Tue, 3 Jun 2025 14:52:53 +0500 Subject: [PATCH 06/20] refactor: use const union instead of strings --- packages/schema/src/types/object.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/schema/src/types/object.ts b/packages/schema/src/types/object.ts index 172438c0e..75cd4167d 100644 --- a/packages/schema/src/types/object.ts +++ b/packages/schema/src/types/object.ts @@ -25,6 +25,14 @@ type AnyObjectSchema = Record< [string, Schema, ObjectXmlOptions?] >; +/** + * Type for dynamically calling validate methods + */ +type SchemaValidationMethods = keyof Pick< + Schema, + 'validateBeforeMap' | 'validateBeforeUnmap' | 'validateBeforeMapXml' +>; + type AllValues = { [P in keyof T]: { key: P; value: T[P][0]; schema: T[P][1] }; }[keyof T]; @@ -264,7 +272,7 @@ function validateObjectBeforeMapXml( const { $: attrs, ...elements } = valueObject; let validationObj = { - validationMethod: 'validateBeforeMapXml', + validationMethod: 'validateBeforeMapXml' as const, propTypeName: 'child elements', propTypePrefix: 'element', valueTypeName: 'element', @@ -388,7 +396,7 @@ function validateValueObject({ skipAdditionalPropValidation, mapAdditionalProps, }: { - validationMethod: string; + validationMethod: SchemaValidationMethods; propTypeName: string; propTypePrefix: string; valueTypeName: string; From 1fc39fda7753c0e87d79ee0ee76c6848bbda2790 Mon Sep 17 00:00:00 2001 From: mrafnadeem-apimatic Date: Tue, 3 Jun 2025 16:51:19 +0500 Subject: [PATCH 07/20] refactor: update types to match func signature --- packages/schema/src/types/bigint.ts | 2 +- packages/schema/src/types/boolean.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/schema/src/types/bigint.ts b/packages/schema/src/types/bigint.ts index 9a3580bd5..e28a3306f 100644 --- a/packages/schema/src/types/bigint.ts +++ b/packages/schema/src/types/bigint.ts @@ -5,7 +5,7 @@ import { toValidator, } from '../utils'; -function isValidBigIntValue(value: unknown, strict: boolean): value is bigint { +function isValidBigIntValue(value: unknown, strict?: boolean): value is bigint { return strict ? typeof value === 'bigint' : typeof value === 'bigint' || diff --git a/packages/schema/src/types/boolean.ts b/packages/schema/src/types/boolean.ts index cf1d148cb..d1ef744ae 100644 --- a/packages/schema/src/types/boolean.ts +++ b/packages/schema/src/types/boolean.ts @@ -1,7 +1,7 @@ import { Schema } from '../schema'; import { createSymmetricSchema, toValidator } from '../utils'; -function isValidBooleanValue(value: unknown, strict: boolean): boolean { +function isValidBooleanValue(value: unknown, strict?: boolean): boolean { return strict ? typeof value === 'boolean' : typeof value === 'boolean' || From bf056517c2d17e08db053adc673587a5a45c551f Mon Sep 17 00:00:00 2001 From: mrafnadeem-apimatic Date: Wed, 4 Jun 2025 13:14:13 +0500 Subject: [PATCH 08/20] refactor: use generic to fix compiler error --- packages/schema/src/types/numberEnum.ts | 2 ++ packages/schema/src/utils.ts | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/schema/src/types/numberEnum.ts b/packages/schema/src/types/numberEnum.ts index 83e556534..a6bdf4c54 100644 --- a/packages/schema/src/types/numberEnum.ts +++ b/packages/schema/src/types/numberEnum.ts @@ -36,6 +36,8 @@ export function numberEnum( type: `Enum<${Object.values(enumVariable) .filter((v) => typeof v === 'number') .join(',')}>`, + // TODO: The correct return type should be TEnumValue | number + // when allowForUnknownProps is true but that would be a breaking change. map: coerceNumericStringToNumber as (value: TEnumValue) => TEnumValue, validate, }); diff --git a/packages/schema/src/utils.ts b/packages/schema/src/utils.ts index f813f3413..f10e17358 100644 --- a/packages/schema/src/utils.ts +++ b/packages/schema/src/utils.ts @@ -103,7 +103,9 @@ export function isNumericString( (typeof value === 'string' && !isNaN(value as any)); } -export function coerceNumericStringToNumber(value: number | string): number { +export function coerceNumericStringToNumber( + value: T | string +): number { return typeof value === 'number' ? value : +value; } From 8ad5cbe1c622776e9f54da710718082cd1bd4995 Mon Sep 17 00:00:00 2001 From: mrafnadeem-apimatic Date: Wed, 4 Jun 2025 13:15:26 +0500 Subject: [PATCH 09/20] test: validateAndMap for numberEnum --- packages/schema/test/types/numberEnum.test.ts | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 packages/schema/test/types/numberEnum.test.ts diff --git a/packages/schema/test/types/numberEnum.test.ts b/packages/schema/test/types/numberEnum.test.ts new file mode 100644 index 000000000..023b14556 --- /dev/null +++ b/packages/schema/test/types/numberEnum.test.ts @@ -0,0 +1,57 @@ +import { numberEnum, validateAndMap } from '../../src'; + +describe('Number Enum', () => { + enum SampleNumberEnum { + Hearts = 1, + Spades, + Clubs, + Diamonds, + } + describe('Mapping', () => { + it('should map known number to enum member', () => { + const input = 3; + const output = validateAndMap(input as any, numberEnum(SampleNumberEnum)); + expect(output.errors).toBeFalsy(); + expect((output as any).result).toBe(SampleNumberEnum.Clubs); + }); + + it('should fail for unknown number', () => { + const input = 5; + const output = validateAndMap(input as any, numberEnum(SampleNumberEnum)); + + expect(output.errors).toBeTruthy(); + expect(output.errors).toMatchInlineSnapshot(` + Array [ + Object { + "branch": Array [ + 5, + ], + "message": "Expected value to be of type 'Enum<1,2,3,4>' but found 'number'. + + Given value: 5 + Type: 'number' + Expected type: 'Enum<1,2,3,4>'", + "path": Array [], + "type": "Enum<1,2,3,4>", + "value": 5, + }, + ] + `); + }); + + it('should map unknown number when allowing unknown props', () => { + const input: SampleNumberEnum | number = 5; + const output = validateAndMap( + input as any, + numberEnum(SampleNumberEnum, true) + ); + + expect(output.errors).toBeFalsy(); + if (output.errors) { + throw new Error('This line is for type narrowing.'); + } + + expect(output.result).toBe(5); + }); + }); +}); From 3af1e30a3fc5b4637787195895f3620659e94709 Mon Sep 17 00:00:00 2001 From: mrafnadeem-apimatic Date: Wed, 4 Jun 2025 13:53:25 +0500 Subject: [PATCH 10/20] fix: compiler errors in tests --- packages/http-headers/test/httpHeaders.test.ts | 11 +++++++++++ packages/schema/test/types/numberEnum.test.ts | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/http-headers/test/httpHeaders.test.ts b/packages/http-headers/test/httpHeaders.test.ts index 9f1163309..531ab4d90 100644 --- a/packages/http-headers/test/httpHeaders.test.ts +++ b/packages/http-headers/test/httpHeaders.test.ts @@ -82,6 +82,10 @@ describe('HTTP Headers', () => { assertHeaders('header'); } catch (error) { expect(error).toBeInstanceOf(TypeError); + if (!(error instanceof TypeError)) { + throw new Error('This check is for type narrowing.'); + } + expect(error.message).toBe('Headers must be an object.'); } }); @@ -91,6 +95,9 @@ describe('HTTP Headers', () => { try { assertHeaders({ 'invalid header': 'test' }); } catch (error) { + if (!(error instanceof Error)) { + throw new Error('This check is for type narrowing.'); + } expect(error.message).toBe( '"invalid header" is not a valid header name.' ); @@ -103,6 +110,10 @@ describe('HTTP Headers', () => { assertHeaders({ 'header-name': 123 }); } catch (error) { expect(error).toBeInstanceOf(TypeError); + if (!(error instanceof TypeError)) { + throw new Error('This check is for type narrowing.'); + } + expect(error.message).toBe( 'Header value must be string but number provided.' ); diff --git a/packages/schema/test/types/numberEnum.test.ts b/packages/schema/test/types/numberEnum.test.ts index 023b14556..11aeda862 100644 --- a/packages/schema/test/types/numberEnum.test.ts +++ b/packages/schema/test/types/numberEnum.test.ts @@ -48,7 +48,7 @@ describe('Number Enum', () => { expect(output.errors).toBeFalsy(); if (output.errors) { - throw new Error('This line is for type narrowing.'); + throw new Error('This check is for type narrowing.'); } expect(output.result).toBe(5); From b442e3a9d1d7cc4ead0fc3b00bc0e6b778fbc751 Mon Sep 17 00:00:00 2001 From: mrafnadeem-apimatic Date: Wed, 4 Jun 2025 15:21:43 +0500 Subject: [PATCH 11/20] fix: compiler errors in core --- packages/core/src/errors/apiError.ts | 3 ++- packages/core/src/http/httpInterceptor.ts | 2 ++ packages/core/src/http/requestBuilder.ts | 9 ++++++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/core/src/errors/apiError.ts b/packages/core/src/errors/apiError.ts index 92d4f6daa..ad7fce540 100644 --- a/packages/core/src/errors/apiError.ts +++ b/packages/core/src/errors/apiError.ts @@ -35,11 +35,12 @@ export class ApiError try { this.result = JSON.parse(response.body); } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); if (process.env.NODE_ENV !== 'production') { if (console) { // tslint:disable-next-line:no-console console.warn( - `Unexpected error: Could not parse HTTP response body as JSON. ${error.message}` + `Unexpected error: Could not parse HTTP response body as JSON. ${errorMessage}` ); } } diff --git a/packages/core/src/http/httpInterceptor.ts b/packages/core/src/http/httpInterceptor.ts index a24f535c5..b8439e778 100644 --- a/packages/core/src/http/httpInterceptor.ts +++ b/packages/core/src/http/httpInterceptor.ts @@ -14,5 +14,7 @@ export function callHttpInterceptors( client: HttpCallExecutor ): HttpCallExecutor { return (request, options) => + // @ts-ignore TODO: REMOVE THIS COMMENT. DO NOT COPY. + // THIS WAS ONLY FOR MIGRATING UNCHECKED CODE TO STRICT TYPE CHECKING. combineHttpInterceptors(interceptors)(request, options, client); } diff --git a/packages/core/src/http/requestBuilder.ts b/packages/core/src/http/requestBuilder.ts index cc36f4f7c..e0a477990 100644 --- a/packages/core/src/http/requestBuilder.ts +++ b/packages/core/src/http/requestBuilder.ts @@ -518,7 +518,8 @@ export class DefaultRequestBuilder result.body ); } catch (error) { - throw new Error(`Could not parse body as XML.\n\n${error.message}`); + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Could not parse body as XML.\n\n${errorMessage}`); } const mappingResult = validateAndMapXml(xmlObject, schema); if (mappingResult.errors) { @@ -588,7 +589,8 @@ export class DefaultRequestBuilder try { context = await next(request, options); } catch (error) { - timeoutError = error; + const typedError = error instanceof Error ? error : new Error(String(error)); + timeoutError = typedError; } if (shouldRetry) { waitTime = getRetryWaitTime( @@ -687,8 +689,9 @@ function parseJsonResult(schema: Schema, res: ApiResponse): T { try { parsed = JSON.parse(res.body); } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); const resUnParseErr = new Error( - `Could not parse body as JSON.\n\n${error.message}` + `Could not parse body as JSON.\n\n${errorMessage}` ); return validateJson(schema, res.body, (_) => resUnParseErr); } From 804c29c1fed99020e56c6e690cf2e1f89c2f5791 Mon Sep 17 00:00:00 2001 From: mrafnadeem-apimatic Date: Wed, 4 Jun 2025 15:24:33 +0500 Subject: [PATCH 12/20] fix: compiler error in test-utilities --- packages/test-utilities/src/assertionUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/test-utilities/src/assertionUtils.ts b/packages/test-utilities/src/assertionUtils.ts index 028e7ccad..9b5969ccb 100644 --- a/packages/test-utilities/src/assertionUtils.ts +++ b/packages/test-utilities/src/assertionUtils.ts @@ -119,6 +119,7 @@ function checkObjects( // Check if right object keys contains this key from left object. expect(rightObjKeys).toContainEqual(key); // Recursive checking for each element in left and right object. + // @ts-expect-error NOTE: We already checked that both left and right objects contain the key. checkIfMatching(left[key], right[key], isOrdered, checkValues); }); } From 608628b8e7278102774e00cbd4f934dffe5c5aab Mon Sep 17 00:00:00 2001 From: mrafnadeem-apimatic Date: Wed, 4 Jun 2025 15:57:33 +0500 Subject: [PATCH 13/20] build: suppress type errors in auth adapter --- .../src/compositeAuthenticationAdapter.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/authentication-adapters/src/compositeAuthenticationAdapter.ts b/packages/authentication-adapters/src/compositeAuthenticationAdapter.ts index 9b836f3a9..4ca5ac6bc 100644 --- a/packages/authentication-adapters/src/compositeAuthenticationAdapter.ts +++ b/packages/authentication-adapters/src/compositeAuthenticationAdapter.ts @@ -131,6 +131,10 @@ function findMatchingAuth( return securityRequirements.find( (andRequirements) => Object.keys(andRequirements).every( + // @ts-ignore TODO: REMOVE THIS COMMENT. DO NOT COPY. + // THIS WAS ONLY FOR MIGRATING UNCHECKED CODE TO STRICT TYPE CHECKING. + // As for the actual compiler error, TS does not support generic keys with the `in` operator. + // https://github.com/microsoft/TypeScript/issues/21732#issuecomment-1423655000 (key) => key in providerConfig && providerConfig[key] ) && Object.values(andRequirements).every((value) => value) ); @@ -145,7 +149,11 @@ function getHttpInterceptorsForAuths( ): Array> { return Object.entries(matchingRequirements).map( ([authProvider, authParam]) => { + // @ts-ignore TODO: REMOVE THIS COMMENT. DO NOT COPY. + // THIS WAS ONLY FOR MIGRATING UNCHECKED CODE TO STRICT TYPE CHECKING. if (providerConfig[authProvider] !== undefined) { + // @ts-ignore TODO: REMOVE THIS COMMENT. DO NOT COPY. + // THIS WAS ONLY FOR MIGRATING UNCHECKED CODE TO STRICT TYPE CHECKING. return providerConfig[authProvider](authParam); } else { return passThroughInterceptor; From d9f54c292302d917bbb5b457d7fbd16489fd34c4 Mon Sep 17 00:00:00 2001 From: mrafnadeem-apimatic Date: Wed, 4 Jun 2025 16:09:47 +0500 Subject: [PATCH 14/20] Revert "fix: compiler errors in core" This reverts commit b442e3a9d1d7cc4ead0fc3b00bc0e6b778fbc751. --- packages/core/src/errors/apiError.ts | 3 +-- packages/core/src/http/httpInterceptor.ts | 2 -- packages/core/src/http/requestBuilder.ts | 9 +++------ 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/core/src/errors/apiError.ts b/packages/core/src/errors/apiError.ts index ad7fce540..92d4f6daa 100644 --- a/packages/core/src/errors/apiError.ts +++ b/packages/core/src/errors/apiError.ts @@ -35,12 +35,11 @@ export class ApiError try { this.result = JSON.parse(response.body); } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); if (process.env.NODE_ENV !== 'production') { if (console) { // tslint:disable-next-line:no-console console.warn( - `Unexpected error: Could not parse HTTP response body as JSON. ${errorMessage}` + `Unexpected error: Could not parse HTTP response body as JSON. ${error.message}` ); } } diff --git a/packages/core/src/http/httpInterceptor.ts b/packages/core/src/http/httpInterceptor.ts index b8439e778..a24f535c5 100644 --- a/packages/core/src/http/httpInterceptor.ts +++ b/packages/core/src/http/httpInterceptor.ts @@ -14,7 +14,5 @@ export function callHttpInterceptors( client: HttpCallExecutor ): HttpCallExecutor { return (request, options) => - // @ts-ignore TODO: REMOVE THIS COMMENT. DO NOT COPY. - // THIS WAS ONLY FOR MIGRATING UNCHECKED CODE TO STRICT TYPE CHECKING. combineHttpInterceptors(interceptors)(request, options, client); } diff --git a/packages/core/src/http/requestBuilder.ts b/packages/core/src/http/requestBuilder.ts index e0a477990..cc36f4f7c 100644 --- a/packages/core/src/http/requestBuilder.ts +++ b/packages/core/src/http/requestBuilder.ts @@ -518,8 +518,7 @@ export class DefaultRequestBuilder result.body ); } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - throw new Error(`Could not parse body as XML.\n\n${errorMessage}`); + throw new Error(`Could not parse body as XML.\n\n${error.message}`); } const mappingResult = validateAndMapXml(xmlObject, schema); if (mappingResult.errors) { @@ -589,8 +588,7 @@ export class DefaultRequestBuilder try { context = await next(request, options); } catch (error) { - const typedError = error instanceof Error ? error : new Error(String(error)); - timeoutError = typedError; + timeoutError = error; } if (shouldRetry) { waitTime = getRetryWaitTime( @@ -689,9 +687,8 @@ function parseJsonResult(schema: Schema, res: ApiResponse): T { try { parsed = JSON.parse(res.body); } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); const resUnParseErr = new Error( - `Could not parse body as JSON.\n\n${errorMessage}` + `Could not parse body as JSON.\n\n${error.message}` ); return validateJson(schema, res.body, (_) => resUnParseErr); } From ccd36f4b704562ba13edf98829c48cefbe8abb4e Mon Sep 17 00:00:00 2001 From: mrafnadeem-apimatic Date: Wed, 4 Jun 2025 21:27:31 +0500 Subject: [PATCH 15/20] build: disable unknown in catch variables When useUnknownInCatchVariables is disabled, the thrown error in the catch block is type any. If you use unknown in catch variables, you might be tempted to use `instanceof` in order to narrow it to an Error type. But this check returns false if you threw a class that inherits from Error. Why? This is because we currently target ES5 in the `tsconfig.json` file instead of ES6/ES2015. Keeping it this way is dangerous for the codebase because it is easy to unintentionally introduce a bug in an effort to keep the code type-safe, since TypeScript won't warn us about this. References: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#custom_error_types https://github.com/microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work --- tsconfig.base.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsconfig.base.json b/tsconfig.base.json index a5930e40b..fd0c196a0 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -21,6 +21,7 @@ "tslib": ["./node_modules/tslib/tslib.d.ts"] }, "forceConsistentCasingInFileNames": true, - "strict": true + "strict": true, + "useUnknownInCatchVariables": false } } From aa711a797c8b4a268b1579438c099df99b43057c Mon Sep 17 00:00:00 2001 From: mrafnadeem-apimatic Date: Wed, 4 Jun 2025 21:29:24 +0500 Subject: [PATCH 16/20] build: suppress errors in core --- packages/core/src/http/httpInterceptor.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/src/http/httpInterceptor.ts b/packages/core/src/http/httpInterceptor.ts index a24f535c5..b8439e778 100644 --- a/packages/core/src/http/httpInterceptor.ts +++ b/packages/core/src/http/httpInterceptor.ts @@ -14,5 +14,7 @@ export function callHttpInterceptors( client: HttpCallExecutor ): HttpCallExecutor { return (request, options) => + // @ts-ignore TODO: REMOVE THIS COMMENT. DO NOT COPY. + // THIS WAS ONLY FOR MIGRATING UNCHECKED CODE TO STRICT TYPE CHECKING. combineHttpInterceptors(interceptors)(request, options, client); } From fd4488d895a95c611712f6b3b8c8bbb0b79d15f8 Mon Sep 17 00:00:00 2001 From: mrafnadeem-apimatic Date: Wed, 4 Jun 2025 22:40:44 +0500 Subject: [PATCH 17/20] fix: handle compiler errors in tests --- .../test/accessTokenAdapter.test.ts | 4 +- .../test/basicAuthenticationAdapter.test.ts | 2 +- .../compositeAuthenticationAdapter.test.ts | 46 +++++++++---------- .../customHeaderAuthenticationAdapter.test.ts | 2 +- .../customQueryAuthenticationAdapter.test.ts | 2 +- .../test/noAuthenticationAdapter.test.ts | 2 +- packages/core/test/http/apiLogger.test.ts | 8 ++-- .../test/assertionUtils.test.ts | 6 +-- 8 files changed, 36 insertions(+), 36 deletions(-) diff --git a/packages/authentication-adapters/test/accessTokenAdapter.test.ts b/packages/authentication-adapters/test/accessTokenAdapter.test.ts index bbafc7002..bbd9c9c47 100644 --- a/packages/authentication-adapters/test/accessTokenAdapter.test.ts +++ b/packages/authentication-adapters/test/accessTokenAdapter.test.ts @@ -26,7 +26,7 @@ describe('test access token authentication scheme', () => { const authenticationProvider = accessTokenAuthenticationProvider(config); const handler = authenticationProvider(true); const interceptor = [handler]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -51,7 +51,7 @@ describe('test access token authentication scheme', () => { const authenticationProvider = accessTokenAuthenticationProvider(config); const handler = authenticationProvider(false); const interceptor = [handler]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); diff --git a/packages/authentication-adapters/test/basicAuthenticationAdapter.test.ts b/packages/authentication-adapters/test/basicAuthenticationAdapter.test.ts index 5792ec93c..276a7ce97 100644 --- a/packages/authentication-adapters/test/basicAuthenticationAdapter.test.ts +++ b/packages/authentication-adapters/test/basicAuthenticationAdapter.test.ts @@ -59,7 +59,7 @@ describe('test basic authentication scheme', () => { ); const handler = authenticationProvider(enableAuthentication); const interceptor = [handler]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); diff --git a/packages/authentication-adapters/test/compositeAuthenticationAdapter.test.ts b/packages/authentication-adapters/test/compositeAuthenticationAdapter.test.ts index 255850783..cb9aa42a2 100644 --- a/packages/authentication-adapters/test/compositeAuthenticationAdapter.test.ts +++ b/packages/authentication-adapters/test/compositeAuthenticationAdapter.test.ts @@ -74,7 +74,7 @@ describe('test composite authentication adapter with false or empty security req const securityRequirements = false; const provider = compositeAuthenticationProvider(authConfig); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -91,10 +91,10 @@ describe('test composite authentication adapter with false or empty security req method: 'GET', url: 'http://apimatic.hopto.org:3000/test/requestBuilder', }; - const securityRequirements = []; + const securityRequirements: any[] = []; const provider = compositeAuthenticationProvider(authConfig); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -143,7 +143,7 @@ describe('test composite authentication adapter with missing credentials object const securityRequirements = [{ apiKey: true }, { apiHeader: true }]; const provider = compositeAuthenticationProvider(authConfig); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -181,7 +181,7 @@ describe('test composite authentication adapter with missing credentials object const securityRequirements = [{ apiKey: true }, { apiHeader: true }]; const provider = compositeAuthenticationProvider(authConfig); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -220,7 +220,7 @@ describe('test composite authentication adapter with missing credentials object const securityRequirements = [{ apiKey: false }, { apiHeader: true }]; const provider = compositeAuthenticationProvider(authConfig); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -263,7 +263,7 @@ describe('test composite authentication adapter with missing credentials object const securityRequirements = [{ apiKey: true }, { apiHeader: false }]; const provider = compositeAuthenticationProvider(authConfig); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -299,7 +299,7 @@ describe('test composite authentication adapter with missing credentials object const securityRequirements = [{ apiKey: false }, { apiHeader: false }]; const provider = compositeAuthenticationProvider(authConfig); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -342,7 +342,7 @@ describe('test composite authentication adapter with missing credentials object const securityRequirements = [{ apiKey: true, apiHeader: true }]; const provider = compositeAuthenticationProvider(authConfig); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -387,7 +387,7 @@ describe('test composite authentication adapter with missing credentials object const securityRequirements = [{ apiKey: true, apiHeader: false }]; const provider = compositeAuthenticationProvider(authConfig); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -432,7 +432,7 @@ describe('test composite authentication adapter with missing credentials object const securityRequirements = [{ apiKey: false, apiHeader: true }]; const provider = compositeAuthenticationProvider(authConfig); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -473,7 +473,7 @@ describe('test composite authentication adapter with missing credentials object const securityRequirements = [{ apiKey: false, apiHeader: false }]; const provider = compositeAuthenticationProvider(authConfig); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -563,7 +563,7 @@ describe('test composite authentication adapter with security requirements combi const securityRequirements = [{ accessToken: true }, { basicAuth: true }]; const provider = compositeAuthenticationProvider(authConfig); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -582,7 +582,7 @@ describe('test composite authentication adapter with security requirements combi const securityRequirements = [{ accessToken: true }, { basicAuth: false }]; const provider = compositeAuthenticationProvider(authConfig); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -601,7 +601,7 @@ describe('test composite authentication adapter with security requirements combi const securityRequirements = [{ accessToken: false }, { basicAuth: true }]; const provider = compositeAuthenticationProvider(authConfig); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -625,7 +625,7 @@ describe('test composite authentication adapter with security requirements combi ]; const provider = compositeAuthenticationProvider(authConfig); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -648,7 +648,7 @@ describe('test composite authentication adapter with security requirements combi const securityRequirements = [{ accessToken: false, basicAuth: true }]; const provider = compositeAuthenticationProvider(authConfig); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -672,7 +672,7 @@ describe('test composite authentication adapter with security requirements combi const securityRequirements = [{ accessToken: true, basicAuth: true }]; const provider = compositeAuthenticationProvider(authConfig); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -695,7 +695,7 @@ describe('test composite authentication adapter with security requirements combi const securityRequirements = [{ accessToken: true, basicAuth: false }]; const provider = compositeAuthenticationProvider(authConfig); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -721,7 +721,7 @@ describe('test composite authentication adapter with security requirements combi ]; const provider = compositeAuthenticationProvider(authConfig); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -750,7 +750,7 @@ describe('test composite authentication adapter with security requirements combi ]; const provider = compositeAuthenticationProvider(authConfig); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -769,7 +769,7 @@ describe('test composite authentication adapter with security requirements combi const securityRequirements = [{ oAuthACG: true, oAuthCCG: true }]; const provider = compositeAuthenticationProvider(authConfig); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); @@ -811,7 +811,7 @@ describe('test composite authentication adapter with security requirements combi const securityRequirements = [{ oAuthACG: true }, { oAuthCCG: true }]; const provider = compositeAuthenticationProvider(auth1Config); const interceptor = [provider(securityRequirements)]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); diff --git a/packages/authentication-adapters/test/customHeaderAuthenticationAdapter.test.ts b/packages/authentication-adapters/test/customHeaderAuthenticationAdapter.test.ts index 1e0b36661..65d536c00 100644 --- a/packages/authentication-adapters/test/customHeaderAuthenticationAdapter.test.ts +++ b/packages/authentication-adapters/test/customHeaderAuthenticationAdapter.test.ts @@ -63,7 +63,7 @@ describe('test custom header authentication scheme', () => { ); const handler = authenticationProvider(enableAuthentication); const interceptor = [handler]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); diff --git a/packages/authentication-adapters/test/customQueryAuthenticationAdapter.test.ts b/packages/authentication-adapters/test/customQueryAuthenticationAdapter.test.ts index a28f14eb2..b2c045342 100644 --- a/packages/authentication-adapters/test/customQueryAuthenticationAdapter.test.ts +++ b/packages/authentication-adapters/test/customQueryAuthenticationAdapter.test.ts @@ -62,7 +62,7 @@ describe('test custom query authentication scheme', () => { ); const handler = authenticationProvider(enableAuthentication); const interceptor = [handler]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); diff --git a/packages/authentication-adapters/test/noAuthenticationAdapter.test.ts b/packages/authentication-adapters/test/noAuthenticationAdapter.test.ts index 258e8bd1c..f3dcdd124 100644 --- a/packages/authentication-adapters/test/noAuthenticationAdapter.test.ts +++ b/packages/authentication-adapters/test/noAuthenticationAdapter.test.ts @@ -18,7 +18,7 @@ describe('test access token authentication scheme', () => { it('should test access token auth with enabled authentication', async () => { const handler = noneAuthenticationProvider(); const interceptor = [handler]; - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response }; }; const executor = callHttpInterceptors(interceptor, client); diff --git a/packages/core/test/http/apiLogger.test.ts b/packages/core/test/http/apiLogger.test.ts index 6c3985b0e..b3b27b912 100644 --- a/packages/core/test/http/apiLogger.test.ts +++ b/packages/core/test/http/apiLogger.test.ts @@ -9,7 +9,7 @@ import { callHttpInterceptors } from '../../src/http/httpInterceptor'; import { NullLogger } from '../../src/logger/nullLogger'; import { mergeLoggingOptions } from '../../src/logger/defaultLoggingConfiguration'; -let loggerSpy; +let loggerSpy: jest.SpyInstance; beforeEach(() => { // Reset the spy on console.log() before each test loggerSpy = jest.spyOn(console, 'log').mockImplementation(); @@ -304,7 +304,7 @@ describe('APILogger with NullLogging', () => { function mockInterceptor(loggingOpt: LoggingOptions) { const apiLogger = new ApiLogger(loggingOpt); - return async (req, options, next) => { + return async (req: HttpRequest, options: any, next: (arg0: any, arg1: any) => any) => { apiLogger.logRequest(req); const context = await next(req, options); apiLogger.logResponse(context.response); @@ -344,14 +344,14 @@ function mockResponse(): HttpResponse { } async function mockClient(loggingOpts: LoggingOptions) { - const client = async (req) => { + const client = async (req: HttpRequest) => { return { request: req, response: mockResponse() }; }; const executor = callHttpInterceptors([mockInterceptor(loggingOpts)], client); return await executor(mockRequest(), undefined); } -function expectLogsToBeLogged(logSpy, expectedConsoleLogs, index = 0) { +function expectLogsToBeLogged(logSpy: jest.SpyInstance, expectedConsoleLogs: string | any[], index = 0) { for (let i = index; i < expectedConsoleLogs.length; i++) { expect(logSpy.mock.calls[i][0]).toEqual(expectedConsoleLogs[i]); } diff --git a/packages/test-utilities/test/assertionUtils.test.ts b/packages/test-utilities/test/assertionUtils.test.ts index 5d694c4b6..1797f6a22 100644 --- a/packages/test-utilities/test/assertionUtils.test.ts +++ b/packages/test-utilities/test/assertionUtils.test.ts @@ -281,7 +281,7 @@ describe('expectMatchingWithOptions', () => { }); it('array: should pass when expected is empty', () => { - const exp = []; + const exp: never[] = []; const actl = [1, 2, 3, 4]; const opts: ExpectOptions = { isOrdered: true, @@ -292,8 +292,8 @@ describe('expectMatchingWithOptions', () => { }); it('array: should pass when both expected and actual are empty', () => { - const exp = []; - const actl = []; + const exp: never[] = []; + const actl: never[] = []; const opts: ExpectOptions = { isOrdered: true, allowExtra: true, From a3965ed60a6527a29adf0a93e02cf86c283b2305 Mon Sep 17 00:00:00 2001 From: mrafnadeem-apimatic Date: Wed, 4 Jun 2025 22:44:20 +0500 Subject: [PATCH 18/20] format: apiLogger test file --- packages/core/test/http/apiLogger.test.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/core/test/http/apiLogger.test.ts b/packages/core/test/http/apiLogger.test.ts index b3b27b912..84dafa232 100644 --- a/packages/core/test/http/apiLogger.test.ts +++ b/packages/core/test/http/apiLogger.test.ts @@ -9,7 +9,10 @@ import { callHttpInterceptors } from '../../src/http/httpInterceptor'; import { NullLogger } from '../../src/logger/nullLogger'; import { mergeLoggingOptions } from '../../src/logger/defaultLoggingConfiguration'; -let loggerSpy: jest.SpyInstance; +let loggerSpy: jest.SpyInstance< + void, + [message?: any, ...optionalParams: any[]] +>; beforeEach(() => { // Reset the spy on console.log() before each test loggerSpy = jest.spyOn(console, 'log').mockImplementation(); @@ -304,7 +307,11 @@ describe('APILogger with NullLogging', () => { function mockInterceptor(loggingOpt: LoggingOptions) { const apiLogger = new ApiLogger(loggingOpt); - return async (req: HttpRequest, options: any, next: (arg0: any, arg1: any) => any) => { + return async ( + req: HttpRequest, + options: any, + next: (arg0: any, arg1: any) => any + ) => { apiLogger.logRequest(req); const context = await next(req, options); apiLogger.logResponse(context.response); @@ -351,7 +358,11 @@ async function mockClient(loggingOpts: LoggingOptions) { return await executor(mockRequest(), undefined); } -function expectLogsToBeLogged(logSpy: jest.SpyInstance, expectedConsoleLogs: string | any[], index = 0) { +function expectLogsToBeLogged( + logSpy: jest.SpyInstance, + expectedConsoleLogs: string | any[], + index = 0 +) { for (let i = index; i < expectedConsoleLogs.length; i++) { expect(logSpy.mock.calls[i][0]).toEqual(expectedConsoleLogs[i]); } From 4564d779c6e5d9a67d0798e923487d312f2d2a40 Mon Sep 17 00:00:00 2001 From: mrafnadeem-apimatic Date: Wed, 4 Jun 2025 23:17:56 +0500 Subject: [PATCH 19/20] revert: instanceof checks for errors in tests --- packages/http-headers/test/httpHeaders.test.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/http-headers/test/httpHeaders.test.ts b/packages/http-headers/test/httpHeaders.test.ts index 531ab4d90..9f1163309 100644 --- a/packages/http-headers/test/httpHeaders.test.ts +++ b/packages/http-headers/test/httpHeaders.test.ts @@ -82,10 +82,6 @@ describe('HTTP Headers', () => { assertHeaders('header'); } catch (error) { expect(error).toBeInstanceOf(TypeError); - if (!(error instanceof TypeError)) { - throw new Error('This check is for type narrowing.'); - } - expect(error.message).toBe('Headers must be an object.'); } }); @@ -95,9 +91,6 @@ describe('HTTP Headers', () => { try { assertHeaders({ 'invalid header': 'test' }); } catch (error) { - if (!(error instanceof Error)) { - throw new Error('This check is for type narrowing.'); - } expect(error.message).toBe( '"invalid header" is not a valid header name.' ); @@ -110,10 +103,6 @@ describe('HTTP Headers', () => { assertHeaders({ 'header-name': 123 }); } catch (error) { expect(error).toBeInstanceOf(TypeError); - if (!(error instanceof TypeError)) { - throw new Error('This check is for type narrowing.'); - } - expect(error.message).toBe( 'Header value must be string but number provided.' ); From ab02a566e6764112139f0adc5480355a450ffa1e Mon Sep 17 00:00:00 2001 From: Muhammad Rafay Nadeem Date: Mon, 18 Aug 2025 15:32:04 +0500 Subject: [PATCH 20/20] fix: use type name instead of this --- packages/core/src/http/requestBuilder.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/core/src/http/requestBuilder.ts b/packages/core/src/http/requestBuilder.ts index 550684a5e..08ef7e811 100644 --- a/packages/core/src/http/requestBuilder.ts +++ b/packages/core/src/http/requestBuilder.ts @@ -175,10 +175,13 @@ export interface RequestBuilder { ): RequestBuilder; paginate( createPagedIterable: ( - req: this, + req: RequestBuilder, updater: ( - req: this - ) => (pointer: string | null, setter: (value: any) => any) => this + req: RequestBuilder + ) => ( + pointer: string | null, + setter: (value: any) => any + ) => RequestBuilder ) => PagedAsyncIterable ): PagedAsyncIterable; call(requestOptions?: RequestOptions): Promise>; @@ -555,10 +558,13 @@ export class DefaultRequestBuilder } public paginate( createPagedIterable: ( - req: this, + req: RequestBuilder, updater: ( - req: this - ) => (pointer: string | null, setter: (value: any) => any) => this + req: RequestBuilder + ) => ( + pointer: string | null, + setter: (value: any) => any + ) => RequestBuilder ) => PagedAsyncIterable ): PagedAsyncIterable { return createPagedIterable(this, (req) =>