Revision 74bbdd9a
Added by koszko over 1 year ago
| .gitmodules | ||
|---|---|---|
| 1 |
[submodule "schemas"] |
|
| 2 |
path = schemas |
|
| 3 |
url = ../hydrilla-json-schemas/ |
|
| common/jsonschema.js | ||
|---|---|---|
| 1 |
/* SPDX-License-Identifier: MIT AND CC0-1.0 |
|
| 2 |
* |
|
| 3 |
* License text of the original lib/index.js from jsonschema library: |
|
| 4 |
* |
|
| 5 |
*************************************** |
|
| 6 |
* |
|
| 7 |
* jsonschema is licensed under MIT license. |
|
| 8 |
* |
|
| 9 |
* Copyright (C) 2012-2015 Tom de Grunt <tom@degrunt.nl> |
|
| 10 |
* |
|
| 11 |
* Permission is hereby granted, free of charge, to any person obtaining a copy of |
|
| 12 |
* this software and associated documentation files (the "Software"), to deal in |
|
| 13 |
* the Software without restriction, including without limitation the rights to |
|
| 14 |
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
|
| 15 |
* of the Software, and to permit persons to whom the Software is furnished to do |
|
| 16 |
* so, subject to the following conditions: |
|
| 17 |
* |
|
| 18 |
* The above copyright notice and this permission notice shall be included in all |
|
| 19 |
* copies or substantial portions of the Software. |
|
| 20 |
* |
|
| 21 |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
| 22 |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
| 23 |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
| 24 |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
| 25 |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
| 26 |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
| 27 |
* SOFTWARE. |
|
| 28 |
* |
|
| 29 |
******************************************************************************* |
|
| 30 |
* |
|
| 31 |
* License notice for the adaptation to use in Haketilo: |
|
| 32 |
* |
|
| 33 |
*************************************** |
|
| 34 |
* |
|
| 35 |
* Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org> |
|
| 36 |
* |
|
| 37 |
* This program is free software: you can redistribute it and/or modify |
|
| 38 |
* it under the terms of the CC0 1.0 Universal License as published by |
|
| 39 |
* the Creative Commons Corporation. |
|
| 40 |
* |
|
| 41 |
* This program is distributed in the hope that it will be useful, |
|
| 42 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 43 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 44 |
* CC0 1.0 Universal License for more details. |
|
| 45 |
*/ |
|
| 46 |
|
|
| 47 |
#FROM common/jsonschema/validator.js IMPORT Validator |
|
| 48 |
#EXPORT Validator |
|
| 49 |
|
|
| 50 |
#FROM common/jsonschema/helpers.js IMPORT ValidatorResult, ValidationError, \ |
|
| 51 |
ValidatorResultError, SchemaError |
|
| 52 |
|
|
| 53 |
#EXPORT ValidatorResult |
|
| 54 |
#EXPORT ValidationError |
|
| 55 |
#EXPORT ValidatorResultError |
|
| 56 |
#EXPORT SchemaError |
|
| 57 |
|
|
| 58 |
#FROM common/jsonschema/scan.js IMPORT SchemaScanResult, scan |
|
| 59 |
|
|
| 60 |
#EXPORT scan |
|
| 61 |
#EXPORT SchemaScanResult |
|
| 62 |
|
|
| 63 |
function validate(instance, schema, options) {
|
|
| 64 |
var v = new Validator(); |
|
| 65 |
return v.validate(instance, schema, options); |
|
| 66 |
}; |
|
| 67 |
#EXPORT validate |
|
| 68 |
|
|
| 69 |
const haketilo_schemas = [ |
|
| 70 |
#INCLUDE schemas/api_query_result-1.0.1.schema.json |
|
| 71 |
, |
|
| 72 |
#INCLUDE schemas/api_mapping_description-1.0.1.schema.json |
|
| 73 |
, |
|
| 74 |
#INCLUDE schemas/api_resource_description-1.0.1.schema.json |
|
| 75 |
, |
|
| 76 |
#INCLUDE schemas/common_definitions-1.0.1.schema.json |
|
| 77 |
].reduce((ac, s) => Object.assign(ac, {[s.$id]: s}), {});
|
|
| 78 |
#EXPORT haketilo_schemas |
|
| 79 |
|
|
| 80 |
const haketilo_validator = new Validator(); |
|
| 81 |
Object.values(haketilo_schemas) |
|
| 82 |
.forEach(s => haketilo_validator.addSchema(s, s.$id)); |
|
| 83 |
#EXPORT haketilo_validator |
|
| common/jsonschema/attribute.js | ||
|---|---|---|
| 1 |
/* SPDX-License-Identifier: MIT |
|
| 2 |
* |
|
| 3 |
* jsonschema is licensed under MIT license. |
|
| 4 |
* |
|
| 5 |
* Copyright (C) 2012-2015 Tom de Grunt <tom@degrunt.nl> |
|
| 6 |
* |
|
| 7 |
* Permission is hereby granted, free of charge, to any person obtaining a copy of |
|
| 8 |
* this software and associated documentation files (the "Software"), to deal in |
|
| 9 |
* the Software without restriction, including without limitation the rights to |
|
| 10 |
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
|
| 11 |
* of the Software, and to permit persons to whom the Software is furnished to do |
|
| 12 |
* so, subject to the following conditions: |
|
| 13 |
* |
|
| 14 |
* The above copyright notice and this permission notice shall be included in all |
|
| 15 |
* copies or substantial portions of the Software. |
|
| 16 |
* |
|
| 17 |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
| 18 |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
| 19 |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
| 20 |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
| 21 |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
| 22 |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
| 23 |
* SOFTWARE. |
|
| 24 |
*/ |
|
| 25 |
|
|
| 26 |
#IMPORT common/jsonschema/helpers.js |
|
| 27 |
|
|
| 28 |
/** @type ValidatorResult */ |
|
| 29 |
var ValidatorResult = helpers.ValidatorResult; |
|
| 30 |
/** @type SchemaError */ |
|
| 31 |
var SchemaError = helpers.SchemaError; |
|
| 32 |
|
|
| 33 |
var attribute = {};
|
|
| 34 |
|
|
| 35 |
const ignoreProperties = {
|
|
| 36 |
// informative properties |
|
| 37 |
'id': true, |
|
| 38 |
'default': true, |
|
| 39 |
'description': true, |
|
| 40 |
'title': true, |
|
| 41 |
// arguments to other properties |
|
| 42 |
'additionalItems': true, |
|
| 43 |
'then': true, |
|
| 44 |
'else': true, |
|
| 45 |
// special-handled properties |
|
| 46 |
'$schema': true, |
|
| 47 |
'$ref': true, |
|
| 48 |
'extends': true, |
|
| 49 |
}; |
|
| 50 |
#EXPORT ignoreProperties |
|
| 51 |
|
|
| 52 |
/** |
|
| 53 |
* @name validators |
|
| 54 |
*/ |
|
| 55 |
const validators = {};
|
|
| 56 |
|
|
| 57 |
/** |
|
| 58 |
* Validates whether the instance if of a certain type |
|
| 59 |
* @param instance |
|
| 60 |
* @param schema |
|
| 61 |
* @param options |
|
| 62 |
* @param ctx |
|
| 63 |
* @return {ValidatorResult|null}
|
|
| 64 |
*/ |
|
| 65 |
validators.type = function validateType (instance, schema, options, ctx) {
|
|
| 66 |
// Ignore undefined instances |
|
| 67 |
if (instance === undefined) {
|
|
| 68 |
return null; |
|
| 69 |
} |
|
| 70 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 71 |
var types = Array.isArray(schema.type) ? schema.type : [schema.type]; |
|
| 72 |
if (!types.some(this.testType.bind(this, instance, schema, options, ctx))) {
|
|
| 73 |
var list = types.map(function (v) {
|
|
| 74 |
if(!v) return; |
|
| 75 |
var id = v.$id || v.id; |
|
| 76 |
return id ? ('<' + id + '>') : (v+'');
|
|
| 77 |
}); |
|
| 78 |
result.addError({
|
|
| 79 |
name: 'type', |
|
| 80 |
argument: list, |
|
| 81 |
message: "is not of a type(s) " + list, |
|
| 82 |
}); |
|
| 83 |
} |
|
| 84 |
return result; |
|
| 85 |
}; |
|
| 86 |
|
|
| 87 |
function testSchemaNoThrow(instance, options, ctx, callback, schema){
|
|
| 88 |
var throwError = options.throwError; |
|
| 89 |
var throwAll = options.throwAll; |
|
| 90 |
options.throwError = false; |
|
| 91 |
options.throwAll = false; |
|
| 92 |
var res = this.validateSchema(instance, schema, options, ctx); |
|
| 93 |
options.throwError = throwError; |
|
| 94 |
options.throwAll = throwAll; |
|
| 95 |
|
|
| 96 |
if (!res.valid && callback instanceof Function) {
|
|
| 97 |
callback(res); |
|
| 98 |
} |
|
| 99 |
return res.valid; |
|
| 100 |
} |
|
| 101 |
|
|
| 102 |
/** |
|
| 103 |
* Validates whether the instance matches some of the given schemas |
|
| 104 |
* @param instance |
|
| 105 |
* @param schema |
|
| 106 |
* @param options |
|
| 107 |
* @param ctx |
|
| 108 |
* @return {ValidatorResult|null}
|
|
| 109 |
*/ |
|
| 110 |
validators.anyOf = function validateAnyOf (instance, schema, options, ctx) {
|
|
| 111 |
// Ignore undefined instances |
|
| 112 |
if (instance === undefined) {
|
|
| 113 |
return null; |
|
| 114 |
} |
|
| 115 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 116 |
var inner = new ValidatorResult(instance, schema, options, ctx); |
|
| 117 |
if (!Array.isArray(schema.anyOf)){
|
|
| 118 |
throw new SchemaError("anyOf must be an array");
|
|
| 119 |
} |
|
| 120 |
if (!schema.anyOf.some( |
|
| 121 |
testSchemaNoThrow.bind( |
|
| 122 |
this, instance, options, ctx, function(res){inner.importErrors(res);}
|
|
| 123 |
))) {
|
|
| 124 |
var list = schema.anyOf.map(function (v, i) {
|
|
| 125 |
var id = v.$id || v.id; |
|
| 126 |
if(id) return '<' + id + '>'; |
|
| 127 |
return(v.title && JSON.stringify(v.title)) || (v['$ref'] && ('<' + v['$ref'] + '>')) || '[subschema '+i+']';
|
|
| 128 |
}); |
|
| 129 |
if (options.nestedErrors) {
|
|
| 130 |
result.importErrors(inner); |
|
| 131 |
} |
|
| 132 |
result.addError({
|
|
| 133 |
name: 'anyOf', |
|
| 134 |
argument: list, |
|
| 135 |
message: "is not any of " + list.join(','),
|
|
| 136 |
}); |
|
| 137 |
} |
|
| 138 |
return result; |
|
| 139 |
}; |
|
| 140 |
|
|
| 141 |
/** |
|
| 142 |
* Validates whether the instance matches every given schema |
|
| 143 |
* @param instance |
|
| 144 |
* @param schema |
|
| 145 |
* @param options |
|
| 146 |
* @param ctx |
|
| 147 |
* @return {String|null}
|
|
| 148 |
*/ |
|
| 149 |
validators.allOf = function validateAllOf (instance, schema, options, ctx) {
|
|
| 150 |
// Ignore undefined instances |
|
| 151 |
if (instance === undefined) {
|
|
| 152 |
return null; |
|
| 153 |
} |
|
| 154 |
if (!Array.isArray(schema.allOf)){
|
|
| 155 |
throw new SchemaError("allOf must be an array");
|
|
| 156 |
} |
|
| 157 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 158 |
var self = this; |
|
| 159 |
schema.allOf.forEach(function(v, i){
|
|
| 160 |
var valid = self.validateSchema(instance, v, options, ctx); |
|
| 161 |
if(!valid.valid){
|
|
| 162 |
var id = v.$id || v.id; |
|
| 163 |
var msg = id || (v.title && JSON.stringify(v.title)) || (v['$ref'] && ('<' + v['$ref'] + '>')) || '[subschema '+i+']';
|
|
| 164 |
result.addError({
|
|
| 165 |
name: 'allOf', |
|
| 166 |
argument: { id: msg, length: valid.errors.length, valid: valid },
|
|
| 167 |
message: 'does not match allOf schema ' + msg + ' with ' + valid.errors.length + ' error[s]:', |
|
| 168 |
}); |
|
| 169 |
result.importErrors(valid); |
|
| 170 |
} |
|
| 171 |
}); |
|
| 172 |
return result; |
|
| 173 |
}; |
|
| 174 |
|
|
| 175 |
/** |
|
| 176 |
* Validates whether the instance matches exactly one of the given schemas |
|
| 177 |
* @param instance |
|
| 178 |
* @param schema |
|
| 179 |
* @param options |
|
| 180 |
* @param ctx |
|
| 181 |
* @return {String|null}
|
|
| 182 |
*/ |
|
| 183 |
validators.oneOf = function validateOneOf (instance, schema, options, ctx) {
|
|
| 184 |
// Ignore undefined instances |
|
| 185 |
if (instance === undefined) {
|
|
| 186 |
return null; |
|
| 187 |
} |
|
| 188 |
if (!Array.isArray(schema.oneOf)){
|
|
| 189 |
throw new SchemaError("oneOf must be an array");
|
|
| 190 |
} |
|
| 191 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 192 |
var inner = new ValidatorResult(instance, schema, options, ctx); |
|
| 193 |
var count = schema.oneOf.filter( |
|
| 194 |
testSchemaNoThrow.bind( |
|
| 195 |
this, instance, options, ctx, function(res) {inner.importErrors(res);}
|
|
| 196 |
) ).length; |
|
| 197 |
var list = schema.oneOf.map(function (v, i) {
|
|
| 198 |
var id = v.$id || v.id; |
|
| 199 |
return id || (v.title && JSON.stringify(v.title)) || (v['$ref'] && ('<' + v['$ref'] + '>')) || '[subschema '+i+']';
|
|
| 200 |
}); |
|
| 201 |
if (count!==1) {
|
|
| 202 |
if (options.nestedErrors) {
|
|
| 203 |
result.importErrors(inner); |
|
| 204 |
} |
|
| 205 |
result.addError({
|
|
| 206 |
name: 'oneOf', |
|
| 207 |
argument: list, |
|
| 208 |
message: "is not exactly one from " + list.join(','),
|
|
| 209 |
}); |
|
| 210 |
} |
|
| 211 |
return result; |
|
| 212 |
}; |
|
| 213 |
|
|
| 214 |
/** |
|
| 215 |
* Validates "then" or "else" depending on the result of validating "if" |
|
| 216 |
* @param instance |
|
| 217 |
* @param schema |
|
| 218 |
* @param options |
|
| 219 |
* @param ctx |
|
| 220 |
* @return {String|null}
|
|
| 221 |
*/ |
|
| 222 |
validators.if = function validateIf (instance, schema, options, ctx) {
|
|
| 223 |
// Ignore undefined instances |
|
| 224 |
if (instance === undefined) return null; |
|
| 225 |
if (!helpers.isSchema(schema.if)) throw new Error('Expected "if" keyword to be a schema');
|
|
| 226 |
var ifValid = testSchemaNoThrow.call(this, instance, options, ctx, null, schema.if); |
|
| 227 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 228 |
var res; |
|
| 229 |
if(ifValid){
|
|
| 230 |
if (schema.then === undefined) return; |
|
| 231 |
if (!helpers.isSchema(schema.then)) throw new Error('Expected "then" keyword to be a schema');
|
|
| 232 |
res = this.validateSchema(instance, schema.then, options, ctx.makeChild(schema.then)); |
|
| 233 |
result.importErrors(res); |
|
| 234 |
}else{
|
|
| 235 |
if (schema.else === undefined) return; |
|
| 236 |
if (!helpers.isSchema(schema.else)) throw new Error('Expected "else" keyword to be a schema');
|
|
| 237 |
res = this.validateSchema(instance, schema.else, options, ctx.makeChild(schema.else)); |
|
| 238 |
result.importErrors(res); |
|
| 239 |
} |
|
| 240 |
return result; |
|
| 241 |
}; |
|
| 242 |
|
|
| 243 |
function getEnumerableProperty(object, key){
|
|
| 244 |
// Determine if `key` shows up in `for(var key in object)` |
|
| 245 |
// First test Object.hasOwnProperty.call as an optimization: that guarantees it does |
|
| 246 |
if(Object.hasOwnProperty.call(object, key)) return object[key]; |
|
| 247 |
// Test `key in object` as an optimization; false means it won't |
|
| 248 |
if(!(key in object)) return; |
|
| 249 |
while( (object = Object.getPrototypeOf(object)) ){
|
|
| 250 |
if(Object.propertyIsEnumerable.call(object, key)) return object[key]; |
|
| 251 |
} |
|
| 252 |
} |
|
| 253 |
|
|
| 254 |
/** |
|
| 255 |
* Validates propertyNames |
|
| 256 |
* @param instance |
|
| 257 |
* @param schema |
|
| 258 |
* @param options |
|
| 259 |
* @param ctx |
|
| 260 |
* @return {String|null|ValidatorResult}
|
|
| 261 |
*/ |
|
| 262 |
validators.propertyNames = function validatePropertyNames (instance, schema, options, ctx) {
|
|
| 263 |
if(!this.types.object(instance)) return; |
|
| 264 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 265 |
var subschema = schema.propertyNames!==undefined ? schema.propertyNames : {};
|
|
| 266 |
if(!helpers.isSchema(subschema)) throw new SchemaError('Expected "propertyNames" to be a schema (object or boolean)');
|
|
| 267 |
|
|
| 268 |
for (var property in instance) {
|
|
| 269 |
if(getEnumerableProperty(instance, property) !== undefined){
|
|
| 270 |
var res = this.validateSchema(property, subschema, options, ctx.makeChild(subschema)); |
|
| 271 |
result.importErrors(res); |
|
| 272 |
} |
|
| 273 |
} |
|
| 274 |
|
|
| 275 |
return result; |
|
| 276 |
}; |
|
| 277 |
|
|
| 278 |
/** |
|
| 279 |
* Validates properties |
|
| 280 |
* @param instance |
|
| 281 |
* @param schema |
|
| 282 |
* @param options |
|
| 283 |
* @param ctx |
|
| 284 |
* @return {String|null|ValidatorResult}
|
|
| 285 |
*/ |
|
| 286 |
validators.properties = function validateProperties (instance, schema, options, ctx) {
|
|
| 287 |
if(!this.types.object(instance)) return; |
|
| 288 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 289 |
var properties = schema.properties || {};
|
|
| 290 |
for (var property in properties) {
|
|
| 291 |
var subschema = properties[property]; |
|
| 292 |
if(subschema===undefined){
|
|
| 293 |
continue; |
|
| 294 |
}else if(subschema===null){
|
|
| 295 |
throw new SchemaError('Unexpected null, expected schema in "properties"');
|
|
| 296 |
} |
|
| 297 |
if (typeof options.preValidateProperty == 'function') {
|
|
| 298 |
options.preValidateProperty(instance, property, subschema, options, ctx); |
|
| 299 |
} |
|
| 300 |
var prop = getEnumerableProperty(instance, property); |
|
| 301 |
var res = this.validateSchema(prop, subschema, options, ctx.makeChild(subschema, property)); |
|
| 302 |
if(res.instance !== result.instance[property]) result.instance[property] = res.instance; |
|
| 303 |
result.importErrors(res); |
|
| 304 |
} |
|
| 305 |
return result; |
|
| 306 |
}; |
|
| 307 |
|
|
| 308 |
/** |
|
| 309 |
* Test a specific property within in instance against the additionalProperties schema attribute |
|
| 310 |
* This ignores properties with definitions in the properties schema attribute, but no other attributes. |
|
| 311 |
* If too many more types of property-existence tests pop up they may need their own class of tests (like `type` has) |
|
| 312 |
* @private |
|
| 313 |
* @return {boolean}
|
|
| 314 |
*/ |
|
| 315 |
function testAdditionalProperty (instance, schema, options, ctx, property, result) {
|
|
| 316 |
if(!this.types.object(instance)) return; |
|
| 317 |
if (schema.properties && schema.properties[property] !== undefined) {
|
|
| 318 |
return; |
|
| 319 |
} |
|
| 320 |
if (schema.additionalProperties === false) {
|
|
| 321 |
result.addError({
|
|
| 322 |
name: 'additionalProperties', |
|
| 323 |
argument: property, |
|
| 324 |
message: "is not allowed to have the additional property " + JSON.stringify(property), |
|
| 325 |
}); |
|
| 326 |
} else {
|
|
| 327 |
var additionalProperties = schema.additionalProperties || {};
|
|
| 328 |
|
|
| 329 |
if (typeof options.preValidateProperty == 'function') {
|
|
| 330 |
options.preValidateProperty(instance, property, additionalProperties, options, ctx); |
|
| 331 |
} |
|
| 332 |
|
|
| 333 |
var res = this.validateSchema(instance[property], additionalProperties, options, ctx.makeChild(additionalProperties, property)); |
|
| 334 |
if(res.instance !== result.instance[property]) result.instance[property] = res.instance; |
|
| 335 |
result.importErrors(res); |
|
| 336 |
} |
|
| 337 |
} |
|
| 338 |
|
|
| 339 |
/** |
|
| 340 |
* Validates patternProperties |
|
| 341 |
* @param instance |
|
| 342 |
* @param schema |
|
| 343 |
* @param options |
|
| 344 |
* @param ctx |
|
| 345 |
* @return {String|null|ValidatorResult}
|
|
| 346 |
*/ |
|
| 347 |
validators.patternProperties = function validatePatternProperties (instance, schema, options, ctx) {
|
|
| 348 |
if(!this.types.object(instance)) return; |
|
| 349 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 350 |
var patternProperties = schema.patternProperties || {};
|
|
| 351 |
|
|
| 352 |
for (var property in instance) {
|
|
| 353 |
var test = true; |
|
| 354 |
for (var pattern in patternProperties) {
|
|
| 355 |
var subschema = patternProperties[pattern]; |
|
| 356 |
if(subschema===undefined){
|
|
| 357 |
continue; |
|
| 358 |
}else if(subschema===null){
|
|
| 359 |
throw new SchemaError('Unexpected null, expected schema in "patternProperties"');
|
|
| 360 |
} |
|
| 361 |
try {
|
|
| 362 |
var regexp = new RegExp(pattern, 'u'); |
|
| 363 |
} catch(_e) {
|
|
| 364 |
// In the event the stricter handling causes an error, fall back on the forgiving handling |
|
| 365 |
// DEPRECATED |
|
| 366 |
regexp = new RegExp(pattern); |
|
| 367 |
} |
|
| 368 |
if (!regexp.test(property)) {
|
|
| 369 |
continue; |
|
| 370 |
} |
|
| 371 |
test = false; |
|
| 372 |
|
|
| 373 |
if (typeof options.preValidateProperty == 'function') {
|
|
| 374 |
options.preValidateProperty(instance, property, subschema, options, ctx); |
|
| 375 |
} |
|
| 376 |
|
|
| 377 |
var res = this.validateSchema(instance[property], subschema, options, ctx.makeChild(subschema, property)); |
|
| 378 |
if(res.instance !== result.instance[property]) result.instance[property] = res.instance; |
|
| 379 |
result.importErrors(res); |
|
| 380 |
} |
|
| 381 |
if (test) {
|
|
| 382 |
testAdditionalProperty.call(this, instance, schema, options, ctx, property, result); |
|
| 383 |
} |
|
| 384 |
} |
|
| 385 |
|
|
| 386 |
return result; |
|
| 387 |
}; |
|
| 388 |
|
|
| 389 |
/** |
|
| 390 |
* Validates additionalProperties |
|
| 391 |
* @param instance |
|
| 392 |
* @param schema |
|
| 393 |
* @param options |
|
| 394 |
* @param ctx |
|
| 395 |
* @return {String|null|ValidatorResult}
|
|
| 396 |
*/ |
|
| 397 |
validators.additionalProperties = function validateAdditionalProperties (instance, schema, options, ctx) {
|
|
| 398 |
if(!this.types.object(instance)) return; |
|
| 399 |
// if patternProperties is defined then we'll test when that one is called instead |
|
| 400 |
if (schema.patternProperties) {
|
|
| 401 |
return null; |
|
| 402 |
} |
|
| 403 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 404 |
for (var property in instance) {
|
|
| 405 |
testAdditionalProperty.call(this, instance, schema, options, ctx, property, result); |
|
| 406 |
} |
|
| 407 |
return result; |
|
| 408 |
}; |
|
| 409 |
|
|
| 410 |
/** |
|
| 411 |
* Validates whether the instance value is at least of a certain length, when the instance value is a string. |
|
| 412 |
* @param instance |
|
| 413 |
* @param schema |
|
| 414 |
* @return {String|null}
|
|
| 415 |
*/ |
|
| 416 |
validators.minProperties = function validateMinProperties (instance, schema, options, ctx) {
|
|
| 417 |
if (!this.types.object(instance)) return; |
|
| 418 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 419 |
var keys = Object.keys(instance); |
|
| 420 |
if (!(keys.length >= schema.minProperties)) {
|
|
| 421 |
result.addError({
|
|
| 422 |
name: 'minProperties', |
|
| 423 |
argument: schema.minProperties, |
|
| 424 |
message: "does not meet minimum property length of " + schema.minProperties, |
|
| 425 |
}); |
|
| 426 |
} |
|
| 427 |
return result; |
|
| 428 |
}; |
|
| 429 |
|
|
| 430 |
/** |
|
| 431 |
* Validates whether the instance value is at most of a certain length, when the instance value is a string. |
|
| 432 |
* @param instance |
|
| 433 |
* @param schema |
|
| 434 |
* @return {String|null}
|
|
| 435 |
*/ |
|
| 436 |
validators.maxProperties = function validateMaxProperties (instance, schema, options, ctx) {
|
|
| 437 |
if (!this.types.object(instance)) return; |
|
| 438 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 439 |
var keys = Object.keys(instance); |
|
| 440 |
if (!(keys.length <= schema.maxProperties)) {
|
|
| 441 |
result.addError({
|
|
| 442 |
name: 'maxProperties', |
|
| 443 |
argument: schema.maxProperties, |
|
| 444 |
message: "does not meet maximum property length of " + schema.maxProperties, |
|
| 445 |
}); |
|
| 446 |
} |
|
| 447 |
return result; |
|
| 448 |
}; |
|
| 449 |
|
|
| 450 |
/** |
|
| 451 |
* Validates items when instance is an array |
|
| 452 |
* @param instance |
|
| 453 |
* @param schema |
|
| 454 |
* @param options |
|
| 455 |
* @param ctx |
|
| 456 |
* @return {String|null|ValidatorResult}
|
|
| 457 |
*/ |
|
| 458 |
validators.items = function validateItems (instance, schema, options, ctx) {
|
|
| 459 |
var self = this; |
|
| 460 |
if (!this.types.array(instance)) return; |
|
| 461 |
if (schema.items===undefined) return; |
|
| 462 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 463 |
instance.every(function (value, i) {
|
|
| 464 |
if(Array.isArray(schema.items)){
|
|
| 465 |
var items = schema.items[i]===undefined ? schema.additionalItems : schema.items[i]; |
|
| 466 |
}else{
|
|
| 467 |
var items = schema.items; |
|
| 468 |
} |
|
| 469 |
if (items === undefined) {
|
|
| 470 |
return true; |
|
| 471 |
} |
|
| 472 |
if (items === false) {
|
|
| 473 |
result.addError({
|
|
| 474 |
name: 'items', |
|
| 475 |
message: "additionalItems not permitted", |
|
| 476 |
}); |
|
| 477 |
return false; |
|
| 478 |
} |
|
| 479 |
var res = self.validateSchema(value, items, options, ctx.makeChild(items, i)); |
|
| 480 |
if(res.instance !== result.instance[i]) result.instance[i] = res.instance; |
|
| 481 |
result.importErrors(res); |
|
| 482 |
return true; |
|
| 483 |
}); |
|
| 484 |
return result; |
|
| 485 |
}; |
|
| 486 |
|
|
| 487 |
/** |
|
| 488 |
* Validates the "contains" keyword |
|
| 489 |
* @param instance |
|
| 490 |
* @param schema |
|
| 491 |
* @param options |
|
| 492 |
* @param ctx |
|
| 493 |
* @return {String|null|ValidatorResult}
|
|
| 494 |
*/ |
|
| 495 |
validators.contains = function validateContains (instance, schema, options, ctx) {
|
|
| 496 |
var self = this; |
|
| 497 |
if (!this.types.array(instance)) return; |
|
| 498 |
if (schema.contains===undefined) return; |
|
| 499 |
if (!helpers.isSchema(schema.contains)) throw new Error('Expected "contains" keyword to be a schema');
|
|
| 500 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 501 |
var count = instance.some(function (value, i) {
|
|
| 502 |
var res = self.validateSchema(value, schema.contains, options, ctx.makeChild(schema.contains, i)); |
|
| 503 |
return res.errors.length===0; |
|
| 504 |
}); |
|
| 505 |
if(count===false){
|
|
| 506 |
result.addError({
|
|
| 507 |
name: 'contains', |
|
| 508 |
argument: schema.contains, |
|
| 509 |
message: "must contain an item matching given schema", |
|
| 510 |
}); |
|
| 511 |
} |
|
| 512 |
return result; |
|
| 513 |
}; |
|
| 514 |
|
|
| 515 |
/** |
|
| 516 |
* Validates minimum and exclusiveMinimum when the type of the instance value is a number. |
|
| 517 |
* @param instance |
|
| 518 |
* @param schema |
|
| 519 |
* @return {String|null}
|
|
| 520 |
*/ |
|
| 521 |
validators.minimum = function validateMinimum (instance, schema, options, ctx) {
|
|
| 522 |
if (!this.types.number(instance)) return; |
|
| 523 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 524 |
if (schema.exclusiveMinimum && schema.exclusiveMinimum === true) {
|
|
| 525 |
if(!(instance > schema.minimum)){
|
|
| 526 |
result.addError({
|
|
| 527 |
name: 'minimum', |
|
| 528 |
argument: schema.minimum, |
|
| 529 |
message: "must be greater than " + schema.minimum, |
|
| 530 |
}); |
|
| 531 |
} |
|
| 532 |
} else {
|
|
| 533 |
if(!(instance >= schema.minimum)){
|
|
| 534 |
result.addError({
|
|
| 535 |
name: 'minimum', |
|
| 536 |
argument: schema.minimum, |
|
| 537 |
message: "must be greater than or equal to " + schema.minimum, |
|
| 538 |
}); |
|
| 539 |
} |
|
| 540 |
} |
|
| 541 |
return result; |
|
| 542 |
}; |
|
| 543 |
|
|
| 544 |
/** |
|
| 545 |
* Validates maximum and exclusiveMaximum when the type of the instance value is a number. |
|
| 546 |
* @param instance |
|
| 547 |
* @param schema |
|
| 548 |
* @return {String|null}
|
|
| 549 |
*/ |
|
| 550 |
validators.maximum = function validateMaximum (instance, schema, options, ctx) {
|
|
| 551 |
if (!this.types.number(instance)) return; |
|
| 552 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 553 |
if (schema.exclusiveMaximum && schema.exclusiveMaximum === true) {
|
|
| 554 |
if(!(instance < schema.maximum)){
|
|
| 555 |
result.addError({
|
|
| 556 |
name: 'maximum', |
|
| 557 |
argument: schema.maximum, |
|
| 558 |
message: "must be less than " + schema.maximum, |
|
| 559 |
}); |
|
| 560 |
} |
|
| 561 |
} else {
|
|
| 562 |
if(!(instance <= schema.maximum)){
|
|
| 563 |
result.addError({
|
|
| 564 |
name: 'maximum', |
|
| 565 |
argument: schema.maximum, |
|
| 566 |
message: "must be less than or equal to " + schema.maximum, |
|
| 567 |
}); |
|
| 568 |
} |
|
| 569 |
} |
|
| 570 |
return result; |
|
| 571 |
}; |
|
| 572 |
|
|
| 573 |
/** |
|
| 574 |
* Validates the number form of exclusiveMinimum when the type of the instance value is a number. |
|
| 575 |
* @param instance |
|
| 576 |
* @param schema |
|
| 577 |
* @return {String|null}
|
|
| 578 |
*/ |
|
| 579 |
validators.exclusiveMinimum = function validateExclusiveMinimum (instance, schema, options, ctx) {
|
|
| 580 |
// Support the boolean form of exclusiveMinimum, which is handled by the "minimum" keyword. |
|
| 581 |
if(typeof schema.exclusiveMinimum === 'boolean') return; |
|
| 582 |
if (!this.types.number(instance)) return; |
|
| 583 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 584 |
var valid = instance > schema.exclusiveMinimum; |
|
| 585 |
if (!valid) {
|
|
| 586 |
result.addError({
|
|
| 587 |
name: 'exclusiveMinimum', |
|
| 588 |
argument: schema.exclusiveMinimum, |
|
| 589 |
message: "must be strictly greater than " + schema.exclusiveMinimum, |
|
| 590 |
}); |
|
| 591 |
} |
|
| 592 |
return result; |
|
| 593 |
}; |
|
| 594 |
|
|
| 595 |
/** |
|
| 596 |
* Validates the number form of exclusiveMaximum when the type of the instance value is a number. |
|
| 597 |
* @param instance |
|
| 598 |
* @param schema |
|
| 599 |
* @return {String|null}
|
|
| 600 |
*/ |
|
| 601 |
validators.exclusiveMaximum = function validateExclusiveMaximum (instance, schema, options, ctx) {
|
|
| 602 |
// Support the boolean form of exclusiveMaximum, which is handled by the "maximum" keyword. |
|
| 603 |
if(typeof schema.exclusiveMaximum === 'boolean') return; |
|
| 604 |
if (!this.types.number(instance)) return; |
|
| 605 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 606 |
var valid = instance < schema.exclusiveMaximum; |
|
| 607 |
if (!valid) {
|
|
| 608 |
result.addError({
|
|
| 609 |
name: 'exclusiveMaximum', |
|
| 610 |
argument: schema.exclusiveMaximum, |
|
| 611 |
message: "must be strictly less than " + schema.exclusiveMaximum, |
|
| 612 |
}); |
|
| 613 |
} |
|
| 614 |
return result; |
|
| 615 |
}; |
|
| 616 |
|
|
| 617 |
/** |
|
| 618 |
* Perform validation for multipleOf and divisibleBy, which are essentially the same. |
|
| 619 |
* @param instance |
|
| 620 |
* @param schema |
|
| 621 |
* @param validationType |
|
| 622 |
* @param errorMessage |
|
| 623 |
* @returns {String|null}
|
|
| 624 |
*/ |
|
| 625 |
var validateMultipleOfOrDivisbleBy = function validateMultipleOfOrDivisbleBy (instance, schema, options, ctx, validationType, errorMessage) {
|
|
| 626 |
if (!this.types.number(instance)) return; |
|
| 627 |
|
|
| 628 |
var validationArgument = schema[validationType]; |
|
| 629 |
if (validationArgument == 0) {
|
|
| 630 |
throw new SchemaError(validationType + " cannot be zero"); |
|
| 631 |
} |
|
| 632 |
|
|
| 633 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 634 |
|
|
| 635 |
var instanceDecimals = helpers.getDecimalPlaces(instance); |
|
| 636 |
var divisorDecimals = helpers.getDecimalPlaces(validationArgument); |
|
| 637 |
|
|
| 638 |
var maxDecimals = Math.max(instanceDecimals , divisorDecimals); |
|
| 639 |
var multiplier = Math.pow(10, maxDecimals); |
|
| 640 |
|
|
| 641 |
if (Math.round(instance * multiplier) % Math.round(validationArgument * multiplier) !== 0) {
|
|
| 642 |
result.addError({
|
|
| 643 |
name: validationType, |
|
| 644 |
argument: validationArgument, |
|
| 645 |
message: errorMessage + JSON.stringify(validationArgument), |
|
| 646 |
}); |
|
| 647 |
} |
|
| 648 |
|
|
| 649 |
return result; |
|
| 650 |
}; |
|
| 651 |
|
|
| 652 |
/** |
|
| 653 |
* Validates divisibleBy when the type of the instance value is a number. |
|
| 654 |
* @param instance |
|
| 655 |
* @param schema |
|
| 656 |
* @return {String|null}
|
|
| 657 |
*/ |
|
| 658 |
validators.multipleOf = function validateMultipleOf (instance, schema, options, ctx) {
|
|
| 659 |
return validateMultipleOfOrDivisbleBy.call(this, instance, schema, options, ctx, "multipleOf", "is not a multiple of (divisible by) "); |
|
| 660 |
}; |
|
| 661 |
|
|
| 662 |
/** |
|
| 663 |
* Validates multipleOf when the type of the instance value is a number. |
|
| 664 |
* @param instance |
|
| 665 |
* @param schema |
|
| 666 |
* @return {String|null}
|
|
| 667 |
*/ |
|
| 668 |
validators.divisibleBy = function validateDivisibleBy (instance, schema, options, ctx) {
|
|
| 669 |
return validateMultipleOfOrDivisbleBy.call(this, instance, schema, options, ctx, "divisibleBy", "is not divisible by (multiple of) "); |
|
| 670 |
}; |
|
| 671 |
|
|
| 672 |
/** |
|
| 673 |
* Validates whether the instance value is present. |
|
| 674 |
* @param instance |
|
| 675 |
* @param schema |
|
| 676 |
* @return {String|null}
|
|
| 677 |
*/ |
|
| 678 |
validators.required = function validateRequired (instance, schema, options, ctx) {
|
|
| 679 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 680 |
if (instance === undefined && schema.required === true) {
|
|
| 681 |
// A boolean form is implemented for reverse-compatibility with schemas written against older drafts |
|
| 682 |
result.addError({
|
|
| 683 |
name: 'required', |
|
| 684 |
message: "is required", |
|
| 685 |
}); |
|
| 686 |
} else if (this.types.object(instance) && Array.isArray(schema.required)) {
|
|
| 687 |
schema.required.forEach(function(n){
|
|
| 688 |
if(getEnumerableProperty(instance, n)===undefined){
|
|
| 689 |
result.addError({
|
|
| 690 |
name: 'required', |
|
| 691 |
argument: n, |
|
| 692 |
message: "requires property " + JSON.stringify(n), |
|
| 693 |
}); |
|
| 694 |
} |
|
| 695 |
}); |
|
| 696 |
} |
|
| 697 |
return result; |
|
| 698 |
}; |
|
| 699 |
|
|
| 700 |
/** |
|
| 701 |
* Validates whether the instance value matches the regular expression, when the instance value is a string. |
|
| 702 |
* @param instance |
|
| 703 |
* @param schema |
|
| 704 |
* @return {String|null}
|
|
| 705 |
*/ |
|
| 706 |
validators.pattern = function validatePattern (instance, schema, options, ctx) {
|
|
| 707 |
if (!this.types.string(instance)) return; |
|
| 708 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 709 |
var pattern = schema.pattern; |
|
| 710 |
try {
|
|
| 711 |
var regexp = new RegExp(pattern, 'u'); |
|
| 712 |
} catch(_e) {
|
|
| 713 |
// In the event the stricter handling causes an error, fall back on the forgiving handling |
|
| 714 |
// DEPRECATED |
|
| 715 |
regexp = new RegExp(pattern); |
|
| 716 |
} |
|
| 717 |
if (!instance.match(regexp)) {
|
|
| 718 |
result.addError({
|
|
| 719 |
name: 'pattern', |
|
| 720 |
argument: schema.pattern, |
|
| 721 |
message: "does not match pattern " + JSON.stringify(schema.pattern.toString()), |
|
| 722 |
}); |
|
| 723 |
} |
|
| 724 |
return result; |
|
| 725 |
}; |
|
| 726 |
|
|
| 727 |
/** |
|
| 728 |
* Validates whether the instance value is of a certain defined format or a custom |
|
| 729 |
* format. |
|
| 730 |
* The following formats are supported for string types: |
|
| 731 |
* - date-time |
|
| 732 |
* - date |
|
| 733 |
* - time |
|
| 734 |
* - ip-address |
|
| 735 |
* - ipv6 |
|
| 736 |
* - uri |
|
| 737 |
* - color |
|
| 738 |
* - host-name |
|
| 739 |
* - alpha |
|
| 740 |
* - alpha-numeric |
|
| 741 |
* - utc-millisec |
|
| 742 |
* @param instance |
|
| 743 |
* @param schema |
|
| 744 |
* @param [options] |
|
| 745 |
* @param [ctx] |
|
| 746 |
* @return {String|null}
|
|
| 747 |
*/ |
|
| 748 |
validators.format = function validateFormat (instance, schema, options, ctx) {
|
|
| 749 |
if (instance===undefined) return; |
|
| 750 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 751 |
if (!result.disableFormat && !helpers.isFormat(instance, schema.format, this)) {
|
|
| 752 |
result.addError({
|
|
| 753 |
name: 'format', |
|
| 754 |
argument: schema.format, |
|
| 755 |
message: "does not conform to the " + JSON.stringify(schema.format) + " format", |
|
| 756 |
}); |
|
| 757 |
} |
|
| 758 |
return result; |
|
| 759 |
}; |
|
| 760 |
|
|
| 761 |
/** |
|
| 762 |
* Validates whether the instance value is at least of a certain length, when the instance value is a string. |
|
| 763 |
* @param instance |
|
| 764 |
* @param schema |
|
| 765 |
* @return {String|null}
|
|
| 766 |
*/ |
|
| 767 |
validators.minLength = function validateMinLength (instance, schema, options, ctx) {
|
|
| 768 |
if (!this.types.string(instance)) return; |
|
| 769 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 770 |
var hsp = instance.match(/[\uDC00-\uDFFF]/g); |
|
| 771 |
var length = instance.length - (hsp ? hsp.length : 0); |
|
| 772 |
if (!(length >= schema.minLength)) {
|
|
| 773 |
result.addError({
|
|
| 774 |
name: 'minLength', |
|
| 775 |
argument: schema.minLength, |
|
| 776 |
message: "does not meet minimum length of " + schema.minLength, |
|
| 777 |
}); |
|
| 778 |
} |
|
| 779 |
return result; |
|
| 780 |
}; |
|
| 781 |
|
|
| 782 |
/** |
|
| 783 |
* Validates whether the instance value is at most of a certain length, when the instance value is a string. |
|
| 784 |
* @param instance |
|
| 785 |
* @param schema |
|
| 786 |
* @return {String|null}
|
|
| 787 |
*/ |
|
| 788 |
validators.maxLength = function validateMaxLength (instance, schema, options, ctx) {
|
|
| 789 |
if (!this.types.string(instance)) return; |
|
| 790 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 791 |
// TODO if this was already computed in "minLength", use that value instead of re-computing |
|
| 792 |
var hsp = instance.match(/[\uDC00-\uDFFF]/g); |
|
| 793 |
var length = instance.length - (hsp ? hsp.length : 0); |
|
| 794 |
if (!(length <= schema.maxLength)) {
|
|
| 795 |
result.addError({
|
|
| 796 |
name: 'maxLength', |
|
| 797 |
argument: schema.maxLength, |
|
| 798 |
message: "does not meet maximum length of " + schema.maxLength, |
|
| 799 |
}); |
|
| 800 |
} |
|
| 801 |
return result; |
|
| 802 |
}; |
|
| 803 |
|
|
| 804 |
/** |
|
| 805 |
* Validates whether instance contains at least a minimum number of items, when the instance is an Array. |
|
| 806 |
* @param instance |
|
| 807 |
* @param schema |
|
| 808 |
* @return {String|null}
|
|
| 809 |
*/ |
|
| 810 |
validators.minItems = function validateMinItems (instance, schema, options, ctx) {
|
|
| 811 |
if (!this.types.array(instance)) return; |
|
| 812 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 813 |
if (!(instance.length >= schema.minItems)) {
|
|
| 814 |
result.addError({
|
|
| 815 |
name: 'minItems', |
|
| 816 |
argument: schema.minItems, |
|
| 817 |
message: "does not meet minimum length of " + schema.minItems, |
|
| 818 |
}); |
|
| 819 |
} |
|
| 820 |
return result; |
|
| 821 |
}; |
|
| 822 |
|
|
| 823 |
/** |
|
| 824 |
* Validates whether instance contains no more than a maximum number of items, when the instance is an Array. |
|
| 825 |
* @param instance |
|
| 826 |
* @param schema |
|
| 827 |
* @return {String|null}
|
|
| 828 |
*/ |
|
| 829 |
validators.maxItems = function validateMaxItems (instance, schema, options, ctx) {
|
|
| 830 |
if (!this.types.array(instance)) return; |
|
| 831 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 832 |
if (!(instance.length <= schema.maxItems)) {
|
|
| 833 |
result.addError({
|
|
| 834 |
name: 'maxItems', |
|
| 835 |
argument: schema.maxItems, |
|
| 836 |
message: "does not meet maximum length of " + schema.maxItems, |
|
| 837 |
}); |
|
| 838 |
} |
|
| 839 |
return result; |
|
| 840 |
}; |
|
| 841 |
|
|
| 842 |
/** |
|
| 843 |
* Deep compares arrays for duplicates |
|
| 844 |
* @param v |
|
| 845 |
* @param i |
|
| 846 |
* @param a |
|
| 847 |
* @private |
|
| 848 |
* @return {boolean}
|
|
| 849 |
*/ |
|
| 850 |
function testArrays (v, i, a) {
|
|
| 851 |
var j, len = a.length; |
|
| 852 |
for (j = i + 1, len; j < len; j++) {
|
|
| 853 |
if (helpers.deepCompareStrict(v, a[j])) {
|
|
| 854 |
return false; |
|
| 855 |
} |
|
| 856 |
} |
|
| 857 |
return true; |
|
| 858 |
} |
|
| 859 |
|
|
| 860 |
/** |
|
| 861 |
* Validates whether there are no duplicates, when the instance is an Array. |
|
| 862 |
* @param instance |
|
| 863 |
* @return {String|null}
|
|
| 864 |
*/ |
|
| 865 |
validators.uniqueItems = function validateUniqueItems (instance, schema, options, ctx) {
|
|
| 866 |
if (schema.uniqueItems!==true) return; |
|
| 867 |
if (!this.types.array(instance)) return; |
|
| 868 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 869 |
if (!instance.every(testArrays)) {
|
|
| 870 |
result.addError({
|
|
| 871 |
name: 'uniqueItems', |
|
| 872 |
message: "contains duplicate item", |
|
| 873 |
}); |
|
| 874 |
} |
|
| 875 |
return result; |
|
| 876 |
}; |
|
| 877 |
|
|
| 878 |
/** |
|
| 879 |
* Validate for the presence of dependency properties, if the instance is an object. |
|
| 880 |
* @param instance |
|
| 881 |
* @param schema |
|
| 882 |
* @param options |
|
| 883 |
* @param ctx |
|
| 884 |
* @return {null|ValidatorResult}
|
|
| 885 |
*/ |
|
| 886 |
validators.dependencies = function validateDependencies (instance, schema, options, ctx) {
|
|
| 887 |
if (!this.types.object(instance)) return; |
|
| 888 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 889 |
for (var property in schema.dependencies) {
|
|
| 890 |
if (instance[property] === undefined) {
|
|
| 891 |
continue; |
|
| 892 |
} |
|
| 893 |
var dep = schema.dependencies[property]; |
|
| 894 |
var childContext = ctx.makeChild(dep, property); |
|
| 895 |
if (typeof dep == 'string') {
|
|
| 896 |
dep = [dep]; |
|
| 897 |
} |
|
| 898 |
if (Array.isArray(dep)) {
|
|
| 899 |
dep.forEach(function (prop) {
|
|
| 900 |
if (instance[prop] === undefined) {
|
|
| 901 |
result.addError({
|
|
| 902 |
// FIXME there's two different "dependencies" errors here with slightly different outputs |
|
| 903 |
// Can we make these the same? Or should we create different error types? |
|
| 904 |
name: 'dependencies', |
|
| 905 |
argument: childContext.propertyPath, |
|
| 906 |
message: "property " + prop + " not found, required by " + childContext.propertyPath, |
|
| 907 |
}); |
|
| 908 |
} |
|
| 909 |
}); |
|
| 910 |
} else {
|
|
| 911 |
var res = this.validateSchema(instance, dep, options, childContext); |
|
| 912 |
if(result.instance !== res.instance) result.instance = res.instance; |
|
| 913 |
if (res && res.errors.length) {
|
|
| 914 |
result.addError({
|
|
| 915 |
name: 'dependencies', |
|
| 916 |
argument: childContext.propertyPath, |
|
| 917 |
message: "does not meet dependency required by " + childContext.propertyPath, |
|
| 918 |
}); |
|
| 919 |
result.importErrors(res); |
|
| 920 |
} |
|
| 921 |
} |
|
| 922 |
} |
|
| 923 |
return result; |
|
| 924 |
}; |
|
| 925 |
|
|
| 926 |
/** |
|
| 927 |
* Validates whether the instance value is one of the enumerated values. |
|
| 928 |
* |
|
| 929 |
* @param instance |
|
| 930 |
* @param schema |
|
| 931 |
* @return {ValidatorResult|null}
|
|
| 932 |
*/ |
|
| 933 |
validators['enum'] = function validateEnum (instance, schema, options, ctx) {
|
|
| 934 |
if (instance === undefined) {
|
|
| 935 |
return null; |
|
| 936 |
} |
|
| 937 |
if (!Array.isArray(schema['enum'])) {
|
|
| 938 |
throw new SchemaError("enum expects an array", schema);
|
|
| 939 |
} |
|
| 940 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 941 |
if (!schema['enum'].some(helpers.deepCompareStrict.bind(null, instance))) {
|
|
| 942 |
result.addError({
|
|
| 943 |
name: 'enum', |
|
| 944 |
argument: schema['enum'], |
|
| 945 |
message: "is not one of enum values: " + schema['enum'].map(String).join(','),
|
|
| 946 |
}); |
|
| 947 |
} |
|
| 948 |
return result; |
|
| 949 |
}; |
|
| 950 |
|
|
| 951 |
/** |
|
| 952 |
* Validates whether the instance exactly matches a given value |
|
| 953 |
* |
|
| 954 |
* @param instance |
|
| 955 |
* @param schema |
|
| 956 |
* @return {ValidatorResult|null}
|
|
| 957 |
*/ |
|
| 958 |
validators['const'] = function validateEnum (instance, schema, options, ctx) {
|
|
| 959 |
if (instance === undefined) {
|
|
| 960 |
return null; |
|
| 961 |
} |
|
| 962 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 963 |
if (!helpers.deepCompareStrict(schema['const'], instance)) {
|
|
| 964 |
result.addError({
|
|
| 965 |
name: 'const', |
|
| 966 |
argument: schema['const'], |
|
| 967 |
message: "does not exactly match expected constant: " + schema['const'], |
|
| 968 |
}); |
|
| 969 |
} |
|
| 970 |
return result; |
|
| 971 |
}; |
|
| 972 |
|
|
| 973 |
/** |
|
| 974 |
* Validates whether the instance if of a prohibited type. |
|
| 975 |
* @param instance |
|
| 976 |
* @param schema |
|
| 977 |
* @param options |
|
| 978 |
* @param ctx |
|
| 979 |
* @return {null|ValidatorResult}
|
|
| 980 |
*/ |
|
| 981 |
validators.not = validators.disallow = function validateNot (instance, schema, options, ctx) {
|
|
| 982 |
var self = this; |
|
| 983 |
if(instance===undefined) return null; |
|
| 984 |
var result = new ValidatorResult(instance, schema, options, ctx); |
|
| 985 |
var notTypes = schema.not || schema.disallow; |
|
| 986 |
if(!notTypes) return null; |
|
| 987 |
if(!Array.isArray(notTypes)) notTypes=[notTypes]; |
|
| 988 |
notTypes.forEach(function (type) {
|
|
| 989 |
if (self.testType(instance, schema, options, ctx, type)) {
|
|
| 990 |
var id = type && (type.$id || type.id); |
|
| 991 |
var schemaId = id || type; |
|
| 992 |
result.addError({
|
|
| 993 |
name: 'not', |
|
| 994 |
argument: schemaId, |
|
| 995 |
message: "is of prohibited type " + schemaId, |
|
| 996 |
}); |
|
| 997 |
} |
|
| 998 |
}); |
|
| 999 |
return result; |
|
| 1000 |
}; |
|
| 1001 |
|
|
| 1002 |
#EXPORT validators |
|
| common/jsonschema/helpers.js | ||
|---|---|---|
| 1 |
/* SPDX-License-Identifier: MIT |
|
| 2 |
* |
|
| 3 |
* jsonschema is licensed under MIT license. |
|
| 4 |
* |
|
| 5 |
* Copyright (C) 2012-2015 Tom de Grunt <tom@degrunt.nl> |
|
| 6 |
* |
|
| 7 |
* Permission is hereby granted, free of charge, to any person obtaining a copy of |
|
| 8 |
* this software and associated documentation files (the "Software"), to deal in |
|
| 9 |
* the Software without restriction, including without limitation the rights to |
|
| 10 |
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
|
| 11 |
* of the Software, and to permit persons to whom the Software is furnished to do |
|
| 12 |
* so, subject to the following conditions: |
|
| 13 |
* |
|
| 14 |
* The above copyright notice and this permission notice shall be included in all |
|
| 15 |
* copies or substantial portions of the Software. |
|
| 16 |
* |
|
| 17 |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
| 18 |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
| 19 |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
| 20 |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
| 21 |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
| 22 |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
| 23 |
* SOFTWARE. |
|
| 24 |
*/ |
|
| 25 |
|
|
| 26 |
#IMPORT common/jsonschema/urllib_mock.js AS uri |
|
| 27 |
|
|
| 28 |
function ValidationError (message, instance, schema, path, name, argument) {
|
|
| 29 |
if(Array.isArray(path)){
|
|
| 30 |
this.path = path; |
|
| 31 |
this.property = path.reduce(function(sum, item){
|
|
| 32 |
return sum + makeSuffix(item); |
|
| 33 |
}, 'instance'); |
|
| 34 |
}else if(path !== undefined){
|
|
| 35 |
this.property = path; |
|
| 36 |
} |
|
| 37 |
if (message) {
|
|
| 38 |
this.message = message; |
|
| 39 |
} |
|
| 40 |
if (schema) {
|
|
| 41 |
var id = schema.$id || schema.id; |
|
| 42 |
this.schema = id || schema; |
|
| 43 |
} |
|
| 44 |
if (instance !== undefined) {
|
|
| 45 |
this.instance = instance; |
|
| 46 |
} |
|
| 47 |
this.name = name; |
|
| 48 |
this.argument = argument; |
|
| 49 |
this.stack = this.toString(); |
|
| 50 |
}; |
|
| 51 |
#EXPORT ValidationError |
|
| 52 |
|
|
| 53 |
ValidationError.prototype.toString = function toString() {
|
|
| 54 |
return this.property + ' ' + this.message; |
|
| 55 |
}; |
|
| 56 |
|
|
| 57 |
function ValidatorResult(instance, schema, options, ctx) {
|
|
| 58 |
this.instance = instance; |
|
| 59 |
this.schema = schema; |
|
| 60 |
this.options = options; |
|
| 61 |
this.path = ctx.path; |
|
| 62 |
this.propertyPath = ctx.propertyPath; |
|
| 63 |
this.errors = []; |
|
| 64 |
this.throwError = options && options.throwError; |
|
| 65 |
this.throwFirst = options && options.throwFirst; |
|
| 66 |
this.throwAll = options && options.throwAll; |
|
| 67 |
this.disableFormat = options && options.disableFormat === true; |
|
| 68 |
}; |
|
| 69 |
|
|
| 70 |
ValidatorResult.prototype.addError = function addError(detail) {
|
|
| 71 |
var err; |
|
| 72 |
if (typeof detail == 'string') {
|
|
| 73 |
err = new ValidationError(detail, this.instance, this.schema, this.path); |
|
| 74 |
} else {
|
|
| 75 |
if (!detail) throw new Error('Missing error detail');
|
|
| 76 |
if (!detail.message) throw new Error('Missing error message');
|
|
| 77 |
if (!detail.name) throw new Error('Missing validator type');
|
|
| 78 |
err = new ValidationError(detail.message, this.instance, this.schema, this.path, detail.name, detail.argument); |
|
| 79 |
} |
|
| 80 |
|
|
| 81 |
this.errors.push(err); |
|
| 82 |
if (this.throwFirst) {
|
|
| 83 |
throw new ValidatorResultError(this); |
|
| 84 |
}else if(this.throwError){
|
|
| 85 |
throw err; |
|
| 86 |
} |
|
| 87 |
return err; |
|
| 88 |
}; |
|
| 89 |
|
|
| 90 |
ValidatorResult.prototype.importErrors = function importErrors(res) {
|
|
| 91 |
if (typeof res == 'string' || (res && res.validatorType)) {
|
|
| 92 |
this.addError(res); |
|
| 93 |
} else if (res && res.errors) {
|
|
| 94 |
Array.prototype.push.apply(this.errors, res.errors); |
|
| 95 |
} |
|
| 96 |
}; |
|
| 97 |
|
|
| 98 |
function stringizer (v,i){
|
|
| 99 |
return i+': '+v.toString()+'\n'; |
|
| 100 |
} |
|
| 101 |
ValidatorResult.prototype.toString = function toString(res) {
|
|
| 102 |
return this.errors.map(stringizer).join('');
|
|
| 103 |
}; |
|
| 104 |
|
|
| 105 |
Object.defineProperty(ValidatorResult.prototype, "valid", { get: function() {
|
|
| 106 |
return !this.errors.length; |
|
| 107 |
} }); |
|
| 108 |
|
|
| 109 |
#EXPORT ValidatorResult |
|
| 110 |
|
|
| 111 |
function ValidatorResultError(result) {
|
|
| 112 |
if(Error.captureStackTrace){
|
|
| 113 |
Error.captureStackTrace(this, ValidatorResultError); |
|
| 114 |
} |
|
| 115 |
this.instance = result.instance; |
|
| 116 |
this.schema = result.schema; |
|
| 117 |
this.options = result.options; |
|
| 118 |
this.errors = result.errors; |
|
| 119 |
} |
|
| 120 |
ValidatorResultError.prototype = new Error(); |
|
| 121 |
ValidatorResultError.prototype.constructor = ValidatorResultError; |
|
| 122 |
ValidatorResultError.prototype.name = "Validation Error"; |
|
| 123 |
#EXPORT ValidatorResultError |
|
| 124 |
|
|
| 125 |
/** |
|
| 126 |
* Describes a problem with a Schema which prevents validation of an instance |
|
| 127 |
* @name SchemaError |
|
| 128 |
* @constructor |
|
| 129 |
*/ |
|
| 130 |
function SchemaError (msg, schema) {
|
|
| 131 |
this.message = msg; |
|
| 132 |
this.schema = schema; |
|
| 133 |
Error.call(this, msg); |
|
| 134 |
if(Error.captureStackTrace){
|
|
| 135 |
Error.captureStackTrace(this, SchemaError); |
|
| 136 |
} |
|
| 137 |
}; |
|
| 138 |
SchemaError.prototype = Object.create(Error.prototype, |
|
| 139 |
{
|
|
| 140 |
constructor: {value: SchemaError, enumerable: false},
|
|
| 141 |
name: {value: 'SchemaError', enumerable: false},
|
|
| 142 |
}); |
|
| 143 |
#EXPORT SchemaError |
|
| 144 |
|
|
| 145 |
function SchemaContext (schema, options, path, base, schemas) {
|
|
| 146 |
this.schema = schema; |
|
| 147 |
this.options = options; |
|
| 148 |
if(Array.isArray(path)){
|
|
| 149 |
this.path = path; |
|
| 150 |
this.propertyPath = path.reduce(function(sum, item){
|
|
| 151 |
return sum + makeSuffix(item); |
|
| 152 |
}, 'instance'); |
|
| 153 |
}else{
|
|
| 154 |
this.propertyPath = path; |
|
| 155 |
} |
|
| 156 |
this.base = base; |
|
| 157 |
this.schemas = schemas; |
|
| 158 |
}; |
|
| 159 |
|
|
| 160 |
SchemaContext.prototype.resolve = function resolve (target) {
|
|
| 161 |
return uri.resolve(this.base, target); |
|
| 162 |
}; |
|
| 163 |
|
|
| 164 |
SchemaContext.prototype.makeChild = function makeChild(schema, propertyName){
|
|
| 165 |
var path = (propertyName===undefined) ? this.path : this.path.concat([propertyName]); |
|
| 166 |
var id = schema.$id || schema.id; |
|
| 167 |
var base = uri.resolve(this.base, id||''); |
|
| 168 |
var ctx = new SchemaContext(schema, this.options, path, base, Object.create(this.schemas)); |
|
| 169 |
if(id && !ctx.schemas[base]){
|
|
| 170 |
ctx.schemas[base] = schema; |
|
| 171 |
} |
|
| 172 |
return ctx; |
|
| 173 |
}; |
|
| 174 |
|
|
| 175 |
#EXPORT SchemaContext |
|
| 176 |
|
|
| 177 |
const FORMAT_REGEXPS = {
|
|
| 178 |
// 7.3.1. Dates, Times, and Duration |
|
| 179 |
'date-time': /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-(3[01]|0[1-9]|[12][0-9])[tT ](2[0-4]|[01][0-9]):([0-5][0-9]):(60|[0-5][0-9])(\.\d+)?([zZ]|[+-]([0-5][0-9]):(60|[0-5][0-9]))$/,
|
|
| 180 |
'date': /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-(3[01]|0[1-9]|[12][0-9])$/,
|
|
| 181 |
'time': /^(2[0-4]|[01][0-9]):([0-5][0-9]):(60|[0-5][0-9])$/, |
|
| 182 |
'duration': /P(T\d+(H(\d+M(\d+S)?)?|M(\d+S)?|S)|\d+(D|M(\d+D)?|Y(\d+M(\d+D)?)?)(T\d+(H(\d+M(\d+S)?)?|M(\d+S)?|S))?|\d+W)/i, |
|
| 183 |
|
|
| 184 |
// 7.3.2. Email Addresses |
|
| 185 |
// TODO: fix the email production |
|
| 186 |
'email': /^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/,
|
|
| 187 |
'idn-email': /^("(?:[!#-\[\]-\u{10FFFF}]|\\[\t -\u{10FFFF}])*"|[!#-'*+\-/-9=?A-Z\^-\u{10FFFF}](?:\.?[!#-'*+\-/-9=?A-Z\^-\u{10FFFF}])*)@([!#-'*+\-/-9=?A-Z\^-\u{10FFFF}](?:\.?[!#-'*+\-/-9=?A-Z\^-\u{10FFFF}])*|\[[!-Z\^-\u{10FFFF}]*\])$/u,
|
|
| 188 |
|
|
| 189 |
// 7.3.3. Hostnames |
|
| 190 |
|
|
| 191 |
// 7.3.4. IP Addresses |
|
| 192 |
'ip-address': /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
|
|
| 193 |
// FIXME whitespace is invalid |
|
| 194 |
'ipv6': /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/,
|
|
| 195 |
|
|
| 196 |
// 7.3.5. Resource Identifiers |
|
| 197 |
// TODO: A more accurate regular expression for "uri" goes: |
|
| 198 |
// [A-Za-z][+\-.0-9A-Za-z]*:((/(/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?)?)?#(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*|(/(/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?[/?]|[!$&-.0-;=?-Z_a-z~])|/?%[0-9A-Fa-f]{2}|[!$&-.0-;=?-Z_a-z~])(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*(#(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*)?|/(/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~])+(:\d*)?|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?:\d*|\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?)?)?
|
|
| 199 |
'uri': /^[a-zA-Z][a-zA-Z0-9+.-]*:[^\s]*$/, |
|
| 200 |
'uri-reference': /^(((([A-Za-z][+\-.0-9A-Za-z]*(:%[0-9A-Fa-f]{2}|:[!$&-.0-;=?-Z_a-z~]|[/?])|\?)(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*|([A-Za-z][+\-.0-9A-Za-z]*:?)?)|([A-Za-z][+\-.0-9A-Za-z]*:)?\/((%[0-9A-Fa-f]{2}|\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?[/?]|[!$&-.0-;=?-Z_a-z~])(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*|(\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?)?))#(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*|(([A-Za-z][+\-.0-9A-Za-z]*)?%[0-9A-Fa-f]{2}|[!$&-.0-9;=@_~]|[A-Za-z][+\-.0-9A-Za-z]*[!$&-*,;=@_~])(%[0-9A-Fa-f]{2}|[!$&-.0-9;=@-Z_a-z~])*((([/?](%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*)?#|[/?])(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*)?|([A-Za-z][+\-.0-9A-Za-z]*(:%[0-9A-Fa-f]{2}|:[!$&-.0-;=?-Z_a-z~]|[/?])|\?)(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*|([A-Za-z][+\-.0-9A-Za-z]*:)?\/((%[0-9A-Fa-f]{2}|\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?[/?]|[!$&-.0-;=?-Z_a-z~])(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*|\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~])+(:\d*)?|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?:\d*|\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?)?|[A-Za-z][+\-.0-9A-Za-z]*:?)?$/,
|
|
| 201 |
'iri': /^[a-zA-Z][a-zA-Z0-9+.-]*:[^\s]*$/, |
|
| 202 |
'iri-reference': /^(((([A-Za-z][+\-.0-9A-Za-z]*(:%[0-9A-Fa-f]{2}|:[!$&-.0-;=?-Z_a-z~-\u{10FFFF}]|[/?])|\?)(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~-\u{10FFFF}])*|([A-Za-z][+\-.0-9A-Za-z]*:?)?)|([A-Za-z][+\-.0-9A-Za-z]*:)?\/((%[0-9A-Fa-f]{2}|\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~-\u{10FFFF}])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~-\u{10FFFF}]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?[/?]|[!$&-.0-;=?-Z_a-z~-\u{10FFFF}])(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~-\u{10FFFF}])*|(\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~-\u{10FFFF}])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~-\u{10FFFF}]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?)?))#(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~-\u{10FFFF}])*|(([A-Za-z][+\-.0-9A-Za-z]*)?%[0-9A-Fa-f]{2}|[!$&-.0-9;=@_~-\u{10FFFF}]|[A-Za-z][+\-.0-9A-Za-z]*[!$&-*,;=@_~-\u{10FFFF}])(%[0-9A-Fa-f]{2}|[!$&-.0-9;=@-Z_a-z~-\u{10FFFF}])*((([/?](%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~-\u{10FFFF}])*)?#|[/?])(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~-\u{10FFFF}])*)?|([A-Za-z][+\-.0-9A-Za-z]*(:%[0-9A-Fa-f]{2}|:[!$&-.0-;=?-Z_a-z~-\u{10FFFF}]|[/?])|\?)(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~-\u{10FFFF}])*|([A-Za-z][+\-.0-9A-Za-z]*:)?\/((%[0-9A-Fa-f]{2}|\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~-\u{10FFFF}])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~-\u{10FFFF}]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?[/?]|[!$&-.0-;=?-Z_a-z~-\u{10FFFF}])(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~-\u{10FFFF}])*|\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~-\u{10FFFF}])+(:\d*)?|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~-\u{10FFFF}]+)?|[.0-:A-Fa-f]+)\])?:\d*|\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~-\u{10FFFF}]+)?|[.0-:A-Fa-f]+)\])?)?|[A-Za-z][+\-.0-9A-Za-z]*:?)?$/u,
|
|
| 203 |
'uuid': /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i,
|
|
| 204 |
|
|
| 205 |
// 7.3.6. uri-template |
|
| 206 |
'uri-template': /(%[0-9a-f]{2}|[!#$&(-;=?@\[\]_a-z~]|\{[!#&+,./;=?@|]?(%[0-9a-f]{2}|[0-9_a-z])(\.?(%[0-9a-f]{2}|[0-9_a-z]))*(:[1-9]\d{0,3}|\*)?(,(%[0-9a-f]{2}|[0-9_a-z])(\.?(%[0-9a-f]{2}|[0-9_a-z]))*(:[1-9]\d{0,3}|\*)?)*\})*/iu,
|
|
| 207 |
|
|
| 208 |
// 7.3.7. JSON Pointers |
|
| 209 |
'json-pointer': /^(\/([\x00-\x2e0-@\[-}\x7f]|~[01])*)*$/iu, |
|
| 210 |
'relative-json-pointer': /^\d+(#|(\/([\x00-\x2e0-@\[-}\x7f]|~[01])*)*)$/iu, |
|
| 211 |
|
|
| 212 |
// hostname regex from: http://stackoverflow.com/a/1420225/5628 |
|
| 213 |
'hostname': /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\.?$/,
|
|
| 214 |
'host-name': /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\.?$/,
|
|
| 215 |
|
|
| 216 |
'utc-millisec': function (input) {
|
|
| 217 |
return (typeof input === 'string') && parseFloat(input) === parseInt(input, 10) && !isNaN(input); |
|
| 218 |
}, |
|
| 219 |
|
|
| 220 |
// 7.3.8. regex |
|
| 221 |
'regex': function (input) {
|
|
| 222 |
var result = true; |
|
| 223 |
try {
|
|
| 224 |
new RegExp(input); |
|
| 225 |
} catch (e) {
|
|
| 226 |
result = false; |
|
| 227 |
} |
|
| 228 |
return result; |
|
| 229 |
}, |
|
| 230 |
|
|
| 231 |
// Other definitions |
|
| 232 |
// "style" was removed from JSON Schema in draft-4 and is deprecated |
|
| 233 |
'style': /[\r\n\t ]*[^\r\n\t ][^:]*:[\r\n\t ]*[^\r\n\t ;]*[\r\n\t ]*;?/, |
|
| 234 |
// "color" was removed from JSON Schema in draft-4 and is deprecated |
|
| 235 |
'color': /^(#?([0-9A-Fa-f]{3}){1,2}\b|aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow|(rgb\(\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*\))|(rgb\(\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*\)))$/,
|
|
| 236 |
'phone': /^\+(?:[0-9] ?){6,14}[0-9]$/,
|
|
| 237 |
'alpha': /^[a-zA-Z]+$/, |
|
| 238 |
'alphanumeric': /^[a-zA-Z0-9]+$/, |
|
| 239 |
}; |
|
| 240 |
|
|
| 241 |
FORMAT_REGEXPS.regexp = FORMAT_REGEXPS.regex; |
|
| 242 |
FORMAT_REGEXPS.pattern = FORMAT_REGEXPS.regex; |
|
| 243 |
FORMAT_REGEXPS.ipv4 = FORMAT_REGEXPS['ip-address']; |
|
| 244 |
|
|
| 245 |
#EXPORT FORMAT_REGEXPS |
|
| 246 |
|
|
| 247 |
function isFormat (input, format, validator) {
|
|
| 248 |
if (typeof input === 'string' && FORMAT_REGEXPS[format] !== undefined) {
|
|
| 249 |
if (FORMAT_REGEXPS[format] instanceof RegExp) {
|
|
| 250 |
return FORMAT_REGEXPS[format].test(input); |
|
| 251 |
} |
|
| 252 |
if (typeof FORMAT_REGEXPS[format] === 'function') {
|
|
| 253 |
return FORMAT_REGEXPS[format](input); |
|
| 254 |
} |
|
| 255 |
} else if (validator && validator.customFormats && |
|
| 256 |
typeof validator.customFormats[format] === 'function') {
|
|
| 257 |
return validator.customFormats[format](input); |
|
| 258 |
} |
|
| 259 |
return true; |
|
| 260 |
}; |
|
| 261 |
|
|
| 262 |
#EXPORT isFormat |
|
| 263 |
|
|
| 264 |
function makeSuffix (key) {
|
|
| 265 |
key = key.toString(); |
|
| 266 |
// This function could be capable of outputting valid a ECMAScript string, but the |
|
| 267 |
// resulting code for testing which form to use would be tens of thousands of characters long |
|
| 268 |
// That means this will use the name form for some illegal forms |
|
| 269 |
if (!key.match(/[.\s\[\]]/) && !key.match(/^[\d]/)) {
|
|
| 270 |
return '.' + key; |
|
| 271 |
} |
|
| 272 |
if (key.match(/^\d+$/)) {
|
|
| 273 |
return '[' + key + ']'; |
|
| 274 |
} |
|
| 275 |
return '[' + JSON.stringify(key) + ']'; |
|
| 276 |
}; |
|
| 277 |
#EXPORT makeSuffix |
|
| 278 |
|
|
| 279 |
function deepCompareStrict (a, b) {
|
|
| 280 |
if (typeof a !== typeof b) {
|
|
| 281 |
return false; |
|
| 282 |
} |
|
| 283 |
if (Array.isArray(a)) {
|
|
| 284 |
if (!Array.isArray(b)) {
|
|
| 285 |
return false; |
|
| 286 |
} |
|
| 287 |
if (a.length !== b.length) {
|
|
| 288 |
return false; |
|
| 289 |
} |
|
| 290 |
return a.every(function (v, i) {
|
|
| 291 |
return deepCompareStrict(a[i], b[i]); |
|
| 292 |
}); |
|
| 293 |
} |
|
| 294 |
if (typeof a === 'object') {
|
|
| 295 |
if (!a || !b) {
|
|
| 296 |
return a === b; |
|
| 297 |
} |
|
| 298 |
var aKeys = Object.keys(a); |
|
| 299 |
var bKeys = Object.keys(b); |
|
| 300 |
if (aKeys.length !== bKeys.length) {
|
|
| 301 |
return false; |
|
| 302 |
} |
|
| 303 |
return aKeys.every(function (v) {
|
|
| 304 |
return deepCompareStrict(a[v], b[v]); |
|
| 305 |
}); |
|
| 306 |
} |
|
| 307 |
return a === b; |
|
| 308 |
}; |
|
| 309 |
#EXPORT deepCompareStrict |
|
| 310 |
|
|
| 311 |
function deepMerger (target, dst, e, i) {
|
|
| 312 |
if (typeof e === 'object') {
|
|
| 313 |
dst[i] = deepMerge(target[i], e); |
|
| 314 |
} else {
|
|
| 315 |
if (target.indexOf(e) === -1) {
|
|
| 316 |
dst.push(e); |
|
| 317 |
} |
|
| 318 |
} |
|
| 319 |
} |
|
| 320 |
|
|
| 321 |
function copyist (src, dst, key) {
|
|
| 322 |
dst[key] = src[key]; |
|
| 323 |
} |
|
| 324 |
|
|
| 325 |
function copyistWithDeepMerge (target, src, dst, key) {
|
|
| 326 |
if (typeof src[key] !== 'object' || !src[key]) {
|
|
| 327 |
dst[key] = src[key]; |
|
| 328 |
} |
|
| 329 |
else {
|
|
| 330 |
if (!target[key]) {
|
|
| 331 |
dst[key] = src[key]; |
|
| 332 |
} else {
|
|
| 333 |
dst[key] = deepMerge(target[key], src[key]); |
|
| 334 |
} |
|
| 335 |
} |
|
| 336 |
} |
|
| 337 |
|
|
| 338 |
function deepMerge (target, src) {
|
|
| 339 |
var array = Array.isArray(src); |
|
| 340 |
var dst = array && [] || {};
|
|
| 341 |
|
|
| 342 |
if (array) {
|
|
| 343 |
target = target || []; |
|
| 344 |
dst = dst.concat(target); |
|
| 345 |
src.forEach(deepMerger.bind(null, target, dst)); |
|
| 346 |
} else {
|
|
| 347 |
if (target && typeof target === 'object') {
|
|
| 348 |
Object.keys(target).forEach(copyist.bind(null, target, dst)); |
|
| 349 |
} |
|
| 350 |
Object.keys(src).forEach(copyistWithDeepMerge.bind(null, target, src, dst)); |
|
| 351 |
} |
|
| 352 |
|
|
| 353 |
return dst; |
|
| 354 |
} |
|
| 355 |
#EXPORT deepMerge |
|
| 356 |
|
|
| 357 |
/** |
|
| 358 |
* Validates instance against the provided schema |
|
| 359 |
* Implements URI+JSON Pointer encoding, e.g. "%7e"="~0"=>"~", "~1"="%2f"=>"/" |
|
| 360 |
* @param o |
|
| 361 |
* @param s The path to walk o along |
|
| 362 |
* @return any |
|
| 363 |
*/ |
|
| 364 |
function objectGetPath(o, s) {
|
|
| 365 |
var parts = s.split('/').slice(1);
|
|
| 366 |
var k; |
|
| 367 |
while (typeof (k=parts.shift()) == 'string') {
|
|
| 368 |
var n = decodeURIComponent(k.replace(/~0/,'~').replace(/~1/g,'/')); |
|
| 369 |
if (!(n in o)) return; |
|
| 370 |
o = o[n]; |
|
| 371 |
} |
|
| 372 |
return o; |
|
Also available in: Unified diff
validate repository responses against JSON schemas