Skip to content

Commit

Permalink
fix(schema): fix lenient validation of string numbers in oneof (#145)
Browse files Browse the repository at this point in the history
 fix(schema): fix lenient validation of string numbers in oneof

closes #144

Co-authored-by: MaryamAdnan3 <[email protected]>
  • Loading branch information
MaryamAdnan3 and MaryamAdnan3 authored Nov 29, 2023
1 parent 94ff4a0 commit 1eaa589
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 26 deletions.
2 changes: 1 addition & 1 deletion packages/schema/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@apimatic/schema",
"author": "APIMatic Ltd.",
"version": "0.7.3",
"version": "0.7.4",
"license": "MIT",
"sideEffects": false,
"main": "lib/index.js",
Expand Down
14 changes: 12 additions & 2 deletions packages/schema/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export interface SchemaContext {
readonly type: string;
readonly branch: unknown[];
readonly path: Array<string | number>;
strictValidation?: boolean;
}

/**
Expand Down Expand Up @@ -178,12 +179,17 @@ export function validateAndUnmapXml<T extends Schema<any, any>>(
/**
* Create a new schema context using the given value and type.
*/
function createNewSchemaContext(value: unknown, type: string): SchemaContext {
function createNewSchemaContext(
value: unknown,
type: string,
strict?: boolean
): SchemaContext {
return {
value,
type,
branch: [value],
path: [],
strictValidation: strict,
};
}

Expand All @@ -203,6 +209,7 @@ function createSchemaContextCreator(
type: childSchema.type(),
branch: [...currentContext.branch, value],
path: [...currentContext.path, key],
strictValidation: currentContext.strictValidation,
});

const mapChildren: SchemaContextCreator['mapChildren'] = (
Expand All @@ -221,7 +228,10 @@ function createSchemaContextCreator(
mapChildren,
fail: (message) => [
{
...currentContext,
value: currentContext.value,
type: currentContext.type,
branch: currentContext.branch,
path: currentContext.path,
message: createErrorMessage(currentContext, message),
},
],
Expand Down
12 changes: 6 additions & 6 deletions packages/schema/src/types/bigint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import {
toValidator,
} from '../utils';

function isValidBigIntValue(value: unknown): value is bigint {
return (
typeof value === 'bigint' ||
typeof value === 'number' ||
(typeof value === 'string' && /^-?\d+$/.test(value))
);
function isValidBigIntValue(value: unknown, strict: boolean): value is bigint {
return strict
? typeof value === 'bigint'
: typeof value === 'bigint' ||
typeof value === 'number' ||
(typeof value === 'string' && /^-?\d+$/.test(value));
}

/** Create a bigint schema */
Expand Down
10 changes: 5 additions & 5 deletions packages/schema/src/types/boolean.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Schema } from '../schema';
import { createSymmetricSchema, toValidator } from '../utils';

function isValidBooleanValue(value: unknown): boolean {
return (
typeof value === 'boolean' ||
(typeof value === 'string' && (value === 'true' || value === 'false'))
);
function isValidBooleanValue(value: unknown, strict: boolean): boolean {
return strict
? typeof value === 'boolean'
: typeof value === 'boolean' ||
(typeof value === 'string' && (value === 'true' || value === 'false'));
}

/** Create a boolean schema. */
Expand Down
6 changes: 3 additions & 3 deletions packages/schema/src/types/oneOf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ function matchAndValidateBeforeMap<T extends Array<Schema<any, any>>>(
ctxt: SchemaContextCreator
) {
const matchedSchemas: Array<Schema<any, any>> = [];

ctxt.strictValidation = true;
for (const schema of schemas) {
const validationErrors = schema.validateBeforeMap(value, ctxt);
if (validationErrors.length === 0) {
Expand All @@ -139,7 +139,7 @@ function matchAndValidateBeforeUnmap<T extends Array<Schema<any, any>>>(
ctxt: SchemaContextCreator
) {
const matchedSchemas: Array<Schema<any, any>> = [];

ctxt.strictValidation = true;
for (const schema of schemas) {
const validationErrors = schema.validateBeforeUnmap(value, ctxt);
if (validationErrors.length === 0) {
Expand All @@ -155,7 +155,7 @@ function matchAndValidateBeforeMapXml<T extends Array<Schema<any, any>>>(
ctxt: SchemaContextCreator
) {
const matchedSchemas: Array<Schema<any, any>> = [];

ctxt.strictValidation = true;
for (const schema of schemas) {
const validationErrors = schema.validateBeforeMapXml(value, ctxt);
if (validationErrors.length === 0) {
Expand Down
17 changes: 10 additions & 7 deletions packages/schema/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ export function identityFn<T>(value: T): T {
}

export function toValidator(
fn: (value: unknown) => boolean
fn: (value: unknown, strict?: boolean) => boolean
): (value: unknown, ctxt: SchemaContextCreator) => SchemaValidationError[] {
return (value, ctxt) => (fn(value) ? [] : ctxt.fail());
return (value, ctxt) => (fn(value, ctxt.strictValidation) ? [] : ctxt.fail());
}

/**
Expand Down Expand Up @@ -93,11 +93,14 @@ function createBasicSchema<T, S>(basicSchema: BasicSchema<T, S>): Schema<T, S> {
};
}

export function isNumericString(value: unknown): value is number | string {
return (
typeof value === 'number' ||
(typeof value === 'string' && !isNaN(value as any))
);
export function isNumericString(
value: unknown,
strict?: boolean
): value is number | string {
return strict
? typeof value === 'number'
: typeof value === 'number' ||
(typeof value === 'string' && !isNaN(value as any));
}

export function coerceNumericStringToNumber(value: number | string): number {
Expand Down
28 changes: 26 additions & 2 deletions packages/schema/test/types/oneOf.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { array, boolean, object } from '../../src';
import { array, bigint, boolean, object } from '../../src';
import { validateAndMap, validateAndUnmap } from '../../src/schema';
import { nullable } from '../../src/types/nullable';
import { number } from '../../src/types/number';
Expand All @@ -22,14 +22,38 @@ import { stringEnum } from '../../src/types/stringEnum';

describe('OnyOf', () => {
describe('Mapping', () => {
it('should map oneOf primitives', () => {
it('should map oneOf primitives with number value', () => {
const input = 1;
const schema = oneOf([string(), number()]);
const output = validateAndMap(input, schema);
expect(output.errors).toBeFalsy();
expect((output as any).result).toStrictEqual(input);
});

it('should map oneOf primitives with number-string value', () => {
const input = '1';
const schema = oneOf([string(), number()]);
const output = validateAndMap(input, schema);
expect(output.errors).toBeFalsy();
expect((output as any).result).toStrictEqual(input);
});

it('should map oneOf primitives with boolean-string value', () => {
const input = 'true';
const schema = oneOf([string(), boolean()]);
const output = validateAndMap(input, schema);
expect(output.errors).toBeFalsy();
expect((output as any).result).toStrictEqual(input);
});

it('should map oneOf primitives with bigint-string value', () => {
const input = '9532532599932222222';
const schema = oneOf([string(), bigint()]);
const output = validateAndMap(input, schema);
expect(output.errors).toBeFalsy();
expect((output as any).result).toStrictEqual(input);
});

it('should map oneOf primitives or complex types', () => {
const input: Boss = {
promotedAt: 45,
Expand Down

0 comments on commit 1eaa589

Please sign in to comment.