Skip to content

Commit 1207261

Browse files
committed
fix: partial()
BREAKING CHANGE: 'required' no longer adds a test for most schema, to determine if a schema is required, check it's `spec.optional` and `spec.nullable` values, also accessible via `describe()`
1 parent 0001b0b commit 1207261

File tree

8 files changed

+110
-63
lines changed

8 files changed

+110
-63
lines changed

src/array.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import isAbsent from './util/isAbsent';
21
import isSchema from './util/isSchema';
32
import printValue from './util/printValue';
43
import parseJson from './util/parseJson';
@@ -192,8 +191,9 @@ export default class ArraySchema<
192191
name: 'length',
193192
exclusive: true,
194193
params: { length },
194+
skipAbsent: true,
195195
test(value) {
196-
return isAbsent(value) || value.length === this.resolve(length);
196+
return value!.length === this.resolve(length);
197197
},
198198
});
199199
}
@@ -206,9 +206,10 @@ export default class ArraySchema<
206206
name: 'min',
207207
exclusive: true,
208208
params: { min },
209+
skipAbsent: true,
209210
// FIXME(ts): Array<typeof T>
210211
test(value) {
211-
return isAbsent(value) || value.length >= this.resolve(min);
212+
return value!.length >= this.resolve(min);
212213
},
213214
});
214215
}
@@ -220,8 +221,9 @@ export default class ArraySchema<
220221
name: 'max',
221222
exclusive: true,
222223
params: { max },
224+
skipAbsent: true,
223225
test(value) {
224-
return isAbsent(value) || value.length <= this.resolve(max);
226+
return value!.length <= this.resolve(max);
225227
},
226228
});
227229
}

src/date.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// @ts-ignore
22
import isoParse from './util/isodate';
33
import { date as locale } from './locale';
4-
import isAbsent from './util/isAbsent';
54
import Ref from './Reference';
65
import type { AnyObject, Message } from './types';
76
import type {
@@ -85,8 +84,9 @@ export default class DateSchema<
8584
name: 'min',
8685
exclusive: true,
8786
params: { min },
87+
skipAbsent: true,
8888
test(value) {
89-
return isAbsent(value) || value >= this.resolve(limit);
89+
return value! >= this.resolve(limit);
9090
},
9191
});
9292
}
@@ -99,8 +99,9 @@ export default class DateSchema<
9999
name: 'max',
100100
exclusive: true,
101101
params: { max },
102+
skipAbsent: true,
102103
test(value) {
103-
return isAbsent(value) || value <= this.resolve(limit);
104+
return value! <= this.resolve(limit);
104105
},
105106
});
106107
}

src/number.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,9 @@ export default class NumberSchema<
6767
name: 'min',
6868
exclusive: true,
6969
params: { min },
70+
skipAbsent: true,
7071
test(value: Maybe<number>) {
71-
return isAbsent(value) || value >= this.resolve(min);
72+
return value! >= this.resolve(min);
7273
},
7374
});
7475
}
@@ -79,8 +80,9 @@ export default class NumberSchema<
7980
name: 'max',
8081
exclusive: true,
8182
params: { max },
83+
skipAbsent: true,
8284
test(value: Maybe<number>) {
83-
return isAbsent(value) || value <= this.resolve(max);
85+
return value! <= this.resolve(max);
8486
},
8587
});
8688
}
@@ -91,8 +93,9 @@ export default class NumberSchema<
9193
name: 'max',
9294
exclusive: true,
9395
params: { less },
96+
skipAbsent: true,
9497
test(value: Maybe<number>) {
95-
return isAbsent(value) || value < this.resolve(less);
98+
return value! < this.resolve(less);
9699
},
97100
});
98101
}
@@ -103,8 +106,9 @@ export default class NumberSchema<
103106
name: 'min',
104107
exclusive: true,
105108
params: { more },
109+
skipAbsent: true,
106110
test(value: Maybe<number>) {
107-
return isAbsent(value) || value > this.resolve(more);
111+
return value! > this.resolve(more);
108112
},
109113
});
110114
}
@@ -121,7 +125,8 @@ export default class NumberSchema<
121125
return this.test({
122126
name: 'integer',
123127
message,
124-
test: (val) => isAbsent(val) || Number.isInteger(val),
128+
skipAbsent: true,
129+
test: (val) => Number.isInteger(val),
125130
});
126131
}
127132

