Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate at #232

Merged
merged 1 commit into from
May 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,28 @@ UserSchema.validate({
```

- `validate(entry: object)` - Runs the rule chain against an entry
- `validateAt(path: string, entry: Record<string | number, any>)`
- ```js
const schema = Nope.object().shape({
foo: Nope.array().of(
Nope.object().shape({
loose: Nope.boolean(),
bar: Nope.string().when('loose', {
is: true,
then: Nope.string().max(5, 'tooLong'),
otherwise: Nope.string().min(5, 'tooShort'),
}),
}),
),
});

const rootValue = {
foo: [{ bar: '123' }, { bar: '123456', loose: true }],
};

schema.validateAt('foo[0].bar', rootValue); // returns 'tooShort';
schema.validateAt('foo[1].bar', rootValue); // returns 'tooLong';
```

- `Array`
- `required(message: string)` - Required field (non falsy)
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"main": "lib/umd/index.js",
"module": "lib/es2015/index.js",
"types": "lib/umd/index.d.ts",
"sideEffects": false,
"license": "MIT",
"description": "Fast and simple JS validator",
"scripts": {
Expand Down
9 changes: 8 additions & 1 deletion src/NopeArray.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Rule, Validatable, Nil } from './types';
import NopePrimitive from './NopePrimitive';
import { deepEquals } from './utils';
import NopeObject from './NopeObject';

class NopeArray<T> implements Validatable<T[]> {
protected _type = 'object';
public validationRules: Rule<T[]>[] = [];
public ofShape: Validatable<T> | NopeObject | null = null;

public getType() {
return this._type;
Expand All @@ -20,7 +22,12 @@ class NopeArray<T> implements Validatable<T[]> {
return this.test(rule);
}

public of(primitive: Validatable<T>, message = 'One or more elements are of invalid type') {
public of(
primitive: Validatable<T> | NopeObject,
message = 'One or more elements are of invalid type',
) {
this.ofShape = primitive;

const rule: Rule<T[]> = (entry) => {
if (entry === undefined || entry === null) {
return;
Expand Down
35 changes: 33 additions & 2 deletions src/NopeObject.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import { Validatable, Rule, ShapeErrors } from './types';
import { pathToArray, getFromPath } from './utils';

interface ObjectShape {
[key: string]: Validatable<any>;
[key: string]: Validatable<any> | NopeObject;
}

class NopeObject {
private objectShape: ObjectShape;
private validationRules: Rule<object>[] = [];
protected _type = 'object';

public constructor(objectShape?: ObjectShape) {
this.objectShape = objectShape || {};
}

public getType() {
return this._type;
}

public shape(shape: ObjectShape) {
this.objectShape = { ...this.objectShape, ...shape };

Expand Down Expand Up @@ -53,7 +59,7 @@ class NopeObject {
return this;
}

public validate(entry: { [key: string]: any }) {
public validate(entry: Record<string | number, any>, context?: Record<string | number, any>) {
for (const rule of this.validationRules) {
const localErrors = rule(entry);

Expand Down Expand Up @@ -82,6 +88,31 @@ class NopeObject {

return undefined;
}

public validateAt(path: string, entry: Record<string | number, any>) {
const arrayPath = pathToArray(path);

let validator: any = this.objectShape;

for (const p of arrayPath) {
if (!isNaN(parseInt(p, 10))) {
continue;
}

if (validator[p]?.objectShape) {
validator = validator[p].objectShape;
} else if (validator[p]?.ofShape) {
validator = validator[p].ofShape.objectShape || validator[p].ofShape;
} else {
validator = validator[p];
}
}

const parentValue = getFromPath(path, entry, true);
const value = getFromPath(path, entry);

return validator.validate(value, parentValue);
}
}

export default NopeObject;
31 changes: 31 additions & 0 deletions src/__tests__/NopeObject.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,35 @@ describe('#NopeObject', () => {
expect(resp).toBeUndefined();
});
});

describe('#validateAt', () => {
it('should work', () => {
const schema = Nope.object().shape({
foo: Nope.array().of(
Nope.object().shape({
loose: Nope.boolean(),
bar: Nope.string().when('loose', {
is: true,
then: Nope.string().max(5, 'tooLong'),
otherwise: Nope.string().min(5, 'tooShort'),
}),
}),
),
});

const rootValue = {
foo: [
{ bar: '123' },
{ bar: '123456', loose: true },
{ bar: '123456' },
{ bar: '123', loose: true },
],
};

expect(schema.validateAt('foo[0].bar', rootValue)).toBe('tooShort');
expect(schema.validateAt('foo[1].bar', rootValue)).toBe('tooLong');
expect(schema.validateAt('foo[2].bar', rootValue)).toBe(undefined);
expect(schema.validateAt('foo[3].bar', rootValue)).toBe(undefined);
});
});
});
7 changes: 7 additions & 0 deletions src/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,11 @@ describe('#utils', () => {
expect(utils.deepEquals({ a: [[2, { a: 42 }]] }, { a: [[2, { a: 41 }]] })).toEqual(false);
});
});

describe('#getFromPath', () => {
it('should work', () => {
expect(utils.getFromPath('a.b.c', { a: { b: { c: 42 } } })).toBe(42);
expect(utils.getFromPath('a.b.c[1].d', { a: { b: { c: [2, { d: 5 }] } } })).toBe(5);
});
});
});
21 changes: 21 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,24 @@ export function deepEquals(a: any, b: any): boolean {

return a === b;
}

export function pathToArray(path: string): string[] {
return path.split(/[,[\].]/g).filter(Boolean);
}

export function getFromPath(path: string, entry: Record<string | number, any>, dropLast = false) {
if (!path) {
return undefined;
}

let pathArray = pathToArray(path);
pathArray = dropLast ? pathArray.slice(0, -1) : pathArray;

let value: any = entry;

for (const key of pathArray) {
value = value[key];
}

return value;
}