├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── README.md ├── package.json └── src └── is-my-schema-valid.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "stage-0" 5 | ], 6 | "plugins": [ 7 | "transform-object-assign" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.json] 13 | [*.yml] 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [*.md] 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | extends: "airbnb/base", 3 | 4 | env: { 5 | "es6": true, 6 | "node": true, 7 | "mocha": true 8 | }, 9 | 10 | parser: "babel-eslint", 11 | 12 | rules: { 13 | "strict": 0, 14 | "quotes": [2, "single"], 15 | "indent": [2, 4, {SwitchCase: 1}], 16 | "semi": [2, "always"], 17 | "no-underscore-dangle": 0, 18 | "no-unused-vars": 1, 19 | "no-unused-expressions": 0, 20 | "new-cap": 0, 21 | "no-extra-boolean-cast": 0, 22 | "yoda": 0, 23 | "no-empty": 0, 24 | "no-use-before-define": 0, 25 | "camelcase": 0, 26 | "object-curly-spacing": 0, 27 | "array-bracket-spacing": 0, 28 | "max-len": 0, 29 | "comma-dangle": [2, "never"], 30 | "space-before-function-paren": 0, 31 | "arrow-body-style": 0, 32 | "no-param-reassign": 0, 33 | "consistent-return": 0, 34 | "no-console": 0, 35 | "func-names": 0, 36 | "no-nested-ternary": 0, 37 | "quote-props": 0, 38 | "space-infix-ops": 0, 39 | "prefer-const": 0, 40 | "prefer-template": 0, 41 | "spaced-comment": 0, 42 | "prefer-rest-params": 0, 43 | "no-shadow": 0, 44 | "no-unneeded-ternary": 0 45 | }, 46 | 47 | "globals": { 48 | "expect": true 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | *.log 4 | *.map 5 | 6 | index.js 7 | 8 | example 9 | examples 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | example 3 | .travis.yml 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 4 4 | before_script: 5 | - npm run lint 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # is-my-schema-valid 2 | 3 | [![build status](http://img.shields.io/travis/voronianski/is-my-schema-valid.svg?style=flat)](https://travis-ci.org/voronianski/is-my-schema-valid) 4 | [![npm version](http://badge.fury.io/js/is-my-schema-valid.svg)](http://badge.fury.io/js/is-my-schema-valid) 5 | [![Dependency Status](http://david-dm.org/voronianski/is-my-schema-valid.svg)](http://david-dm.org/voronianski/is-my-schema-valid) 6 | [![Download Count](http://img.shields.io/npm/dm/is-my-schema-valid.svg?style=flat)](http://www.npmjs.com/package/is-my-schema-valid) 7 | 8 | > Simple function that validates data according to [JSONSchema](http://json-schema.org) spec (under the hood it is powered by [is-my-json-valid](https://github.com/mafintosh/is-my-json-valid) which uses code generation to be extremely fast). 9 | 10 | ## Install 11 | 12 | ```bash 13 | npm install is-my-schema-valid --save 14 | ``` 15 | 16 | ## Usage 17 | 18 | ### `validate(data: Object, schema: Object, options: ?Object) -> { valid: Boolean, errors: ?Array }` 19 | 20 | Validate data object against passed schema. Function alwats returns object with boolean `valid` field, if `valid` is `false` there will be additional `errors` field with the list of error objects. 21 | 22 | ### Options 23 | 24 | - `filter` - filter away fields that are not in the schema, defaults to `false` 25 | - `filterReadonly` - filter away fields that are marked as `readonly: true` in schema, defaults to `false` 26 | 27 | **If you search for express middleware take a look on [is-express-schema-valid](https://github.com/voronianski/is-express-schema-valid).** 28 | 29 | ### Example 30 | 31 | ```javascript 32 | import validate from 'is-my-schema-valid'; 33 | 34 | const schema = { 35 | email: { 36 | type: 'string', 37 | required: true, 38 | format: 'email' 39 | }, 40 | password: { 41 | type: 'string', 42 | required: true, 43 | minLength: 1 44 | } 45 | }; 46 | 47 | const notValidData = {email: 'foo', password: 'bar'}; 48 | const result1 = validate(notValidData, schema); 49 | console.log(result1.valid); // false 50 | console.log(result1.errors); // list of errors 51 | 52 | const validData = {email: 'foo@bar.com', password: '123456'}; 53 | const result2 = validate(validData, schema); 54 | console.log(result2.valid); // true 55 | 56 | ``` 57 | 58 | ### Define schemas 59 | 60 | When defining a schema you are able to pass a plain object. In this case `is-my-schema-valid` will automagically populate your schema with default `object` properties: 61 | 62 | ```javascript 63 | const schema = { 64 | foo: { 65 | type: 'string', 66 | required: true 67 | } 68 | }; 69 | 70 | // will be passed to validator as: 71 | // { 72 | // type: 'object', 73 | // required: true, 74 | // additionalProperties: false, 75 | // properties: { 76 | // foo: { 77 | // type: 'string', 78 | // required: true 79 | // } 80 | // } 81 | // } 82 | ``` 83 | 84 | In other cases when you need a different `type` use a full schema. For example, when payload needs to be an `array`: 85 | 86 | ```javascript 87 | const schema = { 88 | type: 'array', 89 | uniqueItems: true, 90 | items: { 91 | type: 'number' 92 | } 93 | }; 94 | 95 | // it will be used as is by validator 96 | ``` 97 | 98 | ### Formats 99 | 100 | There are several additional formats added for easy validating the requests: 101 | 102 | - `"mongo-object-id"` - check if a string is a valid hex-encoded representation of a [MongoDB ObjectId](http://docs.mongodb.org/manual/reference/object-id/) 103 | - `"alpha"` - check if a string contains only letters (a-zA-Z) 104 | - `"alphanumeric"` - check if a string contains only letters and numbers 105 | - `"numeric"` - check if a string contains only numbers 106 | - `"hexadecimal"` - check if a string is a hexadecimal number 107 | - `"hexcolor"` - check if a string is a hexadecimal color 108 | - `"base64"` - check if a string is [Base64](https://en.wikipedia.org/wiki/Base64) encoded 109 | - `"decimal"` - check if a string is a decimal number, such as 0.1, .3, 1.1, 1.00003, 4.0, etc. 110 | - `"int"` - check if a string is an integer 111 | - `"float"` - check if a string is a float 112 | - `"uuid"` - check if a string is [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) 113 | - `"data-uri` - check if a string is [data uri format](https://developer.mozilla.org/en-US/docs/Web/HTTP/data_URIs) 114 | 115 | In the example below we can ensure that id is valid [MongoDB ObjectId](http://docs.mongodb.org/manual/reference/object-id/): 116 | 117 | ```javascript 118 | import validate from 'is-my-schema-valid'; 119 | 120 | const schema = { 121 | id: { 122 | type: 'string', 123 | format: 'mongo-object-id' 124 | } 125 | }; 126 | ``` 127 | 128 | Just a reminder that there are default **built-in formats** supported by JSONSchema: 129 | 130 | - `"date-time"` - date representation, as defined by [RFC 3339, section 5.6](http://tools.ietf.org/html/rfc3339). 131 | - `"email"` - internet email address, see [RFC 5322, section 3.4.1](http://tools.ietf.org/html/rfc5322). 132 | - `"hostname"` - internet host name, see [RFC 1034, section 3.1](http://tools.ietf.org/html/rfc1034). 133 | - `"ipv4"` - IPv4 address, according to dotted-quad ABNF syntax as defined in [RFC 2673, section 3.2](http://tools.ietf.org/html/rfc2673). 134 | - `"ipv6"` - IPv6 address, as defined in [RFC 2373, section 2.2](http://tools.ietf.org/html/rfc2373). 135 | - `"uri"` - a universal resource identifier (URI), according to [RFC3986](http://tools.ietf.org/html/rfc3986). 136 | 137 | ## JSONSchema 138 | 139 | In order to get comfortable with [JSONSchema spec](http://json-schema.org) and its' features I advice you to check the book ["Understanding JSON Schema"](http://spacetelescope.github.io/understanding-json-schema) (also [PDF](http://spacetelescope.github.io/understanding-json-schema/UnderstandingJSONSchema.pdf) version) or look at [examples](http://json-schema.org/examples.html). 140 | 141 | --- 142 | 143 | **MIT Licensed** 144 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "is-my-schema-valid", 3 | "version": "1.0.3", 4 | "description": "Simple function that validates data according to JSONSchema", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "babel src/is-my-schema-valid.js --out-file index.js", 8 | "prepublish": "npm run build", 9 | "test": "echo \"Coming soon!\" && exit 0", 10 | "lint": "eslint ./src" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/voronianski/is-my-schema-valid.git" 15 | }, 16 | "keywords": [ 17 | "json", 18 | "schema", 19 | "validate" 20 | ], 21 | "author": "Dmitri Voronianski ", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/voronianski/is-my-schema-valid/issues" 25 | }, 26 | "homepage": "https://github.com/voronianski/is-my-schema-valid#readme", 27 | "dependencies": { 28 | "is-my-json-valid": "^2.12.4", 29 | "traverse": "^0.6.6", 30 | "unique-by": "^1.0.0" 31 | }, 32 | "devDependencies": { 33 | "babel-cli": "^6.6.5", 34 | "babel-eslint": "^6.0.0", 35 | "babel-plugin-transform-object-assign": "^6.5.0", 36 | "babel-preset-es2015": "^6.6.0", 37 | "babel-preset-stage-0": "^6.5.0", 38 | "body-parser": "^1.13.1", 39 | "chai": "^3.0.0", 40 | "eslint": "^2.5.1", 41 | "eslint-config-airbnb": "^6.1.0", 42 | "estraverse": "^4.2.0", 43 | "estraverse-fb": "^1.3.1", 44 | "mocha": "^2.2.5" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/is-my-schema-valid.js: -------------------------------------------------------------------------------- 1 | import createValidator from 'is-my-json-valid'; 2 | import uniqueBy from 'unique-by'; 3 | import traverse from 'traverse'; 4 | 5 | const schemaPattern = { 6 | type: 'object', 7 | required: true, 8 | additionalProperties: false 9 | }; 10 | const customFormats = { 11 | 'mongo-object-id': /^[a-fA-F0-9]{24}$/i, 12 | 'alpha': /^[A-Z]+$/i, 13 | 'alphanumeric': /^[0-9A-Z]+$/i, 14 | 'numeric': /^[-+]?[0-9]+$/, 15 | 'hexadecimal': /^[0-9A-F]+$/i, 16 | 'hexcolor': /^#?([0-9A-F]{3}|[0-9A-F]{6})$/i, 17 | 'decimal': /^[-+]?([0-9]+|\.[0-9]+|[0-9]+\.[0-9]+)$/, 18 | 'float': /^(?:[-+]?(?:[0-9]+))?(?:\.[0-9]*)?(?:[eE][\+\-]?(?:[0-9]+))?$/, 19 | 'int': /^(?:[-+]?(?:0|[1-9][0-9]*))$/, 20 | 'base64': /^(?:[A-Z0-9+\/]{4})*(?:[A-Z0-9+\/]{2}==|[A-Z0-9+\/]{3}=|[A-Z0-9+\/]{4})$/i, 21 | 'uuid': /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i, 22 | 'data-uri': /^\s*data:([a-z]+\/[a-z0-9\-\+]+(;[a-z\-]+\=[a-z0-9\-]+)?)?(;base64)?,[a-z0-9\!\$\&\'\,\(\)\*\+\,\;\=\-\.\_\~\:\@\/\?\%\s]*\s*$/i 23 | }; 24 | 25 | function _parseValidatorErrors (errors, options = {}) { 26 | return uniqueBy(errors, obj => { 27 | return obj.message && obj.field; 28 | }).map(error => { 29 | let key = error.field.split(/\.(.+)/)[1]; 30 | let err = {}; 31 | 32 | if (key) { 33 | err.key = key; 34 | err.message = error.message; 35 | } else { 36 | err.message = options.title ? 37 | `${options.title} ${error.message}` : 38 | `data ${error.message}`; 39 | } 40 | 41 | if (options.debug) { 42 | err._raw = error; 43 | } 44 | 45 | return err; 46 | }); 47 | } 48 | 49 | function validate (data, schema = {}, options = {}) { 50 | const schemaObj = schema.type ? schema : Object.assign({}, schemaPattern, {properties: schema}); 51 | const formats = options.formats ? Object.assign({}, customFormats, options.formats) : customFormats; 52 | const validator = createValidator(schemaObj, { formats }); 53 | 54 | if (options.filter) { 55 | const filter = createValidator.filter(schemaObj); 56 | data = filter(data); 57 | } 58 | 59 | const validatedData = validator(data); 60 | if (!validatedData) { 61 | return { 62 | valid: false, 63 | errors: _parseValidatorErrors(validator.errors, { 64 | title: schema.title, 65 | debug: options.debug 66 | }) 67 | }; 68 | } 69 | 70 | if (validatedData && options.filterReadonly) { 71 | const readonlyProperties = traverse(schemaObj).reduce(function (memo, value) { 72 | if (this.key === 'readonly' && value === true) { 73 | memo.push(this.parent.key); 74 | } 75 | return memo; 76 | }, []); 77 | 78 | traverse(data).forEach(function () { 79 | if (readonlyProperties.indexOf(this.key) !== -1) { 80 | this.remove(); 81 | } 82 | }); 83 | } 84 | 85 | return { 86 | valid: true 87 | }; 88 | } 89 | 90 | export default validate; 91 | --------------------------------------------------------------------------------