Skip to content

Commit

Permalink
feat: add describe and meta to lazy, with resolve options
Browse files Browse the repository at this point in the history
  • Loading branch information
jquense committed Dec 14, 2020
1 parent d26c453 commit e56fea3
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 74 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ schema.validate({ name: 'jimmy', age: 11 }).catch(function (err) {
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [`yup`](#yup)
- [`yup.reach(schema: Schema, path: string, value?: object, context?: object): Schema`](#yupreachschema-schema-path-string-value-object-context-object-schema)
- [`yup.addMethod(schemaType: Schema, name: string, method: ()=> Schema): void`](#yupaddmethodschematype-schema-name-string-method--schema-void)
Expand Down
53 changes: 46 additions & 7 deletions src/Lazy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import isSchema from './util/isSchema';
import type { Callback, ValidateOptions } from './types';
import type { ResolveOptions } from './Condition';

import type { AnySchema, CastOptions, ConfigOf } from './schema';
import type {
AnySchema,
CastOptions,
ConfigOf,
SchemaFieldDescription,
SchemaLazyDescription,
} from './schema';
import { Config, TypedSchema, TypeOf } from './util/types';

export type LazyBuilder<T extends AnySchema = any> = (
Expand All @@ -22,6 +28,10 @@ export type LazyType<T> = LazyReturnValue<T> extends TypedSchema
? TypeOf<LazyReturnValue<T>>
: never;

export interface LazySpec {
meta: Record<string, unknown> | undefined;
}

class Lazy<T extends AnySchema, TConfig extends Config = ConfigOf<T>>
implements TypedSchema {
type = 'lazy' as const;
Expand All @@ -31,7 +41,17 @@ class Lazy<T extends AnySchema, TConfig extends Config = ConfigOf<T>>
readonly __type!: T['__type'];
readonly __outputType!: T['__outputType'];

constructor(private builder: LazyBuilder<T>) {}
spec: LazySpec;

constructor(private builder: LazyBuilder<T>) {
this.spec = { meta: undefined };
}

clone(): Lazy<T, TConfig> {
const next = new Lazy(this.builder);
next.spec = { ...this.spec };
return next;
}

private _resolve = (
value: any,
Expand All @@ -48,6 +68,7 @@ class Lazy<T extends AnySchema, TConfig extends Config = ConfigOf<T>>
resolve(options: ResolveOptions<TConfig['context']>) {
return this._resolve(options.value, options);
}

cast(value: any, options?: CastOptions<TConfig['context']>): T['__type'] {
return this._resolve(value, options).cast(value, options);
}
Expand All @@ -67,30 +88,48 @@ class Lazy<T extends AnySchema, TConfig extends Config = ConfigOf<T>>
): T['__outputType'] {
return this._resolve(value, options).validateSync(value, options);
}

validateAt(
path: string,
value: any,
options?: ValidateOptions<TConfig['context']>,
) {
return this._resolve(value, options).validateAt(path, value, options);
}

validateSyncAt(
path: string,
value: any,
options?: ValidateOptions<TConfig['context']>,
) {
return this._resolve(value, options).validateSyncAt(path, value, options);
}
describe() {
return null as any;
}

isValid(value: any, options?: ValidateOptions<TContext>) {
isValid(value: any, options?: ValidateOptions<TConfig['context']>) {
return this._resolve(value, options).isValid(value, options);
}
isValidSync(value: any, options?: ValidateOptions<TContext>) {

isValidSync(value: any, options?: ValidateOptions<TConfig['context']>) {
return this._resolve(value, options).isValidSync(value, options);
}

describe(
options?: ResolveOptions<TConfig['context']>,
): SchemaLazyDescription | SchemaFieldDescription {
return options
? this.resolve(options).describe(options)
: { type: 'lazy', meta: this.spec.meta, label: undefined };
}

meta(): Record<string, unknown> | undefined;
meta(obj: Record<string, unknown>): Lazy<T, TConfig>;
meta(...args: [Record<string, unknown>?]) {
if (args.length === 0) return this.spec.meta;

let next = this.clone();
next.spec.meta = Object.assign(next.spec.meta || {}, args[0]);
return next;
}
}

export default Lazy;
15 changes: 13 additions & 2 deletions src/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import BaseSchema, {
SchemaSpec,
} from './schema';
import Lazy from './Lazy';
import { ResolveOptions } from './Condition';

export type RejectorFn = (value: any, index: number, array: any[]) => boolean;

Expand Down Expand Up @@ -263,9 +264,19 @@ export default class ArraySchema<
);
}

describe() {
describe(options?: ResolveOptions<C['context']>) {
let base = super.describe() as SchemaInnerTypeDescription;
if (this.innerType) base.innerType = this.innerType.describe();
if (this.innerType) {
let innerOptions = options;
if (innerOptions?.value) {
innerOptions = {
...innerOptions,
parent: innerOptions.value,
value: innerOptions.value[0],
};
}
base.innerType = this.innerType.describe(options);
}
return base;
}
}
Expand Down
20 changes: 16 additions & 4 deletions src/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import BaseSchema, {
SchemaSpec,
} from './schema';
import string from './string';
import { ResolveOptions } from './Condition';

export type Assign<T extends {}, U extends {}> = {
[P in keyof T]: P extends keyof U ? U[P] : T[P];
Expand Down Expand Up @@ -194,7 +195,7 @@ export default class ObjectSchema<
parent: intermediateValue,
});

let fieldSpec = 'spec' in field ? field.spec : undefined;
let fieldSpec = field instanceof BaseSchema ? field.spec : undefined;
let strict = fieldSpec?.strict;

if (fieldSpec?.strip) {
Expand Down Expand Up @@ -507,9 +508,20 @@ export default class ObjectSchema<
return this.transformKeys((key) => snakeCase(key).toUpperCase());
}

describe() {
let base = super.describe() as SchemaObjectDescription;
base.fields = mapValues(this.fields, (value) => value.describe());
describe(options?: ResolveOptions<TConfig['context']>) {
let base = super.describe(options) as SchemaObjectDescription;
base.fields = mapValues(this.fields, (value, key) => {
let innerOptions = options;
if (innerOptions?.value) {
innerOptions = {
...innerOptions,
parent: innerOptions.value,
value: innerOptions.value[key],
};
}

return value.describe(innerOptions);
});
return base;
}
}
Expand Down
20 changes: 16 additions & 4 deletions src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,23 @@ export interface SchemaObjectDescription extends SchemaDescription {
fields: Record<string, SchemaFieldDescription>;
}

export interface SchemaLazyDescription {
type: string;
label?: string;
meta: object | undefined;
}

export type SchemaFieldDescription =
| SchemaDescription
| SchemaRefDescription
| SchemaObjectDescription
| SchemaInnerTypeDescription;
| SchemaInnerTypeDescription
| SchemaLazyDescription;

export interface SchemaDescription {
type: string;
label?: string;
meta: object;
meta: object | undefined;
oneOf: unknown[];
notOneOf: unknown[];
nullable: boolean;
Expand Down Expand Up @@ -804,8 +811,13 @@ export default abstract class BaseSchema<
return next as any;
}

describe() {
const next = this.clone();
/**
* Return a serialized description of the schema including validations, flags, types etc.
*
* @param options Provide any needed context for resolving runtime schema alterations (lazy, when conditions, etc).
*/
describe(options?: ResolveOptions<TConfig['context']>) {
const next = (options ? this.resolve(options) : this).clone();
const { label, meta, optional, nullable } = next.spec;
const description: SchemaDescription = {
meta,
Expand Down
13 changes: 12 additions & 1 deletion test/lazy.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { lazy, mixed } from '../src';

describe('lazy', function() {
describe('lazy', function () {
it('should throw on a non-schema value', () => {
(() => lazy(() => undefined).validate()).should.throw();
});
Expand All @@ -26,5 +26,16 @@ describe('lazy', function() {
lazy(mapper).validate(value, context);
mapper.should.have.been.calledWithExactly(value, context);
});

it('should allow meta', () => {
const meta = { a: 1 };
const schema = lazy(mapper).meta(meta);

expect(schema.meta()).to.eql(meta);

expect(schema.meta({ added: true })).to.not.eql(schema.meta());

expect(schema.meta({ added: true }).meta()).to.eql({ a: 1, added: true });
});
});
});
Loading

0 comments on commit e56fea3

Please sign in to comment.