diff --git a/.eslintrc.js b/.eslintrc.js index c39f3c5..c16236f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,8 +16,9 @@ module.exports = { "function-call-argument-newline": ["error", "consistent"], "no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 0 }], "@typescript-eslint/consistent-type-definitions": ["error", "interface"], - "@typescript-eslint/no-empty-interface": ["off"], - "@typescript-eslint/no-namespace": ["off"], + "@typescript-eslint/no-empty-interface": "off", + '@typescript-eslint/no-explicit-any': 'warn', + "@typescript-eslint/no-namespace": "off", "@typescript-eslint/no-shadow": "warn", "@typescript-eslint/no-unused-vars": "off", "sort-imports": ["error", { "allowSeparatedGroups": false, "ignoreDeclarationSort": true }], diff --git a/src/Is.spec.ts b/src/Is.spec.ts new file mode 100644 index 0000000..88e2e93 --- /dev/null +++ b/src/Is.spec.ts @@ -0,0 +1,12 @@ +import { Is } from './Is'; + +describe('Is', () => { + + it('throws when construtcor is called', () => { + + expect(() => new ( + Is as unknown as any // eslint-disable-line @typescript-eslint/no-explicit-any + )()).toThrow(); + }); + +}); diff --git a/src/Is.ts b/src/Is.ts index cd34c89..e89eafc 100644 --- a/src/Is.ts +++ b/src/Is.ts @@ -6,7 +6,7 @@ export class Is { throw new Error('static class'); } - static readonly required: IRoot.Parser = new RootParser(true); + static readonly required: IRoot.Parser = new RootParser(true, false); static readonly boolean = new RootParser().boolean; static readonly int = new RootParser().int; @@ -19,4 +19,6 @@ export class Is { static readonly for = new RootParser().for; static readonly use = new RootParser().use; + + static readonly not = new RootParser(false, true); } diff --git a/src/parsing/IRoot.ts b/src/parsing/IRoot.ts index e0b594b..a1c4488 100644 --- a/src/parsing/IRoot.ts +++ b/src/parsing/IRoot.ts @@ -7,10 +7,14 @@ import { IString } from './strings'; import { IArray } from './arrays'; import { IDictionary } from './dictionaries'; import { IJson } from './json/IJson'; +import { NextBuilder } from './NextBuilder'; export namespace IRoot { - export interface Parser extends IParser, Builder { } + export interface Parser extends IParser, Builder { + readonly not: NextBuilder + + } export interface Builder { readonly boolean: IBoolean.Parser; diff --git a/src/parsing/ParseErrors.ts b/src/parsing/ParseErrors.ts index b11b29a..2bd01a9 100644 --- a/src/parsing/ParseErrors.ts +++ b/src/parsing/ParseErrors.ts @@ -19,6 +19,8 @@ export class ParseErrors { static readonly float = { float: true }; /** value should be a date */ 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 */ diff --git a/src/parsing/RootParser.ts b/src/parsing/RootParser.ts index 1a13760..a3b5b8b 100644 --- a/src/parsing/RootParser.ts +++ b/src/parsing/RootParser.ts @@ -1,37 +1,37 @@ -import { isNullOrEmpty } from '../predicates'; -import { ArrayParser, IArray } from './arrays'; -import { DictionaryParser, IDictionary } from './dictionaries'; -import { BooleanParser, IBoolean } from './booleans'; -import { createParseResult } from './createParseResult'; -import { DateParser, IDate } from './dates'; +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 } from './numbers'; +import { FloatParser, IFloat, IInt, IntParser, provideParseFloat, provideParseInt } from './numbers'; import { ComplexParser, ComplexSchema, IComplex } from './complex'; import { parseChain } from './parseChain'; -import { ParseErrors } from './ParseErrors'; -import { IString, StringParser } from './strings'; -import { IJson, JsonParser } from './json'; +import { IString, StringParser, provideParseString } from './strings'; +import { IJson, JsonParser, provideParseJson } from './json'; +import { provideParseRoot } from './provideParseRoot'; export class RootParser implements IRoot.Parser { constructor( - private isRequried = false + private isRequried = false, + private negate = false ) { } - readonly parse = parseChain(null, value => - this.isRequried && isNullOrEmpty(value) - ? createParseResult(value, ParseErrors.required) - : createParseResult(value)); + readonly parse = parseChain(null, provideParseRoot(this.isRequried)); - readonly boolean: IBoolean.Parser = new BooleanParser(this); - readonly int: IInt.Parser = new IntParser(this); - readonly float: IFloat.Parser = new FloatParser(this); - readonly date: IDate.Parser = new DateParser(this); - readonly string: IString.Parser = new StringParser(this); - readonly array: IArray.Parser = new ArrayParser(this); - readonly dictionary: IDictionary.Parser = new DictionaryParser(this); - readonly json: IJson.Parser = new JsonParser(this); + 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) }); + + get not() { + return new RootParser(this.isRequried, true); + } } diff --git a/src/parsing/arrays/ArrayParser.ts b/src/parsing/arrays/ArrayParser.ts index 3424cce..b69bf38 100644 --- a/src/parsing/arrays/ArrayParser.ts +++ b/src/parsing/arrays/ArrayParser.ts @@ -12,11 +12,11 @@ export class ArrayParser implements IArray.Parser { constructor( private parent: IParser, - private parseCurrent: IParse = provideParseArray(), + private parseCurrent: IParse = null, private negate: boolean = false ) { } - readonly parse = parseChain(this.parent, this.parseCurrent); + readonly parse = parseChain(this.parent, this.parseCurrent ?? provideParseArray(this.negate)); get not() { return new ArrayParser(this.parent, this.parseCurrent, true); diff --git a/src/parsing/arrays/IArray.ts b/src/parsing/arrays/IArray.ts index aff5d94..fcf23ed 100644 --- a/src/parsing/arrays/IArray.ts +++ b/src/parsing/arrays/IArray.ts @@ -5,14 +5,13 @@ import { NextBuilder } from '../NextBuilder'; export namespace IArray { export interface Parser extends IParser { - readonly not: NextBuilder, 'of' | 'each' | 'not'> + readonly not: NextBuilder, 'not' | 'of' | 'each'> - readonly of: () => NextBuilder, 'of' | 'each', 'unique'> - readonly each: (parser: IParser) => NextBuilder, 'of' | 'each'> - - readonly unique: (distinctor: (item: T) => unknown) => NextBuilder, 'of' | 'each' | 'unique'>; - readonly minLength: (value: number, exclusive?: boolean) => NextBuilder, 'of' | 'each'| 'minLength'>; - readonly maxLength: (value: number, exclusive?: boolean) => NextBuilder, 'of' | 'each' | 'maxLength'>; + readonly of: () => NextBuilder, 'of' | 'each', 'unique' | 'not' | 'parse'> + readonly each: (parser: IParser) => NextBuilder, 'of' | 'each', 'unique' | 'not' | 'parse'> + 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'>; } } diff --git a/src/parsing/arrays/arrays-maxLength.spec.ts b/src/parsing/arrays/arrays-maxLength.spec.ts index 849962e..bdfd91c 100644 --- a/src/parsing/arrays/arrays-maxLength.spec.ts +++ b/src/parsing/arrays/arrays-maxLength.spec.ts @@ -3,11 +3,11 @@ import { ParseErrors } from '../ParseErrors'; describe('arrays-maxLength', () => { const MAX_LENGTH = 1; - const parser = Is.array.maxLength(MAX_LENGTH); + const schema = Is.array.maxLength(MAX_LENGTH); it('success', () => { const value = [1]; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(value); @@ -15,7 +15,7 @@ describe('arrays-maxLength', () => { it('failure', () => { const value = [1, 2]; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.maxLength(MAX_LENGTH)); expect(result.value).toBe(value); diff --git a/src/parsing/arrays/arrays-minLength.spec.ts b/src/parsing/arrays/arrays-minLength.spec.ts index 0be9654..45d5e36 100644 --- a/src/parsing/arrays/arrays-minLength.spec.ts +++ b/src/parsing/arrays/arrays-minLength.spec.ts @@ -3,11 +3,11 @@ import { ParseErrors } from '../ParseErrors'; describe('arrays-minLength', () => { const MIN_LENGTH = 1; - const parser = Is.array.minLength(MIN_LENGTH); + const schema = Is.array.minLength(MIN_LENGTH); it('success', () => { const value = [1]; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(value); @@ -15,7 +15,7 @@ describe('arrays-minLength', () => { it('failure', () => { const value = []; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.minLength(MIN_LENGTH)); expect(result.value).toBe(value); diff --git a/src/parsing/arrays/arrays-parser.spec.ts b/src/parsing/arrays/arrays-parser.spec.ts index 139c826..619dd36 100644 --- a/src/parsing/arrays/arrays-parser.spec.ts +++ b/src/parsing/arrays/arrays-parser.spec.ts @@ -2,11 +2,11 @@ import { Is } from '../../Is'; import { ParseErrors } from '../ParseErrors'; describe('arrays-parser', () => { - const parser = Is.array; + const schema = Is.array; it('success', () => { const value = [1]; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(value); @@ -14,18 +14,18 @@ describe('arrays-parser', () => { it('failure', () => { const value = 1; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.array); expect(result.value).toBe(null); }); describe('each', () => { - const eachParser = parser.each(Is.int); + const eachSchema = schema.each(Is.int); it('success', () => { const value = [1]; - const result = eachParser.parse(value); + const result = eachSchema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toEqual(value); @@ -33,10 +33,30 @@ describe('arrays-parser', () => { it('failure', () => { const value = ['a']; - const result = eachParser.parse(value); + const result = eachSchema.parse(value); expect(result.errors).toEqual({ 0: ParseErrors.int }); expect(result.value).toEqual([null]); }); }); + + describe('not', () => { + const notSchema = Is.not.array; + + it('success', () => { + const value = 1; + const result = notSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.empty); + expect(result.value).toBe(null); + }); + + it('failure', () => { + const value = [1]; + const result = notSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.not(ParseErrors.array)); + expect(result.value).toBe(null); + }); + }); }); diff --git a/src/parsing/arrays/arrays-unique.spec.ts b/src/parsing/arrays/arrays-unique.spec.ts index 43f63a8..f5f6c99 100644 --- a/src/parsing/arrays/arrays-unique.spec.ts +++ b/src/parsing/arrays/arrays-unique.spec.ts @@ -5,15 +5,15 @@ describe('arrays-unique', () => { interface IThing { id: number } - const thingParser = Is.for({ id: Is.required.int }); - const parser = Is.array - .each(thingParser) + const thingSchema = Is.for({ id: Is.required.int }); + const schema = Is.array + .each(thingSchema) .minLength(2) .unique(t => t.id); it('success', () => { const value = [{ id: 1 }, { id: 2 }]; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toEqual(value); @@ -21,9 +21,31 @@ describe('arrays-unique', () => { it('failure', () => { const value = [{ id: 1 }, { id: 1 }]; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.unique); expect(result.value).toEqual(value); }); + + describe('not', () => { + const notSchema = Is.array + .each(thingSchema) + .not.unique(t => t.id); + + it('success', () => { + const value = [{ id: 1 }, { id: 1 }]; + const result = notSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.empty); + expect(result.value).toEqual(value); + }); + + it('failure', () => { + const value = [{ id: 1 }, { id: 2 }]; + const result = notSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.not(ParseErrors.unique)); + expect(result.value).toEqual(value); + }); + }); }); diff --git a/src/parsing/arrays/parseAllArray.spec.ts b/src/parsing/arrays/parseAllArray.spec.ts index 255b48e..f596d4d 100644 --- a/src/parsing/arrays/parseAllArray.spec.ts +++ b/src/parsing/arrays/parseAllArray.spec.ts @@ -5,7 +5,7 @@ import { ParseErrors } from '../ParseErrors'; describe('parseAllArray', () => { interface IModel { name: string, age?: number } - const parser = Is.for({ + const schema = Is.for({ name: Is.required.string, age: Is.int.min(25) }); @@ -16,7 +16,7 @@ describe('parseAllArray', () => { { name: 'NAME-2', age: 32 } ]; - const result = parseAllArray(parser.parse)(values); + const result = parseAllArray(schema.parse)(values); expect(result.value).toEqual(values); expect(result.errors).toBe(ParseErrors.empty); @@ -28,7 +28,7 @@ describe('parseAllArray', () => { { name: 'NAME-2', age: 1 } ]; - const result = parseAllArray(parser.parse)(values); + const result = parseAllArray(schema.parse)(values); expect(result.value).toEqual(values); expect(result.errors).toEqual({ diff --git a/src/parsing/arrays/provideParseArray.ts b/src/parsing/arrays/provideParseArray.ts index a5040bf..2275cc9 100644 --- a/src/parsing/arrays/provideParseArray.ts +++ b/src/parsing/arrays/provideParseArray.ts @@ -3,15 +3,33 @@ import { createParseResult } from '../createParseResult'; import { IParseResult } from '../IParseResult'; import { ParseErrors } from '../ParseErrors'; -export function provideParseArray() { +/** + * provides a parser for an array, or not an array + * + * note. if negated result value will be null + * + * @param negate + * @returns parseResult + */ +export function provideParseArray( + negate: boolean = false +) { return (value: T): IParseResult => { if (isNullOrEmpty(value)) return createParseResult(null); - if (Array.isArray(value)) - return createParseResult(value as T[]); + const parsed = Array.isArray(value) + ? value as T[] + : null; - return createParseResult(null, ParseErrors.array); + if (parsed === null === negate) + return createParseResult(parsed); + + const errors = negate + ? ParseErrors.not(ParseErrors.array) + : ParseErrors.array; + + return createParseResult(null, errors); }; } diff --git a/src/parsing/booleans/IBoolean.ts b/src/parsing/booleans/IBoolean.ts index 97f1d52..1310a0d 100644 --- a/src/parsing/booleans/IBoolean.ts +++ b/src/parsing/booleans/IBoolean.ts @@ -4,8 +4,8 @@ import { NextBuilder } from '../NextBuilder'; /** Fluent API interfaces for booleans */ export namespace IBoolean { export interface Parser extends IParser { - readonly not: NextBuilder + readonly not: NextBuilder - equals(value: boolean): NextBuilder + equals(value: boolean): NextBuilder } } diff --git a/src/parsing/booleans/booleans-equals.spec.ts b/src/parsing/booleans/booleans-equals.spec.ts index 2b805c9..afccd8f 100644 --- a/src/parsing/booleans/booleans-equals.spec.ts +++ b/src/parsing/booleans/booleans-equals.spec.ts @@ -2,39 +2,38 @@ import { Is } from '../../Is'; import { ParseErrors } from '../ParseErrors'; describe('booleans-equals', () => { - const expectedValue = true; - const parser = Is.boolean.equals(expectedValue); + const schema = Is.boolean.equals(true); it('success boolean', () => { - const result = parser.parse(expectedValue); + const result = schema.parse(true); expect(result.errors).toEqual(ParseErrors.empty); - expect(result.value).toEqual(expectedValue); + expect(result.value).toEqual(true); }); it('success undefined', () => { - const result = parser.parse(undefined); + const result = schema.parse(undefined); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(null); }); it('success null', () => { - const result = parser.parse(null); + const result = schema.parse(null); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(null); }); it('success string', () => { - const result = parser.parse(expectedValue.toString()); + const result = schema.parse(true.toString()); expect(result.errors).toEqual(ParseErrors.empty); - expect(result.value).toEqual(expectedValue); + expect(result.value).toEqual(true); }); it('success string empty', () => { - const result = parser.parse(''); + const result = schema.parse(''); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(null); @@ -42,9 +41,28 @@ describe('booleans-equals', () => { it('failure not equal', () => { const value = false; - const result = parser.parse(value); + const result = schema.parse(value); - expect(result.errors).toEqual(ParseErrors.equals(expectedValue)); + expect(result.errors).toEqual(ParseErrors.equals(true)); expect(result.value).toEqual(value); }); + + describe('not', () => { + + const notSchema = Is.boolean.not.equals(true); + + it('success', () => { + const result = notSchema.parse('false'); + + expect(result.errors).toEqual(ParseErrors.empty); + expect(result.value).toEqual(false); + }); + + it('failure', () => { + const result = notSchema.parse('on'); + + expect(result.errors).toEqual(ParseErrors.not(ParseErrors.equals(true))); + expect(result.value).toEqual(true); + }); + }); }); diff --git a/src/parsing/booleans/booleans-not.spec.ts b/src/parsing/booleans/booleans-not.spec.ts deleted file mode 100644 index bca5672..0000000 --- a/src/parsing/booleans/booleans-not.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Is } from '../../Is'; -import { ParseErrors } from '../ParseErrors'; - -describe('booleans-not', () => { - const expectedValue = true; - const parser = Is.boolean - .not.equals(expectedValue); - - it('success boolean', () => { - const result = parser.parse(!expectedValue); - - expect(result.errors).toEqual(ParseErrors.empty); - expect(result.value).toEqual(!expectedValue); - }); - - it('failure boolean', () => { - const result = parser.parse(expectedValue); - - expect(result.errors).toEqual(ParseErrors.not(ParseErrors.equals(expectedValue))); - expect(result.value).toEqual(expectedValue); - }); -}); diff --git a/src/parsing/booleans/booleans-parser.spec.ts b/src/parsing/booleans/booleans-parser.spec.ts index edaa9de..3e7c77e 100644 --- a/src/parsing/booleans/booleans-parser.spec.ts +++ b/src/parsing/booleans/booleans-parser.spec.ts @@ -2,25 +2,25 @@ import { Is } from '../../Is'; import { ParseErrors } from '../ParseErrors'; describe('booleans-parser', () => { - const parser = Is.boolean; + const schema = Is.boolean; it('success boolean', () => { const value = true; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toEqual(value); }); it('success undefined', () => { - const result = parser.parse(undefined); + const result = schema.parse(undefined); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(null); }); it('success null', () => { - const result = parser.parse(null); + const result = schema.parse(null); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(null); @@ -28,25 +28,43 @@ describe('booleans-parser', () => { it('success string', () => { const value = 'true'; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toEqual(true); }); it('success string empty', () => { - const result = parser.parse(''); + const result = schema.parse(''); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(null); }); it('failure not boolean', () => { - const result = parser.parse('a'); + const result = schema.parse('a'); - expect(result.errors).toEqual({ - boolean: true - }); + expect(result.errors).toEqual(ParseErrors.boolean); expect(result.value).toBe(null); }); + + describe('not', () => { + const notSchema = Is.not.boolean; + + it('success boolean', () => { + const value = 'a'; + const result = notSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.empty); + expect(result.value).toEqual(null); + }); + + it('failure', () => { + const value = true; + const result = notSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.not(ParseErrors.boolean)); + expect(result.value).toBe(null); + }); + }); }); diff --git a/src/parsing/booleans/provideParseBoolean.ts b/src/parsing/booleans/provideParseBoolean.ts index 03b1114..63a6be2 100644 --- a/src/parsing/booleans/provideParseBoolean.ts +++ b/src/parsing/booleans/provideParseBoolean.ts @@ -4,13 +4,21 @@ import { IParseResult } from '../IParseResult'; import { ParseErrors } from '../ParseErrors'; import { tryParseBoolean } from './tryParseBoolean'; -export function provideParseBoolean() { +export function provideParseBoolean( + negate: boolean = false +) { return (value: unknown): IParseResult => { if (isNullOrEmpty(value)) return createParseResult(null); - value = tryParseBoolean(value); - return (value === null) - ? createParseResult(null, ParseErrors.boolean) - : createParseResult(value as boolean); + const parsed = tryParseBoolean(value); + + if (parsed === null === negate) + return createParseResult(parsed); + + const errors = negate + ? ParseErrors.not(ParseErrors.boolean) + : ParseErrors.boolean; + + return createParseResult(null, errors); }; } diff --git a/src/parsing/complex/complex-parser.spec.ts b/src/parsing/complex/complex-parser.spec.ts index 92868ec..83cfc64 100644 --- a/src/parsing/complex/complex-parser.spec.ts +++ b/src/parsing/complex/complex-parser.spec.ts @@ -6,7 +6,7 @@ describe('complex-parser', () => { it('parse', () => { const value = valid; - const result = personParser.parse(value); + const result = personSchema.parse(value); expect(result.value).toEqual({ name: value.name, @@ -24,7 +24,7 @@ describe('complex-parser', () => { jobType: -1 }; - const result = personParser.parse(value); + const result = personSchema.parse(value); expect(result.errors).toEqual({ jobType: ParseErrors.anyOf(JobTypes) }); }); @@ -35,7 +35,7 @@ describe('complex-parser', () => { scores: null }; - const result = personParser.parse(value); + const result = personSchema.parse(value); expect(result.errors).toEqual({ scores: ParseErrors.required }); }); @@ -46,7 +46,7 @@ describe('complex-parser', () => { scores: ['a'] }; - const result = personParser.parse(value); + const result = personSchema.parse(value); expect(result.errors).toEqual({ scores: { '0': { float: true } } }); }); @@ -57,7 +57,7 @@ describe('complex-parser', () => { emails: [] }; - const result = personParser.parse(value); + const result = personSchema.parse(value); expect(result.errors).toEqual({ emails: ParseErrors.minLength(EMAILS_MIN_LENGTH) }); }); @@ -68,7 +68,7 @@ describe('complex-parser', () => { emails: [{ name: null, address: 'email@example.com' }] }; - const result = personParser.parse(value); + const result = personSchema.parse(value); expect(result.errors).toEqual({ emails: { [0]: { name: ParseErrors.required } } }); }); @@ -79,7 +79,7 @@ describe('complex-parser', () => { emails: [{ ...valid.emails[0], address: 'email@example.co.uk' }] }; - const result = personParser.parse(value); + const result = personSchema.parse(value); expect(result.errors).toEqual({ emails: { [0]: { address: ParseErrors.matches('email') } } }); }); @@ -90,7 +90,7 @@ describe('complex-parser', () => { name: null }; - const result = personParser.parse(value); + const result = personSchema.parse(value); expect(result.errors).toEqual({ name: ParseErrors.required }); }); @@ -103,7 +103,7 @@ describe('complex-parser', () => { } }; - const result = personParser.parse(value); + const result = personSchema.parse(value); expect(result.errors).toEqual({ name: { @@ -118,7 +118,7 @@ describe('complex-parser', () => { dateOfBirth: '3000-01-01' }; - const result = personParser.parse(value); + const result = personSchema.parse(value); expect(result.errors).toEqual({ dateOfBirth: ParseErrors.max(now, false) @@ -162,25 +162,25 @@ describe('complex-parser', () => { const GIVEN_NAME_MAX_LENGTH = 25; const FAMILY_NAME_MAX_LENGTH = 50; - const nameParser = Is.for({ + const nameSchema = Is.for({ given: Is.string.maxLength(GIVEN_NAME_MAX_LENGTH), family: Is.required.string.maxLength(FAMILY_NAME_MAX_LENGTH) }); - const emailParser = Is.for({ + const emailSchema = Is.for({ name: Is.required.string, address: Is.required.string.matches(/.*?\.com/, 'email') }); const EMAILS_MIN_LENGTH = 1; - const personParser = Is.for({ - name: Is.required.use(nameParser), + const personSchema = Is.for({ + name: Is.required.use(nameSchema), dateOfBirth: Is.required.date.max(now), jobType: Is.int.anyOf(JobTypes), salary: Is.float.min(0), scores: Is.required.array.each(Is.float), - emails: Is.array.each(emailParser).minLength(EMAILS_MIN_LENGTH), + emails: Is.array.each(emailSchema).minLength(EMAILS_MIN_LENGTH), lastSeen: Is.date }); }); diff --git a/src/parsing/dates/dates-equals.spec.ts b/src/parsing/dates/dates-equals.spec.ts index cb90319..e0e7f2f 100644 --- a/src/parsing/dates/dates-equals.spec.ts +++ b/src/parsing/dates/dates-equals.spec.ts @@ -4,38 +4,38 @@ import { parseDate } from './parseDate'; describe('dates-equals', () => { const expectedValue = new Date(3000, 0, 1, 0, 0, 0, 0); - const parser = Is.date.equals(expectedValue); + const schema = Is.date.equals(expectedValue); it('success date', () => { - const result = parser.parse(expectedValue); + const result = schema.parse(expectedValue); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toEqual(expectedValue); }); it('success undefined', () => { - const result = parser.parse(undefined); + const result = schema.parse(undefined); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(null); }); it('success null', () => { - const result = parser.parse(null); + const result = schema.parse(null); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(null); }); it('success string', () => { - const result = parser.parse(expectedValue.toString()); + const result = schema.parse(expectedValue.toString()); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toEqual(expectedValue); }); it('success string empty', () => { - const result = parser.parse(''); + const result = schema.parse(''); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(null); @@ -43,18 +43,18 @@ describe('dates-equals', () => { it('failure not equal', () => { const value = new Date(3000, 0, 2, 0, 0, 0, 0); - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.equals(expectedValue)); expect(result.value).toEqual(value); }); describe('not', () => { - const notParser = Is.date.not.equals('2000-01-01'); + const notSchema = Is.date.not.equals('2000-01-01'); it('success', () => { const value = parseDate('3000-01-01'); - const result = notParser.parse(value); + const result = notSchema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toEqual(value); @@ -62,7 +62,7 @@ describe('dates-equals', () => { it('failure', () => { const value = parseDate('2000-01-01'); - const result = notParser.parse(value); + const result = notSchema.parse(value); expect(result.errors).toEqual(ParseErrors.not(ParseErrors.equals(value))); expect(result.value).toEqual(value); diff --git a/src/parsing/dates/dates-max.spec.ts b/src/parsing/dates/dates-max.spec.ts index 34ebf80..25ec089 100644 --- a/src/parsing/dates/dates-max.spec.ts +++ b/src/parsing/dates/dates-max.spec.ts @@ -3,11 +3,11 @@ import { ParseErrors } from '../ParseErrors'; describe('dates-max', () => { const max = new Date(3000, 0, 1, 0, 0, 0, 0); - const parser = Is.date.max(max); + const schema = Is.date.max(max); it('success', () => { const value = new Date(max.getTime() - 1000); - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toEqual(value); @@ -15,7 +15,7 @@ describe('dates-max', () => { it('success string', () => { const value = `${new Date(max.getTime() - 1000)}`; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(`${result.value}`).toEqual(value); @@ -23,7 +23,7 @@ describe('dates-max', () => { it('success null', () => { const value = null; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(value); @@ -31,7 +31,7 @@ describe('dates-max', () => { it('failure', () => { const value = new Date(max.getTime() + 1000); - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.max(max, false)); expect(result.value).toBe(value); diff --git a/src/parsing/dates/dates-min-max.spec.ts b/src/parsing/dates/dates-min-max.spec.ts index c6c095e..8605eeb 100644 --- a/src/parsing/dates/dates-min-max.spec.ts +++ b/src/parsing/dates/dates-min-max.spec.ts @@ -4,11 +4,11 @@ import { ParseErrors } from '../ParseErrors'; describe('dates-min-max', () => { const min = new Date(2000, 0, 1, 0, 0, 0, 0); const max = new Date(3000, 0, 1, 0, 0, 0, 0); - const parser = Is.date.min(min).max(max); + const schema = Is.date.min(min).max(max); it('success', () => { const value = min; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(value); @@ -16,7 +16,7 @@ describe('dates-min-max', () => { it('failure min', () => { const value = new Date(1000, 0, 1, 0, 0, 0, 0); - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.min(min)); expect(result.value).toBe(value); @@ -24,7 +24,7 @@ describe('dates-min-max', () => { it('failure max', () => { const value = new Date(4000, 0, 1, 0, 0, 0, 0); - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.max(max)); expect(result.value).toBe(value); diff --git a/src/parsing/dates/dates-min.spec.ts b/src/parsing/dates/dates-min.spec.ts index 98c4ed2..d22c98f 100644 --- a/src/parsing/dates/dates-min.spec.ts +++ b/src/parsing/dates/dates-min.spec.ts @@ -3,11 +3,11 @@ import { ParseErrors } from '../ParseErrors'; describe('dates-min', () => { const min = new Date(3000, 0, 1, 0, 0, 0, 0); - const parser = Is.date.min(min); + const schema = Is.date.min(min); it('success', () => { const value = new Date(min.getTime() + 1000); - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toEqual(value); @@ -15,7 +15,7 @@ describe('dates-min', () => { it('success string', () => { const value = `${new Date(min.getTime() + 1000)}`; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(`${result.value}`).toEqual(value); @@ -23,7 +23,7 @@ describe('dates-min', () => { it('success null', () => { const value = null; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(value); @@ -31,7 +31,7 @@ describe('dates-min', () => { it('failure', () => { const value = new Date(min.getTime() - 1000); - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.min(min, false)); expect(result.value).toBe(value); diff --git a/src/parsing/dates/dates-parser.spec.ts b/src/parsing/dates/dates-parser.spec.ts index 6b9a917..08ad47a 100644 --- a/src/parsing/dates/dates-parser.spec.ts +++ b/src/parsing/dates/dates-parser.spec.ts @@ -53,21 +53,21 @@ describe('dates-parser', () => { const result = schema.parse('a'); expect(result.value).toBe(null); - expect(result.errors).toEqual({ date: true }); + expect(result.errors).toEqual(ParseErrors.date); }); it('failure not date object', () => { const result = schema.parse({}); expect(result.value).toBe(null); - expect(result.errors).toEqual({ date: true }); + expect(result.errors).toEqual(ParseErrors.date); }); it('failure not date NaN', () => { const result = schema.parse(NaN); expect(result.value).toBe(null); - expect(result.errors).toEqual({ date: true }); + expect(result.errors).toEqual(ParseErrors.date); }); it('format/parse different day first', () => { @@ -83,4 +83,24 @@ describe('dates-parser', () => { DATE_SETTINGS.parseDayFirst = original; }); + + describe('not', () => { + const notSchema = Is.not.date; + + it('success', () => { + const value = 'oo'; + const result = notSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.empty); + expect(result.value).toEqual(null); + }); + + it('failure', () => { + const value = new Date(); + const result = notSchema.parse(value); + + expect(result.value).toBe(null); + expect(result.errors).toEqual(ParseErrors.not(ParseErrors.date)); + }); + }); }); diff --git a/src/parsing/dates/provideParseDate.ts b/src/parsing/dates/provideParseDate.ts index 63bd107..5a30553 100644 --- a/src/parsing/dates/provideParseDate.ts +++ b/src/parsing/dates/provideParseDate.ts @@ -1,18 +1,24 @@ -import { isDateType, isNullOrEmpty } from '../../predicates'; +import { isNullOrEmpty } from '../../predicates'; import { createParseResult } from '../createParseResult'; import { IParseResult } from '../IParseResult'; import { ParseErrors } from '../ParseErrors'; import { DateParsableTypes } from './DateParsableTypes'; import { tryParseDate } from './tryParseDate'; -export function provideParseDate() { +export function provideParseDate(negate: boolean = false) { + return (value: unknown): IParseResult => { if (isNullOrEmpty(value)) return createParseResult(null); - if (isDateType(value)) return createParseResult(value); - const dateValue = tryParseDate(value as DateParsableTypes); - return dateValue === null - ? createParseResult(null, ParseErrors.date) - : createParseResult(dateValue); + const parsed = tryParseDate(value as DateParsableTypes); + + if (parsed === null === negate) + return createParseResult(parsed); + + const errors = negate + ? ParseErrors.not(ParseErrors.date) + : ParseErrors.date; + + return createParseResult(null, errors); }; } diff --git a/src/parsing/dictionaries/dictionaries-parser.spec.ts b/src/parsing/dictionaries/dictionaries-parser.spec.ts index 1c5e1f3..b40591d 100644 --- a/src/parsing/dictionaries/dictionaries-parser.spec.ts +++ b/src/parsing/dictionaries/dictionaries-parser.spec.ts @@ -2,11 +2,11 @@ import { Is } from '../../Is'; import { ParseErrors } from '../ParseErrors'; describe('dictionaries-parser', () => { - const parser = Is.required.dictionary; + const schema = Is.required.dictionary; it('success', () => { const value = { a: '1' }; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(value); @@ -14,7 +14,7 @@ describe('dictionaries-parser', () => { it('failure', () => { const value = 1; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.dictionary); expect(result.value).toBe(null); @@ -22,21 +22,21 @@ describe('dictionaries-parser', () => { it('required', () => { - const result = parser.parse(null); + const result = schema.parse(null); expect(result.errors).toEqual(ParseErrors.required); }); it('not required', () => { - const requiredParser = Is.dictionary; + const requiredSchema = Is.dictionary; - const result = requiredParser.parse(null); + const result = requiredSchema.parse(null); expect(result.errors).toEqual(ParseErrors.empty); }); it('each', () => { - const eachParser = parser.each(Is.int); + const eachParser = schema.each(Is.int); const value = { a: '1' }; const result = eachParser.parse(value); diff --git a/src/parsing/dictionaries/parseAllDictionary.spec.ts b/src/parsing/dictionaries/parseAllDictionary.spec.ts index d263d62..c8cc82f 100644 --- a/src/parsing/dictionaries/parseAllDictionary.spec.ts +++ b/src/parsing/dictionaries/parseAllDictionary.spec.ts @@ -4,12 +4,12 @@ import { ParseErrors } from '../ParseErrors'; describe('parseAllDictionary', () => { - const parser = Is.array.each(Is.int); + const schema = Is.array.each(Is.int); it('success', () => { const value = { a: [1], b: [2] }; - const result = parseAllDictionary(parser.parse)(value); + const result = parseAllDictionary(schema.parse)(value); expect(result.value).toEqual(value); expect(result.errors).toBe(ParseErrors.empty); @@ -18,7 +18,7 @@ describe('parseAllDictionary', () => { it('failure', () => { const value = { a: ['a'], b: ['b'] }; - const result = parseAllDictionary(parser.parse)(value); + const result = parseAllDictionary(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/provideParseDictionary.ts b/src/parsing/dictionaries/provideParseDictionary.ts index 34b51f6..0060c7d 100644 --- a/src/parsing/dictionaries/provideParseDictionary.ts +++ b/src/parsing/dictionaries/provideParseDictionary.ts @@ -5,15 +5,25 @@ import { IParseResult } from '../IParseResult'; import { ParseErrors } from '../ParseErrors'; import { Dictionary } from './Dictionary'; -export function provideParseDictionary() { +export function provideParseDictionary( + negate: boolean = false +) { return (value: T): IParseResult> => { if (isNullOrEmpty(value)) return createParseResult(null); - if (isObject(value)) - return createParseResult(value as Dictionary); + const parsed = isObject(value) + ? value as Dictionary + : null; - return createParseResult(null, ParseErrors.dictionary); + if (parsed === null === negate) + return createParseResult(parsed); + + const errors = negate + ? ParseErrors.not(ParseErrors.dictionary) + : ParseErrors.dictionary; + + return createParseResult(null, errors); }; } diff --git a/src/parsing/json/JsonParser.ts b/src/parsing/json/JsonParser.ts index 20172e9..1380265 100644 --- a/src/parsing/json/JsonParser.ts +++ b/src/parsing/json/JsonParser.ts @@ -9,10 +9,11 @@ export class JsonParser implements IJson.Parser { constructor( private parent: IParser, - private parseCurrent: IParse = provideParseJson() + private parseCurrent: IParse = null, + private negate: boolean = false ) { } - readonly parse = parseChain(this.parent, this.parseCurrent); + readonly parse = parseChain(this.parent, this.parseCurrent ?? provideParseJson(this.negate)); readonly for = (schema: ComplexSchema): IComplex.Parser => new ComplexParser(this, schema); readonly use = (parser: IParser) => ({ parse: parseChain(this, parser.parse) }); diff --git a/src/parsing/json/json-parser.spec.ts b/src/parsing/json/json-parser.spec.ts index 455aaa0..b357a41 100644 --- a/src/parsing/json/json-parser.spec.ts +++ b/src/parsing/json/json-parser.spec.ts @@ -2,41 +2,47 @@ import { Is } from '../../Is'; import { ParseErrors } from '../ParseErrors'; describe('json-parser', () => { - const parser = Is.required.json; + const schema = Is.required.json; it('success', () => { const value = '{}'; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toEqual({}); }); it('failure', () => { + const warn = console.warn; + console.warn = jest.fn(); + const value = '{'; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.json); expect(result.value).toBe(null); + + expect(console.warn).toHaveBeenCalled(); + console.warn = warn; }); it('required', () => { - const result = parser.parse(null); + const result = schema.parse(null); expect(result.errors).toEqual(ParseErrors.required); }); it('not required', () => { - const requiredParser = Is.json; + const requiredSchema = Is.json; - const result = requiredParser.parse(null); + const result = requiredSchema.parse(null); expect(result.errors).toEqual(ParseErrors.empty); }); describe('and complex', () => { - const complexParser = parser.for({ + const complexParser = schema.for({ date: Is.required.date }); diff --git a/src/parsing/json/provideParseJson.ts b/src/parsing/json/provideParseJson.ts index a10f73f..6ff8aed 100644 --- a/src/parsing/json/provideParseJson.ts +++ b/src/parsing/json/provideParseJson.ts @@ -1,23 +1,26 @@ -import { isNullOrEmpty, isStringType } from '../../predicates'; +import { isNullOrEmpty } from '../../predicates'; import { IParseResult } from '../IParseResult'; import { ParseErrors } from '../ParseErrors'; import { createParseResult } from '../createParseResult'; +import { tryParseJson } from './tryParseJson'; -export function provideParseJson() { +export function provideParseJson( + negate: boolean = false +) { return (value: T): IParseResult => { if (isNullOrEmpty(value)) return createParseResult(null); - if (isStringType(value)) - try { - const parsed = JSON.parse(value); + const parsed = tryParseJson(value); - return createParseResult(parsed as T); - } catch (error) { - console.warn(error); - } + if (parsed === null === negate) + return createParseResult(parsed); - return createParseResult(null, ParseErrors.json); + const errors = negate + ? ParseErrors.not(ParseErrors.json) + : ParseErrors.json; + + return createParseResult(null, errors); }; } diff --git a/src/parsing/json/tryParseJson.ts b/src/parsing/json/tryParseJson.ts new file mode 100644 index 0000000..89b3890 --- /dev/null +++ b/src/parsing/json/tryParseJson.ts @@ -0,0 +1,24 @@ +import { isNullOrEmpty, isStringType } from '../../predicates'; + +/** + * try and parse the value as a json object T + * + * @param value value to parse + * @returns T or a null if failed parse or null or empty value + */ + +export function tryParseJson(value: unknown): T | null { + + if (isNullOrEmpty(value)) return null; + + if (isStringType(value)) + try { + const parsed = JSON.parse(value); + + return parsed as T; + } catch (error) { + console.warn(error); + } + + return null; +} diff --git a/src/parsing/numbers/IntParser.ts b/src/parsing/numbers/IntParser.ts index c999783..859c5ba 100644 --- a/src/parsing/numbers/IntParser.ts +++ b/src/parsing/numbers/IntParser.ts @@ -18,12 +18,12 @@ import { NumberEnumMap } from './NumberEnumMap'; export class IntParser implements IInt.Parser { constructor( private parent: IParser, - private parseCurrent: IParse = provideParseInt(), + private parseCurrent: IParse = null, private radix: number = undefined, private negate: boolean = false ) { } - readonly parse = parseChain(this.parent, this.parseCurrent); + readonly parse = parseChain(this.parent, this.parseCurrent ?? provideParseInt(this.negate)); readonly withRadix = (value?: number) => new IntParser(this.parent, this.parseCurrent, value, this.negate); diff --git a/src/parsing/numbers/numbers-float-anyOf.spec.ts b/src/parsing/numbers/numbers-float-anyOf.spec.ts index fcf0749..82ae31f 100644 --- a/src/parsing/numbers/numbers-float-anyOf.spec.ts +++ b/src/parsing/numbers/numbers-float-anyOf.spec.ts @@ -3,28 +3,28 @@ import { ParseErrors } from '../ParseErrors'; describe('numbers-float-anyOf', () => { const anyOf = [1.1, 2, 4.2, 5.3]; - const parser = Is.float.anyOf(anyOf); + const schema = Is.float.anyOf(anyOf); it('anyOf success', () => { - const result = parser.parse(anyOf[2]); + const result = schema.parse(anyOf[2]); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(4.2); }); it('anyOf failure', () => { - const result = parser.parse('3.3'); + const result = schema.parse('3.3'); expect(result.errors).toEqual(ParseErrors.anyOf(anyOf)); expect(result.value).toBe(3.3); }); describe('not', () => { - const notParser = Is.float.not.anyOf(anyOf); + const notSchema = Is.float.not.anyOf(anyOf); it('success', () => { const value = 1; - const result = notParser.parse(value); + const result = notSchema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toEqual(value); @@ -32,7 +32,7 @@ describe('numbers-float-anyOf', () => { it('failure', () => { const value = anyOf[0]; - const result = notParser.parse(value); + const result = notSchema.parse(value); expect(result.errors).toEqual(ParseErrors.not(ParseErrors.anyOf(anyOf))); expect(result.value).toEqual(value); diff --git a/src/parsing/numbers/numbers-float-equals.spec.ts b/src/parsing/numbers/numbers-float-equals.spec.ts index 31c2a3b..d98caac 100644 --- a/src/parsing/numbers/numbers-float-equals.spec.ts +++ b/src/parsing/numbers/numbers-float-equals.spec.ts @@ -3,38 +3,38 @@ import { ParseErrors } from '../ParseErrors'; describe('numbers-float-equals', () => { const expectedValue = 1; - const parser = Is.float.equals(expectedValue); + const schema = Is.float.equals(expectedValue); it('success number', () => { - const result = parser.parse(expectedValue); + const result = schema.parse(expectedValue); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(expectedValue); }); it('success undefined', () => { - const result = parser.parse(undefined); + const result = schema.parse(undefined); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(null); }); it('success null', () => { - const result = parser.parse(null); + const result = schema.parse(null); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(null); }); it('success string', () => { - const result = parser.parse(`${expectedValue}`); + const result = schema.parse(`${expectedValue}`); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(expectedValue); }); it('success string empty', () => { - const result = parser.parse(''); + const result = schema.parse(''); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(null); @@ -42,18 +42,18 @@ describe('numbers-float-equals', () => { it('failure not equal', () => { const value = 2; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.equals(expectedValue)); expect(result.value).toBe(value); }); describe('not', () => { - const notParser = Is.float.not.equals(10); + const notSchema = Is.float.not.equals(10); it('success', () => { const value = 9; - const result = notParser.parse(value); + const result = notSchema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toEqual(value); @@ -61,7 +61,7 @@ describe('numbers-float-equals', () => { it('failure', () => { const value = 10; - const result = notParser.parse(value); + const result = notSchema.parse(value); expect(result.errors).toEqual(ParseErrors.not(ParseErrors.equals(value))); expect(result.value).toEqual(value); diff --git a/src/parsing/numbers/numbers-float-max.spec.ts b/src/parsing/numbers/numbers-float-max.spec.ts new file mode 100644 index 0000000..80e6638 --- /dev/null +++ b/src/parsing/numbers/numbers-float-max.spec.ts @@ -0,0 +1,53 @@ +import { Is } from '../../Is'; +import { ParseErrors } from '../ParseErrors'; + +describe('numbers-float-max', () => { + const max = 10.5; + const schema = Is.float.max(max); + + it('success', () => { + const value = max - .5; + const result = schema.parse(value); + + expect(result.errors).toEqual(ParseErrors.empty); + expect(result.value).toBe(value); + }); + + it('failure', () => { + const value = max + .5; + const result = schema.parse(value); + + expect(result.errors).toEqual(ParseErrors.max(max, false)); + expect(result.value).toBe(value); + }); + + it('failure', () => { + const exclusiveSchema = Is.float.max(max, true); + const value = max; + const result = exclusiveSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.max(max, true)); + expect(result.value).toBe(value); + }); + + describe('not', () => { + const notMaxSchema = Is.float.not.max(max); + + it('success', () => { + const value = max + .5; + const result = notMaxSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.empty); + expect(result.value).toBe(value); + }); + + it('failure', () => { + const value = max - .5; + const result = notMaxSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.not(ParseErrors.max(max, false))); + expect(result.value).toBe(value); + }); + + }); +}); diff --git a/src/parsing/numbers/numbers-float-min.spec.ts b/src/parsing/numbers/numbers-float-min.spec.ts new file mode 100644 index 0000000..5bfb8e3 --- /dev/null +++ b/src/parsing/numbers/numbers-float-min.spec.ts @@ -0,0 +1,53 @@ +import { Is } from '../../Is'; +import { ParseErrors } from '../ParseErrors'; + +describe('numbers-float-min', () => { + const min = 10.5; + const schema = Is.float.min(min); + + it('success', () => { + const value = min + .5; + const result = schema.parse(value); + + expect(result.errors).toEqual(ParseErrors.empty); + expect(result.value).toBe(value); + }); + + it('failure', () => { + const value = min - .5; + const result = schema.parse(value); + + expect(result.errors).toEqual(ParseErrors.min(min, false)); + expect(result.value).toBe(value); + }); + + it('failure', () => { + const exclusiveSchema = Is.float.min(min, true); + const value = min; + const result = exclusiveSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.min(min, true)); + expect(result.value).toBe(value); + }); + + describe('not', () => { + const notMinSchema = Is.float.not.min(min); + + it('success', () => { + const value = min - .5; + const result = notMinSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.empty); + expect(result.value).toBe(value); + }); + + it('failure', () => { + const value = min + .5; + const result = notMinSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.not(ParseErrors.min(min, false))); + expect(result.value).toBe(value); + }); + + }); +}); diff --git a/src/parsing/numbers/numbers-max.spec.ts b/src/parsing/numbers/numbers-int-max.spec.ts similarity index 97% rename from src/parsing/numbers/numbers-max.spec.ts rename to src/parsing/numbers/numbers-int-max.spec.ts index 68406eb..9a07399 100644 --- a/src/parsing/numbers/numbers-max.spec.ts +++ b/src/parsing/numbers/numbers-int-max.spec.ts @@ -1,7 +1,7 @@ import { Is } from '../../Is'; import { ParseErrors } from '../ParseErrors'; -describe('numbers-max', () => { +describe('numbers-int-max', () => { const max = 10; const schema = Is.int.max(max); diff --git a/src/parsing/numbers/numbers-min-max.spec.ts b/src/parsing/numbers/numbers-int-min-max.spec.ts similarity index 95% rename from src/parsing/numbers/numbers-min-max.spec.ts rename to src/parsing/numbers/numbers-int-min-max.spec.ts index 04ff04d..1cd9861 100644 --- a/src/parsing/numbers/numbers-min-max.spec.ts +++ b/src/parsing/numbers/numbers-int-min-max.spec.ts @@ -1,7 +1,7 @@ import { Is } from '../../Is'; import { ParseErrors } from '../ParseErrors'; -describe('numbers-min-max', () => { +describe('numbers-int-min-max', () => { const min = 5; const max = 10; const maxMinSchema = Is.int.min(min).max(max); diff --git a/src/parsing/numbers/numbers-min.spec.ts b/src/parsing/numbers/numbers-int-min.spec.ts similarity index 97% rename from src/parsing/numbers/numbers-min.spec.ts rename to src/parsing/numbers/numbers-int-min.spec.ts index a59ddfb..3a90f7f 100644 --- a/src/parsing/numbers/numbers-min.spec.ts +++ b/src/parsing/numbers/numbers-int-min.spec.ts @@ -1,7 +1,7 @@ import { Is } from '../../Is'; import { ParseErrors } from '../ParseErrors'; -describe('numbers-min', () => { +describe('numbers-int-min', () => { const min = 10; const schema = Is.int.min(min); diff --git a/src/parsing/numbers/numbers-int.spec.ts b/src/parsing/numbers/numbers-int.spec.ts index fca8bdb..4e52b39 100644 --- a/src/parsing/numbers/numbers-int.spec.ts +++ b/src/parsing/numbers/numbers-int.spec.ts @@ -5,51 +5,78 @@ describe('numbers-int', () => { const schema = Is.int; it('success number', () => { - const result = schema.parse(1); + const value = 1; + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); - expect(result.value).toBe(1); + expect(result.value).toBe(value); }); it('success undefined', () => { - const result = schema.parse(undefined); + const value = undefined; + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(null); }); it('success null', () => { - const result = schema.parse(null); + const value = null; + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(null); }); it('success string', () => { - const result = schema.parse('1'); + const value = '1'; + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(1); }); it('success string empty', () => { - const result = schema.parse(''); + const value = ''; + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(null); }); it('failure not int (float)', () => { - const result = schema.parse(1.2); + const value = 1.2; + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.int); expect(result.value).toBe(null); }); it('failure not number', () => { - const result = schema.parse('a'); + const value = 'a'; + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.int); expect(result.value).toBe(null); }); + + describe('not', () => { + const notSchema = Is.not.int; + + it('success', () => { + const value = 'a'; + const result = notSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.empty); + expect(result.value).toBe(null); + }); + + it('failure', () => { + const value = '1'; + const result = notSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.not(ParseErrors.int)); + expect(result.value).toBe(null); + }); + }); }); diff --git a/src/parsing/numbers/parseFloat.spec.ts b/src/parsing/numbers/parseFloat.spec.ts new file mode 100644 index 0000000..77cd332 --- /dev/null +++ b/src/parsing/numbers/parseFloat.spec.ts @@ -0,0 +1,21 @@ +import { parseFloat } from './parseFloat'; + +describe('parseFloat', () => { + + it('success number', () => { + const result = parseFloat('1.1'); + + expect(result).toBe(1.1); + }); + + it('success number int', () => { + const result = parseFloat('1'); + + expect(result).toBe(1.0); + }); + + it('throws not a number', () => { + 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 a7e7886..c06ef75 100644 --- a/src/parsing/numbers/parseInt.spec.ts +++ b/src/parsing/numbers/parseInt.spec.ts @@ -1,14 +1,21 @@ +import { parseInt } from './parseInt'; + describe('parseInt', () => { - it('success number', () => { - const result = parseInt('1'); + it('success number', () => { + const result = parseInt('1'); + + expect(result).toBe(1); + }); - expect(result).toBe(1); - }); + it('success binary', () => { + const result = parseInt('101', 2); - it('success binary', () => { - const result = parseInt('101', 2); + expect(result).toBe(5); + }); - expect(result).toBe(5); - }); + it('throws not a number', () => { + expect(() => parseInt('ooo')) + .toThrow(); + }); }); \ No newline at end of file diff --git a/src/parsing/numbers/parseInt.ts b/src/parsing/numbers/parseInt.ts index be985da..b13ad35 100644 --- a/src/parsing/numbers/parseInt.ts +++ b/src/parsing/numbers/parseInt.ts @@ -3,12 +3,12 @@ import { tryParseInt } from './tryParseInt'; /** * Parse the value passed - * + * * @param value a parsable number type * @param radix base (2-36) defaults to 10 for decimal * @returns number or throws if not */ -export function parseInt(value: NumberParsableTypes, radix: number): number { +export function parseInt(value: NumberParsableTypes, radix: number = 10): number { const result = tryParseInt(value, radix); if (result === null) diff --git a/src/parsing/numbers/provideParseFloat.ts b/src/parsing/numbers/provideParseFloat.ts index efd5427..ed974b6 100644 --- a/src/parsing/numbers/provideParseFloat.ts +++ b/src/parsing/numbers/provideParseFloat.ts @@ -1,18 +1,33 @@ import { isNullOrEmpty } from '../../predicates'; import { createParseResult } from '../createParseResult'; +import { IParseResult } from '../IParseResult'; import { ParseErrors } from '../ParseErrors'; import { NumberParsableTypes } from './NumberParsableTypes'; -import { IParseResult } from '../IParseResult'; import { tryParseFloat } from './tryParseFloat'; -export function provideParseFloat() { +/** + * provides a parser for a float, or not a float + * + * note. if negated result value will be null + * + * @param negate + * @returns parseResult + */ +export function provideParseFloat(negate: boolean = false) { + return (value: unknown): IParseResult => { if (isNullOrEmpty(value)) return createParseResult(null); - const numberValue = tryParseFloat(value as NumberParsableTypes); - return numberValue === null - ? createParseResult(null, ParseErrors.float) - : createParseResult(numberValue); + const parsed = tryParseFloat(value as NumberParsableTypes); + + if (parsed === null === negate) + return createParseResult(parsed); + + const errors = negate + ? ParseErrors.not(ParseErrors.float) + : ParseErrors.float; + + return createParseResult(null, errors); }; } diff --git a/src/parsing/numbers/provideParseInt.ts b/src/parsing/numbers/provideParseInt.ts index 44f9e35..a92a125 100644 --- a/src/parsing/numbers/provideParseInt.ts +++ b/src/parsing/numbers/provideParseInt.ts @@ -5,14 +5,31 @@ import { ParseErrors } from '../ParseErrors'; import { NumberParsableTypes } from './NumberParsableTypes'; import { tryParseInt } from './tryParseInt'; -export function provideParseInt() { +/** + * provides a parser for an integer, or not an integer + * + * note. if negated result value will be null + * + * @param negate + * @returns parseResult + */ +export function provideParseInt( + negate: boolean = false +) { + return (value: unknown): IParseResult => { if (isNullOrEmpty(value)) return createParseResult(null); - const numberValue = tryParseInt(value as NumberParsableTypes); - return numberValue === null - ? createParseResult(null, ParseErrors.int) - : createParseResult(numberValue); + const parsed = tryParseInt(value as NumberParsableTypes); + + if (parsed === null === negate) + return createParseResult(parsed); + + const errors = negate + ? ParseErrors.not(ParseErrors.int) + : ParseErrors.int; + + return createParseResult(null, errors); }; } diff --git a/src/parsing/numbers/tryParseInt.ts b/src/parsing/numbers/tryParseInt.ts index 359ff9b..13a07fc 100644 --- a/src/parsing/numbers/tryParseInt.ts +++ b/src/parsing/numbers/tryParseInt.ts @@ -1,7 +1,14 @@ import { isInt, isNullOrEmpty, isNumberType } from '../../predicates'; import { NumberParsableTypes } from './NumberParsableTypes'; -/** Attempt to parse an integer value */ +/** + * Attempt to parse an integer value + * + * @param value value + * @param [radix=undefined] will default to 10 + * + * @returns number or null + */ export function tryParseInt( value: NumberParsableTypes, radix: number = undefined ): number | null { diff --git a/src/parsing/provideParseRoot.ts b/src/parsing/provideParseRoot.ts new file mode 100644 index 0000000..86eddab --- /dev/null +++ b/src/parsing/provideParseRoot.ts @@ -0,0 +1,18 @@ +import { isNullOrEmpty } from '../predicates'; +import { createParseResult } from './createParseResult'; +import { ParseErrors } from './ParseErrors'; + +/** + * + * @param isRequried value is required + * @returns a parse result + */ + +export function provideParseRoot( + isRequried = false +) { + + return (value: unknown) => isRequried && isNullOrEmpty(value) + ? createParseResult(value, ParseErrors.required) + : createParseResult(value); +} diff --git a/src/parsing/required.spec.ts b/src/parsing/required.spec.ts index 1b2e10f..4eb7795 100644 --- a/src/parsing/required.spec.ts +++ b/src/parsing/required.spec.ts @@ -2,11 +2,11 @@ import { Is } from '../Is'; import { ParseErrors } from './ParseErrors'; describe('required', () => { - const parser = Is.required; + const schema = Is.required; it('success', () => { const value = 1; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(value); @@ -14,7 +14,7 @@ describe('required', () => { it('failure undefined', () => { const value = undefined; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.required); expect(result.value).toBe(value); @@ -22,7 +22,7 @@ describe('required', () => { it('failure null', () => { const value = null; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.required); expect(result.value).toBe(value); @@ -30,7 +30,7 @@ describe('required', () => { it('failure empty string', () => { const value = ''; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.required); expect(result.value).toBe(value); diff --git a/src/parsing/strings/provideParseString.ts b/src/parsing/strings/provideParseString.ts index 9c4d3fa..ba02d4a 100644 --- a/src/parsing/strings/provideParseString.ts +++ b/src/parsing/strings/provideParseString.ts @@ -1,12 +1,25 @@ -import { isNullOrEmpty } from '../../predicates'; +import { isNullOrEmpty, isStringType } from '../../predicates'; import { createParseResult } from '../createParseResult'; import { IParseResult } from '../IParseResult'; +import { ParseErrors } from '../ParseErrors'; + +export function provideParseString(negate: boolean = false) { -export function provideParseString() { return (value: unknown): IParseResult => { if (isNullOrEmpty(value)) return createParseResult(null); - return createParseResult(value.toString()); + const parsed = isStringType(value) + ? value.toString() + : null; + + if (parsed === null === negate) + return createParseResult(parsed); + + const errors = negate + ? ParseErrors.not(ParseErrors.string) + : ParseErrors.string; + + return createParseResult(null, errors); }; -} +} \ No newline at end of file diff --git a/src/parsing/strings/strings-equals.spec.ts b/src/parsing/strings/strings-equals.spec.ts index 8e7d49d..51cf4ff 100644 --- a/src/parsing/strings/strings-equals.spec.ts +++ b/src/parsing/strings/strings-equals.spec.ts @@ -3,10 +3,10 @@ import { ParseErrors } from '../ParseErrors'; describe('strings-equals', () => { const expectedValue = 'value'; - const parser = Is.string.equals(expectedValue); + const schema = Is.string.equals(expectedValue); it('success', () => { - const result = parser.parse(expectedValue); + const result = schema.parse(expectedValue); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toEqual(expectedValue); @@ -14,37 +14,37 @@ describe('strings-equals', () => { it('failure case', () => { const value = expectedValue.toUpperCase(); - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.equals(expectedValue)); expect(result.value).toEqual(value); }); it('success ignore case', () => { - const ignoreCaseParser = Is.string.equals(expectedValue, true); + const ignoreCaseSchema = Is.string.equals(expectedValue, true); const value = expectedValue.toUpperCase(); - const result = ignoreCaseParser.parse(value); + const result = ignoreCaseSchema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toEqual(value); }); it('success undefined', () => { - const result = parser.parse(undefined); + const result = schema.parse(undefined); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(null); }); it('success null', () => { - const result = parser.parse(null); + const result = schema.parse(null); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(null); }); it('success empty', () => { - const result = parser.parse(''); + const result = schema.parse(''); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(null); @@ -52,7 +52,7 @@ describe('strings-equals', () => { it('failure not equal', () => { const value = 'not-value'; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.equals(expectedValue)); expect(result.value).toEqual(value); diff --git a/src/parsing/strings/strings-matches.spec.ts b/src/parsing/strings/strings-matches.spec.ts index 1b8123f..2c07292 100644 --- a/src/parsing/strings/strings-matches.spec.ts +++ b/src/parsing/strings/strings-matches.spec.ts @@ -4,11 +4,11 @@ import { ParseErrors } from '../ParseErrors'; describe('strings-matches', () => { const regexName = 'dot-com'; const regexString = '.com$'; - const parser = Is.string.matches(regexString, regexName); + const schema = Is.string.matches(regexString, regexName); it('success', () => { const value = 'test@example.com'; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toEqual(value); @@ -16,7 +16,7 @@ describe('strings-matches', () => { it('failure', () => { const value = 'test@example.co.uk'; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.matches(regexName)); expect(result.value).toEqual(value); diff --git a/src/parsing/strings/strings-not-equals.spec.ts b/src/parsing/strings/strings-not-equals.spec.ts index 5bd71df..bd07e1b 100644 --- a/src/parsing/strings/strings-not-equals.spec.ts +++ b/src/parsing/strings/strings-not-equals.spec.ts @@ -3,18 +3,18 @@ import { ParseErrors } from '../ParseErrors'; describe('strings-not-equals', () => { const expectedValue = 'value'; - const parser = Is.string.not.equals(expectedValue); + const schema = Is.string.not.equals(expectedValue); it('success', () => { const value = 'not-value'; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(value); }); it('failure', () => { - const result = parser.parse(expectedValue); + const result = schema.parse(expectedValue); expect(result.errors).toEqual(ParseErrors.not(ParseErrors.equals(expectedValue))); expect(result.value).toEqual(expectedValue); diff --git a/src/parsing/strings/strings.spec.ts b/src/parsing/strings/strings.spec.ts index 7f4f809..9d41fc0 100644 --- a/src/parsing/strings/strings.spec.ts +++ b/src/parsing/strings/strings.spec.ts @@ -2,42 +2,63 @@ import { Is } from '../../Is'; import { ParseErrors } from '../ParseErrors'; describe('strings', () => { - const parser = Is.string; + const schema = Is.string; it('success', () => { const value = 'string'; - const result = parser.parse(value); + const result = schema.parse(value); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toEqual(value); }); it('success undefined', () => { - const result = parser.parse(undefined); + const result = schema.parse(undefined); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(null); }); it('success null', () => { - const result = parser.parse(null); + const result = schema.parse(null); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(null); }); it('success empty', () => { - const result = parser.parse(''); + const result = schema.parse(''); expect(result.errors).toEqual(ParseErrors.empty); expect(result.value).toBe(null); }); - it('success not string', () => { + it('failure not string', () => { const value = 1010; - const result = parser.parse(value); + const result = schema.parse(value); + + expect(result.errors).toEqual(ParseErrors.string); + expect(result.value).toEqual(null); + }); + + describe('not', () => { + const notSchema = Is.not.string; + + it('success', () => { + const value = 1010; + const result = notSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.empty); + expect(result.value).toEqual(null); + }); + + it('failure not string', () => { + const value = 'string'; + const result = notSchema.parse(value); + + expect(result.errors).toEqual(ParseErrors.not(ParseErrors.string)); + expect(result.value).toEqual(null); + }); - expect(result.errors).toEqual(ParseErrors.empty); - expect(result.value).toEqual(value.toString()); }); });