Skip to content

Commit 768e5fa

Browse files
committed
chore: prep work for toggling coercion
1 parent c184dcf commit 768e5fa

8 files changed

+64
-33
lines changed

README.md

+31-5
Original file line numberDiff line numberDiff line change
@@ -603,15 +603,35 @@ Overrides the key name which is used in error messages.
603603
Adds to a metadata object, useful for storing data with a schema, that doesn't belong
604604
the cast object itself.
605605

606-
#### `Schema.describe(): SchemaDescription`
606+
#### `Schema.describe(options?: ResolveOptions): SchemaDescription`
607607

608608
Collects schema details (like meta, labels, and active tests) into a serializable
609609
description object.
610610

611611
```ts
612-
const description = object({
612+
const schema = object({
613613
name: string().required(),
614614
});
615+
616+
const description = schema.describe();
617+
```
618+
619+
For schema with dynamic components (references, lazy, or conditions), describe requires
620+
more context to accurately return the schema description. In these cases provide `options`
621+
622+
```ts
623+
import { ref, object, string, boolean } from 'yup';
624+
625+
let schema = object({
626+
isBig: boolean(),
627+
count: number().when('isBig', {
628+
is: true,
629+
then: (schema) => schema.min(5),
630+
otherwise: (schema) => schema.min(0),
631+
}),
632+
});
633+
634+
schema.describe({ value: { isBig: true } });
615635
```
616636

617637
And below is are the description types, which differ a bit depending on the schema type.
@@ -778,6 +798,10 @@ interface CastOptions<TContext extends {}> {
778798
// Remove undefined properties from objects
779799
stripUnknown: boolean = false;
780800

801+
// Throws a TypeError if casting doesn't produce a valid type
802+
// note that the TS return type is inaccurate when this is `false`, use with caution
803+
assert?: boolean = true;
804+
781805
// External values that used to resolve conditions and references
782806
context?: TContext;
783807
}
@@ -988,10 +1012,12 @@ let schema = object({
9881012
count: number()
9891013
.when('isBig', {
9901014
is: true, // alternatively: (val) => val == true
991-
then: (schema) => schema..min(5),
992-
otherwise: (schema) => schema..min(0),
1015+
then: (schema) => schema.min(5),
1016+
otherwise: (schema) => schema.min(0),
9931017
})
994-
.when('$other', ([other], schema) => (other === 4 ? schema.max(6) : schema)),
1018+
.when('$other', ([other], schema) =>
1019+
other === 4 ? schema.max(6) : schema,
1020+
),
9951021
});
9961022

9971023
await schema.validate(value, { context: { other: 4 } });

src/array.ts

+16-16
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
ToggleDefault,
2222
ISchema,
2323
UnsetFlag,
24+
Concat,
2425
} from './util/types';
2526
import Schema, { SchemaInnerTypeDescription, SchemaSpec } from './schema';
2627
import { ResolveOptions } from './Condition';
@@ -59,15 +60,17 @@ export default class ArraySchema<
5960
this.innerType = type;
6061

6162
this.withMutation(() => {
62-
this.transform(function (values) {
63-
if (typeof values === 'string')
63+
this.transform((values, _, ctx) => {
64+
if (!ctx.spec.coarce) return values;
65+
if (typeof values === 'string') {
6466
try {
6567
values = JSON.parse(values);
6668
} catch (err) {
6769
values = null;
6870
}
71+
}
6972

70-
return this.isType(values) ? values : null;
73+
return ctx.isType(values) ? values : null;
7174
});
7275
});
7376
}
@@ -167,8 +170,16 @@ export default class ArraySchema<
167170
return next;
168171
}
169172

170-
concat<TOther extends ArraySchema<any, any, any>>(schema: TOther): TOther;
171-
concat(schema: any): any;
173+
concat<IT, IC, ID, IF extends Flags, IIn extends Maybe<IT[]>>(
174+
schema: ArraySchema<IT, IC, ID, IF, IIn>,
175+
): ArraySchema<
176+
Concat<T, IT>,
177+
TContext & IC,
178+
Extract<IF, 'd'> extends never ? TDefault : ID,
179+
TFlags | IF,
180+
Concat<TIn, IIn>
181+
>;
182+
concat(schema: this): this;
172183
concat(schema: any): any {
173184
let next = super.concat(schema) as this;
174185

@@ -294,17 +305,6 @@ export default interface ArraySchema<
294305
def: Thunk<D>,
295306
): ArraySchema<T, TContext, D, ToggleDefault<TFlags, D>, TIn>;
296307

