Skip to content

Commit

Permalink
Merge pull request ntix#39 from MrAntix/main
Browse files Browse the repository at this point in the history
feat(parsing): major refactor
  • Loading branch information
ntix authored Feb 8, 2024
2 parents d1203f6 + 505dae3 commit a02f256
Show file tree
Hide file tree
Showing 90 changed files with 1,014 additions and 668 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ module.exports = {
"sort-imports": ["error", { "allowSeparatedGroups": false, "ignoreDeclarationSort": true }],
"no-empty-interface": "off",
"no-unused-vars": "off",
"eol-last": ["error", "always"],
},
root: true,
};
28 changes: 15 additions & 13 deletions src/Is.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import { IRoot, RootParser } from './parsing';
import { RootParser, provideParseRoot } from './parsing';
import { asCurrent } from './parsing/asCurrent';

/** Starting point for a new parsing and validating */
export class Is {
private constructor() {
throw new Error('static class');
}

static readonly required: IRoot.Parser = new RootParser(true, false);
private static root = new RootParser(asCurrent(provideParseRoot(false)));

static readonly boolean = new RootParser().boolean;
static readonly int = new RootParser().int;
static readonly float = new RootParser().float;
static readonly date = new RootParser().date;
static readonly string = new RootParser().string;
static readonly array = new RootParser().array;
static readonly dictionary = new RootParser().dictionary;
static readonly json = new RootParser().json;
static readonly not = Is.root.not;
static readonly required = Is.root.required;

static readonly for = new RootParser().for;
static readonly use = new RootParser().use;
static readonly boolean = Is.root.boolean;
static readonly int = Is.root.int;
static readonly float = Is.root.float;
static readonly date = Is.root.date;
static readonly string = Is.root.string;
static readonly array = Is.root.array;
static readonly dictionary = Is.root.dictionary;
static readonly json = Is.root.json;

static readonly not = new RootParser(false, true);
static readonly for = Is.root.for;
static readonly use = Is.root.use;
}
12 changes: 12 additions & 0 deletions src/parsing/ICurrentParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { IParser } from './IParser';

/**
* A parser function and negate value
*
* The parse function should return a negatable (non-normal) result
* ie, it should return errors on success and failure to parse
* so the result can be negated
*/
export interface ICurrentParser<T> extends IParser<T> {
negate: boolean;
}
5 changes: 4 additions & 1 deletion src/parsing/IParse.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { IParseResult } from './IParseResult';

/**
* A parse function
*/
export interface IParse<T> {
(value: unknown): IParseResult<T>
(value: unknown): IParseResult<T>;
}
9 changes: 6 additions & 3 deletions src/parsing/IParseResult.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { IParseErrors } from './IParseErrors';

/**
* A Parse result which always returns with ParseErrors to enable negate
*/
export interface IParseResult<T> {
value: T | null
success: boolean
errors: IParseErrors
value: T | null;
success: boolean;
errors: IParseErrors;
}
3 changes: 3 additions & 0 deletions src/parsing/IParser.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { IParse } from './IParse';

/**
* A parser
*/
export interface IParser<T> {
parse: IParse<T>
}
16 changes: 8 additions & 8 deletions src/parsing/IRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,29 @@ import { IParser } from './IParser';
import { IFloat, IInt } from './numbers';
import { ComplexSchema, IComplex } from './complex';
import { IString } from './strings';
import { IArray } from './arrays';
import { IDictionary } from './dictionaries';
import { IJson } from './json/IJson';
import { NextBuilder } from './NextBuilder';
import { IArray } from './arrays';

