Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: improve internal test run APIs and Error handling #1545

Merged
merged 4 commits into from
Jan 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions CHANGELOG-pre.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
# [1.0.0-beta.0](https://github.com/jquense/yup/compare/v1.0.0-alpha.4...v1.0.0-beta.0) (2021-12-29)


* feat!: add json() method and remove default object/array coercion ([94b73c4](https://github.com/jquense/yup/commit/94b73c438b3d355253f488325e06c69378e71fc1))


### Features

* Make Array generic consistent with others ([a82353f](https://github.com/jquense/yup/commit/a82353f37735daec6e42d18bd4cc0efe52a20f50))


### BREAKING CHANGES

* types only, `ArraySchema` initial generic is the array type not the type of the array element. `array<T>()` is still the inner type.
* object and array schema no longer parse JSON strings by default, nor do they return `null` for invalid casts.

```ts
object().json().cast('{}')
array().json().cast('[]')
```
to mimic the previous behavior



# [1.0.0-alpha.4](https://github.com/jquense/yup/compare/v1.0.0-alpha.3...v1.0.0-alpha.4) (2021-12-29)

### Bug Fixes
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1399,6 +1399,7 @@ and use the result as the limit.

Define an array schema. Arrays can be typed or not, When specifying the element type, `cast` and `isValid`
will apply to the elements as well. Options passed into `isValid` are passed also passed to child schemas.

Inherits from [`Schema`](#Schema).

```js
Expand All @@ -1418,9 +1419,7 @@ array().of(yup.number());
array(yup.number());
```

The default `cast` behavior for `array` is: [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse)

Failed casts return: `null`;
Arrays have no default casting behavior.

#### `array.of(type: Schema): this`

Expand Down
15 changes: 7 additions & 8 deletions src/Lazy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import isSchema from './util/isSchema';
import type { AnyObject, Callback, ValidateOptions } from './types';
import type { AnyObject, ValidateOptions } from './types';
import type { ResolveOptions } from './Condition';

import type {
Expand Down Expand Up @@ -82,13 +82,12 @@ class Lazy<T, TContext = AnyObject, TDefault = any, TFlags extends Flags = any>
return this._resolve(value, options).cast(value, options);
}

validate(
value: any,
options?: ValidateOptions,
maybeCb?: Callback,
): Promise<T> {
// @ts-expect-error missing public callback on type
return this._resolve(value, options).validate(value, options, maybeCb);
asTest(value: any, options?: ValidateOptions<TContext>) {
return this._resolve(value, options).asTest(value, options);
}

validate(value: any, options?: ValidateOptions<TContext>): Promise<T> {
return this._resolve(value, options).validate(value, options);
}

validateSync(value: any, options?: ValidateOptions<TContext>): T {
Expand Down
55 changes: 22 additions & 33 deletions src/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@ import isAbsent from './util/isAbsent';
import isSchema from './util/isSchema';
import printValue from './util/printValue';
import { array as locale } from './locale';
import runTests, { RunTest } from './util/runTests';
import type {
AnyObject,
InternalOptions,
Callback,
Message,
Maybe,
Optionals,
} from './types';
import ValidationError from './ValidationError';
import type Reference from './Reference';
import {
Defined,
Expand All @@ -24,9 +21,14 @@ import {
UnsetFlag,
Concat,
} from './util/types';
import Schema, { SchemaInnerTypeDescription, SchemaSpec } from './schema';
import Schema, {
RunTest,
SchemaInnerTypeDescription,
SchemaSpec,
} from './schema';
import { ResolveOptions } from './Condition';
import parseJson from 'parse-json';
import { ValidationError } from '.';

type InnerType<T> = T extends Array<infer I> ? I : never;

Expand All @@ -36,9 +38,7 @@ export type RejectorFn = (
array: readonly any[],
) => boolean;

export function create<C extends AnyObject = AnyObject, T = any>(
type?: ISchema<T, C>,
) {
export function create<C = AnyObject, T = any>(type?: ISchema<T, C>) {
return new ArraySchema<T[] | undefined, C>(type as any);
}

Expand Down Expand Up @@ -89,28 +89,22 @@ export default class ArraySchema<
protected _validate(
_value: any,
options: InternalOptions<TContext> = {},
callback: Callback,

panic: (err: Error, value: unknown) => void,
callback: (err: ValidationError[], value: unknown) => void,
) {
let errors = [] as ValidationError[];
let sync = options.sync;
let path = options.path;
// let sync = options.sync;
// let path = options.path;
let innerType = this.innerType;
let endEarly = options.abortEarly ?? this.spec.abortEarly;
// let endEarly = options.abortEarly ?? this.spec.abortEarly;
let recursive = options.recursive ?? this.spec.recursive;

let originalValue =
options.originalValue != null ? options.originalValue : _value;

super._validate(_value, options, (err, value) => {
if (err) {
if (!ValidationError.isError(err) || endEarly) {
return void callback(err, value);
}
errors.push(err);
}

super._validate(_value, options, panic, (arrayErrors, value) => {
if (!recursive || !innerType || !this._typeCheck(value)) {
callback(errors[0] || null, value);
callback(arrayErrors, value);
return;
}

Expand All @@ -122,29 +116,24 @@ export default class ArraySchema<
let item = value[idx];
let path = `${options.path || ''}[${idx}]`;

// object._validate note for isStrict explanation
let innerOptions = {
tests[idx] = innerType!.asTest(item, {
...options,
path,
strict: true,
parent: value,
// FIXME
index: idx,
originalValue: originalValue[idx],
};

tests[idx] = (_, cb) => innerType!.validate(item, innerOptions, cb);
});
}

runTests(
this.runTests(
{
sync,
path,
value,
errors,
endEarly,
tests,
},
callback,
panic,
(innerTypeErrors) =>
callback(innerTypeErrors.concat(arrayErrors), value),
);
});
}
Expand Down
81 changes: 28 additions & 53 deletions src/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@ import {
import { object as locale } from './locale';
import sortFields from './util/sortFields';
import sortByKeyOrder from './util/sortByKeyOrder';
import runTests from './util/runTests';
import { InternalOptions, Callback, Maybe, Message } from './types';
import ValidationError from './ValidationError';
import { InternalOptions, Maybe, Message } from './types';
import type { Defined, Thunk, NotNull, _ } from './util/types';
import type Reference from './Reference';
import Reference from './Reference';
import Schema, { SchemaObjectDescription, SchemaSpec } from './schema';
import { ResolveOptions } from './Condition';
import type {
Expand All @@ -30,6 +28,8 @@ import type {
TypeFromShape,
} from './util/objectTypes';
import parseJson from './util/parseJson';
import type { Test } from './util/createValidation';
import type ValidationError from './ValidationError';

export type { AnyObject };

Expand Down Expand Up @@ -205,14 +205,12 @@ export default class ObjectSchema<
protected _validate(
_value: any,
opts: InternalOptions<TContext> = {},
callback: Callback,
panic: (err: Error, value: unknown) => void,
next: (err: ValidationError[], value: unknown) => void,
) {
let errors = [] as ValidationError[];
let {
sync,
from = [],
originalValue = _value,
abortEarly = this.spec.abortEarly,
recursive = this.spec.recursive,
} = opts;

Expand All @@ -224,64 +222,41 @@ export default class ObjectSchema<
opts.originalValue = originalValue;
opts.from = from;

super._validate(_value, opts, (err, value) => {
if (err) {
if (!ValidationError.isError(err) || abortEarly) {
return void callback(err, value);
}
errors.push(err);
}

super._validate(_value, opts, panic, (objectErrors, value) => {
if (!recursive || !isObject(value)) {
callback(errors[0] || null, value);
next(objectErrors, value);
return;
}

originalValue = originalValue || value;

let tests = this._nodes.map((key) => (__: any, cb: Callback) => {
let tests = [] as Test[];
for (let key of this._nodes) {
let field = this.fields[key];

if (!field || Reference.isRef(field)) {
continue;
}

let path =
key.indexOf('.') === -1
? (opts.path ? `${opts.path}.` : '') + key
: `${opts.path || ''}["${key}"]`;

let field = this.fields[key];

if (field && 'validate' in field) {
field.validate(
value[key],
{
...opts,
// @ts-ignore
path,
from,
// inner fields are always strict:
// 1. this isn't strict so the casting will also have cast inner values
// 2. this is strict in which case the nested values weren't cast either
strict: true,
parent: value,
originalValue: originalValue[key],
},
cb,
);
return;
}
tests.push(
field.asTest(value[key], {
...opts,
path,
from,
parent: value,
originalValue: originalValue[key],
}),
);
}

cb(null);
this.runTests({ tests, value }, panic, (fieldErrors) => {
next(fieldErrors.sort(this._sortErrors).concat(objectErrors), value);
});

runTests(
{
sync,
tests,
value,
errors,
endEarly: abortEarly,
sort: this._sortErrors,
path: opts.path,
},
callback,
);
});
}

Expand Down
Loading