Skip to content

Commit

Permalink
fixup object and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
jquense committed Dec 28, 2021
1 parent 7dbabda commit 0bcc8fd
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 55 deletions.
51 changes: 32 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Yup

Yup is a JavaScript schema builder for value parsing and validation. Define a schema, transform a value to match, validate the shape of an existing value, or both. Yup schema are extremely expressive and allow modeling complex, interdependent validations, or value transformations.
Yup is a JavaScript schema builder for value parsing and validation. Define a schema, transform a value to match, assert the shape of an existing value, or both. Yup schema are extremely expressive and allow modeling complex, interdependent validations, or value transformations.

Yup's API is heavily inspired by [Joi](https://github.com/hapijs/joi), but leaner and built with client-side validation as its primary use-case. Yup separates the parsing and validating functions into separate steps. `cast()` transforms data while `validate` checks that the input is the correct shape. Each can be performed together (such as HTML form validation) or seperately (such as deserializing trusted data from APIs).

Expand Down Expand Up @@ -38,27 +38,22 @@ let schema = yup.object().shape({
age: yup.number().required().positive().integer(),
email: yup.string().email(),
website: yup.string().url(),
createdOn: yup.date().default(function () {
return new Date();
}),
createdOn: yup.date().default(() => new Date()),
});

// check validity
schema
.isValid({
name: 'jimmy',
age: 24,
})
.then(function (valid) {
valid; // => true
});
// parse and assert validity
const parsedValue = await schema.validate({
name: 'jimmy',
age: 24,
});

// you can try and type cast objects to the defined schema
schema.cast({
name: 'jimmy',
age: '24',
createdOn: '2014-09-23T19:25:25Z',
});

// => { name: 'jimmy', age: 24, createdOn: Date }
```

Expand All @@ -77,8 +72,6 @@ import {
} from 'yup';
```

> If you're looking for an easily serializable DSL for yup schema, check out [yup-ast](https://github.com/WASD-Team/yup-ast)
### Using a custom locale dictionary

Allows you to customize the default messages used by Yup, when no message is provided with a validation test.
Expand All @@ -102,10 +95,12 @@ let schema = yup.object().shape({
age: yup.number().min(18),
});

schema.validate({ name: 'jimmy', age: 11 }).catch(function (err) {
try {
await schema.validate({ name: 'jimmy', age: 11 });
} catch (err) {
err.name; // => 'ValidationError'
err.errors; // => ['Deve ser maior que 18']
});
}
```

If you need multi-language support, Yup has got you covered. The function `setLocale` accepts functions that can be used to generate error objects with translation keys and values. Just get this output and feed it into your favorite i18n library.
Expand All @@ -131,10 +126,12 @@ let schema = yup.object().shape({
age: yup.number().min(18),
});

schema.validate({ name: 'jimmy', age: 11 }).catch(function (err) {
try {
await schema.validate({ name: 'jimmy', age: 11 });
} catch (err) {
err.name; // => 'ValidationError'
err.errors; // => [{ key: 'field_too_short', values: { min: 18 } }]
});
}
```