src/schema.ts

+3-23
Original file line numberDiff line numberDiff line change
@@ -627,10 +627,6 @@ export default abstract class Schema<
627627
return this.clone({ strict: isStrict });
628628
}
629629

630-
protected _isPresent(value: any) {
631-
return value != null;
632-
}
633-
634630
protected nullability(nullable: boolean, message?: Message<any>) {
635631
const next = this.clone({ nullable });
636632
next.internalTests.nullable = createValidation({
@@ -671,27 +667,11 @@ export default abstract class Schema<
671667

672668
required(message: Message<any> = locale.required): any {
673669
return this.clone().withMutation((next) =>
674-
next
675-
.nonNullable(message)
676-
.defined(message)
677-
.test({
678-
message,
679-
name: 'required',
680-
exclusive: true,
681-
test(value: any) {
682-
return this.schema._isPresent(value);
683-
},
684-
}),
685-
) as any;
670+
next.nonNullable(message).defined(message),
671+
);
686672
}
687-
688673
notRequired(): any {
689-
return this.clone().withMutation((next) => {
690-
next.tests = next.tests.filter(
691-
(test) => test.OPTIONS!.name !== 'required',
692-
);
693-
return next.nullable().optional();
694-
});
674+
return this.clone().withMutation((next) => next.nullable().optional());
695675
}
696676

697677
transform(fn: TransformFunction<this>) {

src/string.ts

+27-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MixedLocale, string as locale } from './locale';
1+
import { MixedLocale, mixed as mixedLocale, string as locale } from './locale';
22
import isAbsent from './util/isAbsent';
33
import type Reference from './Reference';
44
import type { Message, AnyObject } from './types';
@@ -83,8 +83,22 @@ export default class StringSchema<
8383
});
8484
}
8585

86-
protected _isPresent(value: any) {
87-
return super._isPresent(value) && !!value.length;
86+
required(message?: Message<any>) {
87+
return super.required(message).withMutation((schema: this) =>
88+
schema.test({
89+
message: message || mixedLocale.required,
90+
name: 'required',
91+
skipAbsent: true,
92+
test: (value) => !!value!.length,
93+
}),
94+
);
95+
}
96+
97+
notRequired() {
98+
return super.notRequired().withMutation((schema: this) => {
99+
schema.tests.filter((t) => t.OPTIONS!.name !== 'required');
100+
return schema;
101+
});
88102
}
89103

90104
length(
@@ -96,8 +110,9 @@ export default class StringSchema<
96110
name: 'length',
97111
exclusive: true,
98112
params: { length },
113+
skipAbsent: true,
99114
test(value: Maybe<string>) {
100-
return isAbsent(value) || value.length === this.resolve(length);
115+
return value!.length === this.resolve(length);
101116
},
102117
});
103118
}
@@ -111,8 +126,9 @@ export default class StringSchema<
111126
name: 'min',
112127
exclusive: true,
113128
params: { min },
129+
skipAbsent: true,
114130
test(value: Maybe<string>) {
115-
return isAbsent(value) || value.length >= this.resolve(min);
131+
return value!.length >= this.resolve(min);
116132
},
117133
});
118134
}
@@ -126,8 +142,9 @@ export default class StringSchema<
126142
exclusive: true,
127143
message,
128144
params: { max },
145+
skipAbsent: true,
129146
test(value: Maybe<string>) {
130-
return isAbsent(value) || value.length <= this.resolve(max);
147+
return value!.length <= this.resolve(max);
131148
},
132149
});
133150
}
@@ -153,10 +170,9 @@ export default class StringSchema<
153170
name: name || 'matches',
154171
message: message || locale.matches,
155172
params: { regex },
173+
skipAbsent: true,
156174
test: (value: Maybe<string>) =>
157-
isAbsent(value) ||
158-
(value === '' && excludeEmptyString) ||
159-
value.search(regex) !== -1,
175+
(value === '' && excludeEmptyString) || value!.search(regex) !== -1,
160176
});
161177
}
162178