297-
concat<IT, IC, ID, IF extends Flags, IIn extends Maybe<IT[]>>(
298-
schema: ArraySchema<IT, IC, ID, IF, IIn>,
299-
): ArraySchema<
300-
NonNullable<T> | IT,
301-
TContext & IC,
302-
Extract<IF, 'd'> extends never ? TDefault : ID,
303-
TFlags | IF,
304-
IIn
305-
>;
306-
concat(schema: this): this;
307-
308308
defined(
309309
msg?: Message,
310310
): ArraySchema<T, TContext, TDefault, TFlags, Defined<TIn>>;

src/boolean.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ export default class BooleanSchema<
3838
});
3939

4040
this.withMutation(() => {
41-
this.transform(function (value) {
42-
if (!this.isType(value)) {
41+
this.transform((value, _raw, ctx) => {
42+
if (ctx.spec.coarce && !ctx.isType(value)) {
4343
if (/^(true|1)$/i.test(String(value))) return true;
4444
if (/^(false|0)$/i.test(String(value))) return false;
4545
}

src/date.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ export default class DateSchema<
4646
});
4747

4848
this.withMutation(() => {
49-
this.transform(function (value) {
50-
if (this.isType(value)) return value;
49+
this.transform((value, _raw, ctx) => {
50+
if (!ctx.spec.coarce || ctx.isType(value)) return value;
5151

5252
value = isoParse(value);
5353

src/number.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,18 @@ export default class NumberSchema<
4242
});
4343

4444
this.withMutation(() => {
45-
this.transform(function (value) {
46-
let parsed = value;
45+
this.transform((value, _raw, ctx) => {
46+
if (!ctx.spec.coarce) return value;
4747

48+
let parsed = value;
4849
if (typeof parsed === 'string') {
4950
parsed = parsed.replace(/\s/g, '');
5051
if (parsed === '') return NaN;
5152
// don't use parseFloat to avoid positives on alpha-numeric strings
5253
parsed = +parsed;
5354
}
5455

55-
if (this.isType(parsed)) return parsed;
56+
if (ctx.isType(parsed)) return parsed;
5657

5758
return parseFloat(parsed);
5859
});

src/object.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -124,15 +124,15 @@ export default class ObjectSchema<
124124
});
125125

126126
this.withMutation(() => {
127-
this.transform(function coerce(value) {
127+
this.transform((value, _raw, ctx) => {
128128
if (typeof value === 'string') {
129129
try {
130130
value = JSON.parse(value);
131131
} catch (err) {
132132
value = null;
133133
}
134134
}
135-
if (this.isType(value)) return value;
135+
if (ctx.isType(value)) return value;
136136
return null;
137137
});
138138

src/schema.ts

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import type { Flags, ISchema, ResolveFlags, Thunk, _ } from './util/types';
3535
import toArray from './util/toArray';
3636

3737
export type SchemaSpec<TDefault> = {
38+
coarce: boolean;
3839
nullable: boolean;
3940
optional: boolean;
4041
default?: TDefault | (() => TDefault);
@@ -157,6 +158,7 @@ export default abstract class Schema<
157158
recursive: true,
158159
nullable: false,
159160
optional: true,
161+
coarce: true,
160162
...options?.spec,
161163
};
162164

src/string.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -59,19 +59,21 @@ export default class StringSchema<
5959
type: 'string',
6060
check(value): value is NonNullable<TType> {
6161
if (value instanceof String) value = value.valueOf();
62-
6362
return typeof value === 'string';
6463
},
6564
});
6665

6766
this.withMutation(() => {
68-
this.transform(function (value) {
69-
if (this.isType(value)) return value;
67+
this.transform((value, _raw, ctx) => {
68+
if (!ctx.spec.coarce || ctx.isType(value)) return value;
69+
70+
// don't ever convert arrays
7071
if (Array.isArray(value)) return value;
7172

7273
const strValue =
7374
value != null && value.toString ? value.toString() : value;
7475

76+
// no one wants plain objects converted to [Object object]
7577
if (strValue === objStringTag) return value;
7678

7779
return strValue;

0 commit comments

Comments
 (0)