├── .gitattributes ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── lib ├── common.js ├── formats.js ├── json-gate.js ├── valid-object.js └── valid-schema.js ├── package.json └── test ├── async-error-test.js ├── benchmark-test.js ├── common.js ├── config.js ├── object-array-test.js ├── object-basic-test.js ├── object-default-test.js ├── object-dependencies-test.js ├── object-disallow-test.js ├── object-disallow-union-test.js ├── object-enum-test.js ├── object-format-test.js ├── object-number-test.js ├── object-object-test.js ├── object-string-test.js ├── object-type-test.js ├── object-type-union-test.js ├── schema-array-test.js ├── schema-basic-test.js ├── schema-default-test.js ├── schema-dependencies-test.js ├── schema-disallow-test.js ├── schema-enum-test.js ├── schema-format-test.js ├── schema-number-test.js ├── schema-object-test.js ├── schema-required-test.js ├── schema-string-test.js ├── schema-type-test.js └── sync-test.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | test/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2012 Ofer Reichman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # json-gate 2 | 3 | _json-gate_ validates JSON objects against a JSON schema. 4 | In other words, it makes sure an object conforms to the type and structure that your code expects. 5 | For example, a server can use it to ensure input received from a client conforms to the API. 6 | 7 | The JSON schema can also help with documentation and collaboration. 8 | Copy it to your API document and everybody should understand exactly what is expected. 9 | 10 | _json-gate_ likes you! It is... 11 | 12 | * Intuitive - API is super easy to use. 13 | * Produces human-friendly, detailed error messages - for both you and your customers. 14 | * Fast - Your CPU won't have time to blink. 15 | * Well documented, in case you don't feel like digging into the IETF specifications. 16 | Just keep reading, it's all here. 17 | * Conforms to standard - no relearning, no obligation, no surprises. 18 | * Both synchronous and asynchronous modes - your choice. 19 | 20 | ## What's a JSON schema? 21 | 22 | [JSON Schema](http://json-schema.org/) is a proposed Internet draft defining a JSON media type (application/schema+json) with the following goals: 23 | 24 | * Validation - You can use JSON Schema to validate your JSON data. 25 | * Documentation - You can extract documentation information from a JSON Schema, match this documentation to your data, and use that information for user interaction. 26 | * Hyperlinking - You can pair your JSON data with the defining JSON Schema to build hyperlinks into parts of that JSON data. 27 | 28 | _json-gate_ supports most of [JSON Schema Draft 3](http://tools.ietf.org/html/draft-zyp-json-schema-03), minus the hyperlinking and hyper schema parts. 29 | 30 | ## Example 31 | 32 | var createSchema = require('json-gate').createSchema; 33 | 34 | var schema = createSchema({ 35 | type: 'object', 36 | properties: { 37 | query: { 38 | type: 'string', 39 | minLength: 1, 40 | maxLength: 64, 41 | required: true 42 | }, 43 | maxResults: { 44 | type: 'integer', 45 | maximum: 20, 46 | default: 10 47 | } 48 | }, 49 | additionalProperties: false 50 | }); 51 | 52 | try { 53 | schema.validate(input); 54 | } catch(err) { 55 | return res.send(400, err); // 400 Bad Request 56 | } 57 | 58 | This schema is explained below in the _Hello, schema_ section. 59 | 60 | ## Installation 61 | 62 | $ npm install json-gate 63 | 64 | ## Usage 65 | 66 | ### _json-gate.createSchema_(jsonSchema) 67 | 68 | This function gets a JSON Schema definition and returns a new _Schema_ object. 69 | It verifies that the schema is valid. 70 | If the latter is malformed an error will be thrown pinpointing the problem. 71 | 72 | ### _Schema.validate_(jsonObject) 73 | 74 | This function gets a JSON object and validates it against the schema. 75 | If the JSON object does not conform to the schema an error will be thrown (or returned, see _Synchronous/Asynchronous_ below). 76 | 77 | The function stops after encountering an error. It does not return multiple errors. 78 | The function does not return a value. 79 | Be aware that the input JSON object may be edited _in-place_ if the _default_ attribute is used. 80 | 81 | ### Errors 82 | 83 | The error messages are human-friendly and detailed. 84 | For example: "JSON object property 'user.password': length is 3 when it should be at least 6". 85 | Ready to be shrink-wrapped in a _400 Bad Request_ response and shipped to the client! 86 | 87 | Equaly helpful error messages are produced in the case of a malformed schema, to assist you during development. 88 | For example: "Schema property 'num': 'exclusiveMaximum' attribute is a number when it should be a boolean". 89 | 90 | ### Synchronous/Asynchronous 91 | 92 | _Schema.validate_ can be called in two ways, to suit your needs: 93 | 94 | * Synchronously - as in the example above, with one parameter. 95 | As already stated, it returns nothing if the object checks out. Otherwise it throws an error. 96 | * Asynchronously - by providing a 2nd parameter: a callback function. 97 | The callback function gets two arguments: error and result (the original JSON object, which may be modified). 98 | 99 | It should be noted that the JSON object passed to the callback function is the same as the input JSON object. 100 | It is only passed for convenience. 101 | Any _default_ values used will affect the original JSON object, even when calling asynchronously. 102 | 103 | ## Hello, schema 104 | 105 | A _JSON schema_ is defined using a JavaScript object containing various _attributes_. 106 | 107 | Let's start by analyzing the schema given in the example above. 108 | What does it say about the JSON object? 109 | 110 | * The JSON object should be an object (as opposed to an array). 111 | * It should have a property named _query_, which should be a string of 1 to 64 characters. 112 | * It may optionaly have a property named _maxResults_, which should be an integer with a maximum value of 20. 113 | * If _maxResults_ is missing, it will be generated with a value of 10. 114 | * Additional properties (other than _query_ and _maxResults_) are not allowed. 115 | 116 | JSON Schema properties can be nested: objects and arrays include other attributes, which may themselves be objects and arrays. 117 | Notice that objects' properties are unordered, whereas array items are ordered. 118 | 119 | See Attributes section below to learn about more possibilities. 120 | 121 | ## Attributes 122 | 123 | Below are all the supported attributes. 124 | 125 | Terminology: in this section, *instance* refers to a JSON value (object or property) that the schema will be describing and validating. 126 | 127 | ### type 128 | 129 | Defines the expected instance type. 130 | It can take one of two forms: 131 | 132 | * Simple type - any of the following: 'string', 'number', 'integer', 'boolean', 'object', 'array', 'null' or 'any'. 133 | * Union type - an array of simple types and/or schemas. The instance type should be one of the types in the array. 134 | 135 | Example - instance should be either a string or null: 136 | 137 | type: ['string', 'null'] 138 | 139 | The default is 'any'. 140 | 141 | ### disallow 142 | 143 | Defines the disallowed instance type. This is the opposite of _type_. 144 | It can take one of two forms: 145 | 146 | * Simple type - any of the following: 'string', 'number', 'integer', 'boolean', 'object', 'array', 'null' or 'any'. 147 | * Union type - an array of simple types and/or schemas. The instance type should not be any of the types in the array. 148 | For example, if _type_ is ['string', 'null'] then the instance may be neither a string nor null. 149 | 150 | ### required 151 | 152 | A boolean indicating whether an instance is mandatory (true) or optional (false). 153 | 154 | Example with a mandatory property and an optional one: 155 | 156 | { 157 | type: 'object', 158 | properties: { 159 | query: { 160 | type: 'string', 161 | required: true 162 | }, 163 | safeSearch: { 164 | type: 'string', 165 | enum: ['off', 'moderate', 'strict'] 166 | } 167 | } 168 | } 169 | 170 | The default is false (optional). 171 | 172 | ### default 173 | 174 | Defines the default value of the instance when the instance is undefined. 175 | 176 | Example: 177 | 178 | maxResults: { 179 | type: 'integer', 180 | default: 10 181 | } 182 | 183 | The JSON object is edited in-place. 184 | In other words, the default values are set to the original JSON object, not a returned copy. 185 | 186 | ### enum 187 | 188 | An array containing all possible values. 189 | The instance must equal one of the values. 190 | 191 | Example: 192 | 193 | enum: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] 194 | 195 | ### properties 196 | 197 | Applies only to instances of type `'object'`. 198 | 199 | Defines the properties of the instance object. 200 | The _properties_ attribute is an object, matching properties to their valid schema. 201 | 202 | Example: 203 | 204 | { 205 | type: 'object', 206 | properties: { 207 | name: { type: 'string' }, 208 | age: { type: 'integer' } 209 | } 210 | } 211 | 212 | The default is an empty object. 213 | 214 | Note: Properties are considered unordered, the order of the instance properties may be in any order. 215 | 216 | ### patternProperties 217 | 218 | Applies only to instances of type `'object'`. 219 | 220 | This attribute is similar to the _properties_ attribute, but the keys are regular expression patterns instead of property names. 221 | Any instance property whose name fits a pattern must be valid against the appropriate schema. 222 | 223 | Example: 224 | 225 | { 226 | type: 'object', 227 | patternProperties: { 228 | '^sz': { type: 'string' }, 229 | '^n': { type: 'number' }, 230 | '^b': { type: 'boolean' } 231 | } 232 | } 233 | 234 | Note that using this attribute may cause instances to be validated more than once: 235 | 236 | * If a property name is defined by _properties_ and also matches a pattern in _patternProperties_. 237 | * If a property name matches more than one pattern in _patternProperties_. 238 | 239 | ### additionalProperties 240 | 241 | Applies only to instances of type `'object'`. 242 | 243 | Defines a schema for all properties that are not explicitly defined by _properties_ and do not match any pattern in _patternProperties_. 244 | It can take one of two forms: 245 | 246 | * Schema - all the additional properties must be valid against the schema. 247 | * False - additional properties are not allowed. 248 | 249 | Example: 250 | 251 | { 252 | type: 'object', 253 | properties: { 254 | id: 'integer', 255 | name: 'string' 256 | }, 257 | patternProperties: { 258 | '^_debug': { type: 'any' } 259 | }, 260 | additionalProperties: false 261 | } 262 | 263 | The default is an empty schema, which allows any value for additional properties. 264 | 265 | ### dependencies 266 | 267 | Applies only to instances of type `'object'`. 268 | 269 | This attribute is an object, matching properties to their requirements. 270 | If the instance object has a property with the same name as a property in the _dependencies_ attribute object, 271 | then the instance must comply with the requirement. 272 | 273 | The requirement can take one of three forms: 274 | 275 | * Simple dependency - a string. 276 | The instance object must have a property with the same name as the requirement. 277 | Example: `dependencies: { start: 'finish' }`. 278 | Means that if an instance has a property 'start', it must also have a property 'finish'. 279 | * Simple dependency tuple - an array of strings. 280 | The instance object must have a property with the same name as each string in the requirement's array. 281 | Example: `dependencies: { towel: [ 'toothbrush', 'soap', 'space suit' ]}`. 282 | Means that if an instance has a property 'towel', it must also have properties 'toothbrush', 'soap' and 'space suit'. 283 | * Schema dependency - a schema. 284 | The instance object must be valid against the schema. 285 | Example: `dependencies: { 'cuba-libre': { properties: { glass: { format: 'highball', required: true }}}}` 286 | Means that if an instance has a property 'cuba-libre', it must also have a property 'glass' with a 'highball' format. 287 | 288 | ### items 289 | 290 | Applies only to instances of type `'array'`. 291 | 292 | Defines the items of the instance array. 293 | It can take one of two forms: 294 | 295 | * Schema - all the items in the array must be valid against the schema. 296 | * Tuple typing - an array of schemas. 297 | Each position in the instance array must conform to the schema in the corresponding position for this array. 298 | The instance array is not required to contain all defined items. 299 | Additional items are allowed, disallowed, or constrained by the _additionalItems_ attribute. 300 | 301 | ### additionalItems 302 | 303 | Applies only to instances of type `'array'`, and only together with the _tuple typing_ form of the _items_ attribute. 304 | 305 | _additionalItems_ defines the behavior when there are more items in the instance array than in the _items_ array. 306 | It can take one of two forms: 307 | 308 | * Schema - all the additional items must be valid against the schema. 309 | * False - additional items are not allowed. 310 | 311 | Example - a string, an integer and the rest are booleans: 312 | 313 | { 314 | type: 'array', 315 | items: [ 316 | { type: 'string' }, 317 | { type: 'integer' } 318 | ], 319 | additionalItems: { type: 'boolean' } 320 | } 321 | 322 | The default is an empty schema, which allows additional items of any type. 323 | 324 | ### minItems 325 | 326 | Applies only to instances of type `'array'`. 327 | 328 | Defines the minimum number of values in an array. 329 | 330 | ### maxItems 331 | 332 | Applies only to instances of type `'array'`. 333 | 334 | Defines the maximum number of values in an array. 335 | 336 | ### uniqueItems 337 | 338 | Applies only to instances of type `'array'`. 339 | 340 | A boolean that indicates whether all items in the array instance must be unique (contains no two identical values). 341 | 342 | ### minLength 343 | 344 | Applies only to instances of type `'string'`. 345 | 346 | Defines the minimum length of the string. 347 | 348 | ### maxLength 349 | 350 | Applies only to instances of type `'string'`. 351 | 352 | Defines the maximum length of the string. 353 | 354 | ### pattern 355 | 356 | Applies only to instances of type `'string'`. 357 | 358 | A string containing a regular expression. 359 | The instance string must match it. 360 | 361 | Example: 362 | 363 | youtubeVideoId: { 364 | type: 'string', 365 | pattern: '^[A-Za-z0-9_-]{11}$' 366 | } 367 | 368 | ### minimum 369 | 370 | Applies only to instances of type `'number'`. 371 | 372 | Defines the minimum value of the instance property. 373 | 374 | ### exclusiveMinimum 375 | 376 | Applies only to instances of type `'number'`, and only together with the _minimum_ attribute. 377 | 378 | Defines the behavior of the _minimum_ attribute: 379 | 380 | * when true, _minimum_ is exclusive ("greater than") 381 | * when false, _minimum_ is inclusive ("greater than or equal") 382 | 383 | Example: 384 | 385 | rand: { 386 | type: 'number', 387 | minimum: 0, 388 | exclusiveMinimum: false, 389 | maximum: 1, 390 | exclusiveMaximum: true 391 | } 392 | 393 | The default is false. 394 | 395 | ### maximum 396 | 397 | Applies only to instances of type `'number'`. 398 | 399 | Defines the maximum value of the instance property. 400 | 401 | ### exclusiveMaximum 402 | 403 | Applies only to instances of type `'number'`, and only together with the _maximum_ attribute. 404 | 405 | Defines the behavior of the _maximum_ attribute: 406 | 407 | * when true, _maximum_ is exclusive ("less than") 408 | * when false, _maximum_ is inclusive ("less than or equal") 409 | 410 | Example: 411 | 412 | rand: { 413 | type: 'number', 414 | minimum: 0, 415 | exclusiveMinimum: false, 416 | maximum: 1, 417 | exclusiveMaximum: true 418 | } 419 | 420 | The default is false. 421 | 422 | ### divisibleBy 423 | 424 | Applies only to instances of type `'number'`. 425 | 426 | Defines what value the number instance must be divisible by with no remainder. 427 | This value may not be 0. 428 | 429 | ### format 430 | 431 | Applies only to instances of types `'string'` or `'number'`. 432 | 433 | Defines the expected instance format. 434 | 435 | Available formats: 436 | 437 | * date-time - A string containing a date in ISO 8601 format of YYYY-MM-DDThh:mm:ss[.fraction]Z in UTC time. 438 | The decimal fraction is optional and the decimal dot can be replaced with a comma. 439 | Example: `'2012-11-06T09:13:24Z'`. 440 | * date - A string containing a date in the format of YYYY-MM-DD. 441 | Example: `'2012-11-06'`. 442 | * time - A string containing a time in the format of hh:mm:ss. 443 | Example: `'09:13:24'`. 444 | * utc-millisec - A number or an integer containing the number of milliseconds that have elapsed since midnight UTC, 1 January 1970. 445 | * regex - A string containing a regular expression, following the regular expression specification from ECMA 262. 446 | Example: `'^[0-9]{5}-[0-9]{4}$'`. 447 | * color - A string containing a CSS color, based on CSS 2.1 [W3C.CR-CSS21-20070719]. 448 | Examples: `'red'`, `'#FF9900'`, `'f90'`, `'rgb(64, 224, 208)'`, `'rgb(100%, 0%, 25%)'`. 449 | * phone - A string containing a national or international phone number, based on E.123. 450 | No hypens allows, only spaces. 451 | Examples: `'(42) 123 4567'`, `'+31 42 123 4567'`. 452 | * uri - A string containing a URI. 453 | Example: `'https://github.com/oferei/json-gate'`. 454 | * email - A string containing an email address. 455 | Example: `'thepope@gmail.com'`. 456 | * ip-address - A string containing an ip version 4 address. 457 | Example: `'192.168.1.1'`. 458 | * ipv6 - A string containing an ip version 6 address. 459 | Example: `'2001:0db8:85a3:0042:0000:8a2e:0370:7334'`. 460 | * host-name - A string containing a host-name. 461 | Example: `'github.com'`. 462 | 463 | Note: Unknown formats are silently ignored. 464 | 465 | ### title 466 | 467 | A string that provides a short description of instance property. 468 | 469 | It allows to document the JSON schema. 470 | It has no effect on the validation process. 471 | 472 | Example: 473 | 474 | postalPlusFourCode: { 475 | title: 'ZIP+4 code', 476 | description: 'Zip+4 code: 5 digits dash 4 digits', 477 | type: 'string', 478 | pattern: '^[0-9]{5}-[0-9]{4}$' 479 | } 480 | 481 | ### description 482 | 483 | A string that provides a full description of the purpose of the instance property. 484 | 485 | It allows to document the JSON schema. 486 | It has no effect on the validation process. 487 | 488 | Example: 489 | 490 | { 491 | description: 'A person', 492 | type: 'object', 493 | properties: { 494 | name: { type: 'string' }, 495 | age: { type: 'integer' } 496 | } 497 | } 498 | 499 | ## License 500 | 501 | (The MIT License) 502 | 503 | Copyright (c) 2012 Ofer Reichman 504 | 505 | Permission is hereby granted, free of charge, to any person obtaining 506 | a copy of this software and associated documentation files (the 507 | 'Software'), to deal in the Software without restriction, including 508 | without limitation the rights to use, copy, modify, merge, publish, 509 | distribute, sublicense, and/or sell copies of the Software, and to 510 | permit persons to whom the Software is furnished to do so, subject to 511 | the following conditions: 512 | 513 | The above copyright notice and this permission notice shall be 514 | included in all copies or substantial portions of the Software. 515 | 516 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 517 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 518 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 519 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 520 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 521 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 522 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 523 | -------------------------------------------------------------------------------- /lib/common.js: -------------------------------------------------------------------------------- 1 | exports.getType = function (obj) { 2 | switch (Object.prototype.toString.call(obj)) { 3 | case '[object String]': 4 | return 'string'; 5 | case '[object Number]': 6 | return (obj % 1 === 0) ? 'integer' : 'number'; 7 | case '[object Boolean]': 8 | return 'boolean'; 9 | case '[object Object]': 10 | return 'object'; 11 | case '[object Array]': 12 | return 'array'; 13 | case '[object Null]': 14 | return 'null'; 15 | default: 16 | return 'undefined'; 17 | } 18 | } 19 | 20 | exports.prettyType = function(type) { 21 | switch (type) { 22 | case 'string': 23 | case 'number': 24 | case 'boolean': 25 | return 'a ' + type; 26 | case 'integer': 27 | case 'object': 28 | case 'array': 29 | return 'an ' + type; 30 | case 'null': 31 | return 'null'; 32 | case 'any': 33 | return 'any type'; 34 | case 'undefined': 35 | return 'undefined'; 36 | default: 37 | if (typeof type === 'object') { 38 | return 'a schema' 39 | } else { 40 | return type; 41 | } 42 | } 43 | } 44 | 45 | 46 | exports.isOfType = function (obj, type) { 47 | switch (type) { 48 | case 'string': 49 | case 'number': 50 | case 'boolean': 51 | case 'object': 52 | case 'array': 53 | case 'null': 54 | type = type.charAt(0).toUpperCase() + type.slice(1); 55 | return Object.prototype.toString.call(obj) === '[object ' + type + ']'; 56 | case 'integer': 57 | return Object.prototype.toString.call(obj) === '[object Number]' && obj % 1 === 0; 58 | case 'any': 59 | default: 60 | return true; 61 | } 62 | } 63 | 64 | exports.getName = function (names) { 65 | return names.length === 0 ? '' : ' property \'' + names.join('.') + '\''; 66 | }; 67 | 68 | exports.deepEquals = function (obj1, obj2) { 69 | var p; 70 | 71 | if (Object.prototype.toString.call(obj1) !== Object.prototype.toString.call(obj2)) { 72 | return false; 73 | } 74 | 75 | switch (typeof obj1) { 76 | case 'object': 77 | if (obj1.toString() !== obj2.toString()) { 78 | return false; 79 | } 80 | for (p in obj1) { 81 | if (!(p in obj2)) { 82 | return false; 83 | } 84 | if (!exports.deepEquals(obj1[p], obj2[p])) { 85 | return false; 86 | } 87 | } 88 | for (p in obj2) { 89 | if (!(p in obj1)) { 90 | return false; 91 | } 92 | } 93 | return true; 94 | case 'function': 95 | return obj1[p].toString() === obj2[p].toString(); 96 | default: 97 | return obj1 === obj2; 98 | } 99 | }; 100 | -------------------------------------------------------------------------------- /lib/formats.js: -------------------------------------------------------------------------------- 1 | var RE_0_TO_100 = '([1-9]?[0-9]|100)'; 2 | var RE_0_TO_255 = '([1-9]?[0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'; 3 | 4 | function validateFormatUtcMillisec(obj) { 5 | return obj >= 0; 6 | } 7 | 8 | function validateFormatRegExp(obj) { 9 | try { 10 | var re = RegExp(obj); 11 | return true; 12 | } catch(err) { 13 | return false; 14 | } 15 | } 16 | 17 | var COLORS = ['aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple', 'red', 'silver', 'teal', 'white', 'yellow']; 18 | var colorsReHex3 = /^#[0-9A-Fa-f]{3}$/; // #rgb 19 | var colorsReHex6 = /^#[0-9A-Fa-f]{6}$/; // #rrggbb 20 | var colorsReRgbNum = RegExp('^rgb\\(\\s*' + RE_0_TO_255 + '(\\s*,\\s*' + RE_0_TO_255 + '\\s*){2}\\)$'); // rgb(255, 0, 128) 21 | var colorsReRgbPerc = RegExp('^rgb\\(\\s*' + RE_0_TO_100 + '%(\\s*,\\s*' + RE_0_TO_100 + '%\\s*){2}\\)$'); // rgb(100%, 0%, 50%) 22 | 23 | function validateFormatColor(obj) { 24 | return COLORS.indexOf(obj) !== -1 || obj.match(colorsReHex3) || obj.match(colorsReHex6) 25 | || obj.match(colorsReRgbNum) || obj.match(colorsReRgbPerc); 26 | } 27 | 28 | var phoneReNational = /^(\(\d+\)|\d+)( \d+)*$/; 29 | var phoneReInternational = /^\+\d+( \d+)*$/; 30 | 31 | function validateFormatPhone(obj) { 32 | return obj.match(phoneReNational) || obj.match(phoneReInternational); 33 | } 34 | 35 | var formats = { 36 | 'date-time': { // ISO 8601 (YYYY-MM-DDThh:mm:ssZ in UTC time) 37 | types: ['string'], 38 | regex: /^(\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?))?$/ 39 | }, 40 | 'date': { // YYYY-MM-DD 41 | types: ['string'], 42 | regex: /^\d{4}-\d{2}-\d{2}$/ 43 | }, 44 | 'time': { // hh:mm:ss 45 | types: ['string'], 46 | regex: /^[0-2]\d:[0-5]\d:[0-5]\d$/ 47 | }, 48 | 'utc-millisec': { 49 | types: ['number', 'integer'], 50 | func: validateFormatUtcMillisec 51 | }, 52 | 'regex': { // ECMA 262/Perl 5 53 | types: ['string'], 54 | func: validateFormatRegExp 55 | }, 56 | 'color': { // W3C.CR-CSS21-20070719 57 | types: ['string'], 58 | func: validateFormatColor 59 | }, 60 | /* TODO: support style 61 | * style - A string containing a CSS style definition, based on CSS 2.1 [W3C.CR-CSS21-20070719]. 62 | Example: `'color: red; background-color:#FFF'`. 63 | 64 | 'style': { // W3C.CR-CSS21-20070719 65 | types: ['string'], 66 | func: validateFormatStyle 67 | },*/ 68 | 'phone': { // E.123 69 | types: ['string'], 70 | func: validateFormatPhone 71 | }, 72 | 'uri': { 73 | types: ['string'], 74 | regex: RegExp("^([a-z][a-z0-9+.-]*):(?://(?:((?:[a-z0-9-._~!$&'()*+,;=:]|%[0-9A-F]{2})*)@)?((?:[a-z0-9-._~!$&'()*+,;=]|%[0-9A-F]{2})*)(?::(\\d*))?(/(?:[a-z0-9-._~!$&'()*+,;=:@/]|%[0-9A-F]{2})*)?|(/?(?:[a-z0-9-._~!$&'()*+,;=:@]|%[0-9A-F]{2})+(?:[a-z0-9-._~!$&'()*+,;=:@/]|%[0-9A-F]{2})*)?)(?:\\?((?:[a-z0-9-._~!$&'()*+,;=:/?@]|%[0-9A-F]{2})*))?(?:#((?:[a-z0-9-._~!$&'()*+,;=:/?@]|%[0-9A-F]{2})*))?$", 'i') 75 | }, 76 | 'email': { 77 | types: ['string'], 78 | regex: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i 79 | }, 80 | 'ip-address': { 81 | types: ['string'], 82 | regex: RegExp('^' + RE_0_TO_255 + '(\\.' + RE_0_TO_255 + '){3}$') 83 | }, 84 | 'ipv6': { 85 | types: ['string'], 86 | regex: /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i 87 | }, 88 | 'host-name': { 89 | types: ['string'], 90 | regex: /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/ 91 | } 92 | }; 93 | 94 | exports.formats = formats; 95 | -------------------------------------------------------------------------------- /lib/json-gate.js: -------------------------------------------------------------------------------- 1 | var validateSchema = require('./valid-schema'), 2 | validateObject = require('./valid-object'); 3 | 4 | var Schema = function(schema) { 5 | this.schema = schema; 6 | validateSchema(schema); 7 | 8 | this.validate = function(obj, done) { 9 | validateObject(obj, schema, done); 10 | } 11 | } 12 | 13 | module.exports.createSchema = function (schema) { 14 | return new Schema(schema); 15 | } 16 | -------------------------------------------------------------------------------- /lib/valid-object.js: -------------------------------------------------------------------------------- 1 | var formats = require('./formats').formats; 2 | var common = require('./common'), 3 | getType = common.getType, 4 | prettyType = common.prettyType, 5 | isOfType = common.isOfType, 6 | getName = common.getName, 7 | deepEquals = common.deepEquals; 8 | 9 | function throwInvalidValue(names, value, expected) { 10 | throw new Error('JSON object' + getName(names) + ' is ' + value + ' when it should be ' + expected); 11 | } 12 | 13 | function throwInvalidAttributeValue(names, attribFullName, value, expected) { 14 | throw new Error('JSON object' + getName(names) + ': ' + attribFullName + ' is ' + value + ' when it should be ' + expected); 15 | } 16 | 17 | function throwInvalidType(names, value, expected) { 18 | throw new Error('JSON object' + getName(names) + ' is ' + prettyType(getType(value)) + ' when it should be ' + expected); 19 | } 20 | 21 | function throwInvalidDisallow(names, value, expected) { 22 | throw new Error('JSON object' + getName(names) + ' is ' + prettyType(getType(value)) + ' when it should not be ' + expected); 23 | } 24 | 25 | function validateRequired(obj, schema, names) { 26 | //console.log('***', names, 'validateRequired'); 27 | if (schema.required) { 28 | if (obj === undefined) { 29 | throw new Error('JSON object' + getName(names) + ' is required'); 30 | } 31 | } 32 | } 33 | 34 | function applyDefault(obj, schema, names) { 35 | //console.log('***', names, 'applyDefault'); 36 | if (schema.default !== undefined) { 37 | obj = schema.default; 38 | } 39 | 40 | return obj; 41 | } 42 | 43 | function validateType(obj, schema, names) { 44 | //console.log('***', names, 'validateType'); 45 | if (schema.type !== undefined) { 46 | switch (getType(schema.type)) { 47 | case 'string': 48 | // simple type 49 | if (!isOfType(obj, schema.type)) { 50 | throwInvalidType(names, obj, prettyType(schema.type)); 51 | } 52 | break; 53 | case 'array': 54 | // union type 55 | for (var i = 0; i < schema.type.length; ++i) { 56 | switch (getType(schema.type[i])) { 57 | case 'string': 58 | // simple type (inside union type) 59 | if (isOfType(obj, schema.type[i])) { 60 | return; // success 61 | } 62 | break; 63 | case 'object': 64 | // schema (inside union type) 65 | try { 66 | return validateSchema(obj, schema.type[i], names); 67 | } catch(err) { 68 | // validation failed 69 | // TOOD: consider propagating error message upwards 70 | } 71 | break; 72 | } 73 | } 74 | throwInvalidType(names, obj, 'either ' + schema.type.map(prettyType).join(' or ')); 75 | break; 76 | } 77 | } 78 | } 79 | 80 | function validateDisallow(obj, schema, names) { 81 | //console.log('***', names, 'validateDisallow'); 82 | if (schema.disallow !== undefined) { 83 | switch (getType(schema.disallow)) { 84 | case 'string': 85 | // simple type 86 | if (isOfType(obj, schema.disallow)) { 87 | throwInvalidDisallow(names, obj, prettyType(schema.disallow)); 88 | } 89 | break; 90 | case 'array': 91 | // union type 92 | for (var i = 0; i < schema.disallow.length; ++i) { 93 | switch (getType(schema.disallow[i])) { 94 | case 'string': 95 | // simple type (inside union type) 96 | if (isOfType(obj, schema.disallow[i])) { 97 | throwInvalidType(names, obj, 'neither ' + schema.disallow.map(prettyType).join(' nor ')); 98 | } 99 | break; 100 | case 'object': 101 | // schema (inside union type) 102 | try { 103 | validateSchema(obj, schema.disallow[i], names); 104 | } catch(err) { 105 | // validation failed 106 | continue; 107 | } 108 | throwInvalidType(names, obj, 'neither ' + schema.disallow.map(prettyType).join(' nor ')); 109 | // TOOD: consider propagating error message upwards 110 | break; 111 | } 112 | } 113 | break; 114 | } 115 | } 116 | } 117 | 118 | function validateEnum(obj, schema, names) { 119 | //console.log('***', names, 'validateEnum'); 120 | if (schema['enum'] !== undefined) { 121 | for (var i = 0; i < schema['enum'].length; ++i) { 122 | if (deepEquals(obj, schema['enum'][i])) { 123 | return; 124 | } 125 | } 126 | throw new Error('JSON object' + getName(names) + ' is not in enum'); 127 | } 128 | } 129 | 130 | function validateArray(obj, schema, names) { 131 | //console.log('***', names, 'validateArray'); 132 | var i, j; 133 | 134 | if (schema.minItems !== undefined) { 135 | if (obj.length < schema.minItems) { 136 | throwInvalidAttributeValue(names, 'number of items', obj.length, 'at least ' + schema.minItems); 137 | } 138 | } 139 | 140 | if (schema.maxItems !== undefined) { 141 | if (obj.length > schema.maxItems) { 142 | throwInvalidAttributeValue(names, 'number of items', obj.length, 'at most ' + schema.maxItems); 143 | } 144 | } 145 | 146 | if (schema.items !== undefined) { 147 | switch (getType(schema.items)) { 148 | case 'object': 149 | // all the items in the array MUST be valid according to the schema 150 | for (i = 0; i < obj.length; ++i) { 151 | obj[i] = validateSchema(obj[i], schema.items, names.concat([ '['+i+']' ])); 152 | } 153 | break; 154 | case 'array': 155 | // each position in the instance array MUST conform to the schema in the corresponding position for this array 156 | var numChecks = Math.min(obj.length, schema.items.length); 157 | for (i = 0; i < numChecks; ++i) { 158 | obj[i] = validateSchema(obj[i], schema.items[i], names.concat([ '['+i+']' ])); 159 | } 160 | if (obj.length > schema.items.length) { 161 | if (schema.additionalItems !== undefined) { 162 | if (schema.additionalItems === false) { 163 | throwInvalidAttributeValue(names, 'number of items', obj.length, 'at most ' + schema.items.length + ' - the length of schema items'); 164 | } 165 | for (; i < obj.length; ++i) { 166 | obj[i] = validateSchema(obj[i], schema.additionalItems, names.concat([ '['+i+']' ])); 167 | } 168 | } 169 | } 170 | break; 171 | } 172 | } 173 | 174 | if (schema.uniqueItems !== undefined) { 175 | for (i = 0; i < obj.length - 1; ++i) { 176 | for (j = i + 1; j < obj.length; ++j) { 177 | if (deepEquals(obj[i], obj[j])) { 178 | throw new Error('JSON object' + getName(names) + ' items are not unique: element ' + i + ' equals element ' + j); 179 | } 180 | } 181 | } 182 | } 183 | } 184 | 185 | function validateObject(obj, schema, names) { 186 | //console.log('***', names, 'validateObject'); 187 | var prop, property; 188 | if (schema.properties !== undefined) { 189 | for (property in schema.properties) { 190 | prop = validateSchema(obj[property], schema.properties[property], names.concat([property])); 191 | if (prop === undefined) { 192 | delete obj[property]; 193 | } else { 194 | obj[property] = prop; 195 | } 196 | } 197 | } 198 | 199 | var matchingProperties = {}; 200 | if (schema.patternProperties !== undefined) { 201 | for (var reStr in schema.patternProperties) { 202 | var re = RegExp(reStr); 203 | for (property in obj) { 204 | if (property.match(re)) { 205 | matchingProperties[property] = true; 206 | prop = validateSchema(obj[property], schema.patternProperties[reStr], names.concat(['patternProperties./' + property + '/'])); 207 | if (prop === undefined) { 208 | delete obj[property]; 209 | } else { 210 | obj[property] = prop; 211 | } 212 | } 213 | } 214 | } 215 | } 216 | 217 | if (schema.additionalProperties !== undefined) { 218 | for (property in obj) { 219 | if (schema.properties !== undefined && property in schema.properties) { 220 | continue; 221 | } 222 | if (property in matchingProperties) { 223 | continue; 224 | } 225 | // additional 226 | if (schema.additionalProperties === false) { 227 | throw new Error('JSON object' + getName(names.concat([property])) + ' is not explicitly defined and therefore not allowed'); 228 | } 229 | obj[property] = validateSchema(obj[property], schema.additionalProperties, names.concat([property])); 230 | } 231 | } 232 | 233 | if (schema.dependencies !== undefined) { 234 | for (property in schema.dependencies) { 235 | switch (getType(schema.dependencies[property])) { 236 | case 'string': 237 | // simple dependency 238 | if (property in obj && !(schema.dependencies[property] in obj)) { 239 | throw new Error('JSON object' + getName(names.concat([schema.dependencies[property]])) + ' is required by property \'' + property + '\''); 240 | } 241 | break; 242 | case 'array': 243 | // simple dependency tuple 244 | for (var i = 0; i < schema.dependencies[property].length; ++i) { 245 | if (property in obj && !(schema.dependencies[property][i] in obj)) { 246 | throw new Error('JSON object' + getName(names.concat([schema.dependencies[property][i]])) + ' is required by property \'' + property + '\''); 247 | } 248 | } 249 | break; 250 | case 'object': 251 | // schema dependency 252 | validateSchema(obj, schema.dependencies[property], names.concat([ '[dependencies.'+property+']' ])); 253 | break; 254 | } 255 | } 256 | } 257 | } 258 | 259 | function validateNumber(obj, schema, names) { 260 | //console.log('***', names, 'validateNumber'); 261 | 262 | if (schema.minimum !== undefined) { 263 | if (schema.exclusiveMinimum ? obj <= schema.minimum : obj < schema.minimum) { 264 | throwInvalidValue(names, obj, (schema.exclusiveMinimum ? 'greater than' : 'at least') + ' ' + schema.minimum); 265 | } 266 | } 267 | 268 | if (schema.maximum !== undefined) { 269 | if (schema.exclusiveMaximum ? obj >= schema.maximum : obj > schema.maximum) { 270 | throwInvalidValue(names, obj, (schema.exclusiveMaximum ? 'less than' : 'at most') + ' ' + schema.maximum); 271 | } 272 | } 273 | 274 | if (schema.divisibleBy !== undefined) { 275 | if (!isOfType(obj / schema.divisibleBy, 'integer')) { 276 | throwInvalidValue(names, obj, 'divisible by ' + schema.divisibleBy); 277 | } 278 | } 279 | } 280 | 281 | function validateString(obj, schema, names) { 282 | //console.log('***', names, 'validateString'); 283 | 284 | if (schema.minLength !== undefined) { 285 | if (obj.length < schema.minLength) { 286 | throwInvalidAttributeValue(names, 'length', obj.length, 'at least ' + schema.minLength); 287 | } 288 | } 289 | 290 | if (schema.maxLength !== undefined) { 291 | if (obj.length > schema.maxLength) { 292 | throwInvalidAttributeValue(names, 'length', obj.length, 'at most ' + schema.maxLength); 293 | } 294 | } 295 | 296 | if (schema.pattern !== undefined) { 297 | if (!obj.match(RegExp(schema.pattern))) { 298 | throw new Error('JSON object' + getName(names) + ' does not match pattern'); 299 | } 300 | } 301 | } 302 | 303 | function validateFormat(obj, schema, names) { 304 | //console.log('***', names, 'validateFormat'); 305 | if (schema.format !== undefined) { 306 | var format = formats[schema.format]; 307 | if (format !== undefined) { 308 | var conforms = true; 309 | if (format.regex) { 310 | conforms = obj.match(format.regex); 311 | } else if (format.func) { 312 | conforms = format.func(obj); 313 | } 314 | if (!conforms) { 315 | throw new Error('JSON object' + getName(names) + ' does not conform to the \'' + schema.format + '\' format'); 316 | } 317 | } 318 | } 319 | } 320 | 321 | function validateItem(obj, schema, names) { 322 | //console.log('***', names, 'validateItem'); 323 | switch (getType(obj)) { 324 | case 'number': 325 | case 'integer': 326 | validateNumber(obj, schema, names); 327 | break; 328 | case 'string': 329 | validateString(obj, schema, names); 330 | break; 331 | } 332 | 333 | validateFormat(obj, schema, names); 334 | } 335 | 336 | function validateSchema(obj, schema, names) { 337 | //console.log('***', names, 'validateSchema'); 338 | 339 | validateRequired(obj, schema, names); 340 | if (obj === undefined) { 341 | obj = applyDefault(obj, schema, names); 342 | } 343 | if (obj !== undefined) { 344 | validateType(obj, schema, names); 345 | validateDisallow(obj, schema, names); 346 | validateEnum(obj, schema, names); 347 | 348 | switch (getType(obj)) { 349 | case 'object': 350 | validateObject(obj, schema, names); 351 | break; 352 | case 'array': 353 | validateArray(obj, schema, names); 354 | break; 355 | default: 356 | validateItem(obj, schema, names); 357 | } 358 | } 359 | 360 | return obj; 361 | } 362 | 363 | // Two operation modes: 364 | // * Synchronous - done callback is not provided. will return nothing or throw error 365 | // * Asynchronous - done callback is provided. will not throw error. 366 | // will call callback with error as first parameter and object as second 367 | // Schema is expected to be validated. 368 | module.exports = function(obj, schema, done) { 369 | try { 370 | validateSchema(obj, schema, []); 371 | } catch(err) { 372 | if (done) { 373 | done(err); 374 | return; 375 | } else { 376 | throw err; 377 | } 378 | } 379 | 380 | if (done) { 381 | done(null, obj); 382 | } 383 | }; 384 | -------------------------------------------------------------------------------- /lib/valid-schema.js: -------------------------------------------------------------------------------- 1 | var formats = require('./formats').formats; 2 | var common = require('./common'), 3 | getType = common.getType, 4 | prettyType = common.prettyType, 5 | isOfType = common.isOfType, 6 | getName = common.getName, 7 | validateObjectVsSchema = require('./valid-object'); 8 | 9 | function throwInvalidType(names, attribFullName, value, expected) { 10 | throw new Error('Schema' + getName(names) + ': ' + attribFullName + ' is ' + prettyType(getType(value)) + ' when it should be ' + expected); 11 | } 12 | 13 | function assertType(schema, attribName, expectedType, names) { 14 | if (schema[attribName] !== undefined) { 15 | if (!isOfType(schema[attribName], expectedType)) { 16 | throwInvalidType(names, '\'' + attribName + '\' attribute', schema[attribName], prettyType(expectedType)); 17 | } 18 | } 19 | } 20 | 21 | function validateRequired(schema, names) { 22 | assertType(schema, 'required', 'boolean', names); 23 | } 24 | 25 | function validateDefault(schema, names) { 26 | if (schema.default !== undefined) { 27 | try { 28 | validateObjectVsSchema(schema.default, schema); 29 | } catch(err) { 30 | throw new Error('Schema' + getName(names) + ': \'default\' attribute value is not valid according to the schema: ' + err.message); 31 | } 32 | } 33 | } 34 | 35 | function validateType(schema, names) { 36 | if (schema.type !== undefined) { 37 | switch (getType(schema.type)) { 38 | case 'string': 39 | // simple type - nothing to validate 40 | break; 41 | case 'array': 42 | // union type 43 | if (schema.type.length < 2) { 44 | throw new Error('Schema' + getName(names) + ': \'type\' attribute union length is ' + schema.type.length + ' when it should be at least 2'); 45 | } 46 | for (var i = 0; i < schema.type.length; ++i) { 47 | switch (getType(schema.type[i])) { 48 | case 'string': 49 | // simple type (inside union type) - nothing to validate 50 | break; 51 | case 'object': 52 | // schema (inside union type) 53 | try { 54 | validateSchema(schema.type[i], []); 55 | } catch(err) { 56 | throw new Error('Schema' + getName(names) + ': \'type\' attribute union element ' + i + ' is not a valid schema: ' + err.message); 57 | } 58 | break; 59 | default: 60 | throwInvalidType(names, '\'type\' attribute union element ' + i, schema.type[i], 'either an object (schema) or a string'); 61 | } 62 | } 63 | break; 64 | default: 65 | throwInvalidType(names, '\'type\' attribute', schema.type, 'either a string or an array'); 66 | } 67 | } 68 | } 69 | 70 | function validateDisallow(schema, names) { 71 | if (schema.disallow !== undefined) { 72 | switch (getType(schema.disallow)) { 73 | case 'string': 74 | // simple type - nothing to validate 75 | break; 76 | case 'array': 77 | // union type 78 | if (schema.disallow.length < 2) { 79 | throw new Error('Schema' + getName(names) + ': \'disallow\' attribute union length is ' + schema.disallow.length + ' when it should be at least 2'); 80 | } 81 | for (var i = 0; i < schema.disallow.length; ++i) { 82 | switch (getType(schema.disallow[i])) { 83 | case 'string': 84 | // simple type (inside union type) - nothing to validate 85 | break; 86 | case 'object': 87 | // schema (inside union type) 88 | try { 89 | validateSchema(schema.disallow[i], []); 90 | } catch(err) { 91 | throw new Error('Schema' + getName(names) + ': \'disallow\' attribute union element ' + i + ' is not a valid schema: ' + err.message); 92 | } 93 | break; 94 | default: 95 | throwInvalidType(names, '\'disallow\' attribute union element ' + i, schema.disallow[i], 'either an object (schema) or a string'); 96 | } 97 | } 98 | break; 99 | default: 100 | throwInvalidType(names, '\'disallow\' attribute', schema.disallow, 'either a string or an array'); 101 | } 102 | } 103 | } 104 | 105 | function validateEnum(schema, names) { 106 | assertType(schema, 'enum', 'array', names); 107 | } 108 | 109 | function validateArray(schema, names) { 110 | assertType(schema, 'minItems', 'integer', names); 111 | assertType(schema, 'maxItems', 'integer', names); 112 | 113 | if (schema.items !== undefined) { 114 | var i; 115 | switch (getType(schema.items)) { 116 | case 'object': 117 | // all the items in the array MUST be valid according to the schema 118 | try { 119 | validateSchema(schema.items, []); 120 | } catch(err) { 121 | throw new Error('Schema' + getName(names) + ': \'items\' attribute is not a valid schema: ' + err.message); 122 | } 123 | break; 124 | case 'array': 125 | // each position in the instance array MUST conform to the schema in the corresponding position for this array 126 | for (i = 0; i < schema.items.length; ++i) { 127 | try { 128 | validateSchema(schema.items[i], []); 129 | } catch(err) { 130 | throw new Error('Schema' + getName(names) + ': \'items\' attribute element ' + i + ' is not a valid schema: ' + err.message); 131 | } 132 | } 133 | break; 134 | default: 135 | throwInvalidType(names, '\'items\' attribute', schema.items, 'either an object (schema) or an array'); 136 | } 137 | } 138 | 139 | if (schema.additionalItems !== undefined) { 140 | if (schema.additionalItems === false) { 141 | // ok 142 | } else if (!isOfType(schema.additionalItems, 'object')) { 143 | throwInvalidType(names, '\'additionalItems\' attribute', schema.additionalItems, 'either an object (schema) or false'); 144 | } else { 145 | try { 146 | validateSchema(schema.additionalItems, []); 147 | } catch(err) { 148 | throw new Error('Schema' + getName(names) + ': \'additionalItems\' attribute is not a valid schema: ' + err.message); 149 | } 150 | } 151 | } 152 | 153 | assertType(schema, 'uniqueItems', 'boolean', names); 154 | } 155 | 156 | function validateObject(schema, names) { 157 | assertType(schema, 'properties', 'object', names); 158 | if (schema.properties !== undefined) { 159 | for (var property in schema.properties) { 160 | validateSchema(schema.properties[property], names.concat([property])); 161 | } 162 | } 163 | 164 | assertType(schema, 'patternProperties', 'object', names); 165 | if (schema.patternProperties !== undefined) { 166 | for (var reStr in schema.patternProperties) { 167 | validateSchema(schema.patternProperties[reStr], names.concat(['patternProperties./' + reStr + '/'])); 168 | } 169 | } 170 | 171 | if (schema.additionalProperties !== undefined) { 172 | if (schema.additionalProperties === false) { 173 | // ok 174 | } else if (!isOfType(schema.additionalProperties, 'object')) { 175 | throwInvalidType(names, '\'additionalProperties\' attribute', schema.additionalProperties, 'either an object (schema) or false'); 176 | } else { 177 | try { 178 | validateSchema(schema.additionalProperties, []); 179 | } catch(err) { 180 | throw new Error('Schema' + getName(names) + ': \'additionalProperties\' attribute is not a valid schema: ' + err.message); 181 | } 182 | } 183 | } 184 | 185 | assertType(schema, 'dependencies', 'object', names); 186 | if (schema.dependencies !== undefined) { 187 | for (var property in schema.dependencies) { 188 | switch (getType(schema.dependencies[property])) { 189 | case 'string': 190 | // simple dependency - nothing to validate 191 | break; 192 | case 'array': 193 | // simple dependency tuple 194 | for (var i = 0; i < schema.dependencies[property].length; ++i) { 195 | if (isOfType(schema.dependencies[property][i], 'string')) { 196 | // simple dependency (inside array) - nothing to validate 197 | } else { 198 | throwInvalidType(names, '\'dependencies\' attribute: value of property \'' + property + '\' element ' + i, schema.dependencies[property][i], 'a string'); 199 | } 200 | } 201 | break; 202 | case 'object': 203 | // schema dependency 204 | try { 205 | validateSchema(schema.dependencies[property], []); 206 | } catch(err) { 207 | throw new Error('Schema' + getName(names) + ': \'dependencies\' attribute: value of property \'' + property + '\' is not a valid schema: ' + err.message); 208 | } 209 | break; 210 | default: 211 | throwInvalidType(names, '\'dependencies\' attribute: value of property \'' + property + '\'', schema.dependencies[property], 'either a string, an array or an object (schema)'); 212 | } 213 | } 214 | } 215 | } 216 | 217 | function validateNumber(schema, names) { 218 | assertType(schema, 'minimum', 'number', names); 219 | assertType(schema, 'exclusiveMinimum', 'boolean', names); 220 | assertType(schema, 'maximum', 'number', names); 221 | assertType(schema, 'exclusiveMaximum', 'boolean', names); 222 | assertType(schema, 'divisibleBy', 'number', names); 223 | if (schema.divisibleBy !== undefined) { 224 | if (schema.divisibleBy === 0) { 225 | throw new Error('Schema' + getName(names) + ': \'divisibleBy\' attribute must not be 0'); 226 | } 227 | } 228 | }; 229 | 230 | function validateString(schema, names) { 231 | assertType(schema, 'minLength', 'integer', names); 232 | assertType(schema, 'maxLength', 'integer', names); 233 | assertType(schema, 'pattern', 'string', names); 234 | } 235 | 236 | function validateFormat(schema, names) { 237 | assertType(schema, 'format', 'string', names); 238 | 239 | if (schema.format !== undefined) { 240 | if (schema.format in formats) { 241 | if (formats[schema.format].types.indexOf(schema.type) === -1) { 242 | throw new Error('Schema' + getName(names) + ': \'type\' attribute does not conform to the \'' + schema.format + '\' format'); 243 | } 244 | } 245 | } 246 | } 247 | 248 | function validateItem(schema, names) { 249 | validateNumber(schema, names); 250 | validateString(schema, names); 251 | validateFormat(schema, names); 252 | } 253 | 254 | function validateSchema(schema, names) { 255 | if (!isOfType(schema, 'object')) { 256 | throw new Error('Schema' + getName(names) + ' is ' + prettyType(getType(schema)) + ' when it should be an object'); 257 | } 258 | validateRequired(schema, names); 259 | validateType(schema, names); 260 | validateDisallow(schema, names); 261 | validateEnum(schema, names); 262 | validateObject(schema, names); 263 | validateArray(schema, names); 264 | validateItem(schema, names); 265 | // defaults are applied last after schema is validated 266 | validateDefault(schema, names); 267 | } 268 | 269 | module.exports = function(schema) { 270 | if (schema === undefined) { 271 | throw new Error('Schema is undefined'); 272 | } 273 | 274 | // validate schema parameters for object root 275 | if (!isOfType(schema, 'object')) { 276 | throw new Error('Schema is ' + prettyType(getType(schema)) + ' when it should be an object'); 277 | } 278 | 279 | validateSchema(schema, []); 280 | }; 281 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-gate", 3 | "description": "A friendly, fast JSON schema validator", 4 | "version": "0.8.23", 5 | "author": { 6 | "name": "Ofer Reichman", 7 | "email": "oferei@gmail.com" 8 | }, 9 | "contributors": [ 10 | { 11 | "name": "Ofer Reichman", 12 | "email": "oferei@gmail.com" 13 | }, 14 | { 15 | "name": "Jesse Thompson" 16 | }, 17 | { 18 | "name": "Nicolas Pelletier" 19 | }, 20 | { 21 | "name": "Damien (feugy)" 22 | }, 23 | { 24 | "name": "chimmelb" 25 | } 26 | ], 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/oferei/json-gate.git" 30 | }, 31 | "bugs": { 32 | "url" : "https://github.com/oferei/json-gate/issues" 33 | }, 34 | "dependencies": { 35 | }, 36 | "keywords": [ 37 | "json", 38 | "schema", 39 | "validator", 40 | "validate", 41 | "assert" 42 | ], 43 | "main": "lib/json-gate.js", 44 | "scripts": { 45 | "test": "node_modules\\.bin\\vows --spec" 46 | }, 47 | "devDependencies": { 48 | "should": "*", 49 | "vows": "*", 50 | "JSV": "*" 51 | }, 52 | "engines": { 53 | "node": "*" 54 | }, 55 | "license": "MIT" 56 | } 57 | -------------------------------------------------------------------------------- /test/async-error-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'), 4 | should = require('should'); 5 | 6 | var createSchema = require('..').createSchema, 7 | config = require('./config'); 8 | 9 | var schemaSimple = { 10 | type: 'string' 11 | }; 12 | 13 | vows.describe('Async Error').addBatch({ 14 | 'when object is valid and callback does not throw an error': { 15 | topic: function () { 16 | var schema = createSchema(schemaSimple); 17 | var count = 0; 18 | try { 19 | schema.validate('a string', function nice() { 20 | ++count; 21 | if (count === 1) { 22 | // do nothing, play nice 23 | } else { 24 | this.callback(new Error('Callback was called twice')); 25 | } 26 | }.bind(this)); 27 | this.callback(); 28 | } catch(err) { 29 | this.callback(err); 30 | } 31 | }, 32 | 'we get no error': function (err, result) { 33 | if (err) { 34 | console.error(err.toString()); 35 | } 36 | should.not.exist(err); 37 | should.not.exist(result); 38 | } 39 | }, 40 | 'when object is valid and callback throws an error': { 41 | topic: function () { 42 | var schema = createSchema(schemaSimple); 43 | var count = 0; 44 | try { 45 | schema.validate('a string', function naughty() { 46 | ++count; 47 | if (count === 1) { 48 | throw new Error('I am a naughty callback function'); 49 | } else { 50 | this.callback(new Error('Callback was called twice')); 51 | } 52 | }.bind(this)); 53 | this.callback(new Error('we did not get an error')); 54 | } catch(err) { 55 | // error is expected 56 | this.callback(); 57 | } 58 | }, 59 | 'we get an error': function (err, result) { 60 | if (err) { 61 | console.error(err.toString()); 62 | } 63 | should.not.exist(err); 64 | should.not.exist(result); 65 | } 66 | }, 67 | 'when object is invalid and callback throws an error': { 68 | topic: function () { 69 | var schema = createSchema(schemaSimple); 70 | var count = 0; 71 | try { 72 | schema.validate(42, function naughty() { 73 | ++count; 74 | if (count === 1) { 75 | throw new Error('I am a naughty callback function'); 76 | } else { 77 | this.callback(new Error('Callback was called twice')); 78 | } 79 | }.bind(this)); 80 | this.callback(new Error('we did not get an error')); 81 | } catch(err) { 82 | // error is expected 83 | this.callback(); 84 | } 85 | }, 86 | 'we get an error': function (err, result) { 87 | if (err) { 88 | console.error(err.toString()); 89 | } 90 | should.not.exist(err); 91 | should.not.exist(result); 92 | } 93 | } 94 | }).export(module); 95 | -------------------------------------------------------------------------------- /test/benchmark-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | var should = require('should'); 5 | 6 | // json-gate 7 | var createSchema = require('..').createSchema; 8 | var jsonGateNumSimpleRuns = 200000; 9 | var jsonGateNumComplexRuns = 30000; 10 | 11 | // JSV 12 | var JSV = require("JSV").JSV; 13 | var JsvNumSimpleRuns = 10000; 14 | var JsvNumComplexRuns = 2000; 15 | 16 | var objSimple = { 17 | str: 'hi', 18 | num: 42 19 | }; 20 | 21 | var schemaSimple = { 22 | type: 'object', 23 | properties: { 24 | str: { type: 'string', required: true }, 25 | num: { type: 'number', required: true } 26 | } 27 | }; 28 | 29 | var objComplex = { 30 | str: 'hello', 31 | num: 16.2, 32 | int: 9, 33 | bool: false, 34 | arr: [4, 2, 5, 3], 35 | youtubeVideoId: '9bZkp7q19f0', 36 | email: 'support@facebook.com', 37 | primitive: 'walkie talkie', 38 | weekday: 'Tuesday' 39 | }; 40 | 41 | var schemaComplex = { 42 | title: 'complex', 43 | description: 'A complex schema containg various types and attributes', 44 | type: 'object', 45 | properties: { 46 | str: { 47 | type: 'string', 48 | minLength: 3, 49 | maxLength: 18, 50 | required: true 51 | }, 52 | num: { 53 | type: 'number', 54 | minimum: 3.14, 55 | maximum: 31.4, 56 | required: true 57 | }, 58 | int: { 59 | type: 'integer', 60 | maximum: 42, 61 | exclusiveMaximum: true, 62 | divisibleBy: 3, 63 | required: true 64 | }, 65 | bool: { 66 | type: 'boolean', 67 | required: true 68 | }, 69 | arr: { 70 | type: 'array', 71 | items: { type: 'integer', minimum: 0, maximum: 9 }, 72 | minItems: 2, 73 | maxItems: 6, 74 | uniqueItems: true, 75 | required: true 76 | }, 77 | youtubeVideoId: { 78 | type: 'string', 79 | pattern: '^[A-Za-z0-9_-]{11}$', 80 | required: true 81 | }, 82 | email: { 83 | type: 'string', 84 | format: 'email', 85 | required: true 86 | }, 87 | optional: { 88 | type: 'integer' 89 | }, 90 | def: { 91 | type: ['string', 'null'], 92 | default: null 93 | }, 94 | primitive: { 95 | disallow: ['object', 'array', 'null'], 96 | required: true 97 | }, 98 | weekday: { 99 | enum: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], 100 | required: true 101 | } 102 | }, 103 | additionalProperties : false 104 | }; 105 | 106 | function compareUptimesMs(startTime, finishTime) { 107 | var diffSeconds = finishTime[0] - startTime[0]; 108 | var diffNanoseconds = finishTime[1] - startTime[1]; 109 | var diff = diffSeconds * 1000 + diffNanoseconds / 1000000; 110 | return diff; 111 | } 112 | 113 | var jsonBmarkSimple; 114 | var JsvBmarkSimple; 115 | var jsonBmarkComplex; 116 | var JsvBmarkComplex; 117 | 118 | function round3(num) { 119 | return Math.round(num * 1000) / 1000; 120 | } 121 | 122 | vows.describe('Benchmark').addBatch({ 123 | 'when validating simple schema using json-gate': { 124 | topic: function () { 125 | var schema = createSchema(schemaSimple); 126 | var startTime = process.hrtime(); 127 | for (var i = 0; i < jsonGateNumSimpleRuns; ++i) { 128 | schema.validate(objSimple); 129 | } 130 | var finishTime = process.hrtime(); 131 | var diff = compareUptimesMs(startTime, finishTime); 132 | jsonBmarkSimple = diff / jsonGateNumSimpleRuns; 133 | console.log('json-gate:', round3(jsonBmarkSimple), 'ms'); 134 | return true; 135 | }, 136 | 'we get no error': function(err, result) { 137 | should.not.exist(err); 138 | should.exist(result); 139 | result.should.not.be.instanceof(Error); // can't trust vows 140 | result.should.equal(true); 141 | } 142 | } 143 | }).addBatch({ 144 | 'when validating simple schema using JSV': { 145 | topic: function () { 146 | var env = JSV.createEnvironment("json-schema-draft-03"); 147 | var schema = env.createSchema(schemaSimple); 148 | var startTime = process.hrtime(); 149 | for (var i = 0; i < JsvNumSimpleRuns; ++i) { 150 | var report = schema.validate(objSimple); 151 | } 152 | var finishTime = process.hrtime(); 153 | var diff = compareUptimesMs(startTime, finishTime); 154 | JsvBmarkSimple = diff / JsvNumSimpleRuns; 155 | console.log('JSV:', round3(JsvBmarkSimple), 'ms'); 156 | return report; 157 | }, 158 | 'we get no error': function(err, result) { 159 | should.not.exist(err); 160 | should.exist(result); 161 | result.should.not.be.instanceof(Error); // can't trust vows 162 | result.should.have.property('errors'); 163 | result.errors.should.have.length(0); 164 | } 165 | } 166 | }).addBatch({ 167 | 'when comparing simple schema validation speeds': { 168 | topic: function () { 169 | return JsvBmarkSimple / jsonBmarkSimple; 170 | }, 171 | 'we get no error': function(err, result) { 172 | should.not.exist(err); 173 | should.exist(result); 174 | result.should.not.be.instanceof(Error); // can't trust vows 175 | result.should.be.a.Number; 176 | if (result > 1) { 177 | console.log('json-gate is', round3(result), 'times faster than JSV'); 178 | } else { 179 | console.log('JSV is', round3(1 / result), 'times faster than json-gate'); 180 | } 181 | } 182 | } 183 | }).addBatch({ 184 | 'when validating complex schema using json-gate': { 185 | topic: function () { 186 | var schema = createSchema(schemaComplex); 187 | var startTime = process.hrtime(); 188 | for (var i = 0; i < jsonGateNumComplexRuns; ++i) { 189 | schema.validate(objComplex); 190 | } 191 | var finishTime = process.hrtime(); 192 | var diff = compareUptimesMs(startTime, finishTime); 193 | jsonBmarkComplex = diff / jsonGateNumComplexRuns; 194 | console.log('json-gate:', round3(jsonBmarkComplex), 'ms'); 195 | return true; 196 | }, 197 | 'we get no error': function(err, result) { 198 | should.not.exist(err); 199 | should.exist(result); 200 | result.should.not.be.instanceof(Error); // can't trust vows 201 | result.should.equal(true); 202 | } 203 | } 204 | }).addBatch({ 205 | 'when validating complex schema using JSV': { 206 | topic: function () { 207 | var env = JSV.createEnvironment("json-schema-draft-03"); 208 | var schema = env.createSchema(schemaComplex); 209 | var startTime = process.hrtime(); 210 | for (var i = 0; i < JsvNumComplexRuns; ++i) { 211 | var report = schema.validate(objComplex); 212 | } 213 | var finishTime = process.hrtime(); 214 | var diff = compareUptimesMs(startTime, finishTime); 215 | JsvBmarkComplex = diff / JsvNumComplexRuns; 216 | console.log('JSV:', round3(JsvBmarkComplex), 'ms'); 217 | return report; 218 | }, 219 | 'we get no error': function(err, result) { 220 | should.not.exist(err); 221 | should.exist(result); 222 | result.should.not.be.instanceof(Error); // can't trust vows 223 | result.should.have.property('errors'); 224 | result.errors.should.have.length(0); 225 | } 226 | } 227 | }).addBatch({ 228 | 'when comparing complex schema validation speeds': { 229 | topic: function () { 230 | return JsvBmarkComplex / jsonBmarkComplex; 231 | }, 232 | 'we get no error': function(err, result) { 233 | should.not.exist(err); 234 | should.exist(result); 235 | result.should.not.be.instanceof(Error); // can't trust vows 236 | result.should.be.a.Number; 237 | if (result > 1) { 238 | console.log('json-gate is', round3(result), 'times faster than JSV'); 239 | } else { 240 | console.log('JSV is', round3(1 / result), 'times faster than json-gate'); 241 | } 242 | } 243 | } 244 | }).export(module); 245 | -------------------------------------------------------------------------------- /test/common.js: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | 3 | var createSchema = require('..').createSchema, 4 | config = require('./config'); 5 | 6 | exports.schemaShouldBeValid = function (schema, options) { 7 | var context = { 8 | topic: function () { 9 | try { 10 | this.callback(null, createSchema(schema)); 11 | } catch(err) { 12 | this.callback(err); 13 | } 14 | } 15 | }; 16 | var vow = (options && options.vow) || 'we get no error'; 17 | context[vow] = function (err, schema) { 18 | if (config.verbose && err) { 19 | console.log('Error:', err.stack) 20 | } 21 | should.not.exist(err); 22 | should.exist(schema); 23 | schema.should.not.be.instanceof(Error); // can't trust vows 24 | if (options && options.post) { options.post(err, schema); } 25 | }; 26 | return context; 27 | }; 28 | 29 | exports.schemaShouldBeInvalid = function (schema, options) { 30 | var context = { 31 | topic: function () { 32 | try { 33 | this.callback(null, createSchema(schema)); 34 | } catch(err) { 35 | this.callback(err); 36 | } 37 | } 38 | }; 39 | var vow = (options && options.vow) || 'we get an error'; 40 | context[vow] = function (err, schema) { 41 | if (config.verbose && schema && (schema instanceof Error)) { 42 | console.log('Schema is the error:', schema.stack) 43 | } 44 | should.exist(err); 45 | err.should.be.instanceof(Error); // can't trust vows 46 | if (config.verbose) { 47 | console.log('Error:', err) 48 | } 49 | if (options && options.errMsg) { 50 | err.should.have.property('message', options.errMsg); 51 | } 52 | if (options && options.post) { options.post(err, schema); } 53 | }; 54 | return context; 55 | }; 56 | 57 | exports.objectShouldBeValid = function (obj, schemaDef, options) { 58 | var context = { 59 | topic: function () { 60 | var schema = createSchema(schemaDef); 61 | schema.validate(obj, this.callback); 62 | } 63 | }; 64 | var vow = (options && options.vow) || 'we get no error'; 65 | context[vow] = function (err, result) { 66 | if (config.verbose && err) { 67 | console.log('Error:', err) 68 | } 69 | should.not.exist(err); 70 | should.exist(result); 71 | result.should.not.be.instanceof(Error); // can't trust vows 72 | result.should.equal(obj); 73 | if (options && options.post) { options.post(err, result); } 74 | }; 75 | return context; 76 | }; 77 | 78 | exports.objectShouldBeInvalid = function (obj, schemaDef, options) { 79 | var context = { 80 | topic: function () { 81 | var schema = createSchema(schemaDef); 82 | schema.validate(obj, this.callback); 83 | } 84 | }; 85 | var vow = (options && options.vow) || 'we get an error'; 86 | context[vow] = function (err, result) { 87 | should.exist(err); 88 | err.should.be.instanceof(Error); // can't trust vows 89 | should.not.exist(result); 90 | if (config.verbose) { 91 | console.log('Error:', err) 92 | } 93 | if (options && options.errMsg) { 94 | err.should.have.property('message', options.errMsg); 95 | } 96 | if (options && options.post) { options.post(err, result); } 97 | }; 98 | return context; 99 | }; 100 | -------------------------------------------------------------------------------- /test/config.js: -------------------------------------------------------------------------------- 1 | // enable to print out exact error messages 2 | exports.verbose = false; 3 | -------------------------------------------------------------------------------- /test/object-array-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | objectShouldBeValid = common.objectShouldBeValid, 7 | objectShouldBeInvalid = common.objectShouldBeInvalid; 8 | 9 | var arrWeekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; 10 | 11 | var arrTuple = [ 12 | 'John', 13 | 42, 14 | { str: 'hi', num: 3 }, 15 | false 16 | ]; 17 | 18 | var arrTupleInvalidType = [ 19 | 'John', 20 | '42', 21 | { str: 'hi', num: 3 }, 22 | false 23 | ]; 24 | 25 | var arrTupleInvalidNestedType = [ 26 | 'John', 27 | 42, 28 | { str: 'hi', num: '3' }, 29 | false 30 | ]; 31 | 32 | var arrTuplePlus = [ 33 | 'John', 34 | 42, 35 | false, 36 | true, 37 | false, 38 | false, 39 | true 40 | ]; 41 | 42 | var arrTuplePlusInvalidType = [ 43 | 'John', 44 | 42, 45 | false, 46 | true, 47 | false, 48 | false, 49 | 'no no no', 50 | true 51 | ]; 52 | 53 | var arrTuple2 = [ 54 | 'John', 55 | 42 56 | ]; 57 | 58 | var arrNotUnique = ['a', 'b', 'c', 'b', 'd']; 59 | 60 | var schemaAllStrings = { 61 | type: 'array', 62 | items: { type: 'string' } 63 | }; 64 | 65 | var schemaTupleWithProperties = { 66 | type: 'array', 67 | items: [ 68 | { type: 'string' }, 69 | { type: 'integer' }, 70 | { 71 | type: 'object', 72 | properties: { 73 | str: { type: 'string' }, 74 | num: { type: 'number' } 75 | } 76 | }, 77 | { type: 'boolean' } 78 | ] 79 | }; 80 | 81 | var schemaAdditionalItems = { 82 | type: 'array', 83 | items: [ 84 | { type: 'string' }, 85 | { type: 'integer' } 86 | ], 87 | additionalItems: { type: 'boolean' } 88 | }; 89 | 90 | var schemaNoAdditionalItems = { 91 | type: 'array', 92 | items: [ 93 | { type: 'string' }, 94 | { type: 'integer' } 95 | ], 96 | additionalItems: false 97 | }; 98 | 99 | var schemaUniqueStrings = { 100 | type: 'array', 101 | items: { type: 'string' }, 102 | uniqueItems: true 103 | }; 104 | 105 | vows.describe('Object Array').addBatch({ 106 | 'when passing strings for strings': objectShouldBeValid(arrWeekdays, schemaAllStrings), 107 | 'when passing non-strings for strings': objectShouldBeInvalid(arrTuple, schemaAllStrings, { errMsg: 'JSON object property \'[1]\' is an integer when it should be a string' }), 108 | 'when passing a suitable tuple': objectShouldBeValid(arrTuple, schemaTupleWithProperties), 109 | 'when an element has wrong type': objectShouldBeInvalid(arrTupleInvalidType, schemaTupleWithProperties, { errMsg: 'JSON object property \'[1]\' is a string when it should be an integer' }), 110 | 'when an element is an object with a property with wrong type': objectShouldBeInvalid(arrTupleInvalidNestedType, schemaTupleWithProperties, { errMsg: 'JSON object property \'[2].num\' is a string when it should be a number' }), 111 | 'when additional items are correct type': objectShouldBeValid(arrTuplePlus, schemaAdditionalItems), 112 | 'when additional items are wrong type': objectShouldBeInvalid(arrTuplePlusInvalidType, schemaAdditionalItems, { errMsg: 'JSON object property \'[6]\' is a string when it should be a boolean' }), 113 | 'when no additional types is respected': objectShouldBeValid(arrTuple2, schemaNoAdditionalItems), 114 | 'when no additional types is not respected': objectShouldBeInvalid(arrTuple, schemaNoAdditionalItems, { errMsg: 'JSON object: number of items is 4 when it should be at most 2 - the length of schema items' }), 115 | 'when strings are unique as expected': objectShouldBeValid(arrWeekdays, schemaUniqueStrings), 116 | 'when strings are not unique as expected': objectShouldBeInvalid(arrNotUnique, schemaUniqueStrings, { errMsg: 'JSON object items are not unique: element 1 equals element 3' }) 117 | }).export(module); 118 | -------------------------------------------------------------------------------- /test/object-basic-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'), 4 | should = require('should'); 5 | 6 | var createSchema = require('..').createSchema, 7 | config = require('./config'); 8 | 9 | var schemaSimple = { 10 | type: 'string' 11 | }; 12 | 13 | vows.describe('Object Basic').addBatch({ 14 | 'when calling without an object': { 15 | topic: function () { 16 | var schema = createSchema(schemaSimple); 17 | try { 18 | schema.validate(undefined); 19 | this.callback(null); 20 | } catch(err) { 21 | this.callback(err); 22 | } 23 | }, 24 | 'we get no error': function (err, result) { 25 | should.not.exist(err); 26 | should.not.exist(result); 27 | } 28 | } 29 | }).export(module); 30 | -------------------------------------------------------------------------------- /test/object-default-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | objectShouldBeValid = common.objectShouldBeValid, 7 | objectShouldBeInvalid = common.objectShouldBeInvalid; 8 | 9 | var objWithoutProperty = { 10 | }; 11 | 12 | var origValue = 'I am'; 13 | 14 | var objWithProperty = { 15 | str: origValue 16 | }; 17 | 18 | var defaultValue = 'the-default-value'; 19 | 20 | var schemaWithDefault = { 21 | type: 'object', 22 | properties: { 23 | str: { 24 | type: 'string', 25 | default: defaultValue 26 | } 27 | } 28 | }; 29 | 30 | vows.describe('Object Default').addBatch({ 31 | 'when property exists': objectShouldBeValid(objWithProperty, schemaWithDefault, { 32 | vow: 'it is not changed', 33 | post: function (err, result) { 34 | result.should.have.property('str', origValue); 35 | }}), 36 | 'when property is missing': objectShouldBeValid(objWithoutProperty, schemaWithDefault, { 37 | vow: 'we get the default value in-place', 38 | post: function (err, result) { 39 | result.should.have.property('str', defaultValue); 40 | }}) 41 | }).export(module); 42 | -------------------------------------------------------------------------------- /test/object-dependencies-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | objectShouldBeValid = common.objectShouldBeValid, 7 | objectShouldBeInvalid = common.objectShouldBeInvalid; 8 | 9 | var objA = { 10 | a: 1 11 | }; 12 | 13 | var objAB = { 14 | a: 1, 15 | b: 2 16 | }; 17 | 18 | var objABBool = { 19 | a: 1, 20 | b: false 21 | }; 22 | 23 | var objABCD = { 24 | a: 1, 25 | b: 2, 26 | c: 3, 27 | d: 4 28 | }; 29 | 30 | var schemaSimple = { 31 | type: 'object', 32 | dependencies: { 33 | a: 'b' 34 | } 35 | }; 36 | 37 | var schemaSimpleTuple = { 38 | type: 'object', 39 | dependencies: { 40 | a: ['b', 'c', 'd'] 41 | } 42 | }; 43 | 44 | var schemaSchema = { 45 | type: 'object', 46 | dependencies: { 47 | a: { properties: { b: { type: 'integer' }}} 48 | } 49 | }; 50 | 51 | vows.describe('Object Dependencies').addBatch({ 52 | 'when simple dependency is satisfied': objectShouldBeValid(objAB, schemaSimple), 53 | 'when simple dependency is not satisfied': objectShouldBeInvalid(objA, schemaSimple, { errMsg: 'JSON object property \'b\' is required by property \'a\'' }), 54 | 'when simple tuple dependency is satisfied': objectShouldBeValid(objABCD, schemaSimpleTuple), 55 | 'when simple tuple dependency is not satisfied': objectShouldBeInvalid(objAB, schemaSimpleTuple, { errMsg: 'JSON object property \'c\' is required by property \'a\'' }), 56 | 'when schema dependency is satisfied': objectShouldBeValid(objAB, schemaSchema), 57 | 'when schema dependency is not satisfied': objectShouldBeInvalid(objABBool, schemaSchema, { errMsg: 'JSON object property \'[dependencies.a].b\' is a boolean when it should be an integer' }) 58 | }).export(module); 59 | -------------------------------------------------------------------------------- /test/object-disallow-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | objectShouldBeValid = common.objectShouldBeValid, 7 | objectShouldBeInvalid = common.objectShouldBeInvalid; 8 | 9 | var objString = { 10 | val: 'Hello, json-gate' 11 | }; 12 | 13 | var objNumber = { 14 | val: 3.14 15 | }; 16 | 17 | var objInteger = { 18 | val: 42 19 | }; 20 | 21 | var objBoolean = { 22 | val: false 23 | }; 24 | 25 | var objObject = { 26 | val: {} 27 | }; 28 | 29 | var objArray = { 30 | val: [] 31 | }; 32 | 33 | var objNull = { 34 | val: null 35 | }; 36 | 37 | var objAny = { 38 | val: 'sub kuch milega' 39 | }; 40 | 41 | var schemaNonString = { 42 | type: 'object', 43 | properties: { 44 | val: { 45 | disallow: 'string', 46 | required: true 47 | } 48 | } 49 | }; 50 | 51 | var schemaNonNumber = { 52 | type: 'object', 53 | properties: { 54 | val: { 55 | disallow: 'number', 56 | required: true 57 | } 58 | } 59 | }; 60 | 61 | var schemaNonInteger = { 62 | type: 'object', 63 | properties: { 64 | val: { 65 | disallow: 'integer', 66 | required: true 67 | } 68 | } 69 | }; 70 | 71 | var schemaNonBoolean = { 72 | type: 'object', 73 | properties: { 74 | val: { 75 | disallow: 'boolean', 76 | required: true 77 | } 78 | } 79 | }; 80 | 81 | var schemaNonObject = { 82 | type: 'object', 83 | properties: { 84 | val: { 85 | disallow: 'object', 86 | required: true 87 | } 88 | } 89 | }; 90 | 91 | var schemaNonArray = { 92 | type: 'object', 93 | properties: { 94 | val: { 95 | disallow: 'array', 96 | required: true 97 | } 98 | } 99 | }; 100 | 101 | var schemaNonNull = { 102 | type: 'object', 103 | properties: { 104 | val: { 105 | disallow: 'null', 106 | required: true 107 | } 108 | } 109 | }; 110 | 111 | var schemaNonAny = { 112 | type: 'object', 113 | properties: { 114 | val: { 115 | disallow: 'any', 116 | required: true 117 | } 118 | } 119 | }; 120 | 121 | vows.describe('Object Disallow').addBatch({ 122 | 'when a string is passed for a non-string': objectShouldBeInvalid(objString, schemaNonString, { errMsg: 'JSON object property \'val\' is a string when it should not be a string' }), 123 | 'when trying to pass a number for a non-string': objectShouldBeValid(objNumber, schemaNonString), 124 | 'when a number is passed for a non-number': objectShouldBeInvalid(objNumber, schemaNonNumber, { errMsg: 'JSON object property \'val\' is a number when it should not be a number' }), 125 | 'when trying to pass a string for a non-number': objectShouldBeValid(objString, schemaNonNumber), 126 | 'when an integer is passed for a non-number': objectShouldBeInvalid(objInteger, schemaNonNumber, { errMsg: 'JSON object property \'val\' is an integer when it should not be a number' }), 127 | 'when an integer is passed for a non-integer': objectShouldBeInvalid(objInteger, schemaNonInteger, { errMsg: 'JSON object property \'val\' is an integer when it should not be an integer' }), 128 | 'when trying to pass a number for a non-integer': objectShouldBeValid(objNumber, schemaNonInteger), 129 | 'when a boolean is passed for a non-boolean': objectShouldBeInvalid(objBoolean, schemaNonBoolean, { errMsg: 'JSON object property \'val\' is a boolean when it should not be a boolean' }), 130 | 'when trying to pass an integer for a non-boolean': objectShouldBeValid(objInteger, schemaNonBoolean), 131 | 'when an object is passed for a non-object': objectShouldBeInvalid(objObject, schemaNonObject, { errMsg: 'JSON object property \'val\' is an object when it should not be an object' }), 132 | 'when trying to pass an array for a non-object': objectShouldBeValid(objArray, schemaNonObject), 133 | 'when an array is passed for a non-array': objectShouldBeInvalid(objArray, schemaNonArray, { errMsg: 'JSON object property \'val\' is an array when it should not be an array' }), 134 | 'when trying to pass an object for a non-array': objectShouldBeValid(objObject, schemaNonArray), 135 | 'when null is passed for non-null': objectShouldBeInvalid(objNull, schemaNonNull, { errMsg: 'JSON object property \'val\' is null when it should not be null' }), 136 | 'when trying to pass a boolean for non-null': objectShouldBeValid(objBoolean, schemaNonNull), 137 | 'when a string is passed for non-any': objectShouldBeInvalid(objAny, schemaNonAny, { errMsg: 'JSON object property \'val\' is a string when it should not be any type' }) 138 | }).export(module); 139 | -------------------------------------------------------------------------------- /test/object-disallow-union-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | objectShouldBeValid = common.objectShouldBeValid, 7 | objectShouldBeInvalid = common.objectShouldBeInvalid; 8 | 9 | var objString = { 10 | nullable: 'I am' 11 | }; 12 | 13 | var objNull = { 14 | nullable: null 15 | }; 16 | 17 | var objInteger = { 18 | nullable: 42 19 | }; 20 | 21 | var schemaUnionType = { 22 | type: 'object', 23 | properties: { 24 | nullable: { 25 | disallow: ['string', 'null'] 26 | } 27 | } 28 | }; 29 | 30 | var objMan = { 31 | nullable: { 32 | name: 'John', 33 | age: 42 34 | } 35 | }; 36 | 37 | var objWoman = { 38 | nullable: { 39 | name: 'Jane', 40 | age: 'confidential' 41 | } 42 | }; 43 | 44 | var objAlien = { 45 | nullable: { 46 | name: 'Paul', 47 | age: true 48 | } 49 | } 50 | 51 | var schemaHuman = { 52 | type: 'object', 53 | properties: { 54 | name: { 55 | type: 'string', 56 | required: true 57 | }, 58 | age: { 59 | type: ['integer', 'string'], 60 | required: true 61 | } 62 | } 63 | }; 64 | 65 | var schemaUnionTypeWithSchema = { 66 | type: 'object', 67 | properties: { 68 | nullable: { 69 | disallow: ['string', 'null', schemaHuman], 70 | required: true 71 | } 72 | } 73 | }; 74 | 75 | vows.describe('Object Type').addBatch({ 76 | 'when a string is passed for neither a string nor null': objectShouldBeInvalid(objString, schemaUnionType, { errMsg: 'JSON object property \'nullable\' is a string when it should be neither a string nor null' }), 77 | 'when null is passed for neither a string nor null': objectShouldBeInvalid(objNull, schemaUnionType, { errMsg: 'JSON object property \'nullable\' is null when it should be neither a string nor null' }), 78 | 'when trying to pass an integer for neither a string nor null': objectShouldBeValid(objInteger, schemaUnionType), 79 | 'when a string is passed for neither a string nor null nor a human': objectShouldBeInvalid(objString, schemaUnionTypeWithSchema, { errMsg: 'JSON object property \'nullable\' is a string when it should be neither a string nor null nor a schema' }), 80 | 'when null is passed for neither a string nor null nor a human': objectShouldBeInvalid(objNull, schemaUnionTypeWithSchema, { errMsg: 'JSON object property \'nullable\' is null when it should be neither a string nor null nor a schema' }), 81 | 'when a man is passed for neither a string nor null nor a human': objectShouldBeInvalid(objMan, schemaUnionTypeWithSchema, { errMsg: 'JSON object property \'nullable\' is an object when it should be neither a string nor null nor a schema' }), 82 | 'when a woman is passed for neither a string nor null nor a human': objectShouldBeInvalid(objWoman, schemaUnionTypeWithSchema, { errMsg: 'JSON object property \'nullable\' is an object when it should be neither a string nor null nor a schema' }), 83 | 'when trying to pass an alien for neither a string nor null nor a human': objectShouldBeValid(objAlien, schemaUnionTypeWithSchema) 84 | }).export(module); 85 | -------------------------------------------------------------------------------- /test/object-enum-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | objectShouldBeValid = common.objectShouldBeValid, 7 | objectShouldBeInvalid = common.objectShouldBeInvalid; 8 | 9 | var objString = { 10 | val: 'sugar' 11 | }; 12 | 13 | var objWrongString = { 14 | val: 'salt' 15 | }; 16 | 17 | var objNumber = { 18 | val: 42 19 | }; 20 | 21 | var objWrongNumber = { 22 | val: 7 23 | }; 24 | 25 | var objArray = { 26 | val: [1, 2, 3] 27 | }; 28 | 29 | var objWrongArray = { 30 | val: [1, 2, 3, 4] 31 | }; 32 | 33 | var objObject = { 34 | val: {1: 2, 3: 4} 35 | }; 36 | 37 | var objWrongObject = { 38 | val: {1: 2} 39 | }; 40 | 41 | var objNested = { 42 | val: ['abba', 3, {arr: [5, 2]}] 43 | }; 44 | 45 | var objWrongNested = { 46 | val: ['abba', 3, {arr: [5, 2], excess: 'bad'}] 47 | }; 48 | 49 | var schemaEnum = { 50 | type: 'object', 51 | properties: { 52 | val: { 53 | 'enum': [ 54 | 'sugar', 55 | 42, 56 | [1, 2, 3], 57 | {3: 4, 1: 2}, 58 | ['abba', 3, {arr: [5, 2]}] 59 | ], 60 | required: true 61 | } 62 | } 63 | }; 64 | 65 | vows.describe('Object Enum').addBatch({ 66 | 'when string is in enum': objectShouldBeValid(objString, schemaEnum), 67 | 'when string is not in enum': objectShouldBeInvalid(objWrongString, schemaEnum, { errMsg: 'JSON object property \'val\' is not in enum' }), 68 | 'when number is in enum': objectShouldBeValid(objNumber, schemaEnum), 69 | 'when number is not in enum': objectShouldBeInvalid(objWrongNumber, schemaEnum, { errMsg: 'JSON object property \'val\' is not in enum' }), 70 | 'when array is in enum': objectShouldBeValid(objArray, schemaEnum), 71 | 'when array is not in enum': objectShouldBeInvalid(objWrongArray, schemaEnum, { errMsg: 'JSON object property \'val\' is not in enum' }), 72 | 'when object is in enum': objectShouldBeValid(objObject, schemaEnum), 73 | 'when object is not in enum': objectShouldBeInvalid(objWrongObject, schemaEnum, { errMsg: 'JSON object property \'val\' is not in enum' }), 74 | 'when nested object is in enum': objectShouldBeValid(objNested, schemaEnum), 75 | 'when nested object is not in enum': objectShouldBeInvalid(objWrongNested, schemaEnum, { errMsg: 'JSON object property \'val\' is not in enum' }) 76 | }).export(module); 77 | -------------------------------------------------------------------------------- /test/object-format-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | objectShouldBeValid = common.objectShouldBeValid, 7 | objectShouldBeInvalid = common.objectShouldBeInvalid; 8 | 9 | var objDateTime = { 10 | dateTime: '2012-11-06T09:13:24Z' 11 | }; 12 | 13 | var objDateTimeFractionDot = { 14 | dateTime: '2012-11-06T09:13:24.142857Z' 15 | }; 16 | 17 | var objDateTimeFractionComma = { 18 | dateTime: '2012-11-06T09:13:24,142857Z' 19 | }; 20 | 21 | var objDateTimeWithAheadOffset = { 22 | dateTime: '2016-10-18T11:11:11.616+05' 23 | }; 24 | 25 | var objDateTimeWithBehindOffset = { 26 | dateTime: '2016-10-18T11:11:11.616-05' 27 | }; 28 | 29 | var objInvalidDateTime = { 30 | dateTime: '2012-11-06' 31 | }; 32 | 33 | var objDate = { 34 | date: '2012-11-06' 35 | }; 36 | 37 | var objInvalidDate = { 38 | date: '09:13:24' 39 | }; 40 | 41 | var objTime = { 42 | time: '09:13:24' 43 | }; 44 | 45 | var objInvalidTime = { 46 | time: '^[A-Fa-f0-9]{12}$' 47 | }; 48 | 49 | var objMsInt = { 50 | ms: 1000 51 | }; 52 | 53 | var objMsNum = { 54 | ms: 1000.0 55 | }; 56 | 57 | var objRegex = { 58 | re: '^[A-Fa-f0-9]{12}$' 59 | }; 60 | 61 | var objInvalidRegex = { 62 | re: '[' 63 | }; 64 | 65 | var objColorPreDef = { 66 | color: 'green' 67 | }; 68 | 69 | var objColorHex3 = { 70 | color: '#0f8' 71 | }; 72 | 73 | var objColorHex6 = { 74 | color: '#00ff69' 75 | }; 76 | 77 | var objColorRgbNum = { 78 | color: 'rgb(255, 0, 128)' 79 | }; 80 | 81 | var objColorRgbPerc = { 82 | color: 'rgb(100%, 0%, 50%)' 83 | }; 84 | 85 | var objInvalidColor = { 86 | color: 'color: green' 87 | }; 88 | 89 | var objStyle = { 90 | style: 'color: blue' 91 | }; 92 | 93 | var objInvalidStyle = { 94 | style: '+31 42 123 4567' 95 | }; 96 | 97 | var objPhoneNational = { 98 | phone: '(42) 123 4567' 99 | }; 100 | 101 | var objPhoneInternational = { 102 | phone: '+31 42 123 4567' 103 | }; 104 | 105 | var objInvalidPhone = { 106 | phone: 'http://en.wikipedia.org/wiki/E.123' 107 | }; 108 | 109 | var objUri = { 110 | uri: 'https://npmjs.org/package/json-gate' 111 | }; 112 | 113 | var objInvalidUri = { 114 | uri: 'info@facebook.com' 115 | }; 116 | 117 | var objEmail = { 118 | email: 'info@google.com' 119 | }; 120 | 121 | var objInvalidEmail = { 122 | email: '127.0.0.1' 123 | }; 124 | 125 | var objIp4 = { 126 | ip4: '192.168.1.1' 127 | }; 128 | 129 | var objInvalidIp4 = { 130 | ip4: '2001:0db8:85a3:0042:0000:8a2e:0370:7334' 131 | }; 132 | 133 | var objIp6 = { 134 | ip6: '2001:0db8:85a3:0042:0000:8a2e:0370:7334' 135 | }; 136 | 137 | var objInvalidIp6 = { 138 | ip6: 'localhost' 139 | }; 140 | 141 | var objHost = { 142 | host: 'localhost' 143 | }; 144 | 145 | var objInvalidHost = { 146 | host: '2012-11-06T09:13:24Z' 147 | }; 148 | 149 | var schemaDateTime = { 150 | type: 'object', 151 | properties: { 152 | dateTime: { 153 | type: 'string', 154 | format: 'date-time', 155 | required: true 156 | } 157 | } 158 | }; 159 | 160 | var schemaDate = { 161 | type: 'object', 162 | properties: { 163 | date: { 164 | type: 'string', 165 | format: 'date', 166 | required: true 167 | } 168 | } 169 | }; 170 | 171 | var schemaTime = { 172 | type: 'object', 173 | properties: { 174 | time: { 175 | type: 'string', 176 | format: 'time', 177 | required: true 178 | } 179 | } 180 | }; 181 | 182 | var schemaMsInt = { 183 | type: 'object', 184 | properties: { 185 | ms: { 186 | type: 'integer', 187 | format: 'utc-millisec', 188 | required: true 189 | } 190 | } 191 | }; 192 | 193 | var schemaMsNum = { 194 | type: 'object', 195 | properties: { 196 | ms: { 197 | type: 'number', 198 | format: 'utc-millisec', 199 | required: true 200 | } 201 | } 202 | }; 203 | 204 | var schemaRegex = { 205 | type: 'object', 206 | properties: { 207 | re: { 208 | type: 'string', 209 | format: 'regex', 210 | required: true 211 | } 212 | } 213 | }; 214 | 215 | var schemaColor = { 216 | type: 'object', 217 | properties: { 218 | color: { 219 | type: 'string', 220 | format: 'color', 221 | required: true 222 | } 223 | } 224 | }; 225 | 226 | var schemaStyle = { 227 | type: 'object', 228 | properties: { 229 | style: { 230 | type: 'string', 231 | format: 'style', 232 | required: true 233 | } 234 | } 235 | }; 236 | 237 | var schemaPhone = { 238 | type: 'object', 239 | properties: { 240 | phone: { 241 | type: 'string', 242 | format: 'phone', 243 | required: true 244 | } 245 | } 246 | }; 247 | 248 | var schemaUri = { 249 | type: 'object', 250 | properties: { 251 | uri: { 252 | type: 'string', 253 | format: 'uri', 254 | required: true 255 | } 256 | } 257 | }; 258 | 259 | var schemaEmail = { 260 | type: 'object', 261 | properties: { 262 | email: { 263 | type: 'string', 264 | format: 'email', 265 | required: true 266 | } 267 | } 268 | }; 269 | 270 | var schemaIp4 = { 271 | type: 'object', 272 | properties: { 273 | ip4: { 274 | type: 'string', 275 | format: 'ip-address', 276 | required: true 277 | } 278 | } 279 | }; 280 | 281 | var schemaIp6 = { 282 | type: 'object', 283 | properties: { 284 | ip6: { 285 | type: 'string', 286 | format: 'ipv6', 287 | required: true 288 | } 289 | } 290 | }; 291 | 292 | var schemaHost = { 293 | type: 'object', 294 | properties: { 295 | host: { 296 | type: 'string', 297 | format: 'host-name', 298 | required: true 299 | } 300 | } 301 | }; 302 | 303 | var objProprietary = { 304 | prop: 'la la la' 305 | }; 306 | 307 | var schemaProprietary = { 308 | type: 'object', 309 | properties: { 310 | prop: { 311 | type: 'string', 312 | format: 'my-format', 313 | required: true 314 | } 315 | } 316 | }; 317 | 318 | vows.describe('Object Format').addBatch({ 319 | 'when a date-time is passed for a date-time': objectShouldBeValid(objDateTime, schemaDateTime), 320 | 'when a date-time with a dot fraction is passed for a date-time': objectShouldBeValid(objDateTimeFractionDot, schemaDateTime), 321 | 'when a date-time with a comma fraction is passed for a date-time': objectShouldBeValid(objDateTimeFractionComma, schemaDateTime), 322 | 'when a date-time with a ahead offset is passed for a date-time': objectShouldBeValid(objDateTimeWithAheadOffset, schemaDateTime), 323 | 'when a date-time with a behind offset is passed for a date-time': objectShouldBeValid(objDateTimeWithBehindOffset, schemaDateTime), 324 | 'when a date-time is passed for a date-time': objectShouldBeValid(objDateTime, schemaDateTime), 325 | 'when trying to pass a date for a date-time': objectShouldBeInvalid(objInvalidDateTime, schemaDateTime, { errMsg: 'JSON object property \'dateTime\' does not conform to the \'date-time\' format' }), 326 | 'when a date is passed for a date': objectShouldBeValid(objDate, schemaDate), 327 | 'when trying to pass a time for a date': objectShouldBeInvalid(objInvalidDate, schemaDate, { errMsg: 'JSON object property \'date\' does not conform to the \'date\' format' }), 328 | 'when a time is passed for a time': objectShouldBeValid(objTime, schemaTime), 329 | 'when trying to pass a regex for a time': objectShouldBeInvalid(objInvalidTime, schemaTime, { errMsg: 'JSON object property \'time\' does not conform to the \'time\' format' }), 330 | 'when an integer utc-millisec is passed for an integer utc-millisec': objectShouldBeValid(objMsInt, schemaMsInt), 331 | 'when a number utc-millisec is passed for a number utc-millisec': objectShouldBeValid(objMsNum, schemaMsNum), 332 | 'when a regex is passed for a regex': objectShouldBeValid(objRegex, schemaRegex), 333 | 'when trying to pass an invalid regex for a regex': objectShouldBeInvalid(objInvalidRegex, schemaRegex, { errMsg: 'JSON object property \'re\' does not conform to the \'regex\' format' }), 334 | 'when a predefined color is passed for a color': objectShouldBeValid(objColorPreDef, schemaColor), 335 | 'when a 3-hex-digit color is passed for a color': objectShouldBeValid(objColorHex3, schemaColor), 336 | 'when a 6-hex-digit color is passed for a color': objectShouldBeValid(objColorHex6, schemaColor), 337 | 'when a numeric rgb color is passed for a color': objectShouldBeValid(objColorRgbNum, schemaColor), 338 | 'when a percentile rgb color is passed for a color': objectShouldBeValid(objColorRgbPerc, schemaColor), 339 | 'when trying to pass a style for a color': objectShouldBeInvalid(objInvalidColor, schemaColor, { errMsg: 'JSON object property \'color\' does not conform to the \'color\' format' }), 340 | //'when a style is passed for a style': objectShouldBeValid(objStyle, schemaStyle), 341 | //'when trying to pass a phone for a style': objectShouldBeInvalid(objInvalidStyle, schemaStyle, { errMsg: 'JSON object property \'style\' does not conform to the \'style\' format' }), 342 | 'when a national phone is passed for a phone': objectShouldBeValid(objPhoneNational, schemaPhone), 343 | 'when a internation phone is passed for a phone': objectShouldBeValid(objPhoneInternational, schemaPhone), 344 | 'when trying to pass a uri for a phone': objectShouldBeInvalid(objInvalidPhone, schemaPhone, { errMsg: 'JSON object property \'phone\' does not conform to the \'phone\' format' }), 345 | 'when a uri is passed for a uri': objectShouldBeValid(objUri, schemaUri), 346 | 'when trying to pass an email for a uri': objectShouldBeInvalid(objInvalidUri, schemaUri, { errMsg: 'JSON object property \'uri\' does not conform to the \'uri\' format' }), 347 | 'when an email is passed for an email': objectShouldBeValid(objEmail, schemaEmail), 348 | 'when trying to pass an ip-address for an email': objectShouldBeInvalid(objInvalidEmail, schemaEmail, { errMsg: 'JSON object property \'email\' does not conform to the \'email\' format' }), 349 | 'when an ip-address is passed for an ip-address': objectShouldBeValid(objIp4, schemaIp4), 350 | 'when trying to pass an ipv6 for an ip-address': objectShouldBeInvalid(objInvalidIp4, schemaIp4, { errMsg: 'JSON object property \'ip4\' does not conform to the \'ip-address\' format' }), 351 | 'when an ipv6 is passed for an ipv6': objectShouldBeValid(objIp6, schemaIp6), 352 | 'when trying to pass a host-name for an ipv6': objectShouldBeInvalid(objInvalidIp6, schemaIp6, { errMsg: 'JSON object property \'ip6\' does not conform to the \'ipv6\' format' }), 353 | 'when a host-name is passed for a host-name': objectShouldBeValid(objHost, schemaHost), 354 | 'when trying to pass a date-time for a host-name': objectShouldBeInvalid(objInvalidHost, schemaHost, { errMsg: 'JSON object property \'host\' does not conform to the \'host-name\' format' }), 355 | 'when format is unknown': objectShouldBeValid(objProprietary, schemaProprietary) 356 | }).export(module); 357 | -------------------------------------------------------------------------------- /test/object-number-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | objectShouldBeValid = common.objectShouldBeValid, 7 | objectShouldBeInvalid = common.objectShouldBeInvalid; 8 | 9 | var obj = { 10 | num: 108 11 | }; 12 | 13 | var schemaCorrectMinimum = { 14 | type: 'object', 15 | properties: { 16 | num: { 17 | type: 'number', 18 | minimum: 108, 19 | required: true 20 | } 21 | } 22 | }; 23 | 24 | var schemaIncorrectMinimum = { 25 | type: 'object', 26 | properties: { 27 | num: { 28 | type: 'number', 29 | minimum: 109, 30 | required: true 31 | } 32 | } 33 | }; 34 | 35 | var schemaCorrectExclusiveMinimum = { 36 | type: 'object', 37 | properties: { 38 | num: { 39 | type: 'number', 40 | minimum: 107.999, 41 | exclusiveMinimum: true, 42 | required: true 43 | } 44 | } 45 | }; 46 | 47 | var schemaIncorrectExclusiveMinimum = { 48 | type: 'object', 49 | properties: { 50 | num: { 51 | type: 'number', 52 | minimum: 108, 53 | exclusiveMinimum: true, 54 | required: true 55 | } 56 | } 57 | }; 58 | 59 | var schemaCorrectMaximum = { 60 | type: 'object', 61 | properties: { 62 | num: { 63 | type: 'number', 64 | maximum: 108, 65 | required: true 66 | } 67 | } 68 | }; 69 | 70 | var schemaIncorrectMaximum = { 71 | type: 'object', 72 | properties: { 73 | num: { 74 | type: 'number', 75 | maximum: 107, 76 | required: true 77 | } 78 | } 79 | }; 80 | 81 | var schemaCorrectExclusiveMaximum = { 82 | type: 'object', 83 | properties: { 84 | num: { 85 | type: 'number', 86 | maximum: 108.001, 87 | exclusiveMaximum: true, 88 | required: true 89 | } 90 | } 91 | }; 92 | 93 | var schemaIncorrectExclusiveMaximum = { 94 | type: 'object', 95 | properties: { 96 | num: { 97 | type: 'number', 98 | maximum: 108, 99 | exclusiveMaximum: true, 100 | required: true 101 | } 102 | } 103 | }; 104 | 105 | var schemaCorrectDivisibleBy = { 106 | type: 'object', 107 | properties: { 108 | num: { 109 | type: 'number', 110 | divisibleBy: 36, 111 | required: true 112 | } 113 | } 114 | }; 115 | 116 | var schemaIncorrectDivisibleBy = { 117 | type: 'object', 118 | properties: { 119 | num: { 120 | type: 'number', 121 | divisibleBy: 17, 122 | required: true 123 | } 124 | } 125 | }; 126 | 127 | vows.describe('Object Number').addBatch({ 128 | 'when complies with minimum': objectShouldBeValid(obj, schemaCorrectMinimum), 129 | 'when exceeds minimum': objectShouldBeInvalid(obj, schemaIncorrectMinimum, { errMsg: 'JSON object property \'num\' is 108 when it should be at least 109' }), 130 | 'when complies with exclusive minimum': objectShouldBeValid(obj, schemaCorrectExclusiveMinimum), 131 | 'when exceeds exclusive minimum': objectShouldBeInvalid(obj, schemaIncorrectExclusiveMinimum, { errMsg: 'JSON object property \'num\' is 108 when it should be greater than 108' }), 132 | 'when complies with maximum': objectShouldBeValid(obj, schemaCorrectMaximum), 133 | 'when exceeds maximum': objectShouldBeInvalid(obj, schemaIncorrectMaximum, { errMsg: 'JSON object property \'num\' is 108 when it should be at most 107' }), 134 | 'when complies with exclusive maximum': objectShouldBeValid(obj, schemaCorrectExclusiveMaximum), 135 | 'when exceeds exclusive maximum': objectShouldBeInvalid(obj, schemaIncorrectExclusiveMaximum, { errMsg: 'JSON object property \'num\' is 108 when it should be less than 108' }), 136 | 'when divisible by given divisor': objectShouldBeValid(obj, schemaCorrectDivisibleBy), 137 | 'when not divisible by given divisor': objectShouldBeInvalid(obj, schemaIncorrectDivisibleBy, { errMsg: 'JSON object property \'num\' is 108 when it should be divisible by 17' }) 138 | }).export(module); 139 | -------------------------------------------------------------------------------- /test/object-object-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | objectShouldBeValid = common.objectShouldBeValid, 7 | objectShouldBeInvalid = common.objectShouldBeInvalid; 8 | 9 | var objNested = { 10 | str: 'top', 11 | obj1: { 12 | num: 42, 13 | obj2: { 14 | bool: false, 15 | obj3: { 16 | str: 'you are here' 17 | } 18 | } 19 | } 20 | }; 21 | 22 | var objNestedMissing = { 23 | str: 'top', 24 | obj1: { 25 | num: 42, 26 | obj2: { 27 | bool: false, 28 | obj3: { 29 | str2: 'you are here' 30 | } 31 | } 32 | } 33 | }; 34 | 35 | var schemaNested = { 36 | type: 'object', 37 | properties: { 38 | str: { 39 | type: 'string', 40 | required: true 41 | }, 42 | obj1: { 43 | type: 'object', 44 | required: true, 45 | properties: { 46 | num: { 47 | type: 'number', 48 | required: true 49 | }, 50 | obj2: { 51 | type: 'object', 52 | required: true, 53 | properties: { 54 | bool: { 55 | type: 'boolean', 56 | required: true 57 | }, 58 | obj3: { 59 | type: 'object', 60 | required: true, 61 | properties: { 62 | str: { 63 | type: 'string', 64 | required: true 65 | } 66 | } 67 | } 68 | } 69 | } 70 | } 71 | } 72 | } 73 | }; 74 | 75 | var objNumToBoolean = { 76 | 3: true 77 | }; 78 | 79 | var objNumToString = { 80 | 3: 'dog' 81 | }; 82 | 83 | var objNoPatternMatch = { 84 | zzz: 'whatever I want' 85 | }; 86 | 87 | var schemaPatternProperties = { 88 | type: 'object', 89 | patternProperties: { 90 | '[0-9]': { type: 'boolean' } 91 | } 92 | }; 93 | 94 | var objNoAdditional = { 95 | str: 'hi', 96 | 3: true 97 | }; 98 | 99 | var objAdditionalInteger = { 100 | str: 'hi', 101 | 3: true, 102 | extra: 42 103 | }; 104 | 105 | var objAdditionalArray = { 106 | str: 'hi', 107 | 3: true, 108 | extra: [42] 109 | }; 110 | 111 | var schemaNoAdditionalProperties = { 112 | type: 'object', 113 | properties: { 114 | str: { type: 'string' } 115 | }, 116 | patternProperties: { 117 | '[0-9]': { type: 'boolean' } 118 | }, 119 | additionalProperties: false 120 | }; 121 | 122 | var schemaAdditionalPropertiesInteger = { 123 | type: 'object', 124 | properties: { 125 | str: { type: 'string' } 126 | }, 127 | patternProperties: { 128 | '[0-9]': { type: 'boolean' } 129 | }, 130 | additionalProperties: { type: 'integer' } 131 | }; 132 | 133 | vows.describe('Object Object').addBatch({ 134 | 'when nested object is valid': objectShouldBeValid(objNested, schemaNested), 135 | 'when nested object is missing a property': objectShouldBeInvalid(objNestedMissing, schemaNested, { errMsg: 'JSON object property \'obj1.obj2.obj3.str\' is required' }), 136 | 'when property matches pattern and correct type': objectShouldBeValid(objNumToBoolean, schemaPatternProperties), 137 | 'when property matches pattern and wrong type': objectShouldBeInvalid(objNumToString, schemaPatternProperties, { errMsg: 'JSON object property \'patternProperties./3/\' is a string when it should be a boolean' }), 138 | 'when no property matches pattern': objectShouldBeValid(objNoPatternMatch, schemaPatternProperties), 139 | 'when no additional properties is respected': objectShouldBeValid(objNoAdditional, schemaNoAdditionalProperties), 140 | 'when no additional properties is not respected': objectShouldBeInvalid(objAdditionalInteger, schemaNoAdditionalProperties, { errMsg: 'JSON object property \'extra\' is not explicitly defined and therefore not allowed' }), 141 | 'when additional property is correct type': objectShouldBeValid(objAdditionalInteger, schemaAdditionalPropertiesInteger), 142 | 'when additional property is wrong type': objectShouldBeInvalid(objAdditionalArray, schemaAdditionalPropertiesInteger, { errMsg: 'JSON object property \'extra\' is an array when it should be an integer' }) 143 | }).export(module); 144 | -------------------------------------------------------------------------------- /test/object-string-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | objectShouldBeValid = common.objectShouldBeValid, 7 | objectShouldBeInvalid = common.objectShouldBeInvalid; 8 | 9 | var obj = { 10 | str: 'Hello, json-gate' 11 | }; 12 | 13 | var schemaCorrectMinLength = { 14 | type: 'object', 15 | properties: { 16 | str: { 17 | type: 'string', 18 | minLength: 16, 19 | required: true 20 | } 21 | } 22 | }; 23 | 24 | var schemaIncorrectMinLength = { 25 | type: 'object', 26 | properties: { 27 | str: { 28 | type: 'string', 29 | minLength: 17, 30 | required: true 31 | } 32 | } 33 | }; 34 | 35 | var schemaCorrectMaxLength = { 36 | type: 'object', 37 | properties: { 38 | str: { 39 | type: 'string', 40 | maxLength: 16, 41 | required: true 42 | } 43 | } 44 | }; 45 | 46 | var schemaIncorrectMaxLength = { 47 | type: 'object', 48 | properties: { 49 | str: { 50 | type: 'string', 51 | maxLength: 15, 52 | required: true 53 | } 54 | } 55 | }; 56 | 57 | var schemaCorrectPattern = { 58 | type: 'object', 59 | properties: { 60 | str: { 61 | type: 'string', 62 | pattern: '^Hello', 63 | required: true 64 | } 65 | } 66 | }; 67 | 68 | var schemaIncorrectPattern = { 69 | type: 'object', 70 | properties: { 71 | str: { 72 | type: 'string', 73 | pattern: 'Hello$', 74 | required: true 75 | } 76 | } 77 | }; 78 | 79 | vows.describe('Object String').addBatch({ 80 | 'when complies with minimum length': objectShouldBeValid(obj, schemaCorrectMinLength), 81 | 'when exceeds minimum length': objectShouldBeInvalid(obj, schemaIncorrectMinLength, { errMsg: 'JSON object property \'str\': length is 16 when it should be at least 17' }), 82 | 'when complies with maximum length': objectShouldBeValid(obj, schemaCorrectMaxLength), 83 | 'when exceeds maximum length': objectShouldBeInvalid(obj, schemaIncorrectMaxLength, { errMsg: 'JSON object property \'str\': length is 16 when it should be at most 15' }), 84 | 'when complies with pattern': objectShouldBeValid(obj, schemaCorrectPattern), 85 | 'when does not comply with pattern': objectShouldBeInvalid(obj, schemaIncorrectPattern, { errMsg: 'JSON object property \'str\' does not match pattern' }) 86 | }).export(module); 87 | -------------------------------------------------------------------------------- /test/object-type-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | objectShouldBeValid = common.objectShouldBeValid, 7 | objectShouldBeInvalid = common.objectShouldBeInvalid; 8 | 9 | var objString = { 10 | val: 'Hello, json-gate' 11 | }; 12 | 13 | var objNumber = { 14 | val: 3.14 15 | }; 16 | 17 | var objInteger = { 18 | val: 42 19 | }; 20 | 21 | var objBoolean = { 22 | val: false 23 | }; 24 | 25 | var objObject = { 26 | val: {} 27 | }; 28 | 29 | var objArray = { 30 | val: [] 31 | }; 32 | 33 | var objNull = { 34 | val: null 35 | }; 36 | 37 | var objAny = { 38 | val: 'sub kuch milega' 39 | }; 40 | 41 | var schemaString = { 42 | type: 'object', 43 | properties: { 44 | val: { 45 | type: 'string', 46 | required: true 47 | } 48 | } 49 | }; 50 | 51 | var schemaNumber = { 52 | type: 'object', 53 | properties: { 54 | val: { 55 | type: 'number', 56 | required: true 57 | } 58 | } 59 | }; 60 | 61 | var schemaInteger = { 62 | type: 'object', 63 | properties: { 64 | val: { 65 | type: 'integer', 66 | required: true 67 | } 68 | } 69 | }; 70 | 71 | var schemaBoolean = { 72 | type: 'object', 73 | properties: { 74 | val: { 75 | type: 'boolean', 76 | required: true 77 | } 78 | } 79 | }; 80 | 81 | var schemaObject = { 82 | type: 'object', 83 | properties: { 84 | val: { 85 | type: 'object', 86 | required: true 87 | } 88 | } 89 | }; 90 | 91 | var schemaArray = { 92 | type: 'object', 93 | properties: { 94 | val: { 95 | type: 'array', 96 | required: true 97 | } 98 | } 99 | }; 100 | 101 | var schemaNull = { 102 | type: 'object', 103 | properties: { 104 | val: { 105 | type: 'null', 106 | required: true 107 | } 108 | } 109 | }; 110 | 111 | var schemaAny = { 112 | type: 'object', 113 | properties: { 114 | val: { 115 | type: 'any', 116 | required: true 117 | } 118 | } 119 | }; 120 | 121 | var schemaUnknownType = { 122 | type: 'object', 123 | properties: { 124 | val: { 125 | type: 'unknown', 126 | required: true 127 | } 128 | } 129 | }; 130 | 131 | vows.describe('Object Type').addBatch({ 132 | 'when a string is passed for a string': objectShouldBeValid(objString, schemaString), 133 | 'when trying to pass a number for a string': objectShouldBeInvalid(objNumber, schemaString, { errMsg: 'JSON object property \'val\' is a number when it should be a string' }), 134 | 'when a number is passed for a number': objectShouldBeValid(objNumber, schemaNumber), 135 | 'when trying to pass a string for a number': objectShouldBeInvalid(objString, schemaNumber, { errMsg: 'JSON object property \'val\' is a string when it should be a number' }), 136 | 'when an integer is passed for a number': objectShouldBeValid(objInteger, schemaNumber), 137 | 'when an integer is passed for an integer': objectShouldBeValid(objInteger, schemaInteger), 138 | 'when trying to pass a number for an integer': objectShouldBeInvalid(objNumber, schemaInteger, { errMsg: 'JSON object property \'val\' is a number when it should be an integer' }), 139 | 'when a boolean is passed for a boolean': objectShouldBeValid(objBoolean, schemaBoolean), 140 | 'when trying to pass an integer for a boolean': objectShouldBeInvalid(objInteger, schemaBoolean, { errMsg: 'JSON object property \'val\' is an integer when it should be a boolean' }), 141 | 'when an object is passed for an object': objectShouldBeValid(objObject, schemaObject), 142 | 'when trying to pass an array for an object': objectShouldBeInvalid(objArray, schemaObject, { errMsg: 'JSON object property \'val\' is an array when it should be an object' }), 143 | 'when an array is passed for an array': objectShouldBeValid(objArray, schemaArray), 144 | 'when trying to pass an object for an array': objectShouldBeInvalid(objObject, schemaArray, { errMsg: 'JSON object property \'val\' is an object when it should be an array' }), 145 | 'when null is passed for null': objectShouldBeValid(objNull, schemaNull), 146 | 'when trying to pass a boolean for null': objectShouldBeInvalid(objBoolean, schemaNull, { errMsg: 'JSON object property \'val\' is a boolean when it should be null' }), 147 | 'when a string is passed for any': objectShouldBeValid(objAny, schemaAny), 148 | 'when a string is passed for unknown type': objectShouldBeValid(objAny, schemaUnknownType) 149 | }).export(module); 150 | -------------------------------------------------------------------------------- /test/object-type-union-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | objectShouldBeValid = common.objectShouldBeValid, 7 | objectShouldBeInvalid = common.objectShouldBeInvalid; 8 | 9 | var objString = { 10 | nullable: 'I am' 11 | }; 12 | 13 | var objNull = { 14 | nullable: null 15 | }; 16 | 17 | var objInteger = { 18 | nullable: 42 19 | }; 20 | 21 | var schemaUnionType = { 22 | type: 'object', 23 | properties: { 24 | nullable: { type: ['string', 'null'] } 25 | } 26 | }; 27 | 28 | var objMan = { 29 | nullable: { 30 | name: 'John', 31 | age: 42 32 | } 33 | }; 34 | 35 | var objWoman = { 36 | nullable: { 37 | name: 'Jane', 38 | age: 'confidential' 39 | } 40 | }; 41 | 42 | var objAlien = { 43 | nullable: { 44 | name: 'Paul', 45 | age: true 46 | } 47 | } 48 | 49 | var schemaHuman = { 50 | type: 'object', 51 | properties: { 52 | name: { 53 | type: 'string', 54 | required: true 55 | }, 56 | age: { 57 | type: ['integer', 'string'], 58 | required: true 59 | } 60 | } 61 | }; 62 | 63 | var schemaUnionTypeWithSchema = { 64 | type: 'object', 65 | properties: { 66 | nullable: { 67 | type: ['string', 'null', schemaHuman], 68 | required: true 69 | } 70 | } 71 | }; 72 | 73 | vows.describe('Object Type').addBatch({ 74 | 'when a string is passed for either a string or null': objectShouldBeValid(objString, schemaUnionType), 75 | 'when null is passed for either a string or null': objectShouldBeValid(objNull, schemaUnionType), 76 | 'when trying to pass an integer for either a string or null': objectShouldBeInvalid(objInteger, schemaUnionType, { errMsg: 'JSON object property \'nullable\' is an integer when it should be either a string or null' }), 77 | 'when a string is passed for either a string or null or a human': objectShouldBeValid(objString, schemaUnionTypeWithSchema), 78 | 'when null is passed for either a string or null or a human': objectShouldBeValid(objNull, schemaUnionTypeWithSchema), 79 | 'when a man is passed for either a string or null or a human': objectShouldBeValid(objMan, schemaUnionTypeWithSchema), 80 | 'when a woman is passed for either a string or null or a human': objectShouldBeValid(objWoman, schemaUnionTypeWithSchema), 81 | 'when trying to pass an alien for either a string or null or a human': objectShouldBeInvalid(objAlien, schemaUnionTypeWithSchema, { errMsg: 'JSON object property \'nullable\' is an object when it should be either a string or null or a schema' }) 82 | }).export(module); 83 | -------------------------------------------------------------------------------- /test/schema-array-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | schemaShouldBeValid = common.schemaShouldBeValid, 7 | schemaShouldBeInvalid = common.schemaShouldBeInvalid; 8 | 9 | var schemaValidItems = { 10 | type: 'array', 11 | items: { type: 'string' } 12 | }; 13 | 14 | var schemaValidItemsTuple = { 15 | type: 'array', 16 | items: [ 17 | { type: 'string' }, 18 | { type: 'integer' }, 19 | { type: 'integer' }, 20 | { type: 'boolean' } 21 | ] 22 | }; 23 | 24 | var schemaValidItemsTupleWithProperties = { 25 | type: 'array', 26 | items: [ 27 | { type: 'string' }, 28 | { type: 'integer' }, 29 | { 30 | type: 'object', 31 | properties: { 32 | str: { type: 'string' }, 33 | num: { type: 'number' } 34 | } 35 | }, 36 | { type: 'integer' } 37 | ] 38 | }; 39 | 40 | var schemaInvalidItemsString = { 41 | type: 'array', 42 | items: 'string' 43 | }; 44 | 45 | var schemaInvalidItemsArray = { 46 | type: 'array', 47 | items: ['string', 'integer'] 48 | }; 49 | 50 | var schemaInvalidItemsInvalidSchema = { 51 | type: 'array', 52 | items: { type: 3 } 53 | }; 54 | 55 | var schemaInvalidItemsTupleWithInvalidProperties = { 56 | type: 'array', 57 | items: [ 58 | { type: 'string' }, 59 | { type: 'integer' }, 60 | { 61 | type: 'object', 62 | properties: { 63 | str: { type: 'string' }, 64 | num: { type: 3 } 65 | } 66 | }, 67 | { type: 'integer' } 68 | ] 69 | }; 70 | 71 | var schemaValidAdditionalItems = { 72 | type: 'array', 73 | items: [ 74 | { type: 'string' }, 75 | { type: 'integer' } 76 | ], 77 | additionalItems: { type: 'boolean' } 78 | }; 79 | 80 | var schemaValidAdditionalItemsInvalidSchema = { 81 | type: 'array', 82 | items: [ 83 | { type: 'string' }, 84 | { type: 'integer' } 85 | ], 86 | additionalItems: { type: 3 } 87 | }; 88 | 89 | var schemaValidNoAdditionalItems = { 90 | type: 'array', 91 | items: [ 92 | { type: 'string' }, 93 | { type: 'integer' } 94 | ], 95 | additionalItems: false 96 | }; 97 | 98 | var schemaInvalidAdditionalItemsTrue = { 99 | type: 'array', 100 | items: [ 101 | { type: 'string' }, 102 | { type: 'integer' } 103 | ], 104 | additionalItems: true 105 | }; 106 | 107 | var schemaSuperfluousAdditionalItemsNonTuple = { 108 | type: 'array', 109 | items: { type: 'string' }, 110 | additionalItems: false 111 | }; 112 | 113 | var schemaValidUniqueItems = { 114 | type: 'array', 115 | items: { type: 'string' }, 116 | uniqueItems: true 117 | }; 118 | 119 | var schemaInvalidUniqueItems = { 120 | type: 'array', 121 | items: { type: 'string' }, 122 | uniqueItems: 5 123 | }; 124 | 125 | vows.describe('Schema Array').addBatch({ 126 | 'when items is a schema': schemaShouldBeValid(schemaValidItems), 127 | 'when items is a tuple': schemaShouldBeValid(schemaValidItemsTuple), 128 | 'when items is a tuple containing properties': schemaShouldBeValid(schemaValidItemsTupleWithProperties), 129 | 'when items is a string': schemaShouldBeInvalid(schemaInvalidItemsString, { errMsg: 'Schema: \'items\' attribute is a string when it should be either an object (schema) or an array' }), 130 | 'when items is an array with strings': schemaShouldBeInvalid(schemaInvalidItemsArray, { errMsg: 'Schema: \'items\' attribute element 0 is not a valid schema: Schema is a string when it should be an object' }), 131 | 'when items is an invalid schema': schemaShouldBeInvalid(schemaInvalidItemsInvalidSchema, { errMsg: 'Schema: \'items\' attribute is not a valid schema: Schema: \'type\' attribute is an integer when it should be either a string or an array' }), 132 | 'when items array contains an invalid schema': schemaShouldBeInvalid(schemaInvalidItemsTupleWithInvalidProperties, { errMsg: 'Schema: \'items\' attribute element 2 is not a valid schema: Schema property \'num\': \'type\' attribute is an integer when it should be either a string or an array' }), 133 | 'when additionalItems is a valid schema': schemaShouldBeValid(schemaValidAdditionalItems), 134 | 'when additionalItems is an invalid schema': schemaShouldBeInvalid(schemaValidAdditionalItemsInvalidSchema, { errMsg: 'Schema: \'additionalItems\' attribute is not a valid schema: Schema: \'type\' attribute is an integer when it should be either a string or an array' }), 135 | 'when additionalItems is false': schemaShouldBeValid(schemaValidNoAdditionalItems), 136 | 'when additionalItems is true': schemaShouldBeInvalid(schemaInvalidAdditionalItemsTrue, { errMsg: 'Schema: \'additionalItems\' attribute is a boolean when it should be either an object (schema) or false' }), 137 | 'when additionalItems is provided although items is not a tuple': schemaShouldBeValid(schemaSuperfluousAdditionalItemsNonTuple), 138 | 'when uniqueItems is a boolean': schemaShouldBeValid(schemaValidUniqueItems), 139 | 'when uniqueItems is an integer': schemaShouldBeInvalid(schemaInvalidUniqueItems, { errMsg: 'Schema: \'uniqueItems\' attribute is an integer when it should be a boolean' }) 140 | }).export(module); 141 | -------------------------------------------------------------------------------- /test/schema-basic-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | schemaShouldBeValid = common.schemaShouldBeValid, 7 | schemaShouldBeInvalid = common.schemaShouldBeInvalid; 8 | 9 | var schemaNotAnObject = 'not-an-object'; 10 | 11 | var schemaWithoutType = { 12 | }; 13 | 14 | var schemaInvalidType = { 15 | type: 123 16 | }; 17 | 18 | var schemaInappropriateType = { 19 | type: 'string' 20 | }; 21 | 22 | var schemaEmptyObject = { 23 | type: 'object' 24 | }; 25 | 26 | var schemaEmptyArray = { 27 | type: 'array' 28 | }; 29 | 30 | var schemaStringOrNull = { 31 | type: ['string', 'null'] 32 | }; 33 | 34 | vows.describe('Schema Basic').addBatch({ 35 | 'when schema is undefined': schemaShouldBeInvalid(undefined, { errMsg: 'Schema is undefined' }), 36 | 'when schema is not an object': schemaShouldBeInvalid(schemaNotAnObject, { errMsg: 'Schema is a string when it should be an object' }), 37 | 'when type attribute is neither a string nor an array': schemaShouldBeInvalid(schemaInvalidType, { errMsg: 'Schema: \'type\' attribute is an integer when it should be either a string or an array' }), 38 | 'when type attribute is \'object\'': schemaShouldBeValid(schemaEmptyObject), 39 | 'when type attribute is \'array\'': schemaShouldBeValid(schemaEmptyArray), 40 | 'when type attribute is a union type with simple types': schemaShouldBeValid(schemaStringOrNull) 41 | }).export(module); 42 | -------------------------------------------------------------------------------- /test/schema-default-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | schemaShouldBeValid = common.schemaShouldBeValid, 7 | schemaShouldBeInvalid = common.schemaShouldBeInvalid; 8 | 9 | var schemaValidDefaultString = { 10 | type: 'object', 11 | properties: { 12 | str: { 13 | type: 'string', 14 | default: 'hello' 15 | } 16 | } 17 | }; 18 | 19 | var schemaInvalidDefaultString = { 20 | type: 'object', 21 | properties: { 22 | str: { 23 | type: 'string', 24 | default: 42 25 | } 26 | } 27 | }; 28 | 29 | var schemaValidDefaultObject = { 30 | type: 'object', 31 | properties: { 32 | str: { 33 | type: 'object', 34 | properties: { 35 | str: { 36 | type: 'string', 37 | required: true 38 | }, 39 | num: { 40 | type: 'number', 41 | required: true 42 | } 43 | }, 44 | default: { 45 | str: 'hello', 46 | num: 42 47 | } 48 | } 49 | } 50 | }; 51 | 52 | var schemaInvalidDefaultObject = { 53 | type: 'object', 54 | properties: { 55 | str: { 56 | type: 'object', 57 | properties: { 58 | str: { 59 | type: 'string', 60 | required: true 61 | }, 62 | num: { 63 | type: 'number', 64 | required: true 65 | } 66 | }, 67 | default: { 68 | str: 'hello', 69 | num: '42' 70 | } 71 | } 72 | } 73 | }; 74 | 75 | vows.describe('Schema Default').addBatch({ 76 | 'when default value is a string as expected': schemaShouldBeValid(schemaValidDefaultString), 77 | 'when default value is not a string as expected': schemaShouldBeInvalid(schemaInvalidDefaultString, { errMsg: 'Schema property \'str\': \'default\' attribute value is not valid according to the schema: JSON object is an integer when it should be a string' }), 78 | 'when default value conforms to the schema': schemaShouldBeValid(schemaValidDefaultObject), 79 | 'when default value does not conform to the schema': schemaShouldBeInvalid(schemaInvalidDefaultObject, { errMsg: 'Schema property \'str\': \'default\' attribute value is not valid according to the schema: JSON object property \'num\' is a string when it should be a number' }) 80 | }).export(module); 81 | -------------------------------------------------------------------------------- /test/schema-dependencies-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | schemaShouldBeValid = common.schemaShouldBeValid, 7 | schemaShouldBeInvalid = common.schemaShouldBeInvalid; 8 | 9 | var schemaValidSimple = { 10 | type: 'object', 11 | dependencies: { 12 | a: 'b' 13 | } 14 | }; 15 | 16 | var schemaInvalidSimple = { 17 | type: 'object', 18 | dependencies: { 19 | a: 3 20 | } 21 | }; 22 | 23 | var schemaValidSimpleTuple = { 24 | type: 'object', 25 | dependencies: { 26 | a: ['b', 'c', 'd'] 27 | } 28 | }; 29 | 30 | var schemaInvalidSimpleTuple = { 31 | type: 'object', 32 | dependencies: { 33 | a: ['b', 'c', 3] 34 | } 35 | }; 36 | 37 | var schemaValidSchema = { 38 | type: 'object', 39 | dependencies: { 40 | a: { type: 'integer' } 41 | } 42 | }; 43 | 44 | var schemaInvalidSchema = { 45 | type: 'object', 46 | dependencies: { 47 | a: { type: 3 } 48 | } 49 | }; 50 | 51 | var schemaInvalid = { 52 | type: 'object', 53 | dependencies: 3 54 | }; 55 | 56 | vows.describe('Schema Dependencies').addBatch({ 57 | 'when simple dependency is valid': schemaShouldBeValid(schemaValidSimple), 58 | 'when simple dependency is invalid': schemaShouldBeInvalid(schemaInvalidSimple, { errMsg: 'Schema: \'dependencies\' attribute: value of property \'a\' is an integer when it should be either a string, an array or an object (schema)' }), 59 | 'when simple tuple dependency is valid': schemaShouldBeValid(schemaValidSimpleTuple), 60 | 'when simple tuple dependency is invalid': schemaShouldBeInvalid(schemaInvalidSimpleTuple, { errMsg: 'Schema: \'dependencies\' attribute: value of property \'a\' element 2 is an integer when it should be a string' }), 61 | 'when schema dependency is valid': schemaShouldBeValid(schemaValidSchema), 62 | 'when schema dependency is invalid': schemaShouldBeInvalid(schemaInvalidSchema, { errMsg: 'Schema: \'dependencies\' attribute: value of property \'a\' is not a valid schema: Schema: \'type\' attribute is an integer when it should be either a string or an array' }), 63 | 'when dependencies is not an object': schemaShouldBeInvalid(schemaInvalid, { errMsg: 'Schema: \'dependencies\' attribute is an integer when it should be an object' }) 64 | }).export(module); 65 | 66 | -------------------------------------------------------------------------------- /test/schema-disallow-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | schemaShouldBeValid = common.schemaShouldBeValid, 7 | schemaShouldBeInvalid = common.schemaShouldBeInvalid; 8 | 9 | var schemaValid = { 10 | type: 'object', 11 | properties: { 12 | str: { disallow: 'string' }, 13 | num: { disallow: 'number' }, 14 | int: { disallow: 'integer' }, 15 | bool: { disallow: 'boolean' }, 16 | obj: { disallow: 'object' }, 17 | arr: { disallow: 'array' }, 18 | z: { disallow: 'null' }, 19 | value: { disallow: 'any' } 20 | } 21 | }; 22 | 23 | var schemaInvalid = { 24 | type: 'object', 25 | properties: { 26 | seven: { disallow: 7 } 27 | } 28 | }; 29 | 30 | var schemaSimpleUnionType = { 31 | type: 'object', 32 | properties: { 33 | nullable: { disallow: ['string', 'null'] } 34 | } 35 | }; 36 | 37 | var schemaInvalidUnionTypeWithOnlyOne = { 38 | type: 'object', 39 | properties: { 40 | schizo: { disallow: ['string'] } 41 | } 42 | }; 43 | 44 | var schemaUnionTypeWithValidSchema = { 45 | type: 'object', 46 | properties: { 47 | deep: { 48 | disallow: ['string', 49 | { 50 | type: 'object', 51 | properties: { 52 | num: { type: 'number' } 53 | } 54 | } 55 | ] 56 | } 57 | } 58 | }; 59 | 60 | var schemaUnionTypeWithInvalidSchema = { 61 | type: 'object', 62 | properties: { 63 | deep: { 64 | disallow: ['string', 65 | { 66 | type: 'object', 67 | properties: { 68 | num: { 69 | type: 'number', 70 | minimum: 'dog' 71 | } 72 | } 73 | } 74 | ] 75 | } 76 | } 77 | }; 78 | 79 | vows.describe('Schema Disallow').addBatch({ 80 | 'when attributes are all simple types': schemaShouldBeValid(schemaValid), 81 | 'when attribute is neither a string nor an array': schemaShouldBeInvalid(schemaInvalid, { errMsg: 'Schema property \'seven\': \'disallow\' attribute is an integer when it should be either a string or an array' }), 82 | 'when attribute is a union type with simple types': schemaShouldBeValid(schemaSimpleUnionType), 83 | 'when attribute is a union type with only one simple type': schemaShouldBeInvalid(schemaInvalidUnionTypeWithOnlyOne, { errMsg: 'Schema property \'schizo\': \'disallow\' attribute union length is 1 when it should be at least 2' }), 84 | 'when attribute is a union type with a valid schema': schemaShouldBeValid(schemaUnionTypeWithValidSchema), 85 | 'when attribute is a union type with an invalid schema': schemaShouldBeInvalid(schemaUnionTypeWithInvalidSchema, { errMsg: 'Schema property \'deep\': \'disallow\' attribute union element 1 is not a valid schema: Schema property \'num\': \'minimum\' attribute is a string when it should be a number' }) 86 | }).export(module); 87 | -------------------------------------------------------------------------------- /test/schema-enum-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | schemaShouldBeValid = common.schemaShouldBeValid, 7 | schemaShouldBeInvalid = common.schemaShouldBeInvalid; 8 | 9 | var schemaValidEnum = { 10 | type: 'object', 11 | properties: { 12 | prop: { 13 | enum: [] 14 | } 15 | } 16 | }; 17 | 18 | var schemaInvalidEnum = { 19 | type: 'object', 20 | properties: { 21 | prop: { 22 | enum: {} 23 | } 24 | } 25 | }; 26 | 27 | vows.describe('Schema Enum').addBatch({ 28 | 'when enum attribute is an array': schemaShouldBeValid(schemaValidEnum), 29 | 'when enum attribute is not an array': schemaShouldBeInvalid(schemaInvalidEnum, { errMsg: 'Schema property \'prop\': \'enum\' attribute is an object when it should be an array' }) 30 | }).export(module); 31 | -------------------------------------------------------------------------------- /test/schema-format-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | schemaShouldBeValid = common.schemaShouldBeValid, 7 | schemaShouldBeInvalid = common.schemaShouldBeInvalid; 8 | 9 | var schemaValidFormats = { 10 | type: 'object', 11 | properties: { 12 | dateTime: { 13 | type: 'string', 14 | format: 'date-time' 15 | }, 16 | date: { 17 | type: 'string', 18 | format: 'date' 19 | }, 20 | time: { 21 | type: 'string', 22 | format: 'time' 23 | }, 24 | ms: { 25 | type: 'integer', 26 | format: 'utc-millisec' 27 | }, 28 | ms2: { 29 | type: 'number', 30 | format: 'utc-millisec' 31 | }, 32 | re: { 33 | type: 'string', 34 | format: 'regex' 35 | }, 36 | color: { 37 | type: 'string', 38 | format: 'color' 39 | }, 40 | style: { 41 | type: 'string', 42 | format: 'style' 43 | }, 44 | phone: { 45 | type: 'string', 46 | format: 'phone' 47 | }, 48 | uri: { 49 | type: 'string', 50 | format: 'uri' 51 | }, 52 | email: { 53 | type: 'string', 54 | format: 'email' 55 | }, 56 | ip4: { 57 | type: 'string', 58 | format: 'ip-address' 59 | }, 60 | ip6: { 61 | type: 'string', 62 | format: 'ipv6' 63 | }, 64 | host: { 65 | type: 'string', 66 | format: 'host-name' 67 | } 68 | } 69 | }; 70 | 71 | var schemaUnknownFormat = { 72 | type: 'object', 73 | properties: { 74 | prop: { 75 | format: 'my-format' 76 | } 77 | } 78 | }; 79 | 80 | var schemaInvalidFormatType = { 81 | type: 'object', 82 | properties: { 83 | seq: { 84 | format: [1, 2, 3] 85 | } 86 | } 87 | }; 88 | 89 | var schemaInvalidTypeIp = { 90 | type: 'object', 91 | properties: { 92 | ip: { 93 | type: 'integer', 94 | format: 'ip-address' 95 | } 96 | } 97 | }; 98 | 99 | var schemaInvalidTypeMs = { 100 | type: 'object', 101 | properties: { 102 | ms: { 103 | type: 'string', 104 | format: 'utc-millisec' 105 | } 106 | } 107 | }; 108 | 109 | vows.describe('Schema Format').addBatch({ 110 | 'when formats are valid': schemaShouldBeValid(schemaValidFormats), 111 | 'when format is unknown': schemaShouldBeValid(schemaUnknownFormat), 112 | 'when format is not a string': schemaShouldBeInvalid(schemaInvalidFormatType, { errMsg: 'Schema property \'seq\': \'format\' attribute is an array when it should be a string' }), 113 | 'when \'ip-address\' format is applied to the wrong type': schemaShouldBeInvalid(schemaInvalidTypeIp, { errMsg: 'Schema property \'ip\': \'type\' attribute does not conform to the \'ip-address\' format' }), 114 | 'when \'utc-millisec\' format is applied to the wrong type': schemaShouldBeInvalid(schemaInvalidTypeMs, { errMsg: 'Schema property \'ms\': \'type\' attribute does not conform to the \'utc-millisec\' format' }) 115 | }).export(module); 116 | -------------------------------------------------------------------------------- /test/schema-number-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | schemaShouldBeValid = common.schemaShouldBeValid, 7 | schemaShouldBeInvalid = common.schemaShouldBeInvalid; 8 | 9 | var schemaValidMinimum = { 10 | type: 'object', 11 | properties: { 12 | num: { 13 | type: 'number', 14 | minimum: 3.14 15 | } 16 | } 17 | }; 18 | 19 | var schemaInvalidMinimum = { 20 | type: 'object', 21 | properties: { 22 | num: { 23 | type: 'number', 24 | minimum: '3.14' 25 | } 26 | } 27 | }; 28 | 29 | var schemaValidExclusiveMinimum = { 30 | type: 'object', 31 | properties: { 32 | num: { 33 | type: 'number', 34 | minimum: 108, 35 | exclusiveMinimum: true 36 | } 37 | } 38 | }; 39 | 40 | var schemaInvalidExclusiveMinimum = { 41 | type: 'object', 42 | properties: { 43 | num: { 44 | type: 'number', 45 | minimum: 108, 46 | exclusiveMinimum: 'true' 47 | } 48 | } 49 | }; 50 | 51 | var schemaValidMaximum = { 52 | type: 'object', 53 | properties: { 54 | num: { 55 | type: 'number', 56 | maximum: 3.14 57 | } 58 | } 59 | }; 60 | 61 | var schemaInvalidMaximum = { 62 | type: 'object', 63 | properties: { 64 | num: { 65 | type: 'number', 66 | maximum: '3.14' 67 | } 68 | } 69 | }; 70 | 71 | var schemaValidExclusiveMaximum = { 72 | type: 'object', 73 | properties: { 74 | num: { 75 | type: 'number', 76 | maximum: 108, 77 | exclusiveMaximum: true 78 | } 79 | } 80 | }; 81 | 82 | var schemaInvalidExclusiveMaximum = { 83 | type: 'object', 84 | properties: { 85 | num: { 86 | type: 'number', 87 | maximum: 108, 88 | exclusiveMaximum: 'true' 89 | } 90 | } 91 | }; 92 | 93 | var schemaValidDivisibleBy = { 94 | type: 'object', 95 | properties: { 96 | num: { 97 | type: 'number', 98 | divisibleBy: 3.14 99 | } 100 | } 101 | }; 102 | 103 | var schemaInvalidDivisibleBy = { 104 | type: 'object', 105 | properties: { 106 | num: { 107 | type: 'number', 108 | divisibleBy: '3.14' 109 | } 110 | } 111 | }; 112 | 113 | vows.describe('Schema Number').addBatch({ 114 | 'when minimum attribute is a number': schemaShouldBeValid(schemaValidMinimum), 115 | 'when minimum attribute is not a number': schemaShouldBeInvalid(schemaInvalidMinimum, { errMsg: 'Schema property \'num\': \'minimum\' attribute is a string when it should be a number' }), 116 | 'when exclusiveMinimum attribute is a boolean': schemaShouldBeValid(schemaValidExclusiveMinimum), 117 | 'when exclusiveMinimum attribute is not a boolean': schemaShouldBeInvalid(schemaInvalidExclusiveMinimum, { errMsg: 'Schema property \'num\': \'exclusiveMinimum\' attribute is a string when it should be a boolean' }), 118 | 'when maximum attribute is a number': schemaShouldBeValid(schemaValidMaximum), 119 | 'when maximum attribute is not a number': schemaShouldBeInvalid(schemaInvalidMaximum, { errMsg: 'Schema property \'num\': \'maximum\' attribute is a string when it should be a number' }), 120 | 'when exclusiveMaximum attribute is a boolean': schemaShouldBeValid(schemaValidExclusiveMaximum), 121 | 'when exclusiveMaximum attribute is not a boolean': schemaShouldBeInvalid(schemaInvalidExclusiveMaximum, { errMsg: 'Schema property \'num\': \'exclusiveMaximum\' attribute is a string when it should be a boolean' }), 122 | 'when divisibleBy attribute is a number': schemaShouldBeValid(schemaValidDivisibleBy), 123 | 'when divisibleBy attribute is not a number': schemaShouldBeInvalid(schemaInvalidDivisibleBy, { errMsg: 'Schema property \'num\': \'divisibleBy\' attribute is a string when it should be a number' }) 124 | }).export(module); 125 | -------------------------------------------------------------------------------- /test/schema-object-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | schemaShouldBeValid = common.schemaShouldBeValid, 7 | schemaShouldBeInvalid = common.schemaShouldBeInvalid; 8 | 9 | var schemaValidProperties = { 10 | type: 'object', 11 | properties: { 12 | str: { type: 'string' }, 13 | obj: { 14 | type: 'object', 15 | properties: { 16 | arr: { type: 'array' } 17 | } 18 | } 19 | } 20 | }; 21 | 22 | var schemaInvalidPropertiesType = { 23 | type: 'object', 24 | properties: 5 25 | }; 26 | 27 | var schemaInvalidProperties = { 28 | type: 'object', 29 | properties: { 30 | obj: { 31 | type: 'object', 32 | properties: { 33 | arr: { 34 | type: 'array', 35 | minItems: 'bug' 36 | } 37 | } 38 | } 39 | } 40 | }; 41 | 42 | var schemaValidPatternProperties = { 43 | type: 'object', 44 | patternProperties: { 45 | '[0-9]': { type: 'boolean' } 46 | } 47 | }; 48 | 49 | var schemaInvalidPatternPropertiesType = { 50 | type: 'object', 51 | patternProperties: 'seven' 52 | }; 53 | 54 | var schemaInvalidPatternPropertiesInvalidSchema = { 55 | type: 'object', 56 | patternProperties: { 57 | '[0-9]': 'boolean' 58 | } 59 | }; 60 | 61 | var schemaValidAdditionalProperties = { 62 | type: 'object', 63 | additionalProperties: { type: 'boolean' } 64 | }; 65 | 66 | var schemaInvalidNoAdditionalPropertiesInvalidSchema = { 67 | type: 'object', 68 | additionalProperties: { type: 3 } 69 | }; 70 | 71 | var schemaValidNoAdditionalProperties = { 72 | type: 'object', 73 | additionalProperties: false 74 | }; 75 | 76 | var schemaInvalidAdditionalPropertiesTrue = { 77 | type: 'object', 78 | additionalProperties: true 79 | }; 80 | 81 | vows.describe('Schema Object').addBatch({ 82 | 'when properties is valid': schemaShouldBeValid(schemaValidProperties), 83 | 'when properties is not an object': schemaShouldBeInvalid(schemaInvalidPropertiesType, { errMsg: 'Schema: \'properties\' attribute is an integer when it should be an object' }), 84 | 'when properties is invalid': schemaShouldBeInvalid(schemaInvalidProperties, { errMsg: 'Schema property \'obj.arr\': \'minItems\' attribute is a string when it should be an integer' }), 85 | 'when patternProperties is valid': schemaShouldBeValid(schemaValidPatternProperties), 86 | 'when patternProperties is not an object': schemaShouldBeInvalid(schemaInvalidPatternPropertiesType, { errMsg: 'Schema: \'patternProperties\' attribute is a string when it should be an object' }), 87 | 'when patternProperties is invalid': schemaShouldBeInvalid(schemaInvalidPatternPropertiesInvalidSchema, { errMsg: 'Schema property \'patternProperties./[0-9]/\' is a string when it should be an object' }), 88 | 'when additionalProperties is a valid schema': schemaShouldBeValid(schemaValidAdditionalProperties), 89 | 'when additionalProperties is an invalid schema': schemaShouldBeInvalid(schemaInvalidNoAdditionalPropertiesInvalidSchema, { errMsg: 'Schema: \'additionalProperties\' attribute is not a valid schema: Schema: \'type\' attribute is an integer when it should be either a string or an array' }), 90 | 'when additionalProperties is false': schemaShouldBeValid(schemaValidNoAdditionalProperties), 91 | 'when additionalProperties is true': schemaShouldBeInvalid(schemaInvalidAdditionalPropertiesTrue, { errMsg: 'Schema: \'additionalProperties\' attribute is a boolean when it should be either an object (schema) or false' }) 92 | }).export(module); 93 | 94 | -------------------------------------------------------------------------------- /test/schema-required-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | schemaShouldBeValid = common.schemaShouldBeValid, 7 | schemaShouldBeInvalid = common.schemaShouldBeInvalid; 8 | 9 | var schemaValidRequired = { 10 | type: 'object', 11 | properties: { 12 | mandatory: { 13 | required: true 14 | } 15 | } 16 | }; 17 | 18 | var schemaInvalidRequired = { 19 | type: 'object', 20 | properties: { 21 | mandatory: { 22 | required: '123' 23 | } 24 | } 25 | }; 26 | 27 | vows.describe('Schema Required').addBatch({ 28 | 'when required attribute is a boolean': schemaShouldBeValid(schemaValidRequired), 29 | 'when required attribute is not a boolean': schemaShouldBeInvalid(schemaInvalidRequired, { errMsg: 'Schema property \'mandatory\': \'required\' attribute is a string when it should be a boolean' }) 30 | }).export(module); 31 | -------------------------------------------------------------------------------- /test/schema-string-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | schemaShouldBeValid = common.schemaShouldBeValid, 7 | schemaShouldBeInvalid = common.schemaShouldBeInvalid; 8 | 9 | var schemaValidMinLength = { 10 | type: 'object', 11 | properties: { 12 | str: { 13 | type: 'string', 14 | minLength: 3 15 | } 16 | } 17 | }; 18 | 19 | var schemaInvalidMinLength = { 20 | type: 'object', 21 | properties: { 22 | str: { 23 | type: 'string', 24 | minLength: 3.14 25 | } 26 | } 27 | }; 28 | 29 | var schemaValidMaxLength = { 30 | type: 'object', 31 | properties: { 32 | str: { 33 | type: 'string', 34 | maxLength: 3 35 | } 36 | } 37 | }; 38 | 39 | var schemaInvalidMaxLength = { 40 | type: 'object', 41 | properties: { 42 | str: { 43 | type: 'string', 44 | maxLength: 3.14 45 | } 46 | } 47 | }; 48 | 49 | var schemaValidPattern = { 50 | type: 'object', 51 | properties: { 52 | str: { 53 | type: 'string', 54 | pattern: '^Hello, JSON$' 55 | } 56 | } 57 | }; 58 | 59 | var schemaInvalidPattern = { 60 | type: 'object', 61 | properties: { 62 | str: { 63 | type: 'string', 64 | pattern: {hello: 'JSON'} 65 | } 66 | } 67 | }; 68 | 69 | vows.describe('Schema String').addBatch({ 70 | 'when minLength attribute is an integer': schemaShouldBeValid(schemaValidMinLength), 71 | 'when minLength attribute is not an integer': schemaShouldBeInvalid(schemaInvalidMinLength, { errMsg: 'Schema property \'str\': \'minLength\' attribute is a number when it should be an integer' }), 72 | 'when maxLength attribute is an integer': schemaShouldBeValid(schemaValidMaxLength), 73 | 'when maxLength attribute is not an integer': schemaShouldBeInvalid(schemaInvalidMaxLength, { errMsg: 'Schema property \'str\': \'maxLength\' attribute is a number when it should be an integer' }), 74 | 'when pattern attribute is a string': schemaShouldBeValid(schemaValidPattern), 75 | 'when pattern attribute is not a string': schemaShouldBeInvalid(schemaInvalidPattern, { errMsg: 'Schema property \'str\': \'pattern\' attribute is an object when it should be a string' }) 76 | }).export(module); 77 | -------------------------------------------------------------------------------- /test/schema-type-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'); 4 | 5 | var common = require('./common'), 6 | schemaShouldBeValid = common.schemaShouldBeValid, 7 | schemaShouldBeInvalid = common.schemaShouldBeInvalid; 8 | 9 | var schemaValid = { 10 | type: 'object', 11 | properties: { 12 | str: { type: 'string' }, 13 | num: { type: 'number' }, 14 | int: { type: 'integer' }, 15 | bool: { type: 'boolean' }, 16 | obj: { type: 'object' }, 17 | arr: { type: 'array' }, 18 | z: { type: 'null' }, 19 | value: { type: 'any' } 20 | } 21 | }; 22 | 23 | var schemaInvalid = { 24 | type: 'object', 25 | properties: { 26 | seven: { type: 7 } 27 | } 28 | }; 29 | 30 | var schemaSimpleUnionType = { 31 | type: 'object', 32 | properties: { 33 | nullable: { type: ['string', 'null'] } 34 | } 35 | }; 36 | 37 | var schemaInvalidUnionTypeWithOnlyOne = { 38 | type: 'object', 39 | properties: { 40 | schizo: { type: ['string'] } 41 | } 42 | }; 43 | 44 | var schemaUnionTypeWithValidSchema = { 45 | type: 'object', 46 | properties: { 47 | deep: { 48 | type: ['string', 49 | { 50 | type: 'object', 51 | properties: { 52 | num: { type: 'number' } 53 | } 54 | } 55 | ] 56 | } 57 | } 58 | }; 59 | 60 | var schemaUnionTypeWithInvalidSchema = { 61 | type: 'object', 62 | properties: { 63 | deep: { 64 | type: ['string', 65 | { 66 | type: 'object', 67 | properties: { 68 | num: { 69 | type: 'number', 70 | minimum: 'dog' 71 | } 72 | } 73 | } 74 | ] 75 | } 76 | } 77 | }; 78 | 79 | vows.describe('Schema Type').addBatch({ 80 | 'when attributes are all simple types': schemaShouldBeValid(schemaValid), 81 | 'when attribute is neither a string nor an array': schemaShouldBeInvalid(schemaInvalid, { errMsg: 'Schema property \'seven\': \'type\' attribute is an integer when it should be either a string or an array' }), 82 | 'when attribute is a union type with simple types': schemaShouldBeValid(schemaSimpleUnionType), 83 | 'when attribute is a union type with only one simple type': schemaShouldBeInvalid(schemaInvalidUnionTypeWithOnlyOne, { errMsg: 'Schema property \'schizo\': \'type\' attribute union length is 1 when it should be at least 2' }), 84 | 'when attribute is a union type with a valid schema': schemaShouldBeValid(schemaUnionTypeWithValidSchema), 85 | 'when attribute is a union type with an invalid schema': schemaShouldBeInvalid(schemaUnionTypeWithInvalidSchema, { errMsg: 'Schema property \'deep\': \'type\' attribute union element 1 is not a valid schema: Schema property \'num\': \'minimum\' attribute is a string when it should be a number' }) 86 | }).export(module); 87 | -------------------------------------------------------------------------------- /test/sync-test.js: -------------------------------------------------------------------------------- 1 | // this test is run by Vows (as all files matching *test.js) 2 | 3 | var vows = require('vows'), 4 | should = require('should'); 5 | 6 | var createSchema = require('..').createSchema, 7 | config = require('./config'); 8 | 9 | var emptyObject = {}; 10 | 11 | var schemaEmptyObject = { 12 | type: 'object' 13 | }; 14 | 15 | var schemaEmptyArray = { 16 | type: 'array' 17 | }; 18 | 19 | vows.describe('Sync-Async').addBatch({ 20 | 'when calling synchronously with a valid object': { 21 | topic: function () { 22 | var schema = createSchema(schemaEmptyObject); 23 | try { 24 | schema.validate(emptyObject); 25 | this.callback(null); 26 | } catch(err) { 27 | this.callback(err); 28 | } 29 | }, 30 | 'we get back nothing': function (err, result) { 31 | should.not.exist(err); 32 | should.not.exist(result); 33 | } 34 | } 35 | }).addBatch({ 36 | 'when calling synchronously with an invalid object': { 37 | topic: function () { 38 | var schema = createSchema(schemaEmptyArray); 39 | try { 40 | schema.validate(emptyObject); 41 | this.callback(null, emptyObject); 42 | } catch(err) { 43 | this.callback(err); 44 | } 45 | }, 46 | 'we get an error': function (err, result) { 47 | should.exist(err); 48 | should.not.exist(result); 49 | if (config.verbose) { 50 | console.log('Error:', err) 51 | } 52 | err.should.have.property('message', 'JSON object is an object when it should be an array'); 53 | } 54 | } 55 | }).addBatch({ 56 | 'when calling asynchronously with a valid object': { 57 | topic: function () { 58 | var schema = createSchema(schemaEmptyObject); 59 | schema.validate(emptyObject, this.callback); 60 | }, 61 | 'we get back the object': function (err, result) { 62 | should.not.exist(err); 63 | should.exist(result); 64 | result.should.eql(emptyObject); 65 | } 66 | } 67 | }).addBatch({ 68 | 'when calling asynchronously with an invalid object': { 69 | topic: function () { 70 | var schema = createSchema(schemaEmptyArray); 71 | schema.validate(emptyObject, this.callback); 72 | }, 73 | 'we get an error': function (err, result) { 74 | should.exist(err); 75 | should.not.exist(result); 76 | if (config.verbose) { 77 | console.log('Error:', err) 78 | } 79 | err.should.have.property('message', 'JSON object is an object when it should be an array'); 80 | } 81 | } 82 | }).export(module); 83 | --------------------------------------------------------------------------------