## API
Expand Down Expand Up @@ -385,6 +382,16 @@ SchemaDescription {
#### `mixed.concat(schema: Schema): Schema`

Creates a new instance of the schema by combining two schemas. Only schemas of the same type can be concatenated.
`concat` is not a "merge" function in the sense that all settings from the provided schema, override ones in the
base, including type, presence and nullability.

```ts
mixed<string>().defined().concat(mixed<number>().nullable());

// produces the equivalent to:

mixed<number>().defined().nullable();
```

#### `mixed.validate(value: any, options?: object): Promise<any, ValidationError>`

Expand Down Expand Up @@ -1159,6 +1166,12 @@ object({
});
```

#### `object.concat(schemaB: ObjectSchema): ObjectSchema`

Creates a object schema, by applying all settings and fields from `schemaB` to the base, producing a new schema.
The object shape is shallowly merged with common fields from `schemaB` taking precedence over the base
fields.

#### `object.pick(keys: string[]): Schema`

Create a new schema from a subset of the original's fields.
Expand Down
27 changes: 6 additions & 21 deletions docs/typescript.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,8 @@
## TypeScript Support

`yup` comes with robust typescript support! However, because of how dynamic `yup` is
not everything can be statically typed safely, but for most cases it's "Good Enough".

Note that `yup` schema actually produce _two_ different types: the result of casting an input, and the value after validation.
Why are these types different? Because a schema can produce a value via casting that
would not pass validation!

```js
const schema = string().nullable().required();

schema.cast(null); // -> null
schema.validateSync(null); // ValidationError this is required!
```

By itself this seems weird, but has it's uses when handling user input. To get a
TypeScript type that matches all possible `cast()` values, use `yup.TypeOf<typeof schema>`.
To produce a type that matches a valid object for the schema use `yup.Asserts<typeof schema>>`
`yup` comes with robust typescript support, producing values and types from schema that
provide compile time type safety as well as runtime parsing and validation. Schema refine
their output as

```ts
import * as yup from 'yup';
Expand Down Expand Up @@ -46,12 +32,11 @@ const personSchema = yup.object({
You can derive a type for the final validated object as follows:

```ts
import type { Asserts } from 'yup';
import type { InferType } from 'yup';

// you can also use a type alias by this displays better in tooling
interface Person extends Asserts<typeof personSchema> {}
type Person = InferType<typeof personSchema>;

const validated: Person = personSchema.validateSync(parsed);
const validated: Person = await personSchema.validate(value);
```

If you want the type produced by casting:
Expand Down
25 changes: 11 additions & 14 deletions src/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@ import { getter, normalizePath, join } from 'property-expr';
import { camelCase, snakeCase } from 'tiny-case';

import {
Concat,
Flags,
ISchema,
SetFlag,
ToggleDefault,
UnsetFlag,
} from './util/types';

import { object as locale } from './locale';
import sortFields from './util/sortFields';
import sortByKeyOrder from './util/sortByKeyOrder';
Expand All @@ -23,6 +21,7 @@ import BaseSchema, { SchemaObjectDescription, SchemaSpec } from './schema';
import { ResolveOptions } from './Condition';
import type {
AnyObject,
ConcatObjectTypes,
DefaultFromShape,
MakePartial,
MergeObjectTypes,
Expand Down Expand Up @@ -74,7 +73,7 @@ export default interface ObjectSchema<
// important that this is `any` so that using `ObjectSchema<MyType>`'s default
// will match object schema regardless of defaults
TDefault = any,
TFlags extends Flags = 'd',
TFlags extends Flags = '',
> extends BaseSchema<MakeKeysOptional<TIn>, TContext, TDefault, TFlags> {
default<D extends Maybe<AnyObject>>(
def: Thunk<D>,
Expand Down Expand Up @@ -105,14 +104,14 @@ export default class ObjectSchema<
TIn extends Maybe<AnyObject>,
TContext = AnyObject,
TDefault = any,
TFlags extends Flags = 'd',
TFlags extends Flags = '',
> extends BaseSchema<MakeKeysOptional<TIn>, TContext, TDefault, TFlags> {
fields: Shape<NonNullable<TIn>, TContext> = Object.create(null);

declare spec: ObjectSchemaSpec;

private _sortErrors = defaultSort;
private _nodes: string[] = []; //readonly (keyof TIn & string)[]
private _nodes: string[] = [];

private _excludedEdges: readonly [nodeA: string, nodeB: string][] = [];

Expand Down Expand Up @@ -312,22 +311,20 @@ export default class ObjectSchema<

concat<IIn, IC, ID, IF extends Flags>(
schema: ObjectSchema<IIn, IC, ID, IF>,
): ObjectSchema<Concat<TIn, IIn>, TContext & IC, TDefault & ID, TFlags | IF>;
): ObjectSchema<
ConcatObjectTypes<TIn, IIn>,
TContext & IC,
Extract<IF, 'd'> extends never ? _<ConcatObjectTypes<TDefault, ID>> : ID,
TFlags | IF
>;
concat(schema: this): this;
concat(schema: any): any {
let next = super.concat(schema) as any;

let nextFields = next.fields;
for (let [field, schemaOrRef] of Object.entries(this.fields)) {
const target = nextFields[field];
if (target === undefined) {
nextFields[field] = schemaOrRef;
} else if (
target instanceof BaseSchema &&
schemaOrRef instanceof BaseSchema
) {
nextFields[field] = schemaOrRef.concat(target);
}
nextFields[field] = target === undefined ? schemaOrRef : target;
}

return next.withMutation((s: any) =>
Expand Down
9 changes: 9 additions & 0 deletions src/util/objectTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ export type MergeObjectTypes<T extends Maybe<AnyObject>, U extends AnyObject> =
| ({ [P in keyof T]: P extends keyof U ? U[P] : T[P] } & U)
| Optionals<T>;

export type ConcatObjectTypes<
T extends Maybe<AnyObject>,
U extends Maybe<AnyObject>,
> =
| ({
[P in keyof T]: P extends keyof NonNullable<U> ? NonNullable<U>[P] : T[P];
} & U)
| Optionals<U>;

export type PartialDeep<T> = T extends
| string
| number
Expand Down
11 changes: 10 additions & 1 deletion test/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -633,8 +633,17 @@ Object: {
name: string(),
}).nullable();

// $ExpectType "foo" | null
// $ExpectType { name?: string | undefined; other: string; field: number; } | null
obj1.concat(obj2).cast('');

// $ExpectType { name?: string | undefined; other: string; field: number; }
obj1.nullable().concat(obj2.nonNullable()).cast('');

// $ExpectType { field: 1; other: ""; name: undefined; }
obj1.nullable().concat(obj2.nonNullable()).getDefault();

// $ExpectType null
obj1.concat(obj2.default(null)).getDefault();
}

SchemaOfDate: {
Expand Down

0 comments on commit 0bcc8fd

Please sign in to comment.