@@ -206,6 +222,7 @@ export default class StringSchema<
206222
message,
207223
name: 'string_case',
208224
exclusive: true,
225+
skipAbsent: true,
209226
test: (value: Maybe<string>) =>
210227
isAbsent(value) || value === value.toLowerCase(),
211228
});
@@ -218,6 +235,7 @@ export default class StringSchema<
218235
message,
219236
name: 'string_case',
220237
exclusive: true,
238+
skipAbsent: true,
221239
test: (value: Maybe<string>) =>
222240
isAbsent(value) || value === value.toUpperCase(),
223241
});

src/util/createValidation.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from '../types';
1111
import Reference from '../Reference';
1212
import type { AnySchema } from '../schema';
13+
import isAbsent from './isAbsent';
1314

1415
export type PanicCallback = (err: Error) => void;
1516

@@ -58,6 +59,7 @@ export type TestConfig<TValue = unknown, TContext = {}> = {
5859
test: TestFunction<TValue, TContext>;
5960
params?: ExtraParams;
6061
exclusive?: boolean;
62+
skipAbsent?: boolean;
6163
};
6264

6365
export type Test = ((
@@ -73,6 +75,7 @@ export default function createValidation(config: {
7375
test: TestFunction;
7476
params?: ExtraParams;
7577
message?: Message<any>;
78+
skipAbsent?: boolean;
7679
}) {
7780
function validate<TSchema extends AnySchema = AnySchema>(
7881
{
@@ -88,7 +91,7 @@ export default function createValidation(config: {
8891
panic: PanicCallback,
8992
next: NextCallback,
9093
) {
91-
const { name, test, params, message } = config;
94+
const { name, test, params, message, skipAbsent } = config;
9295
let { parent, context, abortEarly = rest.schema.spec.abortEarly } = options;
9396

9497
function resolve<T>(item: T | Reference<T>) {
@@ -144,9 +147,11 @@ export default function createValidation(config: {
144147
else panic(err);
145148
};
146149

150+
const shouldSkip = skipAbsent && isAbsent(value);
151+
147152
if (!sync) {
148153
try {
149-
Promise.resolve(test.call(ctx, value, ctx)).then(
154+
Promise.resolve(!shouldSkip ? test.call(ctx, value, ctx) : true).then(
150155
handleResult,
151156
handleError,
152157
);
@@ -159,7 +164,7 @@ export default function createValidation(config: {
159164

160165
let result: ReturnType<TestFunction>;
161166
try {
162-
result = test.call(ctx, value, ctx);
167+
result = !shouldSkip ? test.call(ctx, value, ctx) : true;
163168

164169
if (typeof (result as any)?.then === 'function') {
165170
throw new Error(

test/mixed.ts

+6-15
Original file line numberDiff line numberDiff line change
@@ -711,10 +711,11 @@ describe('Mixed Types ', () => {
711711
await expect(inst.isValid('a')).resolves.toBe(true);
712712
});
713713

714-
it('concat should maintain explicit presence', async function () {
715-
let inst = string().required().concat(string());
714+
it('concat should override presence', async function () {
715+
let inst = string().required().concat(string().nullable());
716716

717-
await expect(inst.isValid(undefined)).resolves.toBe(false);
717+
await expect(inst.isValid(undefined)).resolves.toBe(true);
718+
await expect(inst.isValid(null)).resolves.toBe(true);
718719
});
719720

720721
it('gives whitelist precedence to second in concat', async function () {
@@ -968,12 +969,7 @@ describe('Mixed Types ', () => {
968969
label: undefined,
969970
nullable: false,
970971
optional: false,
971-
tests: [
972-
{
973-
name: 'required',
974-
params: undefined,
975-
},
976-
],
972+
tests: [],
977973
oneOf: [],
978974
notOneOf: [],
979975
innerType: {
@@ -1040,12 +1036,7 @@ describe('Mixed Types ', () => {
10401036
label: undefined,
10411037
nullable: false,
10421038
optional: false,
1043-
tests: [
1044-
{
1045-
name: 'required',
1046-
params: undefined,
1047-
},
1048-
],
1039+
tests: [],
10491040
oneOf: [],
10501041
notOneOf: [],
10511042
innerType: {

0 commit comments

Comments
 (0)