Skip to content
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
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* 13 built-in validators
* custom validators
* nested objects & array handling
* strict object validation
* multiple validators
* customizable error messages
* programmable error object
Expand Down Expand Up @@ -155,6 +156,19 @@ v.validate({ name: "John" }, schema); // Valid
v.validate({ age: 42 }, schema); // Fail
```

# Strict validation
Object properties which are not specified on the schema are ignored by default. If you set the `$$strict` option to `true` any aditional properties will result in an `strictObject` error.

```js
let schema = {
name: { type: "string" }, // required
$$strict: true // no additional properties allowed
}

v.validate({ name: "John" }, schema); // Valid
v.validate({ name: "John", age: 42 }, schema); // Fail
```

# Multiple validators
It is possible to define more validators for a field. In this case, only one validator needs to succeed for the field to be valid.

Expand Down Expand Up @@ -409,6 +423,10 @@ v.validate({
}, schema); // Fail ("The 'address.zip' field is required!")
```

### Properties
Property | Default | Description
-------- | -------- | -----------
`strict` | `false`| if `true` any properties which are not defined on the schema will throw an error.

## `string`
This is a `String`.
Expand Down
2 changes: 2 additions & 0 deletions lib/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ module.exports = {

enumValue: "The '{field} field value '{expected}' does not match any of the allowed values!",

object: "The '{field}' must be an Object!",
objectStrict: "The object '{field}' contains invalid keys: '{actual}'!",
uuid: "The {field} field must be a valid UUID",
uuidVersion: "The {field} field must be a valid version provided",
};
17 changes: 16 additions & 1 deletion lib/rules/object.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
"use strict";

module.exports = function checkObject(value) {
module.exports = function checkObject(value, schema) {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
return this.makeError("object");
}

if (schema.strict === true && schema.props) {
const allowedProps = Object.keys(schema.props);
const invalidProps = [];
const props = Object.keys(value);

for (let i = 0; i < props.length; i++) {
if (allowedProps.indexOf(props[i]) === -1) {
invalidProps.push(props[i]);
}
}
if (invalidProps.length !== 0) {
return this.makeError("objectStrict", undefined, invalidProps.join(", "));
}
}

return true;
};
18 changes: 18 additions & 0 deletions lib/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ Validator.prototype.compileSchemaObject = function(schemaObject) {
throw new Error("Invalid schema!");
}

const strict = schemaObject.$$strict;
delete schemaObject.$$strict;

let compiledObject = this.cache.get(schemaObject);
if (compiledObject) {
compiledObject.cycle = true;
Expand All @@ -133,6 +136,11 @@ Validator.prototype.compileSchemaObject = function(schemaObject) {
sourceCode.push("let res;");
sourceCode.push("let propertyPath;");
sourceCode.push("const errors = [];");

if (strict === true) {
sourceCode.push("const givenProps = new Map(Object.keys(value).map(key => [key, true]));");
}

for (let i = 0; i < compiledObject.properties.length; i++) {
const property = compiledObject.properties[i];
const name = escapeEvalString(property.name);
Expand All @@ -147,6 +155,16 @@ Validator.prototype.compileSchemaObject = function(schemaObject) {
sourceCode.push("if (res !== true) {");
sourceCode.push(`\tthis.handleResult(errors, propertyPath, res, properties[${i}].compiledType.messages);`);
sourceCode.push("}");

if (strict === true) {
sourceCode.push(`givenProps.delete("${name}");`);
}
}

if (strict === true) {
sourceCode.push("if (givenProps.size !== 0) {");
sourceCode.push("\tthis.handleResult(errors, path || 'rootObject', this.makeError('objectStrict', undefined, [...givenProps.keys()].join(', ')), this.messages);");
sourceCode.push("}");
}

sourceCode.push("return errors.length === 0 ? true : errors;");
Expand Down
2 changes: 2 additions & 0 deletions test/messages.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ describe("Test Messages", () => {
expect(msg.forbidden).toBeDefined();
expect(msg.email).toBeDefined();
expect(msg.url).toBeDefined();
expect(msg.object).toBeDefined();
expect(msg.objectStrict).toBeDefined();
expect(msg.uuid).toBeDefined();
expect(msg.uuidVersion).toBeDefined();

Expand Down
6 changes: 5 additions & 1 deletion test/rules/object.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ describe("Test checkObject", () => {
it("should check values", () => {
const s = { type: "object" };
const err = { type: "object" };

const strict = { type: "object", strict: true, props: { a: "string" } };
const strictErr = {type: "objectStrict" };

expect(check(null, s)).toEqual(err);
expect(check(undefined, s)).toEqual(err);
expect(check(0, s)).toEqual(err);
Expand All @@ -21,5 +23,7 @@ describe("Test checkObject", () => {
expect(check(true, s)).toEqual(err);
expect(check([], s)).toEqual(err);
expect(check({}, s)).toEqual(true);
expect(check({a: "string"}, strict)).toEqual(true);
expect(check({a: "string", b: "string"}, strict)).toMatchObject(strictErr);
});
});
57 changes: 57 additions & 0 deletions test/validator.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1209,3 +1209,60 @@ describe("Test irregular object property names", () => {
expect(res).toBe(true);
});
});

describe("Test $$strict schema restriction on root-level", () => {
const v = new Validator();

let schema = {
name: "string",
$$strict: true
};

let check = v.compile(schema);

it("should give error if the object contains additional properties on the root-level", () => {
let obj = {
name: "test",
additionalProperty: "additional"
};

let res = check(obj);

expect(res).toBeInstanceOf(Array);
expect(res.length).toBe(1);
expect(res[0].field).toBe("rootObject");
expect(res[0].type).toBe("objectStrict");
});
});

describe("Test $$strict schema restriction on sub-level", () => {
const v = new Validator();

let schema = {
address: {
type: "object",
props: {
street: "string",
$$strict: true
}
}
};

let check = v.compile(schema);

it("should give error if the object contains additional properties on the sub-level", () => {
let obj = {
address: {
street: "test",
additionalProperty: "additional"
}
};

let res = check(obj);

expect(res).toBeInstanceOf(Array);
expect(res.length).toBe(1);
expect(res[0].field).toBe("address");
expect(res[0].type).toBe("objectStrict");
});
});