export namespace IRoot {

export interface Parser extends IParser<unknown>, Builder {
readonly not: NextBuilder<Parser, 'not' | 'parse'>

}
export interface Parser extends IParser<unknown> {
readonly not: NextBuilder<Parser, 'not' | 'parse'>;
readonly required: NextBuilder<Parser, 'required'>

export interface Builder {
readonly boolean: IBoolean.Parser;
readonly int: IInt.Parser;
readonly float: IFloat.Parser;
readonly date: IDate.Parser;
readonly string: IString.Parser;
readonly array: IArray.Parser;
readonly dictionary: IDictionary.Parser;
readonly json: IJson.Parser;
readonly array: NextBuilder<IArray.Parser, never, 'of'>;
readonly dictionary: IDictionary.Parser;

/** get a complex schema */
readonly for: <T>(schema: ComplexSchema<T>) => IComplex.Parser<T>;
/** use a parser function to parse the value */
readonly use: <T>(parser: IParser<T>) => IParser<T>;
}
}
15 changes: 5 additions & 10 deletions src/parsing/NextBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
/**
* removes/adds given methods from the builder
* and any returned builder from other methods
*
* recursive
*/
export type NextBuilder<B, exclude extends keyof B = never, include extends keyof B = never> =
{
[key in Exclude<keyof B, Exclude<exclude, include>>]
: B[key] extends (...args: infer A) => NextBuilder<B, infer e, infer i>
? (...args: A) => NextBuilder<B, e | Exclude<exclude, include>, i> // cascade excludes and includes
: B[key];
};
export type NextBuilder<P, remove extends keyof P = never, add extends keyof P = never> =
{
[K in Exclude<keyof P, Exclude<remove, add>>]
: P[K]
}
20 changes: 11 additions & 9 deletions src/parsing/ParseErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,34 @@ export class ParseErrors {
static readonly date = { date: true };
/** value should be a string */
static readonly string = { string: true };
/** value should be equal to the value */
static readonly equals = <T>(value: T) => ({ equals: value });
/** value should be equal to any of the values */
static readonly anyOf = <T>(values: T[] | NumberEnumMap) => ({ anyOf: Array.isArray(values) ? values : getNumberEnumValues(values) });
/** value should be equal to the value, ignoreCase only valid for string */
static readonly equals = <T>(value: T, ignoreCase?: boolean) => ({ equals: { value, ...(ignoreCase !== undefined && { ignoreCase }) } });
/** value should be equal to any of the values, ignoreCase only valid for string */
static readonly anyOf = <T>(values: T[] | NumberEnumMap, ignoreCase?: boolean) => ({ anyOf: { value: Array.isArray(values) ? values : getNumberEnumValues(values), ...(ignoreCase !== undefined && { ignoreCase }) } });
/** value should be at least */
static readonly min = <T>(value: T, exclusive: boolean = false) => ({ min: { value, exclusive } });
/** value should be at most */
static readonly max = <T>(value: T, exclusive: boolean = false) => ({ max: { value, exclusive } });
/** value length should be at least */
static readonly minLength = <T>(value: T) => ({ minLength: value });
static readonly minLength = <T>(value: T, exclusive: boolean = false) => ({ minLength: value, exclusive });
/** value length should be at most */
static readonly maxLength = <T>(value: T) => ({ maxLength: value });
static readonly maxLength = <T>(value: T, exclusive: boolean = false) => ({ maxLength: value, exclusive });
/** value should be an array */
static readonly array = { array: true };
/** value should be a array */
static readonly dictionary = { dictionary: true };
/** value should be a complex object */
static readonly complex = { object: true };
/** value should be a json */
static readonly json = { json: true };
/** value includes */
static readonly matches = <T>(name: T) => ({ matches: name });
/** value includes */
static readonly includes = <T>(value: T, ignoreCase: boolean = false) => ({ includes: { value, ignoreCase } });
static readonly includes = <T>(value: T, ignoreCase?: boolean) => ({ includes: { value, ...(ignoreCase !== undefined && { ignoreCase }) } });
/** starts with */
static readonly startsWith = <T>(value: T, ignoreCase: boolean = false) => ({ startsWith: { value, ignoreCase } });
static readonly startsWith = <T>(value: T, ignoreCase?: boolean) => ({ startsWith: { value, ...(ignoreCase !== undefined && { ignoreCase }) } });
/** ends with */
static readonly endsWith = <T>(value: T, ignoreCase: boolean = false) => ({ endsWith: { value, ignoreCase } });
static readonly endsWith = <T>(value: T, ignoreCase?: boolean) => ({ endsWith: { value, ...(ignoreCase !== undefined && { ignoreCase }) } });
/** values unique */
static readonly unique = { unique: true };
}
44 changes: 26 additions & 18 deletions src/parsing/RootParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,44 @@ import { ArrayParser, IArray, provideParseArray } from './arrays';
import { DictionaryParser, IDictionary, provideParseDictionary } from './dictionaries';
import { BooleanParser, IBoolean, provideParseBoolean } from './booleans';
import { DateParser, IDate, provideParseDate } from './dates';
import { IParser } from './IParser';
import { IRoot } from './IRoot';
import { FloatParser, IFloat, IInt, IntParser, provideParseFloat, provideParseInt } from './numbers';
import { ComplexParser, ComplexSchema, IComplex } from './complex';
import { ComplexParser, ComplexSchema, IComplex, provideParseComplex } from './complex';
import { parseChain } from './parseChain';
import { IString, StringParser, provideParseString } from './strings';
import { IJson, JsonParser, provideParseJson } from './json';
import { provideParseRoot } from './provideParseRoot';
import { NextBuilder } from './NextBuilder';
import { ICurrentParser } from './ICurrentParser';
import { asCurrent } from './asCurrent';
import { IParser } from './IParser';

export class RootParser implements IRoot.Parser {

constructor(
private isRequried = false,
private negate = false
private parseCurrent: ICurrentParser<unknown>,
private readonly negate = false
) { }

readonly parse = parseChain(null, provideParseRoot(this.isRequried));

readonly boolean: IBoolean.Parser = new BooleanParser(this, provideParseBoolean(this.negate));
readonly int: IInt.Parser = new IntParser(this, provideParseInt(this.negate));
readonly float: IFloat.Parser = new FloatParser(this, provideParseFloat(this.negate));
readonly date: IDate.Parser = new DateParser(this, provideParseDate(this.negate));
readonly string: IString.Parser = new StringParser(this, provideParseString(this.negate));
readonly array: IArray.Parser = new ArrayParser(this, provideParseArray(this.negate));
readonly dictionary: IDictionary.Parser = new DictionaryParser(this, provideParseDictionary(this.negate));
readonly json: IJson.Parser = new JsonParser(this, provideParseJson(this.negate));

readonly for = <T>(schema: ComplexSchema<T>): IComplex.Parser<T> => new ComplexParser(this, schema);
readonly use = <T>(parser: IParser<T>) => ({ parse: parseChain<T>(this, parser.parse) });
readonly parse = parseChain(null, this.parseCurrent, 'ROOT');

get not() {
return new RootParser(this.isRequried, true);
return new RootParser(this.parseCurrent, !this.negate);
}

get required() {
return new RootParser(asCurrent(provideParseRoot(true), this.negate));
}

readonly boolean: IBoolean.Parser = new BooleanParser(this, asCurrent(provideParseBoolean(), this.negate));
readonly int: IInt.Parser = new IntParser(this, asCurrent(provideParseInt(), this.negate));
readonly float: IFloat.Parser = new FloatParser(this, asCurrent(provideParseFloat(), this.negate));
readonly date: IDate.Parser = new DateParser(this, asCurrent(provideParseDate(), this.negate));
readonly string: IString.Parser = new StringParser(this, asCurrent(provideParseString(), this.negate));
readonly json: IJson.Parser = new JsonParser(this, asCurrent(provideParseJson(), this.negate));
readonly array: NextBuilder<IArray.Parser> = new ArrayParser(this, asCurrent(provideParseArray(), this.negate));
readonly dictionary: IDictionary.Parser = new DictionaryParser(this, asCurrent(provideParseDictionary(), this.negate));

readonly for = <T>(schema: ComplexSchema<T>): IComplex.Parser<T> => new ComplexParser(this, asCurrent(provideParseComplex<T>(schema), this.negate));
readonly use = <T>(parser: IParser<T>) => ({ parse: parseChain<T>(this, { ... this.parseCurrent, parse: parser.parse }, 'ROOT-USE') });
}
23 changes: 12 additions & 11 deletions src/parsing/arrays/ArrayParser.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
import { IParse } from '../IParse';
import { IParser } from '../IParser';
import { parseAllArray } from './parseAllArray';
import { provideParseArrayValues } from './provideParseArrayValues';
import { parseChain } from '../parseChain';
import { ICurrentParser } from '../ICurrentParser';
import { provideMaxLength } from '../provideMaxLength';
import { provideMinLength } from '../provideMinLength';
import { IArray } from './IArray';
import { provideParseArray } from './provideParseArray';
import { provideUniqueArray } from './provideUniqueArray';
import { provideParseUniqueArray } from './provideParseUniqueArray';
import { asCurrent } from '../asCurrent';

export class ArrayParser<T> implements IArray.Parser<T> {

constructor(
private parent: IParser<unknown>,
private parseCurrent: IParse<T[]> = null,
private parseCurrent: ICurrentParser<T[]>,
private negate: boolean = false
) { }

readonly parse = parseChain<T[]>(this.parent, this.parseCurrent ?? provideParseArray<T>(this.negate));
readonly parse = parseChain(this.parent, this.parseCurrent, 'ARRAY');

get not() {
return new ArrayParser<T>(this.parent, this.parseCurrent, true);
return new ArrayParser<T>(this.parent, this.parseCurrent, !this.negate);
}

readonly of = <U>() => new ArrayParser<U>(this.parent);
readonly each = <U = T>(parser: IParser<U>) => new ArrayParser<U>(this, parseAllArray(parser.parse), this.negate);
readonly of = <U>() => new ArrayParser<U>(this.parent, asCurrent(provideParseArray<U>(), this.negate));
readonly each = <U = T>(parser: IParser<U>) => new ArrayParser<U>(this, asCurrent(provideParseArrayValues(parser.parse), this.negate));

readonly unique = (distinctor: (item: T) => unknown) => new ArrayParser<T>(this, provideUniqueArray(distinctor, this.negate));
readonly minLength = (minValue: number, exclusive = false) => new ArrayParser<T>(this, provideMinLength<T[]>(minValue, exclusive, this.negate));
readonly maxLength = (maxValue: number, exclusive = false) => new ArrayParser<T>(this, provideMaxLength<T[]>(maxValue, exclusive, this.negate));
readonly unique = <U = T>(distinctor: (item: U) => unknown) => new ArrayParser<U>(this, asCurrent(provideParseUniqueArray(distinctor), this.negate));
readonly minLength = (minValue: number, exclusive = false) => new ArrayParser<T>(this, asCurrent(provideMinLength<T[]>(minValue, exclusive), this.negate));
readonly maxLength = (maxValue: number, exclusive = false) => new ArrayParser<T>(this, asCurrent(provideMaxLength<T[]>(maxValue, exclusive), this.negate));
}
17 changes: 11 additions & 6 deletions src/parsing/arrays/IArray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ import { NextBuilder } from '../NextBuilder';
export namespace IArray {

export interface Parser<T = unknown> extends IParser<T[]> {
readonly not: NextBuilder<Parser<T>, 'not' | 'of' | 'each'>
readonly not: NextBuilder<Parser<T>, 'not' | 'parse'>

readonly of: <U>() => NextBuilder<Parser<U>, 'of' | 'each', 'unique' | 'not' | 'parse'>
readonly each: <U = T>(parser: IParser<U>) => NextBuilder<Parser<U>, 'of' | 'each', 'unique' | 'not' | 'parse'>
/** declare the type of each item */
readonly of: <U = T>() => NextBuilder<Parser<U>, 'of' | 'each'>
/** parse each item with a parser */
readonly each: <U = T>(parser: IParser<U>) => NextBuilder<Parser<U>, 'each' | 'of'>

readonly unique: (distinctor: (item: T) => unknown) => NextBuilder<Parser<T>, 'of' | 'each' | 'unique', 'not' | 'parse'>;
readonly minLength: (value: number, exclusive?: boolean) => NextBuilder<Parser<T>, 'of' | 'each' | 'minLength', 'not' | 'parse'>;
readonly maxLength: (value: number, exclusive?: boolean) => NextBuilder<Parser<T>, 'of' | 'each' | 'maxLength', 'not' | 'parse'>;
/** check for uniqueness by a given distintor */
readonly unique: <U = T>(distinctor: (item: U) => unknown) => NextBuilder<Parser<U>, 'unique' | 'of' | 'each'>;
/** minimum length of the array */
readonly minLength: (value: number, exclusive?: boolean) => NextBuilder<Parser<T>, 'minLength' | 'of' | 'each'>;
/** maximum length of the array */
readonly maxLength: (value: number, exclusive?: boolean) => NextBuilder<Parser<T>, 'maxLength' | 'of' | 'each'>;
}
}
2 changes: 1 addition & 1 deletion src/parsing/arrays/arrays-parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe('arrays-parser', () => {
const result = notSchema.parse(value);

expect(result.errors).toEqual(ParseErrors.not(ParseErrors.array));
expect(result.value).toBe(null);
expect(result.value).toBe(value);
});
});
});
4 changes: 2 additions & 2 deletions src/parsing/arrays/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@

export * from './ArrayParser';
export * from './IArray';
export * from './parseAllArray';
export * from './provideParseArray';
export * from './provideUniqueArray';
export * from './provideParseArrayValues';
export * from './provideParseUniqueArray';
34 changes: 0 additions & 34 deletions src/parsing/arrays/parseAllArray.ts

This file was deleted.

Loading

0 comments on commit a02f256

Please sign in to comment.