From 505dae36ea096c3a65de69d8d83cb26c0a69176a Mon Sep 17 00:00:00 2001 From: MrAntix Date: Thu, 8 Feb 2024 15:49:51 +0000 Subject: [PATCH] feat(parsing): major refactor BREAKING CHANGE: chain parser revised --- .eslintrc.js | 1 + src/Is.ts | 28 ++++---- src/parsing/ICurrentParser.ts | 12 ++++ src/parsing/IParse.ts | 5 +- src/parsing/IParseResult.ts | 9 ++- src/parsing/IParser.ts | 3 + src/parsing/IRoot.ts | 16 ++--- src/parsing/NextBuilder.ts | 15 ++--- src/parsing/ParseErrors.ts | 20 +++--- src/parsing/RootParser.ts | 44 +++++++------ src/parsing/arrays/ArrayParser.ts | 23 +++---- src/parsing/arrays/IArray.ts | 17 +++-- src/parsing/arrays/arrays-parser.spec.ts | 2 +- src/parsing/arrays/index.ts | 4 +- src/parsing/arrays/parseAllArray.ts | 34 ---------- src/parsing/arrays/provideParseArray.ts | 32 +++++----- ...pec.ts => provideParseArrayValues.spec.ts} | 8 +-- src/parsing/arrays/provideParseArrayValues.ts | 51 +++++++++++++++ src/parsing/arrays/provideParseUniqueArray.ts | 35 ++++++++++ src/parsing/arrays/provideUniqueArray.ts | 29 --------- src/parsing/asCurrent.ts | 20 ++++++ src/parsing/booleans/BooleanParser.ts | 16 ++--- src/parsing/booleans/IBoolean.ts | 2 +- src/parsing/booleans/booleans-parser.spec.ts | 2 +- src/parsing/booleans/provideParseBoolean.ts | 28 ++++---- src/parsing/complex/ComplexParser.ts | 42 ++++-------- src/parsing/complex/IComplex.ts | 7 +- src/parsing/complex/complex-parser.spec.ts | 27 +++++++- src/parsing/complex/index.ts | 1 + src/parsing/complex/provideParseComplex.ts | 56 ++++++++++++++++ src/parsing/createParseResult.ts | 16 +++-- src/parsing/dates/DateParser.ts | 19 +++--- src/parsing/dates/IDate.ts | 2 +- src/parsing/dates/dates-parser.spec.ts | 2 +- src/parsing/dates/parseInt.spec.ts | 15 +++++ src/parsing/dates/provideParseDate.ts | 28 ++++---- src/parsing/dictionaries/Dictionary.ts | 4 +- src/parsing/dictionaries/DictionaryParser.ts | 21 ++++-- src/parsing/dictionaries/IDictionary.ts | 7 ++ .../dictionaries-maxLength.spec.ts | 64 +++++++++++++++++++ .../dictionaries-minLength.spec.ts | 63 ++++++++++++++++++ src/parsing/dictionaries/index.ts | 3 +- .../dictionaries/parseAllDictionary.ts | 35 ---------- .../dictionaries/provideDictionaryValues.ts | 23 +++++++ .../dictionaries/provideParseDictionary.ts | 32 +++++----- ...s => provideParseDictionaryValues.spec.ts} | 10 +-- .../provideParseDictionaryValues.ts | 54 ++++++++++++++++ src/parsing/index.ts | 3 + src/parsing/json/IJson.ts | 5 +- src/parsing/json/JsonParser.ts | 16 ++--- src/parsing/json/index.ts | 1 + src/parsing/json/json-parser.spec.ts | 33 +++++++++- src/parsing/json/provideParseJson.ts | 31 +++++---- src/parsing/numbers/FloatParser.ts | 19 +++--- src/parsing/numbers/IFloat.ts | 2 +- src/parsing/numbers/IInt.ts | 2 +- src/parsing/numbers/IntParser.ts | 25 ++++---- src/parsing/numbers/numbers-int-min.spec.ts | 2 +- src/parsing/numbers/numbers-int.spec.ts | 2 +- src/parsing/numbers/parseFloat.spec.ts | 2 +- src/parsing/numbers/parseInt.spec.ts | 2 +- src/parsing/numbers/provideParseFloat.ts | 29 +++++---- src/parsing/numbers/provideParseInt.ts | 31 +++++---- src/parsing/parseChain.ts | 41 ++++++++---- src/parsing/provideAnyOf.ts | 25 +++----- src/parsing/provideEquals.ts | 24 +++---- src/parsing/provideMax.ts | 24 +++---- src/parsing/provideMaxLength.ts | 27 +++----- src/parsing/provideMin.ts | 21 +++--- src/parsing/provideMinLength.ts | 27 +++----- src/parsing/provideParseRoot.ts | 18 +++--- src/parsing/strings/StringParser.ts | 27 ++++---- src/parsing/strings/provideAnyOfString.ts | 30 ++++----- src/parsing/strings/provideEndsWithString.ts | 32 ++++------ src/parsing/strings/provideEqualsString.ts | 31 ++++----- src/parsing/strings/provideIncludesString.ts | 31 ++++----- src/parsing/strings/provideMatchesString.ts | 29 ++++----- src/parsing/strings/provideParseString.ts | 35 +++++----- .../strings/provideStartsWithString.ts | 32 ++++------ src/parsing/strings/strings-any-of.spec.ts | 4 +- src/parsing/strings/strings-ends-with.spec.ts | 4 +- src/parsing/strings/strings-equals.spec.ts | 4 +- src/parsing/strings/strings-includes.spec.ts | 4 +- .../strings/strings-max-length.spec.ts | 4 +- .../strings/strings-min-length.spec.ts | 4 +- .../strings/strings-not-equals.spec.ts | 2 +- .../strings/strings-starts-with.spec.ts | 4 +- src/parsing/strings/strings.spec.ts | 2 +- src/predicates/isObject.spec.ts | 2 +- src/predicates/isObject.ts | 2 +- 90 files changed, 1014 insertions(+), 668 deletions(-) create mode 100644 src/parsing/ICurrentParser.ts delete mode 100644 src/parsing/arrays/parseAllArray.ts rename src/parsing/arrays/{parseAllArray.spec.ts => provideParseArrayValues.spec.ts} (73%) create mode 100644 src/parsing/arrays/provideParseArrayValues.ts create mode 100644 src/parsing/arrays/provideParseUniqueArray.ts delete mode 100644 src/parsing/arrays/provideUniqueArray.ts create mode 100644 src/parsing/asCurrent.ts create mode 100644 src/parsing/complex/provideParseComplex.ts create mode 100644 src/parsing/dates/parseInt.spec.ts create mode 100644 src/parsing/dictionaries/dictionaries-maxLength.spec.ts create mode 100644 src/parsing/dictionaries/dictionaries-minLength.spec.ts delete mode 100644 src/parsing/dictionaries/parseAllDictionary.ts create mode 100644 src/parsing/dictionaries/provideDictionaryValues.ts rename src/parsing/dictionaries/{parseAllDictionary.spec.ts => provideParseDictionaryValues.spec.ts} (58%) create mode 100644 src/parsing/dictionaries/provideParseDictionaryValues.ts diff --git a/.eslintrc.js b/.eslintrc.js index c16236f..6584450 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -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, }; diff --git a/src/Is.ts b/src/Is.ts index e89eafc..d4b8da2 100644 --- a/src/Is.ts +++ b/src/Is.ts @@ -1,4 +1,5 @@ -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 { @@ -6,19 +7,20 @@ export class Is { 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; } diff --git a/src/parsing/ICurrentParser.ts b/src/parsing/ICurrentParser.ts new file mode 100644 index 0000000..fe9c97b --- /dev/null +++ b/src/parsing/ICurrentParser.ts @@ -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 extends IParser { + negate: boolean; +} diff --git a/src/parsing/IParse.ts b/src/parsing/IParse.ts index 0fd3fac..8011662 100644 --- a/src/parsing/IParse.ts +++ b/src/parsing/IParse.ts @@ -1,5 +1,8 @@ import { IParseResult } from './IParseResult'; +/** + * A parse function + */ export interface IParse { - (value: unknown): IParseResult + (value: unknown): IParseResult; } diff --git a/src/parsing/IParseResult.ts b/src/parsing/IParseResult.ts index f746fe8..79040a8 100644 --- a/src/parsing/IParseResult.ts +++ b/src/parsing/IParseResult.ts @@ -1,7 +1,10 @@ import { IParseErrors } from './IParseErrors'; +/** + * A Parse result which always returns with ParseErrors to enable negate + */ export interface IParseResult { - value: T | null - success: boolean - errors: IParseErrors + value: T | null; + success: boolean; + errors: IParseErrors; } diff --git a/src/parsing/IParser.ts b/src/parsing/IParser.ts index cde2099..a8edb70 100644 --- a/src/parsing/IParser.ts +++ b/src/parsing/IParser.ts @@ -1,5 +1,8 @@ import { IParse } from './IParse'; +/** + * A parser + */ export interface IParser { parse: IParse } diff --git a/src/parsing/IRoot.ts b/src/parsing/IRoot.ts index a1c4488..58c6a1a 100644 --- a/src/parsing/IRoot.ts +++ b/src/parsing/IRoot.ts @@ -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, Builder { - readonly not: NextBuilder - - } + export interface Parser extends IParser { + readonly not: NextBuilder; + readonly required: NextBuilder - 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; + readonly dictionary: IDictionary.Parser; + /** get a complex schema */ readonly for: (schema: ComplexSchema) => IComplex.Parser; + /** use a parser function to parse the value */ readonly use: (parser: IParser) => IParser; } } diff --git a/src/parsing/NextBuilder.ts b/src/parsing/NextBuilder.ts index b0f1c9a..ec55695 100644 --- a/src/parsing/NextBuilder.ts +++ b/src/parsing/NextBuilder.ts @@ -1,13 +1,8 @@ /** * removes/adds given methods from the builder - * and any returned builder from other methods - * - * recursive */ -export type NextBuilder = - { - [key in Exclude>] - : B[key] extends (...args: infer A) => NextBuilder - ? (...args: A) => NextBuilder, i> // cascade excludes and includes - : B[key]; - }; +export type NextBuilder = + { + [K in Exclude>] + : P[K] + } diff --git a/src/parsing/ParseErrors.ts b/src/parsing/ParseErrors.ts index 2bd01a9..59f8621 100644 --- a/src/parsing/ParseErrors.ts +++ b/src/parsing/ParseErrors.ts @@ -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 = (value: T) => ({ equals: value }); - /** value should be equal to any of the values */ - static readonly anyOf = (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 = (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 = (values: T[] | NumberEnumMap, ignoreCase?: boolean) => ({ anyOf: { value: Array.isArray(values) ? values : getNumberEnumValues(values), ...(ignoreCase !== undefined && { ignoreCase }) } }); /** value should be at least */ static readonly min = (value: T, exclusive: boolean = false) => ({ min: { value, exclusive } }); /** value should be at most */ static readonly max = (value: T, exclusive: boolean = false) => ({ max: { value, exclusive } }); /** value length should be at least */ - static readonly minLength = (value: T) => ({ minLength: value }); + static readonly minLength = (value: T, exclusive: boolean = false) => ({ minLength: value, exclusive }); /** value length should be at most */ - static readonly maxLength = (value: T) => ({ maxLength: value }); + static readonly maxLength = (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 = (name: T) => ({ matches: name }); /** value includes */ - static readonly includes = (value: T, ignoreCase: boolean = false) => ({ includes: { value, ignoreCase } }); + static readonly includes = (value: T, ignoreCase?: boolean) => ({ includes: { value, ...(ignoreCase !== undefined && { ignoreCase }) } }); /** starts with */ - static readonly startsWith = (value: T, ignoreCase: boolean = false) => ({ startsWith: { value, ignoreCase } }); + static readonly startsWith = (value: T, ignoreCase?: boolean) => ({ startsWith: { value, ...(ignoreCase !== undefined && { ignoreCase }) } }); /** ends with */ - static readonly endsWith = (value: T, ignoreCase: boolean = false) => ({ endsWith: { value, ignoreCase } }); + static readonly endsWith = (value: T, ignoreCase?: boolean) => ({ endsWith: { value, ...(ignoreCase !== undefined && { ignoreCase }) } }); /** values unique */ static readonly unique = { unique: true }; } diff --git a/src/parsing/RootParser.ts b/src/parsing/RootParser.ts index a3b5b8b..92ac5a2 100644 --- a/src/parsing/RootParser.ts +++ b/src/parsing/RootParser.ts @@ -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, + 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 = (schema: ComplexSchema): IComplex.Parser => new ComplexParser(this, schema); - readonly use = (parser: IParser) => ({ parse: parseChain(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 = new ArrayParser(this, asCurrent(provideParseArray(), this.negate)); + readonly dictionary: IDictionary.Parser = new DictionaryParser(this, asCurrent(provideParseDictionary(), this.negate)); + + readonly for = (schema: ComplexSchema): IComplex.Parser => new ComplexParser(this, asCurrent(provideParseComplex(schema), this.negate)); + readonly use = (parser: IParser) => ({ parse: parseChain(this, { ... this.parseCurrent, parse: parser.parse }, 'ROOT-USE') }); } diff --git a/src/parsing/arrays/ArrayParser.ts b/src/parsing/arrays/ArrayParser.ts index b69bf38..e7eb9c9 100644 --- a/src/parsing/arrays/ArrayParser.ts +++ b/src/parsing/arrays/ArrayParser.ts @@ -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 implements IArray.Parser { constructor( private parent: IParser, - private parseCurrent: IParse = null, + private parseCurrent: ICurrentParser, private negate: boolean = false ) { } - readonly parse = parseChain(this.parent, this.parseCurrent ?? provideParseArray(this.negate)); + readonly parse = parseChain(this.parent, this.parseCurrent, 'ARRAY'); get not() { - return new ArrayParser(this.parent, this.parseCurrent, true); + return new ArrayParser(this.parent, this.parseCurrent, !this.negate); } - readonly of = () => new ArrayParser(this.parent); - readonly each = (parser: IParser) => new ArrayParser(this, parseAllArray(parser.parse), this.negate); + readonly of = () => new ArrayParser(this.parent, asCurrent(provideParseArray(), this.negate)); + readonly each = (parser: IParser) => new ArrayParser(this, asCurrent(provideParseArrayValues(parser.parse), this.negate)); - readonly unique = (distinctor: (item: T) => unknown) => new ArrayParser(this, provideUniqueArray(distinctor, this.negate)); - readonly minLength = (minValue: number, exclusive = false) => new ArrayParser(this, provideMinLength(minValue, exclusive, this.negate)); - readonly maxLength = (maxValue: number, exclusive = false) => new ArrayParser(this, provideMaxLength(maxValue, exclusive, this.negate)); + readonly unique = (distinctor: (item: U) => unknown) => new ArrayParser(this, asCurrent(provideParseUniqueArray(distinctor), this.negate)); + readonly minLength = (minValue: number, exclusive = false) => new ArrayParser(this, asCurrent(provideMinLength(minValue, exclusive), this.negate)); + readonly maxLength = (maxValue: number, exclusive = false) => new ArrayParser(this, asCurrent(provideMaxLength(maxValue, exclusive), this.negate)); } diff --git a/src/parsing/arrays/IArray.ts b/src/parsing/arrays/IArray.ts index fcf23ed..f7b6574 100644 --- a/src/parsing/arrays/IArray.ts +++ b/src/parsing/arrays/IArray.ts @@ -5,13 +5,18 @@ import { NextBuilder } from '../NextBuilder'; export namespace IArray { export interface Parser extends IParser { - readonly not: NextBuilder, 'not' | 'of' | 'each'> + readonly not: NextBuilder, 'not' | 'parse'> - readonly of: () => NextBuilder, 'of' | 'each', 'unique' | 'not' | 'parse'> - readonly each: (parser: IParser) => NextBuilder, 'of' | 'each', 'unique' | 'not' | 'parse'> + /** declare the type of each item */ + readonly of: () => NextBuilder, 'of' | 'each'> + /** parse each item with a parser */ + readonly each: (parser: IParser) => NextBuilder, 'each' | 'of'> - readonly unique: (distinctor: (item: T) => unknown) => NextBuilder, 'of' | 'each' | 'unique', 'not' | 'parse'>; - readonly minLength: (value: number, exclusive?: boolean) => NextBuilder, 'of' | 'each' | 'minLength', 'not' | 'parse'>; - readonly maxLength: (value: number, exclusive?: boolean) => NextBuilder, 'of' | 'each' | 'maxLength', 'not' | 'parse'>; + /** check for uniqueness by a given distintor */ + readonly unique: (distinctor: (item: U) => unknown) => NextBuilder, 'unique' | 'of' | 'each'>; + /** minimum length of the array */ + readonly minLength: (value: number, exclusive?: boolean) => NextBuilder, 'minLength' | 'of' | 'each'>; + /** maximum length of the array */ + readonly maxLength: (value: number, exclusive?: boolean) => NextBuilder, 'maxLength' | 'of' | 'each'>; } } diff --git a/src/parsing/arrays/arrays-parser.spec.ts b/src/parsing/arrays/arrays-parser.spec.ts index 619dd36..cec01d1 100644 --- a/src/parsing/arrays/arrays-parser.spec.ts +++ b/src/parsing/arrays/arrays-parser.spec.ts @@ -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); }); }); }); diff --git a/src/parsing/arrays/index.ts b/src/parsing/arrays/index.ts index 33c738e..48c5463 100644 --- a/src/parsing/arrays/index.ts +++ b/src/parsing/arrays/index.ts @@ -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'; diff --git a/src/parsing/arrays/parseAllArray.ts b/src/parsing/arrays/parseAllArray.ts deleted file mode 100644 index 036e96c..0000000 --- a/src/parsing/arrays/parseAllArray.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { createParseResult } from '../createParseResult'; -import { IParse } from '../IParse'; -import { IParseResult } from '../IParseResult'; - -/** - * parse all elements of an array - * - * @param parse function - * @returns IParseResult - */ -export function parseAllArray( - parse: IParse -) { - - return (values: unknown[]) => { - if (values == null) return createParseResult(null); - - return values.reduce>( - (r, value, index) => { - - const result = parse(value); - const errors = result.success - ? r.errors - : { ...r.errors, [index]: result.errors }; - - return createParseResult( - [...r.value, result.value], - errors - ); - - }, createParseResult([]) - ); - }; -} diff --git a/src/parsing/arrays/provideParseArray.ts b/src/parsing/arrays/provideParseArray.ts index 2275cc9..3f7b1a4 100644 --- a/src/parsing/arrays/provideParseArray.ts +++ b/src/parsing/arrays/provideParseArray.ts @@ -1,6 +1,5 @@ import { isNullOrEmpty } from '../../predicates'; -import { createParseResult } from '../createParseResult'; -import { IParseResult } from '../IParseResult'; +import { IParse } from '../IParse'; import { ParseErrors } from '../ParseErrors'; /** @@ -12,24 +11,23 @@ import { ParseErrors } from '../ParseErrors'; * @returns parseResult */ export function provideParseArray( - negate: boolean = false -) { +): IParse { - return (value: T): IParseResult => { - if (isNullOrEmpty(value)) - return createParseResult(null); + return (value: unknown) => { + let success = true; + let parsed = null; - const parsed = Array.isArray(value) - ? value as T[] - : null; + if (!isNullOrEmpty(value)) { - if (parsed === null === negate) - return createParseResult(parsed); + success = Array.isArray(value); + if (success) + parsed = value as T[]; + } - const errors = negate - ? ParseErrors.not(ParseErrors.array) - : ParseErrors.array; - - return createParseResult(null, errors); + return { + value: parsed, + success, + errors: ParseErrors.array + }; }; } diff --git a/src/parsing/arrays/parseAllArray.spec.ts b/src/parsing/arrays/provideParseArrayValues.spec.ts similarity index 73% rename from src/parsing/arrays/parseAllArray.spec.ts rename to src/parsing/arrays/provideParseArrayValues.spec.ts index f596d4d..fc29ea4 100644 --- a/src/parsing/arrays/parseAllArray.spec.ts +++ b/src/parsing/arrays/provideParseArrayValues.spec.ts @@ -1,5 +1,5 @@ import { Is } from '../../Is'; -import { parseAllArray } from './parseAllArray'; +import { provideParseArrayValues } from './provideParseArrayValues'; import { ParseErrors } from '../ParseErrors'; describe('parseAllArray', () => { @@ -16,10 +16,10 @@ describe('parseAllArray', () => { { name: 'NAME-2', age: 32 } ]; - const result = parseAllArray(schema.parse)(values); + const result = provideParseArrayValues(schema.parse)(values); expect(result.value).toEqual(values); - expect(result.errors).toBe(ParseErrors.empty); + expect(result.errors).toBe(ParseErrors.array); }); it('failure', () => { @@ -28,7 +28,7 @@ describe('parseAllArray', () => { { name: 'NAME-2', age: 1 } ]; - const result = parseAllArray(schema.parse)(values); + const result = provideParseArrayValues(schema.parse)(values); expect(result.value).toEqual(values); expect(result.errors).toEqual({ diff --git a/src/parsing/arrays/provideParseArrayValues.ts b/src/parsing/arrays/provideParseArrayValues.ts new file mode 100644 index 0000000..e2660ea --- /dev/null +++ b/src/parsing/arrays/provideParseArrayValues.ts @@ -0,0 +1,51 @@ +import { isNullOrEmpty } from '../../predicates'; +import { IParse } from '../IParse'; +import { IParseErrors } from '../IParseErrors'; +import { IParseResult } from '../IParseResult'; +import { ParseErrors } from '../ParseErrors'; +import { createParseResult } from '../createParseResult'; + +/** + * parse all elements of an array + * + * @param parse function + * @returns IParseResult + */ +export function provideParseArrayValues( + parse: IParse, +): IParse { + + return (value: unknown[]) => { + let success = true; + let errors: IParseErrors = ParseErrors.array; + + if (!isNullOrEmpty(value)) { + + const result = value.reduce>( + (ar, v, i) => { + + const r = parse(v); + const e = r.success + ? ar.errors + : { ...ar.errors, [i]: r.errors }; + + return createParseResult( + [...ar.value, r.value], + e + ); + + }, createParseResult([]) + ); + + value = result.value; + success = result.success; + if (!success) errors = result.errors; + } + + return { + value: value as T[], + success, + errors: errors + }; + }; +} diff --git a/src/parsing/arrays/provideParseUniqueArray.ts b/src/parsing/arrays/provideParseUniqueArray.ts new file mode 100644 index 0000000..d6db06e --- /dev/null +++ b/src/parsing/arrays/provideParseUniqueArray.ts @@ -0,0 +1,35 @@ +import { isNullOrEmpty } from '../../predicates'; +import { IParse } from '../IParse'; +import { ParseErrors } from '../ParseErrors'; + +/** + * provides a uniqueness check on array + * + * @param distinctor property selector which marks uniqueness + * @param negate negate result + * @returns + */ +export function provideParseUniqueArray( + distinctor: (t: T) => unknown, +): IParse { + + return (values: T[]) => { + + return { + value: values, + success: isNullOrEmpty(values) || (() => { + + const index = new Set(); + return !values.some(v => { + const key = distinctor(v); + const exists = index.has(key); + index.add(key); + + return exists; + }); + + })(), + errors: ParseErrors.unique + }; + }; +} diff --git a/src/parsing/arrays/provideUniqueArray.ts b/src/parsing/arrays/provideUniqueArray.ts deleted file mode 100644 index aaff10d..0000000 --- a/src/parsing/arrays/provideUniqueArray.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { createParseResult } from '../createParseResult'; -import { IParse } from '../IParse'; -import { ParseErrors } from '../ParseErrors'; - -/** - * provides a uniqueness check on array - * - * @param distinctor property selector which marks uniqueness - * @param negate negate result - * @returns - */ -export function provideUniqueArray(distinctor: (t: T) => unknown, negate: boolean): IParse { - return (values: T[]) => { - - const index = new Set(); - const hasDuplicates = values.some(v => { - const key = distinctor(v); - const exists = index.has(key); - index.add(key); - - return exists; - }); - - return createParseResult(values, - hasDuplicates !== negate - ? negate ? ParseErrors.not(ParseErrors.unique) : ParseErrors.unique - : ParseErrors.empty); - }; -} diff --git a/src/parsing/asCurrent.ts b/src/parsing/asCurrent.ts new file mode 100644 index 0000000..b62334e --- /dev/null +++ b/src/parsing/asCurrent.ts @@ -0,0 +1,20 @@ +import { IParseResult } from './IParseResult'; +import { ICurrentParser } from './ICurrentParser'; + +/** + * Creates a wrap around a parse function + * + * Allows the result to be negated. + * The parse function should return errors for every result + * to allow negation + * + * @param parse parse function that is negatable + * @param negate negate the result + * @returns a current parser + */ +export function asCurrent( + parse: (value: unknown) => IParseResult, + negate: boolean = false +): ICurrentParser { + return { parse, negate }; +} diff --git a/src/parsing/booleans/BooleanParser.ts b/src/parsing/booleans/BooleanParser.ts index 168802e..e4129e7 100644 --- a/src/parsing/booleans/BooleanParser.ts +++ b/src/parsing/booleans/BooleanParser.ts @@ -1,8 +1,8 @@ -import { provideEquals } from '../provideEquals'; -import { IParse } from '../IParse'; +import { ICurrentParser } from '../ICurrentParser'; import { IParser } from '../IParser'; import { parseChain } from '../parseChain'; -import { provideParseBoolean } from './provideParseBoolean'; +import { provideEquals } from '../provideEquals'; +import { asCurrent } from '../asCurrent'; import { IBoolean } from './IBoolean'; /** @@ -11,15 +11,15 @@ import { IBoolean } from './IBoolean'; export class BooleanParser implements IBoolean.Parser { constructor( private parent: IParser, - private parseCurrent: IParse = provideParseBoolean(), + private parseCurrent: ICurrentParser, private negate: boolean = false ) { } - readonly parse = parseChain(this.parent, this.parseCurrent); - - readonly equals = (equalToValue: boolean) => new BooleanParser(this, provideEquals(equalToValue, this.negate)); + readonly parse = parseChain(this.parent, this.parseCurrent, 'BOOLEAN'); get not() { - return new BooleanParser(this.parent, provideParseBoolean(), true); + return new BooleanParser(this.parent, this.parseCurrent, !this.negate); } + + readonly equals = (equalToValue: boolean) => new BooleanParser(this, asCurrent(provideEquals(equalToValue), this.negate)); } diff --git a/src/parsing/booleans/IBoolean.ts b/src/parsing/booleans/IBoolean.ts index 1310a0d..e5bfc41 100644 --- a/src/parsing/booleans/IBoolean.ts +++ b/src/parsing/booleans/IBoolean.ts @@ -6,6 +6,6 @@ export namespace IBoolean { export interface Parser extends IParser { readonly not: NextBuilder - equals(value: boolean): NextBuilder + equals(value: boolean | null | undefined): NextBuilder } } diff --git a/src/parsing/booleans/booleans-parser.spec.ts b/src/parsing/booleans/booleans-parser.spec.ts index 3e7c77e..fcce745 100644 --- a/src/parsing/booleans/booleans-parser.spec.ts +++ b/src/parsing/booleans/booleans-parser.spec.ts @@ -64,7 +64,7 @@ describe('booleans-parser', () => { const result = notSchema.parse(value); expect(result.errors).toEqual(ParseErrors.not(ParseErrors.boolean)); - expect(result.value).toBe(null); + expect(result.value).toBe(value); }); }); }); diff --git a/src/parsing/booleans/provideParseBoolean.ts b/src/parsing/booleans/provideParseBoolean.ts index 63a6be2..66b3606 100644 --- a/src/parsing/booleans/provideParseBoolean.ts +++ b/src/parsing/booleans/provideParseBoolean.ts @@ -1,24 +1,24 @@ import { isNullOrEmpty } from '../../predicates'; -import { createParseResult } from '../createParseResult'; -import { IParseResult } from '../IParseResult'; +import { IParse } from '../IParse'; import { ParseErrors } from '../ParseErrors'; import { tryParseBoolean } from './tryParseBoolean'; export function provideParseBoolean( - negate: boolean = false -) { - return (value: unknown): IParseResult => { - if (isNullOrEmpty(value)) return createParseResult(null); +): IParse { + return (value: unknown) => { + let success = true; + let parsed = null; - const parsed = tryParseBoolean(value); + if (!isNullOrEmpty(value)) { - if (parsed === null === negate) - return createParseResult(parsed); + parsed = tryParseBoolean(value); + success = parsed !== null; + } - const errors = negate - ? ParseErrors.not(ParseErrors.boolean) - : ParseErrors.boolean; - - return createParseResult(null, errors); + return { + value: parsed, + success, + errors: ParseErrors.boolean + }; }; } diff --git a/src/parsing/complex/ComplexParser.ts b/src/parsing/complex/ComplexParser.ts index 5471419..e7d38f7 100644 --- a/src/parsing/complex/ComplexParser.ts +++ b/src/parsing/complex/ComplexParser.ts @@ -1,38 +1,22 @@ -import { isEqual, isNullOrEmpty } from '../../predicates'; -import { createParseResult } from '../createParseResult'; import { IParser } from '../IParser'; -import { IParseResult } from '../IParseResult'; import { parseChain } from '../parseChain'; -import { ParseErrors } from '../ParseErrors'; +import { ICurrentParser } from '../ICurrentParser'; import { IComplex } from './IComplex'; -import { ComplexSchema } from './ComplexSchema'; +import { provideEquals } from '../provideEquals'; +import { asCurrent } from '../asCurrent'; export class ComplexParser implements IComplex.Parser { - constructor(private parent: IParser, private schema: ComplexSchema) { } + constructor( + private parent: IParser, + private parseCurrent: ICurrentParser, + private negate: boolean = false + ) { } - readonly parse = parseChain(this.parent, (originalValue: unknown) => { - if (isNullOrEmpty(originalValue)) - return createParseResult(null); + readonly parse = parseChain(this.parent, this.parseCurrent, 'COMPLEX'); - return Object - .keys(this.schema) - .reduce>((r, key) => { - const result = this.schema[key].parse(originalValue[key]); - const value - = result.value == null - ? r.value - : { - ...r.value, - [key]: result.value - }; - const errors = isEqual(result.errors, ParseErrors.empty) - ? r.errors - : { - ...r.errors, - [key]: result.errors - }; + get not() { + return new ComplexParser(this.parent, this.parseCurrent, !this.negate); + } - return createParseResult(value, errors); - }, createParseResult(originalValue as T)); - }); + readonly equals = (equalToValue: T) => new ComplexParser(this, asCurrent(provideEquals(equalToValue), this.negate)); } diff --git a/src/parsing/complex/IComplex.ts b/src/parsing/complex/IComplex.ts index 28fe34e..c5c4fe0 100644 --- a/src/parsing/complex/IComplex.ts +++ b/src/parsing/complex/IComplex.ts @@ -1,6 +1,11 @@ import { IParser } from '../IParser'; +import { NextBuilder } from '../NextBuilder'; export namespace IComplex { - export interface Parser extends IParser { } + export interface Parser extends IParser { + readonly not: NextBuilder, 'not' | 'parse'> + + equals(value: T | null | undefined): NextBuilder, 'equals', 'parse' | 'not'> + } } diff --git a/src/parsing/complex/complex-parser.spec.ts b/src/parsing/complex/complex-parser.spec.ts index 83cfc64..1b50dcc 100644 --- a/src/parsing/complex/complex-parser.spec.ts +++ b/src/parsing/complex/complex-parser.spec.ts @@ -125,6 +125,29 @@ describe('complex-parser', () => { }); }); + it('parse array', () => { + const invalid = { + ...valid, + name: { + family: 'X'.repeat(100) + } + }; + + const value = [invalid, null]; + + const result = peopleSchema.parse(value); + + expect(result.errors).toEqual({ + 0: { + name: { + family: ParseErrors.maxLength(FAMILY_NAME_MAX_LENGTH) + } + }, + 1: ParseErrors.not(ParseErrors.equals(null)), + ...ParseErrors.minLength(3) + }); + }); + /** data */ const now = new Date(); const valid = { @@ -182,5 +205,7 @@ describe('complex-parser', () => { scores: Is.required.array.each(Is.float), emails: Is.array.each(emailSchema).minLength(EMAILS_MIN_LENGTH), lastSeen: Is.date - }); + }).not.equals(null); + + const peopleSchema = Is.array.each(personSchema).minLength(3).unique(i => i?.name); }); diff --git a/src/parsing/complex/index.ts b/src/parsing/complex/index.ts index 34ac99f..8adee1a 100644 --- a/src/parsing/complex/index.ts +++ b/src/parsing/complex/index.ts @@ -5,3 +5,4 @@ export * from './ComplexParser'; export * from './ComplexSchema'; export * from './IComplex'; +export * from './provideParseComplex'; diff --git a/src/parsing/complex/provideParseComplex.ts b/src/parsing/complex/provideParseComplex.ts new file mode 100644 index 0000000..4a6509a --- /dev/null +++ b/src/parsing/complex/provideParseComplex.ts @@ -0,0 +1,56 @@ +import { isEqual, isNullOrEmpty, isObject } from '../../predicates'; +import { createParseResult } from '../createParseResult'; +import { IParseResult } from '../IParseResult'; +import { ParseErrors } from '../ParseErrors'; +import { ComplexSchema } from './ComplexSchema'; +import { IParse } from '../IParse'; +import { IParseErrors } from '../IParseErrors'; + +/** + * provides a parse method for object typed as T + * + * @param negate negate the result + * @returns parser + */ +export function provideParseComplex( + schema: ComplexSchema, +): IParse { + + return (value: unknown) => { + let success = true; + let errors: IParseErrors = ParseErrors.complex; + + if (isObject(value) && !isNullOrEmpty(value)) { + + const result = Object + .keys(schema) + .reduce>((ar, k) => { + const r = schema[k].parse(value[k]); + const v = r.value == null + ? ar.value + : { + ...ar.value, + [k]: r.value + }; + const e = isEqual(r.errors, ParseErrors.empty) + ? ar.errors + : { + ...ar.errors, + [k]: r.errors + }; + + return createParseResult(v, e); + }, createParseResult(value as T)); + + value = result.value; + success = result.success; + if (!success) errors = result.errors; + } + + return { + value: value as T, + success, + errors: errors + }; + }; +} diff --git a/src/parsing/createParseResult.ts b/src/parsing/createParseResult.ts index 5260748..0c2c45b 100644 --- a/src/parsing/createParseResult.ts +++ b/src/parsing/createParseResult.ts @@ -3,15 +3,19 @@ import { ParseErrors } from './ParseErrors'; import { IParseResult } from './IParseResult'; import { IParseErrors } from './IParseErrors'; +/** + * creates a normalised parse result + * + * @param value parsed value + * @param errors errors, success is true if empty + * @returns + */ export function createParseResult( - value: T, errors: IParseErrors = ParseErrors.empty + value: T, + errors: IParseErrors = ParseErrors.empty ): IParseResult { const success = isEqual(errors, ParseErrors.empty); - return { - value, - success, - errors: success ? ParseErrors.empty : errors // for quick check - }; + return { value, success, errors }; } diff --git a/src/parsing/dates/DateParser.ts b/src/parsing/dates/DateParser.ts index 8d69eb5..0b3a50e 100644 --- a/src/parsing/dates/DateParser.ts +++ b/src/parsing/dates/DateParser.ts @@ -1,7 +1,7 @@ import { IParser } from '../IParser'; import { provideMax } from '../provideMax'; import { parseChain } from '../parseChain'; -import { IParse } from '../IParse'; +import { ICurrentParser } from '../ICurrentParser'; import { provideEquals } from '../provideEquals'; import { provideMin } from '../provideMin'; import { provideAnyOf } from '../provideAnyOf'; @@ -9,7 +9,7 @@ import { ensureDateArray } from './ensureDateArray'; import { IDate } from './IDate'; import { parseDate } from './parseDate'; import { DateParsableTypes } from './DateParsableTypes'; -import { provideParseDate } from './provideParseDate'; +import { asCurrent } from '../asCurrent'; /** * Fluent builder for parsing dates @@ -19,17 +19,18 @@ export class DateParser implements IDate.Parser { constructor( private parent: IParser, - private parseCurrent: IParse = provideParseDate(), + private parseCurrent: ICurrentParser, private negate: boolean = false ) { } - readonly parse = parseChain(this.parent, this.parseCurrent); - readonly equals = (value: DateParsableTypes) => new DateParser(this, provideEquals(parseDate(value), this.negate)); - readonly anyOf = (values: DateParsableTypes[]) => new DateParser(this, provideAnyOf(ensureDateArray(values), this.negate)); - readonly min = (value: DateParsableTypes, exclusive = false) => new DateParser(this, provideMin(parseDate(value), exclusive, this.negate)); - readonly max = (value: DateParsableTypes, exclusive = false) => new DateParser(this, provideMax(parseDate(value), exclusive, this.negate)); + readonly parse = parseChain(this.parent, this.parseCurrent, 'DATE'); get not() { - return new DateParser(this.parent, this.parseCurrent, true); + return new DateParser(this.parent, this.parseCurrent, !this.negate); } + + readonly equals = (value: DateParsableTypes) => new DateParser(this, asCurrent(provideEquals(parseDate(value)), this.negate)); + readonly anyOf = (values: DateParsableTypes[]) => new DateParser(this, asCurrent(provideAnyOf(ensureDateArray(values)), this.negate)); + readonly min = (value: DateParsableTypes, exclusive = false) => new DateParser(this, asCurrent(provideMin(parseDate(value), exclusive), this.negate)); + readonly max = (value: DateParsableTypes, exclusive = false) => new DateParser(this, asCurrent(provideMax(parseDate(value), exclusive), this.negate)); } diff --git a/src/parsing/dates/IDate.ts b/src/parsing/dates/IDate.ts index d98175d..e270041 100644 --- a/src/parsing/dates/IDate.ts +++ b/src/parsing/dates/IDate.ts @@ -7,7 +7,7 @@ export namespace IDate { export interface Parser extends IParser { readonly not: NextBuilder; - equals(value: DateParsableTypes): NextBuilder; + equals(value: DateParsableTypes | null | undefined): NextBuilder; anyOf(values: DateParsableTypes[]): NextBuilder; min(value: DateParsableTypes, exclusive?: boolean): NextBuilder; diff --git a/src/parsing/dates/dates-parser.spec.ts b/src/parsing/dates/dates-parser.spec.ts index 08ad47a..05d868e 100644 --- a/src/parsing/dates/dates-parser.spec.ts +++ b/src/parsing/dates/dates-parser.spec.ts @@ -99,7 +99,7 @@ describe('dates-parser', () => { const value = new Date(); const result = notSchema.parse(value); - expect(result.value).toBe(null); + expect(result.value).toBe(value); expect(result.errors).toEqual(ParseErrors.not(ParseErrors.date)); }); }); diff --git a/src/parsing/dates/parseInt.spec.ts b/src/parsing/dates/parseInt.spec.ts new file mode 100644 index 0000000..e17e7c8 --- /dev/null +++ b/src/parsing/dates/parseInt.spec.ts @@ -0,0 +1,15 @@ +import { parseDate } from './parseDate'; + +describe('parseDate', () => { + + it('success', () => { + const result = parseDate('3000-02-01'); + + expect(result).toEqual(new Date('3000-02-01')); + }); + + it('throws not a date', () => { + expect(() => parseDate('ooo')) + .toThrow(); + }); +}); diff --git a/src/parsing/dates/provideParseDate.ts b/src/parsing/dates/provideParseDate.ts index 5a30553..abfbae1 100644 --- a/src/parsing/dates/provideParseDate.ts +++ b/src/parsing/dates/provideParseDate.ts @@ -1,24 +1,26 @@ import { isNullOrEmpty } from '../../predicates'; -import { createParseResult } from '../createParseResult'; -import { IParseResult } from '../IParseResult'; +import { IParse } from '../IParse'; import { ParseErrors } from '../ParseErrors'; import { DateParsableTypes } from './DateParsableTypes'; import { tryParseDate } from './tryParseDate'; -export function provideParseDate(negate: boolean = false) { +export function provideParseDate( +): IParse { - return (value: unknown): IParseResult => { - if (isNullOrEmpty(value)) return createParseResult(null); + return (value: DateParsableTypes) => { + let success = true; + let parsed = null; - const parsed = tryParseDate(value as DateParsableTypes); + if (!isNullOrEmpty(value)) { - if (parsed === null === negate) - return createParseResult(parsed); + parsed = tryParseDate(value); + success = parsed !== null; + } - const errors = negate - ? ParseErrors.not(ParseErrors.date) - : ParseErrors.date; - - return createParseResult(null, errors); + return { + value: parsed, + success, + errors: ParseErrors.date + }; }; } diff --git a/src/parsing/dictionaries/Dictionary.ts b/src/parsing/dictionaries/Dictionary.ts index 89f20f8..d0afb86 100644 --- a/src/parsing/dictionaries/Dictionary.ts +++ b/src/parsing/dictionaries/Dictionary.ts @@ -1,2 +1,4 @@ -export interface Dictionary { [k: string]: T; } +export interface Dictionary { + [k: string]: T; +} diff --git a/src/parsing/dictionaries/DictionaryParser.ts b/src/parsing/dictionaries/DictionaryParser.ts index 3c03483..b5b9831 100644 --- a/src/parsing/dictionaries/DictionaryParser.ts +++ b/src/parsing/dictionaries/DictionaryParser.ts @@ -1,25 +1,32 @@ -import { IParse } from '../IParse'; import { IParser } from '../IParser'; -import { parseAllDictionary } from './parseAllDictionary'; +import { provideParseDictionaryValues } from './provideParseDictionaryValues'; import { parseChain } from '../parseChain'; +import { ICurrentParser } from '../ICurrentParser'; import { Dictionary } from './Dictionary'; import { IDictionary } from './IDictionary'; import { provideParseDictionary } from './provideParseDictionary'; +import { provideMaxLength } from '../provideMaxLength'; +import { provideMinLength } from '../provideMinLength'; +import { provideDictionaryValues } from './provideDictionaryValues'; +import { asCurrent } from '../asCurrent'; export class DictionaryParser implements IDictionary.Parser { constructor( private parent: IParser, - private parseCurrent: IParse> = provideParseDictionary(), + private parseCurrent: ICurrentParser>, private negate: boolean = false ) { } - readonly parse = parseChain>(this.parent, this.parseCurrent); + readonly parse = parseChain>(this.parent, this.parseCurrent, 'DICTIONARY'); get not() { - return new DictionaryParser(this.parent, this.parseCurrent, true); + return new DictionaryParser(this.parent, this.parseCurrent, !this.negate); } - readonly of = () => new DictionaryParser(this.parent); - readonly each = (parser: IParser) => new DictionaryParser(this, parseAllDictionary(parser.parse), this.negate); + readonly of = () => new DictionaryParser(this.parent, asCurrent(provideParseDictionary(), this.negate)); + readonly each = (parser: IParser) => new DictionaryParser(this, asCurrent(provideParseDictionaryValues(parser.parse), this.negate)); + + readonly minLength = (minValue: number, exclusive = false) => new DictionaryParser(this, asCurrent(provideDictionaryValues(provideMinLength(minValue, exclusive)), this.negate)); + readonly maxLength = (maxValue: number, exclusive = false) => new DictionaryParser(this, asCurrent(provideDictionaryValues(provideMaxLength(maxValue, exclusive)), this.negate)); } diff --git a/src/parsing/dictionaries/IDictionary.ts b/src/parsing/dictionaries/IDictionary.ts index c09dd6e..6529d58 100644 --- a/src/parsing/dictionaries/IDictionary.ts +++ b/src/parsing/dictionaries/IDictionary.ts @@ -8,7 +8,14 @@ export namespace IDictionary { export interface Parser extends IParser> { readonly not: NextBuilder, 'of' | 'each' | 'not'> + /** declare the type of each item */ readonly of: () => NextBuilder, 'of' | 'each'> + /** parse each item with a parser */ readonly each: (parser: IParser) => NextBuilder, 'of' | 'each'> + + /** minimum length of the array */ + readonly minLength: (value: number, exclusive?: boolean) => NextBuilder, 'of' | 'each' | 'minLength', 'not' | 'parse'>; + /** maximum length of the array */ + readonly maxLength: (value: number, exclusive?: boolean) => NextBuilder, 'of' | 'each' | 'maxLength', 'not' | 'parse'>; } } diff --git a/src/parsing/dictionaries/dictionaries-maxLength.spec.ts b/src/parsing/dictionaries/dictionaries-maxLength.spec.ts new file mode 100644 index 0000000..d866530 --- /dev/null +++ b/src/parsing/dictionaries/dictionaries-maxLength.spec.ts @@ -0,0 +1,64 @@ +import { Is } from '../../Is'; +import { ParseErrors } from '../ParseErrors'; + +describe('dictionary-maxLength', () => { + const MAX_LENGTH = 1; + const schema = Is.dictionary.maxLength(MAX_LENGTH); + + it('success', () => { + const value = { 'a': 1 }; + const result = schema.parse(value); + + expect(result.errors).toEqual(ParseErrors.empty); + expect(result.value).toBe(value); + }); + + it('failure', () => { + const value = { 'a': 1, 'b': 2 }; + const result = schema.parse(value); + + expect(result.errors).toEqual(ParseErrors.maxLength(MAX_LENGTH)); + expect(result.value).toBe(value); + }); + + describe('not', () => { + + const notSchema = Is.dictionary.not.maxLength(MAX_LENGTH); + + it('success', () => { + const value = { 'a': 1, 'b': 2 }; + const result = notSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.empty); + expect(result.value).toBe(value); + }); + + it('failure', () => { + const value = { 'a': 1 }; + const result = notSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.not(ParseErrors.maxLength(MAX_LENGTH))); + expect(result.value).toBe(value); + }); + }); + + describe('exclusive', () => { + const exclusiveSchema = Is.dictionary.maxLength(MAX_LENGTH, true); + + it('success', () => { + const value = {}; + const result = exclusiveSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.empty); + expect(result.value).toBe(value); + }); + + it('failure', () => { + const value = { 'a': 1 }; + const result = exclusiveSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.maxLength(MAX_LENGTH, true)); + expect(result.value).toBe(value); + }); + }); +}); diff --git a/src/parsing/dictionaries/dictionaries-minLength.spec.ts b/src/parsing/dictionaries/dictionaries-minLength.spec.ts new file mode 100644 index 0000000..b14b242 --- /dev/null +++ b/src/parsing/dictionaries/dictionaries-minLength.spec.ts @@ -0,0 +1,63 @@ +import { Is } from '../../Is'; +import { ParseErrors } from '../ParseErrors'; + +describe('dictionary-minLength', () => { + const MIN_LENGTH = 2; + const schema = Is.dictionary.minLength(MIN_LENGTH); + + it('success', () => { + const value = { 'a': 1, 'b': 2 }; + const result = schema.parse(value); + + expect(result.errors).toEqual(ParseErrors.empty); + expect(result.value).toBe(value); + }); + + it('failure', () => { + const value = { 'a': 1 }; + const result = schema.parse(value); + + expect(result.errors).toEqual(ParseErrors.minLength(MIN_LENGTH)); + expect(result.value).toBe(value); + }); + + describe('not', () => { + const notSchema = Is.dictionary.not.minLength(MIN_LENGTH); + + it('success', () => { + const value = { 'a': 1 }; + const result = notSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.empty); + expect(result.value).toBe(value); + }); + + it('failure', () => { + const value = { 'a': 1, 'b': 2 }; + const result = notSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.not(ParseErrors.minLength(MIN_LENGTH))); + expect(result.value).toBe(value); + }); + }); + + describe('exclusive', () => { + const exclusiveSchema = Is.dictionary.minLength(MIN_LENGTH, true); + + it('success', () => { + const value = { 'a': 1, 'b': 2, 'c': 3 }; + const result = exclusiveSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.empty); + expect(result.value).toBe(value); + }); + + it('failure', () => { + const value = { 'a': 1 }; + const result = exclusiveSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.minLength(MIN_LENGTH, true)); + expect(result.value).toBe(value); + }); + }); +}); diff --git a/src/parsing/dictionaries/index.ts b/src/parsing/dictionaries/index.ts index cc4124b..b6f75e1 100644 --- a/src/parsing/dictionaries/index.ts +++ b/src/parsing/dictionaries/index.ts @@ -5,5 +5,6 @@ export * from './Dictionary'; export * from './DictionaryParser'; export * from './IDictionary'; -export * from './parseAllDictionary'; +export * from './provideDictionaryValues'; export * from './provideParseDictionary'; +export * from './provideParseDictionaryValues'; diff --git a/src/parsing/dictionaries/parseAllDictionary.ts b/src/parsing/dictionaries/parseAllDictionary.ts deleted file mode 100644 index 2f25995..0000000 --- a/src/parsing/dictionaries/parseAllDictionary.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { IParse } from '../IParse'; -import { IParseResult } from '../IParseResult'; -import { createParseResult } from '../createParseResult'; -import { Dictionary } from './Dictionary'; - -/** - * parse all properties on an object (Dictionary) - * - * @param parse function - * @returns IParseResult> - */ -export function parseAllDictionary( - parse: IParse -): IParse> { - - return value => { - if (value == null) return createParseResult(null); - - return Object.keys(value).reduce>>( - (r, n) => { - - const result = parse(value[n]); - const errors = result.success - ? r.errors - : { ...r.errors, [n]: result.errors }; - - return createParseResult( - { ...r.value, [n]: result.value }, - errors - ); - - }, createParseResult>({}) - ); - }; -} diff --git a/src/parsing/dictionaries/provideDictionaryValues.ts b/src/parsing/dictionaries/provideDictionaryValues.ts new file mode 100644 index 0000000..e4b59a5 --- /dev/null +++ b/src/parsing/dictionaries/provideDictionaryValues.ts @@ -0,0 +1,23 @@ +import { IParse } from '../IParse'; +import { Dictionary } from './Dictionary'; + +/** + * uses a dictionaries values to check the length + * + * @param inner inner parse function + * @returns a dictionary values result + */ +export function provideDictionaryValues( + inner: IParse +): IParse> { + + return (value: Dictionary) => { + + const result = inner(Object.values(value)); + + return { + ...result, + value + }; + }; +} diff --git a/src/parsing/dictionaries/provideParseDictionary.ts b/src/parsing/dictionaries/provideParseDictionary.ts index 0060c7d..3bd8fed 100644 --- a/src/parsing/dictionaries/provideParseDictionary.ts +++ b/src/parsing/dictionaries/provideParseDictionary.ts @@ -1,29 +1,27 @@ import { isNullOrEmpty } from '../../predicates'; import { isObject } from '../../predicates/isObject'; -import { createParseResult } from '../createParseResult'; -import { IParseResult } from '../IParseResult'; +import { IParse } from '../IParse'; import { ParseErrors } from '../ParseErrors'; import { Dictionary } from './Dictionary'; export function provideParseDictionary( - negate: boolean = false -) { +): IParse> { - return (value: T): IParseResult> => { - if (isNullOrEmpty(value)) - return createParseResult(null); + return (value: unknown) => { + let success = true; + let parsed = null; - const parsed = isObject(value) - ? value as Dictionary - : null; + if (!isNullOrEmpty(value)) { - if (parsed === null === negate) - return createParseResult(parsed); + success = isObject(value); + if (success) + parsed = value as Dictionary; + } - const errors = negate - ? ParseErrors.not(ParseErrors.dictionary) - : ParseErrors.dictionary; - - return createParseResult(null, errors); + return { + value: parsed, + success, + errors: ParseErrors.dictionary + }; }; } diff --git a/src/parsing/dictionaries/parseAllDictionary.spec.ts b/src/parsing/dictionaries/provideParseDictionaryValues.spec.ts similarity index 58% rename from src/parsing/dictionaries/parseAllDictionary.spec.ts rename to src/parsing/dictionaries/provideParseDictionaryValues.spec.ts index c8cc82f..a66adeb 100644 --- a/src/parsing/dictionaries/parseAllDictionary.spec.ts +++ b/src/parsing/dictionaries/provideParseDictionaryValues.spec.ts @@ -1,24 +1,24 @@ import { Is } from '../../Is'; -import { parseAllDictionary } from './parseAllDictionary'; +import { provideParseDictionaryValues } from './provideParseDictionaryValues'; import { ParseErrors } from '../ParseErrors'; -describe('parseAllDictionary', () => { +describe('provideParseDictionaryValues', () => { const schema = Is.array.each(Is.int); it('success', () => { const value = { a: [1], b: [2] }; - const result = parseAllDictionary(schema.parse)(value); + const result = provideParseDictionaryValues(schema.parse)(value); expect(result.value).toEqual(value); - expect(result.errors).toBe(ParseErrors.empty); + expect(result.errors).toBe(ParseErrors.dictionary); }); it('failure', () => { const value = { a: ['a'], b: ['b'] }; - const result = parseAllDictionary(schema.parse)(value); + const result = provideParseDictionaryValues(schema.parse)(value); expect(result.value).toEqual({ a: [null], b: [null] }); expect(result.errors).toEqual({ a: { 0: { int: true } }, b: { 0: { int: true } } }); diff --git a/src/parsing/dictionaries/provideParseDictionaryValues.ts b/src/parsing/dictionaries/provideParseDictionaryValues.ts new file mode 100644 index 0000000..15cb1df --- /dev/null +++ b/src/parsing/dictionaries/provideParseDictionaryValues.ts @@ -0,0 +1,54 @@ +import { isNullOrEmpty } from '../../predicates'; +import { IParse } from '../IParse'; +import { IParseErrors } from '../IParseErrors'; +import { IParseResult } from '../IParseResult'; +import { ParseErrors } from '../ParseErrors'; +import { createParseResult } from '../createParseResult'; +import { Dictionary } from './Dictionary'; + +/** + * parse all properties on an object (Dictionary) + * + * @param parse function + * @returns IParseResult> + */ +export function provideParseDictionaryValues( + parse: IParse, +): IParse> { + + return (value: unknown) => { + let success = true; + let errors: IParseErrors = ParseErrors.dictionary; + + if (!isNullOrEmpty(value)) { + + const result = Object + .keys(value) + .reduce>>( + (ar, n) => { + + const r = parse(value[n]); + const e = r.success + ? ar.errors + : { ...ar.errors, [n]: r.errors }; + + return createParseResult( + { ...ar.value, [n]: r.value }, + e + ); + + }, createParseResult>({}) + ); + + value = result.value; + success = result.success; + if (!success) errors = result.errors; + } + + return { + value: value as Dictionary, + success, + errors: errors + }; + }; +} diff --git a/src/parsing/index.ts b/src/parsing/index.ts index e9612b0..19b0050 100644 --- a/src/parsing/index.ts +++ b/src/parsing/index.ts @@ -3,6 +3,7 @@ */ export * from './createParseResult'; +export * from './ICurrentParser'; export * from './IHasLength'; export * from './IParse'; export * from './IParseErrors'; @@ -19,8 +20,10 @@ export * from './provideMax'; export * from './provideMaxLength'; export * from './provideMin'; export * from './provideMinLength'; +export * from './provideParseRoot'; export * from './RelationalValidatorTypes'; export * from './RootParser'; +export * from './asCurrent'; export * from './arrays/index'; export * from './booleans/index'; export * from './complex/index'; diff --git a/src/parsing/json/IJson.ts b/src/parsing/json/IJson.ts index 8912cfe..49b20a7 100644 --- a/src/parsing/json/IJson.ts +++ b/src/parsing/json/IJson.ts @@ -1,3 +1,4 @@ +import { ICurrentParser } from '../ICurrentParser'; import { IParser } from '../IParser'; import { ComplexSchema, IComplex } from '../complex'; @@ -6,7 +7,9 @@ export namespace IJson { export interface Parser extends IParser { + // use a complex type schema to parse the json object readonly for: (schema: ComplexSchema) => IComplex.Parser; - readonly use: (parser: IParser) => IParser; + /** use a parser function to parse the json object */ + readonly use: (parser: ICurrentParser) => IParser; } } diff --git a/src/parsing/json/JsonParser.ts b/src/parsing/json/JsonParser.ts index 1380265..45fc838 100644 --- a/src/parsing/json/JsonParser.ts +++ b/src/parsing/json/JsonParser.ts @@ -1,20 +1,20 @@ -import { IParse } from '../IParse'; import { IParser } from '../IParser'; -import { ComplexParser, ComplexSchema, IComplex } from '../complex'; +import { ComplexParser, ComplexSchema, IComplex, provideParseComplex } from '../complex'; import { parseChain } from '../parseChain'; +import { ICurrentParser } from '../ICurrentParser'; import { IJson } from './IJson'; -import { provideParseJson } from './provideParseJson'; +import { asCurrent } from '../asCurrent'; export class JsonParser implements IJson.Parser { constructor( private parent: IParser, - private parseCurrent: IParse = null, - private negate: boolean = false + private parseCurrent: ICurrentParser, + private negate = false ) { } - readonly parse = parseChain(this.parent, this.parseCurrent ?? provideParseJson(this.negate)); + readonly parse = parseChain(this.parent, this.parseCurrent, 'JSON'); - readonly for = (schema: ComplexSchema): IComplex.Parser => new ComplexParser(this, schema); - readonly use = (parser: IParser) => ({ parse: parseChain(this, parser.parse) }); + readonly for = (schema: ComplexSchema): IComplex.Parser => new ComplexParser(this, asCurrent(provideParseComplex(schema), this.negate)); + readonly use = (parser: ICurrentParser) => ({ parse: parseChain(this, { ...this.parseCurrent, parse: parser.parse }, 'JSON-USE') }); } diff --git a/src/parsing/json/index.ts b/src/parsing/json/index.ts index 5631657..2305cf6 100644 --- a/src/parsing/json/index.ts +++ b/src/parsing/json/index.ts @@ -5,3 +5,4 @@ export * from './IJson'; export * from './JsonParser'; export * from './provideParseJson'; +export * from './tryParseJson'; diff --git a/src/parsing/json/json-parser.spec.ts b/src/parsing/json/json-parser.spec.ts index b357a41..a63e8de 100644 --- a/src/parsing/json/json-parser.spec.ts +++ b/src/parsing/json/json-parser.spec.ts @@ -42,16 +42,43 @@ describe('json-parser', () => { }); describe('and complex', () => { - const complexParser = schema.for({ + + const complexSchema = schema.for({ date: Is.required.date }); it('success', () => { const value = '{"date":"1 Jan 2000"}'; - const result = complexParser.parse(value); + const result = complexSchema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toEqual({ 'date': new Date('2000-01-01T00:00:00.000Z') }); }); }); -}); \ No newline at end of file + + // todo + // describe('and keyed', () => { + // const keyedSchema = schema.forKeyed( + // Is.string, + // { + // date: Is.required.date + // } + // ); + + // it('success', () => { + // const value = '{"a":{"date":"1 Jan 2000"}}'; + // const result = keyedSchema.parse(value); + + // expect(result.errors).toEqual(ParseErrors.empty); + // expect(result.value).toEqual({ 'date': new Date('2000-01-01T00:00:00.000Z') }); + // }); + + // it('failure', () => { + // const value = '{"a":{"date":"1 Jan 2000"}}'; + // const result = keyedSchema.parse(value); + + // expect(result.errors).toEqual(ParseErrors.unique); + // expect(result.value).toEqual({ 'date': new Date('2000-01-01T00:00:00.000Z') }); + // }); + // }); +}); diff --git a/src/parsing/json/provideParseJson.ts b/src/parsing/json/provideParseJson.ts index 6ff8aed..ba6df21 100644 --- a/src/parsing/json/provideParseJson.ts +++ b/src/parsing/json/provideParseJson.ts @@ -1,26 +1,25 @@ -import { isNullOrEmpty } from '../../predicates'; -import { IParseResult } from '../IParseResult'; import { ParseErrors } from '../ParseErrors'; -import { createParseResult } from '../createParseResult'; import { tryParseJson } from './tryParseJson'; +import { IParse } from '../IParse'; +import { isNullOrEmpty } from '../../predicates'; export function provideParseJson( - negate: boolean = false -) { - - return (value: T): IParseResult => { - if (isNullOrEmpty(value)) - return createParseResult(null); +): IParse { - const parsed = tryParseJson(value); + return (value: string) => { + let success = true; + let parsed = null; - if (parsed === null === negate) - return createParseResult(parsed); + if (!isNullOrEmpty(value)) { - const errors = negate - ? ParseErrors.not(ParseErrors.json) - : ParseErrors.json; + parsed = tryParseJson(value); + success = parsed !== null; + } - return createParseResult(null, errors); + return { + value: parsed, + success, + errors: ParseErrors.json + }; }; } diff --git a/src/parsing/numbers/FloatParser.ts b/src/parsing/numbers/FloatParser.ts index 9c52794..bdf8f80 100644 --- a/src/parsing/numbers/FloatParser.ts +++ b/src/parsing/numbers/FloatParser.ts @@ -1,15 +1,15 @@ import { IParser } from '../IParser'; import { parseChain } from '../parseChain'; +import { ICurrentParser } from '../ICurrentParser'; import { IFloat } from './IFloat'; import { parseFloat } from './parseFloat'; -import { provideParseFloat } from './provideParseFloat'; import { NumberParsableTypes } from './NumberParsableTypes'; -import { IParse } from '../IParse'; import { provideAnyOf } from '../provideAnyOf'; import { provideEquals } from '../provideEquals'; import { provideMax } from '../provideMax'; import { provideMin } from '../provideMin'; import { ensureNumberArray } from './ensureNumberArray'; +import { asCurrent } from '../asCurrent'; /** * Fluent builder for parsing floats @@ -17,17 +17,18 @@ import { ensureNumberArray } from './ensureNumberArray'; export class FloatParser implements IFloat.Parser { constructor( private parent: IParser, - private parseCurrent: IParse = provideParseFloat(), + private parseCurrent: ICurrentParser, private negate: boolean = false ) { } - readonly parse = parseChain(this.parent, this.parseCurrent); - readonly equals = (value: NumberParsableTypes) => new FloatParser(this, provideEquals(parseFloat(value), this.negate)); - readonly anyOf = (values: NumberParsableTypes[]) => new FloatParser(this, provideAnyOf(ensureNumberArray(values), this.negate)); - readonly min = (value: NumberParsableTypes, exclusive = false) => new FloatParser(this, provideMin(parseFloat(value), exclusive, this.negate)); - readonly max = (value: NumberParsableTypes, exclusive = false) => new FloatParser(this, provideMax(parseFloat(value), exclusive, this.negate)); + readonly parse = parseChain(this.parent, this.parseCurrent, 'FLOAT'); get not() { - return new FloatParser(this.parent, this.parseCurrent, true); + return new FloatParser(this.parent, this.parseCurrent, !this.negate); } + + readonly equals = (value: NumberParsableTypes) => new FloatParser(this, asCurrent(provideEquals(parseFloat(value)), this.negate)); + readonly anyOf = (values: NumberParsableTypes[]) => new FloatParser(this, asCurrent(provideAnyOf(ensureNumberArray(values)), this.negate)); + readonly min = (value: NumberParsableTypes, exclusive = false) => new FloatParser(this, asCurrent(provideMin(parseFloat(value), exclusive), this.negate)); + readonly max = (value: NumberParsableTypes, exclusive = false) => new FloatParser(this, asCurrent(provideMax(parseFloat(value), exclusive), this.negate)); } diff --git a/src/parsing/numbers/IFloat.ts b/src/parsing/numbers/IFloat.ts index 6dab626..f0a5fed 100644 --- a/src/parsing/numbers/IFloat.ts +++ b/src/parsing/numbers/IFloat.ts @@ -8,7 +8,7 @@ export namespace IFloat { export interface Parser extends IParser { readonly not: NextBuilder; - equals(value: NumberParsableTypes): NextBuilder; + equals(value: NumberParsableTypes | null | undefined): NextBuilder; anyOf(values: NumberParsableTypes[]): NextBuilder; min(value: NumberParsableTypes, exclusive?: boolean): NextBuilder; diff --git a/src/parsing/numbers/IInt.ts b/src/parsing/numbers/IInt.ts index b028d26..fb60311 100644 --- a/src/parsing/numbers/IInt.ts +++ b/src/parsing/numbers/IInt.ts @@ -11,7 +11,7 @@ export namespace IInt { readonly not: NextBuilder; - equals(value: NumberParsableTypes): NextBuilder; + equals(value: NumberParsableTypes | null | undefined): NextBuilder; anyOf(values: NumberParsableTypes[] | NumberEnumMap): NextBuilder; min(value: NumberParsableTypes, exclusive?: boolean): NextBuilder; diff --git a/src/parsing/numbers/IntParser.ts b/src/parsing/numbers/IntParser.ts index 859c5ba..03bd7c3 100644 --- a/src/parsing/numbers/IntParser.ts +++ b/src/parsing/numbers/IntParser.ts @@ -1,16 +1,17 @@ import { IParser } from '../IParser'; import { parseChain } from '../parseChain'; +import { ICurrentParser } from '../ICurrentParser'; import { NumberParsableTypes } from './NumberParsableTypes'; -import { IParse } from '../IParse'; import { provideAnyOf } from '../provideAnyOf'; import { provideEquals } from '../provideEquals'; import { provideMax } from '../provideMax'; import { provideMin } from '../provideMin'; import { ensureNumberArray } from './ensureNumberArray'; import { IInt } from './IInt'; -import { provideParseInt } from './provideParseInt'; import { parseInt } from './parseInt'; import { NumberEnumMap } from './NumberEnumMap'; +import { provideParseInt } from './provideParseInt'; +import { asCurrent } from '../asCurrent'; /** * Fluent builder for parsing ints @@ -18,21 +19,21 @@ import { NumberEnumMap } from './NumberEnumMap'; export class IntParser implements IInt.Parser { constructor( private parent: IParser, - private parseCurrent: IParse = null, + private parseCurrent: ICurrentParser, private radix: number = undefined, private negate: boolean = false ) { } - readonly parse = parseChain(this.parent, this.parseCurrent ?? provideParseInt(this.negate)); - - readonly withRadix = (value?: number) => new IntParser(this.parent, this.parseCurrent, value, this.negate); - - readonly equals = (value: NumberParsableTypes) => new IntParser(this, provideEquals(parseInt(value, this.radix), this.negate)); - readonly anyOf = (values: NumberParsableTypes[] | NumberEnumMap) => new IntParser(this, provideAnyOf(ensureNumberArray(values), this.negate)); - readonly min = (value: NumberParsableTypes, exclusive = false) => new IntParser(this, provideMin(parseInt(value, this.radix), exclusive, this.negate)); - readonly max = (value: NumberParsableTypes, exclusive = false) => new IntParser(this, provideMax(parseInt(value, this.radix), exclusive, this.negate)); + readonly parse = parseChain(this.parent, this.parseCurrent, 'INT'); get not() { - return new IntParser(this.parent, this.parseCurrent, this.radix, true); + return new IntParser(this.parent, this.parseCurrent, this.radix, !this.negate); } + + readonly withRadix = (value?: number) => new IntParser(this.parent, asCurrent(provideParseInt(value), this.negate), this.radix, this.negate); + + readonly equals = (value: NumberParsableTypes) => new IntParser(this, asCurrent(provideEquals(parseInt(value, this.radix)), this.negate), this.radix); + readonly anyOf = (values: NumberParsableTypes[] | NumberEnumMap) => new IntParser(this, asCurrent(provideAnyOf(ensureNumberArray(values)), this.negate), this.radix); + readonly min = (value: NumberParsableTypes, exclusive = false) => new IntParser(this, asCurrent(provideMin(parseInt(value, this.radix), exclusive), this.negate), this.radix); + readonly max = (value: NumberParsableTypes, exclusive = false) => new IntParser(this, asCurrent(provideMax(parseInt(value, this.radix), exclusive), this.negate), this.radix); } diff --git a/src/parsing/numbers/numbers-int-min.spec.ts b/src/parsing/numbers/numbers-int-min.spec.ts index 3a90f7f..6e4a0de 100644 --- a/src/parsing/numbers/numbers-int-min.spec.ts +++ b/src/parsing/numbers/numbers-int-min.spec.ts @@ -21,7 +21,7 @@ describe('numbers-int-min', () => { expect(result.value).toBe(value); }); - it('success null', () => { + it('success null #fail', () => { const value = null; const result = schema.parse(value); diff --git a/src/parsing/numbers/numbers-int.spec.ts b/src/parsing/numbers/numbers-int.spec.ts index 4e52b39..f7c68c2 100644 --- a/src/parsing/numbers/numbers-int.spec.ts +++ b/src/parsing/numbers/numbers-int.spec.ts @@ -76,7 +76,7 @@ describe('numbers-int', () => { const result = notSchema.parse(value); expect(result.errors).toEqual(ParseErrors.not(ParseErrors.int)); - expect(result.value).toBe(null); + expect(result.value).toBe(1); }); }); }); diff --git a/src/parsing/numbers/parseFloat.spec.ts b/src/parsing/numbers/parseFloat.spec.ts index 77cd332..8064df0 100644 --- a/src/parsing/numbers/parseFloat.spec.ts +++ b/src/parsing/numbers/parseFloat.spec.ts @@ -18,4 +18,4 @@ describe('parseFloat', () => { expect(() => parseFloat('ooo')) .toThrow(); }); -}); \ No newline at end of file +}); diff --git a/src/parsing/numbers/parseInt.spec.ts b/src/parsing/numbers/parseInt.spec.ts index c06ef75..1bdaf21 100644 --- a/src/parsing/numbers/parseInt.spec.ts +++ b/src/parsing/numbers/parseInt.spec.ts @@ -18,4 +18,4 @@ describe('parseInt', () => { expect(() => parseInt('ooo')) .toThrow(); }); -}); \ No newline at end of file +}); diff --git a/src/parsing/numbers/provideParseFloat.ts b/src/parsing/numbers/provideParseFloat.ts index ed974b6..e6b96b6 100644 --- a/src/parsing/numbers/provideParseFloat.ts +++ b/src/parsing/numbers/provideParseFloat.ts @@ -1,6 +1,5 @@ import { isNullOrEmpty } from '../../predicates'; -import { createParseResult } from '../createParseResult'; -import { IParseResult } from '../IParseResult'; +import { IParse } from '../IParse'; import { ParseErrors } from '../ParseErrors'; import { NumberParsableTypes } from './NumberParsableTypes'; import { tryParseFloat } from './tryParseFloat'; @@ -13,21 +12,23 @@ import { tryParseFloat } from './tryParseFloat'; * @param negate * @returns parseResult */ -export function provideParseFloat(negate: boolean = false) { +export function provideParseFloat( +): IParse { - return (value: unknown): IParseResult => { - if (isNullOrEmpty(value)) - return createParseResult(null); + return (value: NumberParsableTypes) => { + let success = true; + let parsed = null; - const parsed = tryParseFloat(value as NumberParsableTypes); + if (!isNullOrEmpty(value)) { - if (parsed === null === negate) - return createParseResult(parsed); + parsed = tryParseFloat(value); + success = parsed !== null; + } - const errors = negate - ? ParseErrors.not(ParseErrors.float) - : ParseErrors.float; - - return createParseResult(null, errors); + return { + value: parsed, + success, + errors: ParseErrors.float + }; }; } diff --git a/src/parsing/numbers/provideParseInt.ts b/src/parsing/numbers/provideParseInt.ts index a92a125..7d214bb 100644 --- a/src/parsing/numbers/provideParseInt.ts +++ b/src/parsing/numbers/provideParseInt.ts @@ -1,6 +1,5 @@ import { isNullOrEmpty } from '../../predicates'; -import { createParseResult } from '../createParseResult'; -import { IParseResult } from '../IParseResult'; +import { IParse } from '../IParse'; import { ParseErrors } from '../ParseErrors'; import { NumberParsableTypes } from './NumberParsableTypes'; import { tryParseInt } from './tryParseInt'; @@ -10,26 +9,26 @@ import { tryParseInt } from './tryParseInt'; * * note. if negated result value will be null * - * @param negate * @returns parseResult */ export function provideParseInt( - negate: boolean = false -) { + radix: number = undefined, +): IParse { - return (value: unknown): IParseResult => { - if (isNullOrEmpty(value)) - return createParseResult(null); + return (value: NumberParsableTypes) => { + let success = true; + let parsed = null; - const parsed = tryParseInt(value as NumberParsableTypes); + if (!isNullOrEmpty(value)) { - if (parsed === null === negate) - return createParseResult(parsed); + parsed = tryParseInt(value, radix); + success = parsed !== null; + } - const errors = negate - ? ParseErrors.not(ParseErrors.int) - : ParseErrors.int; - - return createParseResult(null, errors); + return { + value: parsed, + success, + errors: ParseErrors.int + }; }; } diff --git a/src/parsing/parseChain.ts b/src/parsing/parseChain.ts index 5eb5c8b..2ede328 100644 --- a/src/parsing/parseChain.ts +++ b/src/parsing/parseChain.ts @@ -1,23 +1,42 @@ import { IParser } from './IParser'; import { createParseResult } from './createParseResult'; import { IParse } from './IParse'; +import { ParseErrors } from './ParseErrors'; +import { ICurrentParser } from './ICurrentParser'; +/** + * Gets a chained, parent and current parser + * + * @param parent parent parser + * @param current current parser + * @param _parserName name for debugging + * @returns a chained parse function + */ export function parseChain( - parent: IParser, current: IParse + parent: IParser, + current: ICurrentParser, + _parserName: string ): IParse { return (value: unknown) => { - if (parent == null) { - // when root Schema - return current(value); - } - const parentResult = parent.parse(value); - const result = current(parentResult.value); + let result = createParseResult(value); - return createParseResult(result.value, { - ...parentResult.errors, - ...result.errors - }); + if (parent != null) + result = parent.parse(result.value); + + const currentResult = current.parse(result.value); + + return createParseResult( + currentResult.value, + { + ...result.errors, + ...currentResult.success !== current.negate // negate the result if needed + ? ParseErrors.empty + : current.negate + ? ParseErrors.not(currentResult.errors) + : currentResult.errors + } + ); }; } diff --git a/src/parsing/provideAnyOf.ts b/src/parsing/provideAnyOf.ts index 191478c..295e941 100644 --- a/src/parsing/provideAnyOf.ts +++ b/src/parsing/provideAnyOf.ts @@ -1,26 +1,17 @@ import { isEqual, isNullOrEmpty } from '../predicates'; -import { createParseResult } from './createParseResult'; +import { IParse } from './IParse'; import { ParseErrors } from './ParseErrors'; /** * Validate a value is any of values passed */ - export function provideAnyOf( - values: T[], negate: boolean -) { - - return (value: T) => { - if (isNullOrEmpty(value)) - return createParseResult(null); - - if (values.some(v => isEqual(v, value)) === !negate) - return createParseResult(value); - - const errors = negate - ? ParseErrors.not(ParseErrors.anyOf(values)) - : ParseErrors.anyOf(values); + values: unknown[], +): IParse { - return createParseResult(value, errors); - }; + return (value: T) => ({ + value, + success: isNullOrEmpty(value) || values.some(v => isEqual(v, value)), + errors: ParseErrors.anyOf(values) + }); } diff --git a/src/parsing/provideEquals.ts b/src/parsing/provideEquals.ts index 12196ac..a69f4cb 100644 --- a/src/parsing/provideEquals.ts +++ b/src/parsing/provideEquals.ts @@ -1,20 +1,14 @@ import { isEqual, isNullOrEmpty } from '../predicates'; -import { createParseResult } from './createParseResult'; -import { IParseErrors } from './IParseErrors'; +import { IParse } from './IParse'; import { ParseErrors } from './ParseErrors'; export function provideEquals( - equalToValue: T, negate: boolean -) { - - return (value: T) => { - if (isNullOrEmpty(value) || isEqual(value, equalToValue) !== negate) - return createParseResult(value); - - let errors: IParseErrors = ParseErrors.equals(equalToValue); - if (negate) - errors = ParseErrors.not(errors); - - return createParseResult(value, errors); - }; + equalToValue: T, +): IParse { + + return (value: T) => ({ + value, + success: isNullOrEmpty(value) || isEqual(value, equalToValue), + errors: ParseErrors.equals(equalToValue) + }); } diff --git a/src/parsing/provideMax.ts b/src/parsing/provideMax.ts index 459d251..061246e 100644 --- a/src/parsing/provideMax.ts +++ b/src/parsing/provideMax.ts @@ -1,6 +1,5 @@ import { isNullOrEmpty } from '../predicates'; -import { createParseResult } from './createParseResult'; -import { IParseErrors } from './IParseErrors'; +import { IParse } from './IParse'; import { ParseErrors } from './ParseErrors'; import { RelationalValidatorTypes } from './RelationalValidatorTypes'; @@ -8,18 +7,13 @@ import { RelationalValidatorTypes } from './RelationalValidatorTypes'; * Validate a value is a maximum */ export function provideMax( - maxValue: T, exclusive: boolean, negate: boolean -) { + maxValue: T, + exclusive: boolean, +): IParse { - return (value: T) => { - if (isNullOrEmpty(value) - || (exclusive ? value < maxValue : value <= maxValue) !== negate) - return createParseResult(value); - - let errors: IParseErrors = ParseErrors.max(maxValue, exclusive); - if (negate) - errors = ParseErrors.not(errors); - - return createParseResult(value, errors); - }; + return (value: T) => ({ + value, + success: isNullOrEmpty(value) || (exclusive ? value < maxValue : value <= maxValue), + errors: ParseErrors.max(maxValue, exclusive) + }); } diff --git a/src/parsing/provideMaxLength.ts b/src/parsing/provideMaxLength.ts index 4b0e461..97d01f0 100644 --- a/src/parsing/provideMaxLength.ts +++ b/src/parsing/provideMaxLength.ts @@ -1,23 +1,16 @@ -import { isNullOrEmpty } from '../predicates'; -import { createParseResult } from './createParseResult'; -import { IParseErrors } from './IParseErrors'; import { ParseErrors } from './ParseErrors'; import { IHasLength } from './IHasLength'; -import { IParseResult } from './IParseResult'; +import { isNullOrEmpty } from '../predicates'; +import { IParse } from './IParse'; export function provideMaxLength( - maxLength: number, exclusive: boolean, negate: boolean -) { - - return (value: T): IParseResult => { - if (isNullOrEmpty(value) - || (exclusive ? value.length < maxLength : value.length <= maxLength) !== negate) - return createParseResult(value); - - let errors: IParseErrors = ParseErrors.maxLength(maxLength); - if (negate) - errors = ParseErrors.not(errors); + maxLength: number, + exclusive: boolean, +): IParse { - return createParseResult(value, errors); - }; + return (value: T) => ({ + value, + success: isNullOrEmpty(value) || (exclusive ? value.length < maxLength : value.length <= maxLength), + errors: ParseErrors.maxLength(maxLength, exclusive) + }); } diff --git a/src/parsing/provideMin.ts b/src/parsing/provideMin.ts index 8f6548d..09c514b 100644 --- a/src/parsing/provideMin.ts +++ b/src/parsing/provideMin.ts @@ -1,6 +1,5 @@ import { isNullOrEmpty } from '../predicates'; -import { createParseResult } from './createParseResult'; -import { IParseErrors } from './IParseErrors'; +import { IParse } from './IParse'; import { ParseErrors } from './ParseErrors'; import { RelationalValidatorTypes } from './RelationalValidatorTypes'; @@ -8,18 +7,16 @@ import { RelationalValidatorTypes } from './RelationalValidatorTypes'; * Validate a value is a minimum */ export function provideMin( - minValue: T, exclusive: boolean, negate: boolean -) { + minValue: T, + exclusive: boolean, +): IParse { return (value: T) => { - if (isNullOrEmpty(value) - || (exclusive ? value > minValue : value >= minValue) !== negate) - return createParseResult(value); - let errors: IParseErrors = ParseErrors.min(minValue, exclusive); - if (negate) - errors = ParseErrors.not(errors); - - return createParseResult(value, errors); + return { + value, + success: isNullOrEmpty(value) || (exclusive ? value > minValue : value >= minValue), + errors: ParseErrors.min(minValue, exclusive) + }; }; } diff --git a/src/parsing/provideMinLength.ts b/src/parsing/provideMinLength.ts index 4071008..0ec2b62 100644 --- a/src/parsing/provideMinLength.ts +++ b/src/parsing/provideMinLength.ts @@ -1,23 +1,16 @@ -import { isNullOrEmpty } from '../predicates'; -import { createParseResult } from './createParseResult'; -import { IParseErrors } from './IParseErrors'; import { ParseErrors } from './ParseErrors'; import { IHasLength } from './IHasLength'; -import { IParseResult } from './IParseResult'; +import { isNullOrEmpty } from '../predicates'; +import { IParse } from './IParse'; export function provideMinLength( - minLength: number, exclusive: boolean, negate: boolean -) { - - return (value: T): IParseResult => { - if (isNullOrEmpty(value) - || (exclusive ? value.length > minLength : value.length >= minLength) !== negate) - return createParseResult(value); - - let errors: IParseErrors = ParseErrors.minLength(minLength); - if (negate) - errors = ParseErrors.not(errors); + minLength: number, + exclusive: boolean, +): IParse { - return createParseResult(value, errors); - }; + return (value: T) => ({ + value, + success: isNullOrEmpty(value) || (exclusive ? value.length > minLength : value.length >= minLength), + errors: ParseErrors.minLength(minLength, exclusive) + }); } diff --git a/src/parsing/provideParseRoot.ts b/src/parsing/provideParseRoot.ts index 86eddab..2269eea 100644 --- a/src/parsing/provideParseRoot.ts +++ b/src/parsing/provideParseRoot.ts @@ -1,18 +1,20 @@ import { isNullOrEmpty } from '../predicates'; -import { createParseResult } from './createParseResult'; +import { IParse } from './IParse'; import { ParseErrors } from './ParseErrors'; /** - * + * provides a funtion to parse a root value + * * @param isRequried value is required * @returns a parse result */ - export function provideParseRoot( - isRequried = false -) { + isRequried: boolean, +): IParse { - return (value: unknown) => isRequried && isNullOrEmpty(value) - ? createParseResult(value, ParseErrors.required) - : createParseResult(value); + return (value: unknown) => ({ + value, + success: !isRequried || !isNullOrEmpty(value), + errors: ParseErrors.required + }); } diff --git a/src/parsing/strings/StringParser.ts b/src/parsing/strings/StringParser.ts index b0345ec..107ea21 100644 --- a/src/parsing/strings/StringParser.ts +++ b/src/parsing/strings/StringParser.ts @@ -1,16 +1,16 @@ -import { IParse } from '../IParse'; import { IParser } from '../IParser'; import { parseChain } from '../parseChain'; +import { ICurrentParser } from '../ICurrentParser'; import { provideMaxLength } from '../provideMaxLength'; import { provideMinLength } from '../provideMinLength'; import { IString } from './IString'; import { provideEndsWithString } from './provideEndsWithString'; -import { provideParseString } from './provideParseString'; import { provideIncludesString } from './provideIncludesString'; import { provideStartsWithString } from './provideStartsWithString'; import { provideMatchesString } from './provideMatchesString'; import { provideEqualsString } from './provideEqualsString'; import { provideAnyOfString } from './provideAnyOfString'; +import { asCurrent } from '../asCurrent'; /** * Fluent builder for parsing strings @@ -18,21 +18,22 @@ import { provideAnyOfString } from './provideAnyOfString'; export class StringParser implements IString.Parser { constructor( private parent: IParser, - private parseCurrent: IParse = provideParseString(), + private parseCurrent: ICurrentParser, private negate: boolean = false ) { } - readonly parse = parseChain(this.parent, this.parseCurrent); - readonly equals = (value: string, ignoreCase = false) => new StringParser(this, provideEqualsString(value, ignoreCase, this.negate)); - readonly anyOf = (values: string[], ignoreCase = false) => new StringParser(this, provideAnyOfString(values, ignoreCase, this.negate)); - readonly minLength = (value: number, exclusive = false) => new StringParser(this, provideMinLength(value, exclusive, this.negate)); - readonly maxLength = (value: number, exclusive = false) => new StringParser(this, provideMaxLength(value, exclusive, this.negate)); - readonly matches = (value: string | RegExp, name: string = null) => new StringParser(this, provideMatchesString(value, name, this.negate)); - readonly includes = (value: string, ignoreCase = false) => new StringParser(this, provideIncludesString(value, ignoreCase, this.negate)); - readonly startsWith = (value: string, ignoreCase = false) => new StringParser(this, provideStartsWithString(value, ignoreCase, this.negate)); - readonly endsWith = (value: string, ignoreCase = false) => new StringParser(this, provideEndsWithString(value, ignoreCase, this.negate)); + readonly parse = parseChain(this.parent, this.parseCurrent, 'STRING'); get not() { - return new StringParser(this.parent, this.parseCurrent, true); + return new StringParser(this.parent, this.parseCurrent, !this.negate); } + + readonly equals = (value: string, ignoreCase = false) => new StringParser(this, asCurrent(provideEqualsString(value, ignoreCase), this.negate)); + readonly anyOf = (values: string[], ignoreCase = false) => new StringParser(this, asCurrent(provideAnyOfString(values, ignoreCase), this.negate)); + readonly minLength = (value: number, exclusive = false) => new StringParser(this, asCurrent(provideMinLength(value, exclusive), this.negate)); + readonly maxLength = (value: number, exclusive = false) => new StringParser(this, asCurrent(provideMaxLength(value, exclusive), this.negate)); + readonly matches = (value: string | RegExp, name: string = null) => new StringParser(this, asCurrent(provideMatchesString(value, name), this.negate)); + readonly includes = (value: string, ignoreCase = false) => new StringParser(this, asCurrent(provideIncludesString(value, ignoreCase), this.negate)); + readonly startsWith = (value: string, ignoreCase = false) => new StringParser(this, asCurrent(provideStartsWithString(value, ignoreCase), this.negate)); + readonly endsWith = (value: string, ignoreCase = false) => new StringParser(this, asCurrent(provideEndsWithString(value, ignoreCase), this.negate)); } diff --git a/src/parsing/strings/provideAnyOfString.ts b/src/parsing/strings/provideAnyOfString.ts index 70edc74..24a624e 100644 --- a/src/parsing/strings/provideAnyOfString.ts +++ b/src/parsing/strings/provideAnyOfString.ts @@ -1,28 +1,24 @@ import { isEqual, isNullOrEmpty } from '../../predicates'; -import { createParseResult } from '../createParseResult'; +import { IParse } from '../IParse'; import { ParseErrors } from '../ParseErrors'; /** * Validate a value is any of values passed */ export function provideAnyOfString( - values: string[], ignoreCase: boolean, negate: boolean -) { + values: string[], + ignoreCase: boolean, +): IParse { - return (value: string) => { - if (isNullOrEmpty(value)) - return createParseResult(null); + return (value: string) => ({ + value, + success: isNullOrEmpty(value) || (() => { + const a = ignoreCase ? value.toLowerCase() : value; + const b = ignoreCase ? values.map(v => v.toLowerCase()) : values; - const a = ignoreCase ? value.toLowerCase() : value; - const b = ignoreCase ? values.map(v => v.toLowerCase()) : values; + return b.some(v => isEqual(v, a)); + })(), + errors: ParseErrors.anyOf(values, ignoreCase) + }); - if (b.some(v => isEqual(v, a)) !== negate) - return createParseResult(value); - - const errors = negate - ? ParseErrors.not(ParseErrors.anyOf(values)) - : ParseErrors.anyOf(values); - - return createParseResult(value, errors); - }; } diff --git a/src/parsing/strings/provideEndsWithString.ts b/src/parsing/strings/provideEndsWithString.ts index 7041c23..c26986d 100644 --- a/src/parsing/strings/provideEndsWithString.ts +++ b/src/parsing/strings/provideEndsWithString.ts @@ -1,26 +1,20 @@ import { isNullOrEmpty } from '../../predicates'; -import { createParseResult } from '../createParseResult'; -import { IParseResult } from '../IParseResult'; +import { IParse } from '../IParse'; import { ParseErrors } from '../ParseErrors'; export function provideEndsWithString( - endswithValue: string, ignoreCase: boolean, negate: boolean -) { + endswithValue: string, + ignoreCase: boolean, +): IParse { - return (value: string): IParseResult => { - if (isNullOrEmpty(value)) - return createParseResult(null); + return (value: string) => ({ + value, + success: isNullOrEmpty(value) || (() => { + const a = ignoreCase ? value.toLowerCase() : value; + const b = ignoreCase ? endswithValue.toLowerCase() : endswithValue; - const a = ignoreCase ? value.toLowerCase() : value; - const b = ignoreCase ? endswithValue.toLowerCase() : endswithValue; - - if (a.endsWith(b) !== negate) - return createParseResult(value); - - const errors = negate - ? ParseErrors.not(ParseErrors.endsWith(endswithValue)) - : ParseErrors.endsWith(endswithValue); - - return createParseResult(value, errors); - }; + return a.endsWith(b); + })(), + errors: ParseErrors.endsWith(endswithValue, ignoreCase) + }); } diff --git a/src/parsing/strings/provideEqualsString.ts b/src/parsing/strings/provideEqualsString.ts index f57aca1..0681f0b 100644 --- a/src/parsing/strings/provideEqualsString.ts +++ b/src/parsing/strings/provideEqualsString.ts @@ -1,25 +1,20 @@ import { isEqual, isNullOrEmpty } from '../../predicates'; -import { createParseResult } from '../createParseResult'; +import { IParse } from '../IParse'; import { ParseErrors } from '../ParseErrors'; export function provideEqualsString( - equalToValue: string, ignoreCase: boolean, negate: boolean -) { + equalToValue: string, + ignoreCase: boolean, +): IParse { - return (value: string) => { - if (isNullOrEmpty(value)) - return createParseResult(value); + return (value: string) => ({ + value, + success: isNullOrEmpty(value) || (() => { + const a = ignoreCase ? value.toLowerCase() : value; + const b = ignoreCase ? equalToValue.toLowerCase() : equalToValue; - const a = ignoreCase ? value.toLowerCase() : value; - const b = ignoreCase ? equalToValue.toLowerCase() : equalToValue; - - if (isEqual(a, b) !== negate) - return createParseResult(value); - - const errors = negate - ? ParseErrors.not(ParseErrors.equals(equalToValue)) - : ParseErrors.equals(equalToValue); - - return createParseResult(value, errors); - }; + return isEqual(a, b); + })(), + errors: ParseErrors.equals(equalToValue, ignoreCase) + }); } diff --git a/src/parsing/strings/provideIncludesString.ts b/src/parsing/strings/provideIncludesString.ts index 866a3b4..8a75a87 100644 --- a/src/parsing/strings/provideIncludesString.ts +++ b/src/parsing/strings/provideIncludesString.ts @@ -1,26 +1,21 @@ import { isNullOrEmpty } from '../../predicates'; -import { createParseResult } from '../createParseResult'; -import { IParseResult } from '../IParseResult'; +import { IParse } from '../IParse'; import { ParseErrors } from '../ParseErrors'; export function provideIncludesString( - includesValue: string, ignoreCase = false, negate: boolean -) { + includesValue: string, + ignoreCase = false, +): IParse { - return (value: string): IParseResult => { - if (isNullOrEmpty(value)) - return createParseResult(null); + return (value: string) => ({ + value, + success: isNullOrEmpty(value) || (() => { - const a = ignoreCase ? value.toLowerCase() : value; - const b = ignoreCase ? includesValue.toLowerCase() : includesValue; + const a = ignoreCase ? value.toLowerCase() : value; + const b = ignoreCase ? includesValue.toLowerCase() : includesValue; - if (a.includes(b) !== negate) - return createParseResult(value); - - const errors = negate - ? ParseErrors.not(ParseErrors.includes(includesValue)) - : ParseErrors.includes(includesValue); - - return createParseResult(value, errors); - }; + return a.includes(b); + })(), + errors: ParseErrors.includes(includesValue, ignoreCase) + }); } diff --git a/src/parsing/strings/provideMatchesString.ts b/src/parsing/strings/provideMatchesString.ts index ab7e3f3..1c1fa11 100644 --- a/src/parsing/strings/provideMatchesString.ts +++ b/src/parsing/strings/provideMatchesString.ts @@ -1,23 +1,22 @@ import { isNullOrEmpty, isStringType } from '../../predicates'; -import { createParseResult } from '../createParseResult'; -import { IParseResult } from '../IParseResult'; +import { IParse } from '../IParse'; import { ParseErrors } from '../ParseErrors'; export function provideMatchesString( - matchValue: string | RegExp, name: string, negate: boolean -) { + matchValue: string | RegExp, + name: string, +): IParse { - return (value: string): IParseResult => { - if (isNullOrEmpty(value)) - return createParseResult(null); + return (value: string) => ({ + value, + success: isNullOrEmpty(value) || (() => { - const re = isStringType(matchValue) - ? new RegExp(matchValue) - : matchValue; + const re = isStringType(matchValue) + ? new RegExp(matchValue) + : matchValue; - if (re.test(value) !== negate) - return createParseResult(value); - - return createParseResult(value, ParseErrors.matches(name ?? matchValue)); - }; + return re.test(value); + })(), + errors: ParseErrors.matches(name ?? matchValue) + }); } diff --git a/src/parsing/strings/provideParseString.ts b/src/parsing/strings/provideParseString.ts index ba02d4a..5ac658a 100644 --- a/src/parsing/strings/provideParseString.ts +++ b/src/parsing/strings/provideParseString.ts @@ -1,25 +1,26 @@ import { isNullOrEmpty, isStringType } from '../../predicates'; -import { createParseResult } from '../createParseResult'; -import { IParseResult } from '../IParseResult'; +import { IParse } from '../IParse'; import { ParseErrors } from '../ParseErrors'; -export function provideParseString(negate: boolean = false) { +export function provideParseString( +): IParse { - return (value: unknown): IParseResult => { - if (isNullOrEmpty(value)) - return createParseResult(null); + return (value: unknown) => { + let success = true; + let parsed = null; - const parsed = isStringType(value) - ? value.toString() - : null; + if (!isNullOrEmpty(value)) { - if (parsed === null === negate) - return createParseResult(parsed); + parsed = isStringType(value) + ? value + : null; + success = parsed !== null; + } - const errors = negate - ? ParseErrors.not(ParseErrors.string) - : ParseErrors.string; - - return createParseResult(null, errors); + return { + value: parsed, + success, + errors: ParseErrors.string + }; }; -} \ No newline at end of file +} diff --git a/src/parsing/strings/provideStartsWithString.ts b/src/parsing/strings/provideStartsWithString.ts index d4808eb..9757cc2 100644 --- a/src/parsing/strings/provideStartsWithString.ts +++ b/src/parsing/strings/provideStartsWithString.ts @@ -1,26 +1,20 @@ import { isNullOrEmpty } from '../../predicates'; -import { createParseResult } from '../createParseResult'; -import { IParseResult } from '../IParseResult'; +import { IParse } from '../IParse'; import { ParseErrors } from '../ParseErrors'; export function provideStartsWithString( - startswithValue: string, ignoreCase: boolean, negate: boolean -) { + startswithValue: string, + ignoreCase: boolean, +): IParse { - return (value: string): IParseResult => { - if (isNullOrEmpty(value)) - return createParseResult(null); + return (value: string) => ({ + value, + success: isNullOrEmpty(value) || (() => { + const a = ignoreCase ? value.toLowerCase() : value; + const b = ignoreCase ? startswithValue.toLowerCase() : startswithValue; - const a = ignoreCase ? value.toLowerCase() : value; - const b = ignoreCase ? startswithValue.toLowerCase() : startswithValue; - - if (a.startsWith(b) !== negate) - return createParseResult(value); - - const errors = negate - ? ParseErrors.not(ParseErrors.startsWith(startswithValue)) - : ParseErrors.startsWith(startswithValue); - - return createParseResult(value, errors); - }; + return a.startsWith(b); + })(), + errors: ParseErrors.startsWith(startswithValue, ignoreCase) + }); } diff --git a/src/parsing/strings/strings-any-of.spec.ts b/src/parsing/strings/strings-any-of.spec.ts index a667c70..af5504f 100644 --- a/src/parsing/strings/strings-any-of.spec.ts +++ b/src/parsing/strings/strings-any-of.spec.ts @@ -38,7 +38,7 @@ describe('strings-any-of', () => { const value = 'o'; const result = schama.parse(value); - expect(result.errors).toEqual(ParseErrors.anyOf(strings)); + expect(result.errors).toEqual(ParseErrors.anyOf(strings, false)); expect(result.value).toBe(value); }); @@ -70,7 +70,7 @@ describe('strings-any-of', () => { const value = strings[0]; const result = notSchama.parse(value); - expect(result.errors).toEqual(ParseErrors.not(ParseErrors.anyOf(strings))); + expect(result.errors).toEqual(ParseErrors.not(ParseErrors.anyOf(strings, false))); expect(result.value).toBe(value); }); }); diff --git a/src/parsing/strings/strings-ends-with.spec.ts b/src/parsing/strings/strings-ends-with.spec.ts index ead633e..262c2e6 100644 --- a/src/parsing/strings/strings-ends-with.spec.ts +++ b/src/parsing/strings/strings-ends-with.spec.ts @@ -38,7 +38,7 @@ describe('strings-ends-with', () => { const value = 'o'; const result = schama.parse(value); - expect(result.errors).toEqual(ParseErrors.endsWith(endsWith)); + expect(result.errors).toEqual(ParseErrors.endsWith(endsWith, false)); expect(result.value).toBe(value); }); @@ -70,7 +70,7 @@ describe('strings-ends-with', () => { const value = 'o' + endsWith; const result = notSchama.parse(value); - expect(result.errors).toEqual(ParseErrors.not(ParseErrors.endsWith(endsWith))); + expect(result.errors).toEqual(ParseErrors.not(ParseErrors.endsWith(endsWith, false))); expect(result.value).toBe(value); }); }); diff --git a/src/parsing/strings/strings-equals.spec.ts b/src/parsing/strings/strings-equals.spec.ts index 51cf4ff..a76f683 100644 --- a/src/parsing/strings/strings-equals.spec.ts +++ b/src/parsing/strings/strings-equals.spec.ts @@ -16,7 +16,7 @@ describe('strings-equals', () => { const value = expectedValue.toUpperCase(); const result = schema.parse(value); - expect(result.errors).toEqual(ParseErrors.equals(expectedValue)); + expect(result.errors).toEqual(ParseErrors.equals(expectedValue, false)); expect(result.value).toEqual(value); }); @@ -54,7 +54,7 @@ describe('strings-equals', () => { const value = 'not-value'; const result = schema.parse(value); - expect(result.errors).toEqual(ParseErrors.equals(expectedValue)); + expect(result.errors).toEqual(ParseErrors.equals(expectedValue, false)); expect(result.value).toEqual(value); }); }); diff --git a/src/parsing/strings/strings-includes.spec.ts b/src/parsing/strings/strings-includes.spec.ts index 9ab53ed..f8f88fa 100644 --- a/src/parsing/strings/strings-includes.spec.ts +++ b/src/parsing/strings/strings-includes.spec.ts @@ -38,7 +38,7 @@ describe('strings-includes', () => { const value = 'o'; const result = schama.parse(value); - expect(result.errors).toEqual(ParseErrors.includes(includes)); + expect(result.errors).toEqual(ParseErrors.includes(includes, false)); expect(result.value).toBe(value); }); @@ -70,7 +70,7 @@ describe('strings-includes', () => { const value = 'o' + includes + 'o'; const result = notSchama.parse(value); - expect(result.errors).toEqual(ParseErrors.not(ParseErrors.includes(includes))); + expect(result.errors).toEqual(ParseErrors.not(ParseErrors.includes(includes, false))); expect(result.value).toBe(value); }); }); diff --git a/src/parsing/strings/strings-max-length.spec.ts b/src/parsing/strings/strings-max-length.spec.ts index 5843419..7f1babf 100644 --- a/src/parsing/strings/strings-max-length.spec.ts +++ b/src/parsing/strings/strings-max-length.spec.ts @@ -27,7 +27,7 @@ describe('strings-max-length', () => { const value = stringOfLength(max); const result = exclusiveSchama.parse(value); - expect(result.errors).toEqual(ParseErrors.maxLength(max)); + expect(result.errors).toEqual(ParseErrors.maxLength(max, true)); expect(result.value).toBe(value); }); @@ -51,4 +51,4 @@ describe('strings-max-length', () => { }); }); -}); \ No newline at end of file +}); diff --git a/src/parsing/strings/strings-min-length.spec.ts b/src/parsing/strings/strings-min-length.spec.ts index c2cbc34..f7cca1c 100644 --- a/src/parsing/strings/strings-min-length.spec.ts +++ b/src/parsing/strings/strings-min-length.spec.ts @@ -27,7 +27,7 @@ describe('strings-min-length', () => { const value = stringOfLength(min); const result = exclusiveSchama.parse(value); - expect(result.errors).toEqual(ParseErrors.minLength(min)); + expect(result.errors).toEqual(ParseErrors.minLength(min, true)); expect(result.value).toBe(value); }); @@ -51,4 +51,4 @@ describe('strings-min-length', () => { }); }); -}); \ No newline at end of file +}); diff --git a/src/parsing/strings/strings-not-equals.spec.ts b/src/parsing/strings/strings-not-equals.spec.ts index bd07e1b..00a5d29 100644 --- a/src/parsing/strings/strings-not-equals.spec.ts +++ b/src/parsing/strings/strings-not-equals.spec.ts @@ -16,7 +16,7 @@ describe('strings-not-equals', () => { it('failure', () => { const result = schema.parse(expectedValue); - expect(result.errors).toEqual(ParseErrors.not(ParseErrors.equals(expectedValue))); + expect(result.errors).toEqual(ParseErrors.not(ParseErrors.equals(expectedValue, false))); expect(result.value).toEqual(expectedValue); }); }); diff --git a/src/parsing/strings/strings-starts-with.spec.ts b/src/parsing/strings/strings-starts-with.spec.ts index 745eef2..6ddd6da 100644 --- a/src/parsing/strings/strings-starts-with.spec.ts +++ b/src/parsing/strings/strings-starts-with.spec.ts @@ -38,7 +38,7 @@ describe('strings-starts-with', () => { const value = 'o'; const result = schama.parse(value); - expect(result.errors).toEqual(ParseErrors.startsWith(startsWith)); + expect(result.errors).toEqual(ParseErrors.startsWith(startsWith, false)); expect(result.value).toBe(value); }); @@ -70,7 +70,7 @@ describe('strings-starts-with', () => { const value = startsWith + '0'; const result = notSchama.parse(value); - expect(result.errors).toEqual(ParseErrors.not(ParseErrors.startsWith(startsWith))); + expect(result.errors).toEqual(ParseErrors.not(ParseErrors.startsWith(startsWith, false))); expect(result.value).toBe(value); }); }); diff --git a/src/parsing/strings/strings.spec.ts b/src/parsing/strings/strings.spec.ts index 9d41fc0..0c85576 100644 --- a/src/parsing/strings/strings.spec.ts +++ b/src/parsing/strings/strings.spec.ts @@ -57,7 +57,7 @@ describe('strings', () => { const result = notSchema.parse(value); expect(result.errors).toEqual(ParseErrors.not(ParseErrors.string)); - expect(result.value).toEqual(null); + expect(result.value).toEqual(value); }); }); diff --git a/src/predicates/isObject.spec.ts b/src/predicates/isObject.spec.ts index ab3a0e4..c70c62e 100644 --- a/src/predicates/isObject.spec.ts +++ b/src/predicates/isObject.spec.ts @@ -16,4 +16,4 @@ describe('isObject', () => { expect(isObject(value)).toBe(expected); }); }); -}); \ No newline at end of file +}); diff --git a/src/predicates/isObject.ts b/src/predicates/isObject.ts index 81f838f..9687cb5 100644 --- a/src/predicates/isObject.ts +++ b/src/predicates/isObject.ts @@ -6,4 +6,4 @@ */ export const isObject : (value: unknown) => boolean - = value => typeof value === 'object' && value !== null && !Array.isArray(value); \ No newline at end of file + = value => typeof value === 'object' && value !== null && !Array.isArray(value);