Skip to content

Commit

Permalink
fix: types + mutate type
Browse files Browse the repository at this point in the history
  • Loading branch information
izatop committed Jul 26, 2022
1 parent 7a559c0 commit 2545ff2
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 33 deletions.
10 changes: 5 additions & 5 deletions packages/input/src/Type/Fields.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {entriesReverse, isFunction, isInstanceOf, isObject, toError} from "@bunt/util";
import {AssertionObjectError, AssertionTypeError, IReadableTypeError} from "../Assertion";
import {ObjectFields, ObjectTypeMerge} from "../interfaces";
import {FieldsSchema} from "../interfaces";
import {TypeAbstract} from "../TypeAbstract";

export class Fields<TValue extends Record<string, any>> extends TypeAbstract<TValue> {
readonly #fields: ObjectFields<TValue>;
readonly #fields: FieldsSchema<TValue>;
readonly #name: string;

constructor(fields: ObjectFields<TValue>, name = "Object") {
constructor(fields: FieldsSchema<TValue>, name = "Object") {
super();
this.#fields = fields;
this.#name = name;
Expand All @@ -17,11 +17,11 @@ export class Fields<TValue extends Record<string, any>> extends TypeAbstract<TVa
return this.#name;
}

public get fields(): ObjectFields<TValue> {
public get fields(): FieldsSchema<TValue> {
return this.#fields;
}

public merge<F extends Record<string, any>>(from: ObjectTypeMerge<F>): Fields<TValue & F> {
public merge<F extends Record<string, any>>(from: Fields<F> | FieldsSchema<F>): Fields<TValue & F> {
if (isInstanceOf(from, Fields)) {
return new Fields<TValue & F>(
Object.assign({}, this.fields, from.fields) as any,
Expand Down
18 changes: 18 additions & 0 deletions packages/input/src/Type/Mutate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {SuperType} from "../SuperType";
import {TypeAbstract} from "../TypeAbstract";

export type MutateFunction<TIn, TOut> = (value: TIn) => TOut;

export class Mutate<TValue, SValue> extends SuperType<TValue, SValue> {
readonly #mutate: MutateFunction<SValue, TValue>;
constructor(type: TypeAbstract<SValue>, mutate: MutateFunction<SValue, TValue>) {
super(type);
this.#mutate = mutate;
}

public async validate(payload: unknown): Promise<TValue> {
const value = await this.type.validate(payload);

return this.#mutate(value);
}
}
6 changes: 4 additions & 2 deletions packages/input/src/Type/Nullable.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {isNull, isUndefined, Promisify} from "@bunt/util";
import {isNull, isUndefined, MayNullable, Promisify} from "@bunt/util";
import {SuperType} from "../SuperType";

export class Nullable<TValue> extends SuperType<TValue | undefined, TValue> {
export class Nullable<TValue> extends SuperType<MayNullable<TValue>, Exclude<TValue, undefined | null>> {
public readonly nullable = true;

public validate(payload: unknown): Promisify<TValue | undefined> {
if (isNull(payload) || isUndefined(payload)) {
return undefined;
Expand Down
25 changes: 17 additions & 8 deletions packages/input/src/Type/Union.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
import {Promisify} from "@bunt/util";
import {isObject, Promisify} from "@bunt/util";
import {TypeAbstract} from "../TypeAbstract";
import {IScalarType} from "./ScalarType";

export type UnionSelector = (input: unknown) => TypeAbstract<unknown> | undefined;
export type UnionSelector<TValue> = (input: unknown) => TypeAbstract<TValue> | undefined;

export class Union<TValue> extends TypeAbstract<TValue> {
readonly #selector: UnionSelector;
readonly #selector: UnionSelector<TValue>;
readonly #name: string;

constructor(selector: UnionSelector, name = "Union") {
constructor(config: {name?: string; selector: IScalarType<TValue>});
constructor(selector: UnionSelector<TValue>);
constructor(...args: any[]) {
super();
this.#selector = selector;
this.#name = name;
const [arg1, arg2] = args;
if (isObject(arg1)) {
this.#selector = arg1.selector;
this.#name = arg1.name ?? "Union";
} else {
this.#selector = arg1;
this.#name = arg2;
}
}

public get name(): string {
return this.#name;
}

public validate(input: unknown): Promisify<TValue> {
const type = this.#selector(input);
this.assert(!!type, `${this.name} detection was failed`, input);
const type = this.#selector.call(this, input);
this.assert(!!type, `${this.name} type detection failed`, input);

return type.validate(input) as Promisify<TValue>;
}
Expand Down
1 change: 1 addition & 0 deletions packages/input/src/Type/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export * from "./RecordType";
export * from "./Enum";
export * from "./EmailAddress";
export * from "./ScalarType";
export * from "./Mutate";
26 changes: 9 additions & 17 deletions packages/input/src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
import {Fields, List, Union, RecordType} from "./Type";
import {KeyOf} from "@bunt/util";
import {Fields, Int, Nullable} from "./Type";
import {TypeAbstract} from "./TypeAbstract";

export type FieldFn<T> = () => T;
export type FieldType<T> = T | FieldFn<T>;
export type FieldMayFn<T> = T | (() => T);

export type FieldsSchema<T> = {
[K in keyof T]-?: T[K] extends Array<infer S>
? FieldType<List<S>>
: T[K] extends Date
? FieldType<TypeAbstract<T[K]>>
: T[K] extends Record<any, any>
? FieldType<Fields<T[K]> | Union<T[K]> | typeof RecordType>
: FieldType<TypeAbstract<T[K]>>;
export type FieldsSchema<T extends Record<string, any>> = {
[K in KeyOf<T>]-?: FieldSelectType<T[K]>;
};

export type ObjectFields<T> = T extends Promise<infer A>
? FieldsSchema<Exclude<A, undefined | null>>
: FieldsSchema<Exclude<T, undefined | null>>;
export type FieldSelectType<T> = FieldMayFn<TypeAbstract<T>>;

export type FieldSelectType<T> = FieldType<TypeAbstract<T>>;

export type ObjectTypeMerge<T extends Record<string, any>> = Fields<T> | ObjectFields<T>;
type Foo = {foo?: number; bar: number};
new Fields<Foo>({foo: Int, bar: Int});
new Fields<Foo>({foo: new Nullable(Int), bar: Int});
13 changes: 12 additions & 1 deletion packages/input/test/src/Main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
validate,
Varchar,
RecordType,
FieldSelectType,
} from "../../src";
import {ITestDescription, ITestHobby, ITestType} from "./interfaces";
import {TestEnum, TestEnumType} from "./Type/TestEnum";
Expand Down Expand Up @@ -47,7 +48,7 @@ describe("Test Input", () => {
[undefined, [1], new NonNull(new List(Int), [1])],
["text", "text", Text],
["text", "text", new Varchar({min: 0, max: 4})],
[{v: 1, b: true, n: []}, {v: 1, b: true}, new Fields({v: Int, b: Bool})],
[{v: 1, b: true}, {v: 1, b: true}, new Fields<{v: number; b: boolean}>({v: Int, b: Bool})],
[[1, 2, 3], [1, 2, 3], new List(Int)],
[false, false, union],
["2020-01-01", new Date("2020-01-01"), union],
Expand All @@ -57,6 +58,7 @@ describe("Test Input", () => {
["1", 1, JSONString],
["[1]", [1], JSONString],
["STR", TestEnum.STR, TestEnumType],
[[{n: 1}], [{n: 1}], new List<{n: number}>(new Fields<{n: number}>({n: Int}))],
];

test.each(samples)(
Expand Down Expand Up @@ -147,4 +149,13 @@ describe("Test Input", () => {
.rejects
.toThrow();
});

test("List", async () => {
const t = [
{n: 1},
];

const validator: FieldSelectType<{n: number}[]> = new List(new Fields({n: Int}));
await expect(validator.validate(t)).resolves.toEqual(t);
});
});
5 changes: 5 additions & 0 deletions packages/util/src/is.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ export const isNumber = (value: unknown): value is number => {
return typeof value === "number" && value === +value;
};

export const isNullish = (value: unknown): value is null | undefined => isNull(value) || isUndefined(value);
export const isNotNullish = <T>(value: T | null | undefined): value is null | undefined => (
!(isNull(value) || isUndefined(value))
);

export const isFunction = <T extends (...args: any) => any>(value: unknown): value is T => {
return typeof value === "function" && !isClass(value);
};
Expand Down

0 comments on commit 2545ff2

Please sign in to comment.