Skip to content

Commit

Permalink
Merge pull request #38 from MrAntix/main
Browse files Browse the repository at this point in the history
feat(parse): allow negate on types
  • Loading branch information
ntix authored Feb 3, 2024
2 parents 1bbbd64 + 27d6685 commit d1203f6
Show file tree
Hide file tree
Showing 55 changed files with 700 additions and 276 deletions.
5 changes: 3 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }],
Expand Down
12 changes: 12 additions & 0 deletions src/Is.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
});

});
4 changes: 3 additions & 1 deletion src/Is.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}
6 changes: 5 additions & 1 deletion src/parsing/IRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<unknown>, Builder { }
export interface Parser extends IParser<unknown>, Builder {
readonly not: NextBuilder<Parser, 'not' | 'parse'>

}

export interface Builder {
readonly boolean: IBoolean.Parser;
Expand Down
2 changes: 2 additions & 0 deletions src/parsing/ParseErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <T>(value: T) => ({ equals: value });
/** value should be equal to any of the values */
Expand Down
46 changes: 23 additions & 23 deletions src/parsing/RootParser.ts
Original file line number Diff line number Diff line change
@@ -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 = <T>(schema: ComplexSchema<T>): IComplex.Parser<T> => new ComplexParser(this, schema);
readonly use = <T>(parser: IParser<T>) => ({ parse: parseChain<T>(this, parser.parse) });

get not() {
return new RootParser(this.isRequried, true);
}
}
4 changes: 2 additions & 2 deletions src/parsing/arrays/ArrayParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ export class ArrayParser<T> implements IArray.Parser<T> {

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

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

get not() {
return new ArrayParser<T>(this.parent, this.parseCurrent, true);
Expand Down
13 changes: 6 additions & 7 deletions src/parsing/arrays/IArray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ import { NextBuilder } from '../NextBuilder';
export namespace IArray {

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

readonly of: <U>() => NextBuilder<Parser<U>, 'of' | 'each', 'unique'>
readonly each: <U = T>(parser: IParser<U>) => NextBuilder<Parser<U>, 'of' | 'each'>

readonly unique: (distinctor: (item: T) => unknown) => NextBuilder<Parser<T>, 'of' | 'each' | 'unique'>;
readonly minLength: (value: number, exclusive?: boolean) => NextBuilder<Parser<T>, 'of' | 'each'| 'minLength'>;
readonly maxLength: (value: number, exclusive?: boolean) => NextBuilder<Parser<T>, 'of' | 'each' | 'maxLength'>;
readonly of: <U>() => NextBuilder<Parser<U>, 'of' | 'each', 'unique' | 'not' | 'parse'>
readonly each: <U = T>(parser: IParser<U>) => NextBuilder<Parser<U>, 'of' | 'each', 'unique' | 'not' | 'parse'>

readonly unique: (distinctor: (item: T) => unknown) => NextBuilder<Parser<T>, 'of' | 'each' | 'unique', 'not' | 'parse'>;
readonly minLength: (value: number, exclusive?: boolean) => NextBuilder<Parser<T>, 'of' | 'each' | 'minLength', 'not' | 'parse'>;
readonly maxLength: (value: number, exclusive?: boolean) => NextBuilder<Parser<T>, 'of' | 'each' | 'maxLength', 'not' | 'parse'>;
}
}
6 changes: 3 additions & 3 deletions src/parsing/arrays/arrays-maxLength.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ 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);
});

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);
Expand Down
6 changes: 3 additions & 3 deletions src/parsing/arrays/arrays-minLength.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ 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);
});

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);
Expand Down
32 changes: 26 additions & 6 deletions src/parsing/arrays/arrays-parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,61 @@ 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);
});

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);
});

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);
});
});
});
32 changes: 27 additions & 5 deletions src/parsing/arrays/arrays-unique.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,47 @@ describe('arrays-unique', () => {
interface IThing {
id: number
}
const thingParser = Is.for<IThing>({ id: Is.required.int });
const parser = Is.array
.each(thingParser)
const thingSchema = Is.for<IThing>({ 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);
});

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);
});
});
});
6 changes: 3 additions & 3 deletions src/parsing/arrays/parseAllArray.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ParseErrors } from '../ParseErrors';
describe('parseAllArray', () => {

interface IModel { name: string, age?: number }
const parser = Is.for<IModel>({
const schema = Is.for<IModel>({
name: Is.required.string,
age: Is.int.min(25)
});
Expand All @@ -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);
Expand All @@ -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({
Expand Down
26 changes: 22 additions & 4 deletions src/parsing/arrays/provideParseArray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,33 @@ import { createParseResult } from '../createParseResult';
import { IParseResult } from '../IParseResult';
import { ParseErrors } from '../ParseErrors';

export function provideParseArray<T>() {
/**
* 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<T>(
negate: boolean = false
) {

return (value: T): IParseResult<T[]> => {
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);
};
}
Loading

0 comments on commit d1203f6

Please sign in to comment.