json-schema.js (51681B)
1 /* 2 * Copyright (c) 2020 Jeremy Danyow 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to deal 6 * in the Software without restriction, including without limitation the rights 7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 * copies of the Software, and to permit persons to whom the Software is 9 * furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in 12 * all copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 * SOFTWARE. 21 */ 22 23 'use strict'; 24 25 function deepCompareStrict(a, b) { 26 const typeofa = typeof a; 27 if (typeofa !== typeof b) { 28 return false; 29 } 30 if (Array.isArray(a)) { 31 if (!Array.isArray(b)) { 32 return false; 33 } 34 const length = a.length; 35 if (length !== b.length) { 36 return false; 37 } 38 for (let i = 0; i < length; i++) { 39 if (!deepCompareStrict(a[i], b[i])) { 40 return false; 41 } 42 } 43 return true; 44 } 45 if (typeofa === 'object') { 46 if (!a || !b) { 47 return a === b; 48 } 49 const aKeys = Object.keys(a); 50 const bKeys = Object.keys(b); 51 const length = aKeys.length; 52 if (length !== bKeys.length) { 53 return false; 54 } 55 for (const k of aKeys) { 56 if (!deepCompareStrict(a[k], b[k])) { 57 return false; 58 } 59 } 60 return true; 61 } 62 return a === b; 63 } 64 65 function encodePointer(p) { 66 return encodeURI(escapePointer(p)); 67 } 68 function escapePointer(p) { 69 return p.replace(/~/g, '~0').replace(/\//g, '~1'); 70 } 71 72 const schemaKeyword = { 73 additionalItems: true, 74 unevaluatedItems: true, 75 items: true, 76 contains: true, 77 additionalProperties: true, 78 unevaluatedProperties: true, 79 propertyNames: true, 80 not: true, 81 if: true, 82 then: true, 83 else: true 84 }; 85 const schemaArrayKeyword = { 86 prefixItems: true, 87 items: true, 88 allOf: true, 89 anyOf: true, 90 oneOf: true 91 }; 92 const schemaMapKeyword = { 93 $defs: true, 94 definitions: true, 95 properties: true, 96 patternProperties: true, 97 dependentSchemas: true 98 }; 99 const ignoredKeyword = { 100 id: true, 101 $id: true, 102 $ref: true, 103 $schema: true, 104 $anchor: true, 105 $vocabulary: true, 106 $comment: true, 107 default: true, 108 enum: true, 109 const: true, 110 required: true, 111 type: true, 112 maximum: true, 113 minimum: true, 114 exclusiveMaximum: true, 115 exclusiveMinimum: true, 116 multipleOf: true, 117 maxLength: true, 118 minLength: true, 119 pattern: true, 120 format: true, 121 maxItems: true, 122 minItems: true, 123 uniqueItems: true, 124 maxProperties: true, 125 minProperties: true 126 }; 127 let initialBaseURI = typeof self !== 'undefined' && self.location 128 ? 129 new URL(self.location.origin + self.location.pathname + location.search) 130 : new URL('https://github.com/cfworker'); 131 function dereference(schema, lookup = Object.create(null), baseURI = initialBaseURI, basePointer = '') { 132 if (schema && typeof schema === 'object' && !Array.isArray(schema)) { 133 const id = schema.$id || schema.id; 134 if (id) { 135 const url = new URL(id, baseURI.href); 136 if (url.hash.length > 1) { 137 lookup[url.href] = schema; 138 } 139 else { 140 url.hash = ''; 141 if (basePointer === '') { 142 baseURI = url; 143 } 144 else { 145 dereference(schema, lookup, baseURI); 146 } 147 } 148 } 149 } 150 else if (schema !== true && schema !== false) { 151 return lookup; 152 } 153 const schemaURI = baseURI.href + (basePointer ? '#' + basePointer : ''); 154 if (lookup[schemaURI] !== undefined) { 155 throw new Error(`Duplicate schema URI "${schemaURI}".`); 156 } 157 lookup[schemaURI] = schema; 158 if (schema === true || schema === false) { 159 return lookup; 160 } 161 if (schema.__absolute_uri__ === undefined) { 162 Object.defineProperty(schema, '__absolute_uri__', { 163 enumerable: false, 164 value: schemaURI 165 }); 166 } 167 if (schema.$ref && schema.__absolute_ref__ === undefined) { 168 const url = new URL(schema.$ref, baseURI.href); 169 url.hash = url.hash; 170 Object.defineProperty(schema, '__absolute_ref__', { 171 enumerable: false, 172 value: url.href 173 }); 174 } 175 if (schema.$recursiveRef && schema.__absolute_recursive_ref__ === undefined) { 176 const url = new URL(schema.$recursiveRef, baseURI.href); 177 url.hash = url.hash; 178 Object.defineProperty(schema, '__absolute_recursive_ref__', { 179 enumerable: false, 180 value: url.href 181 }); 182 } 183 if (schema.$anchor) { 184 const url = new URL('#' + schema.$anchor, baseURI.href); 185 lookup[url.href] = schema; 186 } 187 for (let key in schema) { 188 if (ignoredKeyword[key]) { 189 continue; 190 } 191 const keyBase = `${basePointer}/${encodePointer(key)}`; 192 const subSchema = schema[key]; 193 if (Array.isArray(subSchema)) { 194 if (schemaArrayKeyword[key]) { 195 const length = subSchema.length; 196 for (let i = 0; i < length; i++) { 197 dereference(subSchema[i], lookup, baseURI, `${keyBase}/${i}`); 198 } 199 } 200 } 201 else if (schemaMapKeyword[key]) { 202 for (let subKey in subSchema) { 203 dereference(subSchema[subKey], lookup, baseURI, `${keyBase}/${encodePointer(subKey)}`); 204 } 205 } 206 else { 207 dereference(subSchema, lookup, baseURI, keyBase); 208 } 209 } 210 return lookup; 211 } 212 213 const DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/; 214 const DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; 215 const TIME = /^(\d\d):(\d\d):(\d\d)(\.\d+)?(z|[+-]\d\d(?::?\d\d)?)?$/i; 216 const HOSTNAME = /^(?=.{1,253}\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\.?$/i; 217 const URIREF = /^(?:[a-z][a-z0-9+\-.]*:)?(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[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})*)*)?(?:\?(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i; 218 const URITEMPLATE = /^(?:(?:[^\x00-\x20"'<>%\\^`{|}]|%[0-9a-f]{2})|\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?)*\})*$/i; 219 const URL_ = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u{00a1}-\u{ffff}0-9]+-?)*[a-z\u{00a1}-\u{ffff}0-9]+)(?:\.(?:[a-z\u{00a1}-\u{ffff}0-9]+-?)*[a-z\u{00a1}-\u{ffff}0-9]+)*(?:\.(?:[a-z\u{00a1}-\u{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/iu; 220 const UUID = /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i; 221 const JSON_POINTER = /^(?:\/(?:[^~/]|~0|~1)*)*$/; 222 const JSON_POINTER_URI_FRAGMENT = /^#(?:\/(?:[a-z0-9_\-.!$&'()*+,;:=@]|%[0-9a-f]{2}|~0|~1)*)*$/i; 223 const RELATIVE_JSON_POINTER = /^(?:0|[1-9][0-9]*)(?:#|(?:\/(?:[^~/]|~0|~1)*)*)$/; 224 const FASTDATE = /^\d\d\d\d-[0-1]\d-[0-3]\d$/; 225 const FASTTIME = /^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)?$/i; 226 const FASTDATETIME = /^\d\d\d\d-[0-1]\d-[0-3]\d[t\s](?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i; 227 const FASTURIREFERENCE = /^(?:(?:[a-z][a-z0-9+-.]*:)?\/?\/)?(?:[^\\\s#][^\s#]*)?(?:#[^\\\s]*)?$/i; 228 const EMAIL = (input) => { 229 if (input[0] === '"') 230 return false; 231 const [name, host, ...rest] = input.split('@'); 232 if (!name || 233 !host || 234 rest.length !== 0 || 235 name.length > 64 || 236 host.length > 253) 237 return false; 238 if (name[0] === '.' || name.endsWith('.') || name.includes('..')) 239 return false; 240 if (!/^[a-z0-9.-]+$/i.test(host) || 241 !/^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+$/i.test(name)) 242 return false; 243 return host 244 .split('.') 245 .every(part => /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/i.test(part)); 246 }; 247 const IPV4 = /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/; 248 const IPV6 = /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-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-f]{1,4}:){5}(((:[0-9a-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-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-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-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-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-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-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-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-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-f]{1,4}){1,7})|((:[0-9a-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}))|:)))$/i; 249 const DURATION = (input) => input.length > 1 && 250 input.length < 80 && 251 (/^P\d+([.,]\d+)?W$/.test(input) || 252 (/^P[\dYMDTHS]*(\d[.,]\d+)?[YMDHS]$/.test(input) && 253 /^P([.,\d]+Y)?([.,\d]+M)?([.,\d]+D)?(T([.,\d]+H)?([.,\d]+M)?([.,\d]+S)?)?$/.test(input))); 254 function bind(r) { 255 return r.test.bind(r); 256 } 257 const fullFormat = { 258 date, 259 time: time.bind(undefined, false), 260 'date-time': date_time, 261 duration: DURATION, 262 uri, 263 'uri-reference': bind(URIREF), 264 'uri-template': bind(URITEMPLATE), 265 url: bind(URL_), 266 email: EMAIL, 267 hostname: bind(HOSTNAME), 268 ipv4: bind(IPV4), 269 ipv6: bind(IPV6), 270 regex: regex, 271 uuid: bind(UUID), 272 'json-pointer': bind(JSON_POINTER), 273 'json-pointer-uri-fragment': bind(JSON_POINTER_URI_FRAGMENT), 274 'relative-json-pointer': bind(RELATIVE_JSON_POINTER) 275 }; 276 const fastFormat = { 277 ...fullFormat, 278 date: bind(FASTDATE), 279 time: bind(FASTTIME), 280 'date-time': bind(FASTDATETIME), 281 'uri-reference': bind(FASTURIREFERENCE) 282 }; 283 function isLeapYear(year) { 284 return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0); 285 } 286 function date(str) { 287 const matches = str.match(DATE); 288 if (!matches) 289 return false; 290 const year = +matches[1]; 291 const month = +matches[2]; 292 const day = +matches[3]; 293 return (month >= 1 && 294 month <= 12 && 295 day >= 1 && 296 day <= (month == 2 && isLeapYear(year) ? 29 : DAYS[month])); 297 } 298 function time(full, str) { 299 const matches = str.match(TIME); 300 if (!matches) 301 return false; 302 const hour = +matches[1]; 303 const minute = +matches[2]; 304 const second = +matches[3]; 305 const timeZone = !!matches[5]; 306 return (((hour <= 23 && minute <= 59 && second <= 59) || 307 (hour == 23 && minute == 59 && second == 60)) && 308 (!full || timeZone)); 309 } 310 const DATE_TIME_SEPARATOR = /t|\s/i; 311 function date_time(str) { 312 const dateTime = str.split(DATE_TIME_SEPARATOR); 313 return dateTime.length == 2 && date(dateTime[0]) && time(true, dateTime[1]); 314 } 315 const NOT_URI_FRAGMENT = /\/|:/; 316 const URI_PATTERN = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[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})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i; 317 function uri(str) { 318 return NOT_URI_FRAGMENT.test(str) && URI_PATTERN.test(str); 319 } 320 const Z_ANCHOR = /[^\\]\\Z/; 321 function regex(str) { 322 if (Z_ANCHOR.test(str)) 323 return false; 324 try { 325 new RegExp(str); 326 return true; 327 } 328 catch (e) { 329 return false; 330 } 331 } 332 333 function ucs2length(s) { 334 let result = 0; 335 let length = s.length; 336 let index = 0; 337 let charCode; 338 while (index < length) { 339 result++; 340 charCode = s.charCodeAt(index++); 341 if (charCode >= 0xd800 && charCode <= 0xdbff && index < length) { 342 charCode = s.charCodeAt(index); 343 if ((charCode & 0xfc00) == 0xdc00) { 344 index++; 345 } 346 } 347 } 348 return result; 349 } 350 351 function validate(instance, schema, draft = '2019-09', lookup = dereference(schema), shortCircuit = true, recursiveAnchor = null, instanceLocation = '#', schemaLocation = '#', evaluated = Object.create(null)) { 352 if (schema === true) { 353 return { valid: true, errors: [] }; 354 } 355 if (schema === false) { 356 return { 357 valid: false, 358 errors: [ 359 { 360 instanceLocation, 361 keyword: 'false', 362 keywordLocation: instanceLocation, 363 error: 'False boolean schema.' 364 } 365 ] 366 }; 367 } 368 const rawInstanceType = typeof instance; 369 let instanceType; 370 switch (rawInstanceType) { 371 case 'boolean': 372 case 'number': 373 case 'string': 374 instanceType = rawInstanceType; 375 break; 376 case 'object': 377 if (instance === null) { 378 instanceType = 'null'; 379 } 380 else if (Array.isArray(instance)) { 381 instanceType = 'array'; 382 } 383 else { 384 instanceType = 'object'; 385 } 386 break; 387 default: 388 throw new Error(`Instances of "${rawInstanceType}" type are not supported.`); 389 } 390 const { $ref, $recursiveRef, $recursiveAnchor, type: $type, const: $const, enum: $enum, required: $required, not: $not, anyOf: $anyOf, allOf: $allOf, oneOf: $oneOf, if: $if, then: $then, else: $else, format: $format, properties: $properties, patternProperties: $patternProperties, additionalProperties: $additionalProperties, unevaluatedProperties: $unevaluatedProperties, minProperties: $minProperties, maxProperties: $maxProperties, propertyNames: $propertyNames, dependentRequired: $dependentRequired, dependentSchemas: $dependentSchemas, dependencies: $dependencies, prefixItems: $prefixItems, items: $items, additionalItems: $additionalItems, unevaluatedItems: $unevaluatedItems, contains: $contains, minContains: $minContains, maxContains: $maxContains, minItems: $minItems, maxItems: $maxItems, uniqueItems: $uniqueItems, minimum: $minimum, maximum: $maximum, exclusiveMinimum: $exclusiveMinimum, exclusiveMaximum: $exclusiveMaximum, multipleOf: $multipleOf, minLength: $minLength, maxLength: $maxLength, pattern: $pattern, __absolute_ref__, __absolute_recursive_ref__ } = schema; 391 const errors = []; 392 if ($recursiveAnchor === true && recursiveAnchor === null) { 393 recursiveAnchor = schema; 394 } 395 if ($recursiveRef === '#') { 396 const refSchema = recursiveAnchor === null 397 ? lookup[__absolute_recursive_ref__] 398 : recursiveAnchor; 399 const keywordLocation = `${schemaLocation}/$recursiveRef`; 400 const result = validate(instance, recursiveAnchor === null ? schema : recursiveAnchor, draft, lookup, shortCircuit, refSchema, instanceLocation, keywordLocation, evaluated); 401 if (!result.valid) { 402 errors.push({ 403 instanceLocation, 404 keyword: '$recursiveRef', 405 keywordLocation, 406 error: 'A subschema had errors.' 407 }, ...result.errors); 408 } 409 } 410 if ($ref !== undefined) { 411 const uri = __absolute_ref__ || $ref; 412 const refSchema = lookup[uri]; 413 if (refSchema === undefined) { 414 let message = `Unresolved $ref "${$ref}".`; 415 if (__absolute_ref__ && __absolute_ref__ !== $ref) { 416 message += ` Absolute URI "${__absolute_ref__}".`; 417 } 418 message += `\nKnown schemas:\n- ${Object.keys(lookup).join('\n- ')}`; 419 throw new Error(message); 420 } 421 const keywordLocation = `${schemaLocation}/$ref`; 422 const result = validate(instance, refSchema, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, keywordLocation, evaluated); 423 if (!result.valid) { 424 errors.push({ 425 instanceLocation, 426 keyword: '$ref', 427 keywordLocation, 428 error: 'A subschema had errors.' 429 }, ...result.errors); 430 } 431 if (draft === '4' || draft === '7') { 432 return { valid: errors.length === 0, errors }; 433 } 434 } 435 if (Array.isArray($type)) { 436 let length = $type.length; 437 let valid = false; 438 for (let i = 0; i < length; i++) { 439 if (instanceType === $type[i] || 440 ($type[i] === 'integer' && 441 instanceType === 'number' && 442 instance % 1 === 0 && 443 instance === instance)) { 444 valid = true; 445 break; 446 } 447 } 448 if (!valid) { 449 errors.push({ 450 instanceLocation, 451 keyword: 'type', 452 keywordLocation: `${schemaLocation}/type`, 453 error: `Instance type "${instanceType}" is invalid. Expected "${$type.join('", "')}".` 454 }); 455 } 456 } 457 else if ($type === 'integer') { 458 if (instanceType !== 'number' || instance % 1 || instance !== instance) { 459 errors.push({ 460 instanceLocation, 461 keyword: 'type', 462 keywordLocation: `${schemaLocation}/type`, 463 error: `Instance type "${instanceType}" is invalid. Expected "${$type}".` 464 }); 465 } 466 } 467 else if ($type !== undefined && instanceType !== $type) { 468 errors.push({ 469 instanceLocation, 470 keyword: 'type', 471 keywordLocation: `${schemaLocation}/type`, 472 error: `Instance type "${instanceType}" is invalid. Expected "${$type}".` 473 }); 474 } 475 if ($const !== undefined) { 476 if (instanceType === 'object' || instanceType === 'array') { 477 if (!deepCompareStrict(instance, $const)) { 478 errors.push({ 479 instanceLocation, 480 keyword: 'const', 481 keywordLocation: `${schemaLocation}/const`, 482 error: `Instance does not match ${JSON.stringify($const)}.` 483 }); 484 } 485 } 486 else if (instance !== $const) { 487 errors.push({ 488 instanceLocation, 489 keyword: 'const', 490 keywordLocation: `${schemaLocation}/const`, 491 error: `Instance does not match ${JSON.stringify($const)}.` 492 }); 493 } 494 } 495 if ($enum !== undefined) { 496 if (instanceType === 'object' || instanceType === 'array') { 497 if (!$enum.some(value => deepCompareStrict(instance, value))) { 498 errors.push({ 499 instanceLocation, 500 keyword: 'enum', 501 keywordLocation: `${schemaLocation}/enum`, 502 error: `Instance does not match any of ${JSON.stringify($enum)}.` 503 }); 504 } 505 } 506 else if (!$enum.some(value => instance === value)) { 507 errors.push({ 508 instanceLocation, 509 keyword: 'enum', 510 keywordLocation: `${schemaLocation}/enum`, 511 error: `Instance does not match any of ${JSON.stringify($enum)}.` 512 }); 513 } 514 } 515 if ($not !== undefined) { 516 const keywordLocation = `${schemaLocation}/not`; 517 const result = validate(instance, $not, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, keywordLocation); 518 if (result.valid) { 519 errors.push({ 520 instanceLocation, 521 keyword: 'not', 522 keywordLocation, 523 error: 'Instance matched "not" schema.' 524 }); 525 } 526 } 527 let subEvaluateds = []; 528 if ($anyOf !== undefined) { 529 const keywordLocation = `${schemaLocation}/anyOf`; 530 const errorsLength = errors.length; 531 let anyValid = false; 532 for (let i = 0; i < $anyOf.length; i++) { 533 const subSchema = $anyOf[i]; 534 const subEvaluated = Object.create(evaluated); 535 const result = validate(instance, subSchema, draft, lookup, shortCircuit, $recursiveAnchor === true ? recursiveAnchor : null, instanceLocation, `${keywordLocation}/${i}`, subEvaluated); 536 errors.push(...result.errors); 537 anyValid = anyValid || result.valid; 538 if (result.valid) { 539 subEvaluateds.push(subEvaluated); 540 } 541 } 542 if (anyValid) { 543 errors.length = errorsLength; 544 } 545 else { 546 errors.splice(errorsLength, 0, { 547 instanceLocation, 548 keyword: 'anyOf', 549 keywordLocation, 550 error: 'Instance does not match any subschemas.' 551 }); 552 } 553 } 554 if ($allOf !== undefined) { 555 const keywordLocation = `${schemaLocation}/allOf`; 556 const errorsLength = errors.length; 557 let allValid = true; 558 for (let i = 0; i < $allOf.length; i++) { 559 const subSchema = $allOf[i]; 560 const subEvaluated = Object.create(evaluated); 561 const result = validate(instance, subSchema, draft, lookup, shortCircuit, $recursiveAnchor === true ? recursiveAnchor : null, instanceLocation, `${keywordLocation}/${i}`, subEvaluated); 562 errors.push(...result.errors); 563 allValid = allValid && result.valid; 564 if (result.valid) { 565 subEvaluateds.push(subEvaluated); 566 } 567 } 568 if (allValid) { 569 errors.length = errorsLength; 570 } 571 else { 572 errors.splice(errorsLength, 0, { 573 instanceLocation, 574 keyword: 'allOf', 575 keywordLocation, 576 error: `Instance does not match every subschema.` 577 }); 578 } 579 } 580 if ($oneOf !== undefined) { 581 const keywordLocation = `${schemaLocation}/oneOf`; 582 const errorsLength = errors.length; 583 const matches = $oneOf.filter((subSchema, i) => { 584 const subEvaluated = Object.create(evaluated); 585 const result = validate(instance, subSchema, draft, lookup, shortCircuit, $recursiveAnchor === true ? recursiveAnchor : null, instanceLocation, `${keywordLocation}/${i}`, subEvaluated); 586 errors.push(...result.errors); 587 if (result.valid) { 588 subEvaluateds.push(subEvaluated); 589 } 590 return result.valid; 591 }).length; 592 if (matches === 1) { 593 errors.length = errorsLength; 594 } 595 else { 596 errors.splice(errorsLength, 0, { 597 instanceLocation, 598 keyword: 'oneOf', 599 keywordLocation, 600 error: `Instance does not match exactly one subschema (${matches} matches).` 601 }); 602 } 603 } 604 if (instanceType === 'object' || instanceType === 'array') { 605 Object.assign(evaluated, ...subEvaluateds); 606 } 607 if ($if !== undefined) { 608 const keywordLocation = `${schemaLocation}/if`; 609 const conditionResult = validate(instance, $if, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, keywordLocation, evaluated).valid; 610 if (conditionResult) { 611 if ($then !== undefined) { 612 const thenResult = validate(instance, $then, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, `${schemaLocation}/then`, evaluated); 613 if (!thenResult.valid) { 614 errors.push({ 615 instanceLocation, 616 keyword: 'if', 617 keywordLocation, 618 error: `Instance does not match "then" schema.` 619 }, ...thenResult.errors); 620 } 621 } 622 } 623 else if ($else !== undefined) { 624 const elseResult = validate(instance, $else, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, `${schemaLocation}/else`, evaluated); 625 if (!elseResult.valid) { 626 errors.push({ 627 instanceLocation, 628 keyword: 'if', 629 keywordLocation, 630 error: `Instance does not match "else" schema.` 631 }, ...elseResult.errors); 632 } 633 } 634 } 635 if (instanceType === 'object') { 636 if ($required !== undefined) { 637 for (const key of $required) { 638 if (!(key in instance)) { 639 errors.push({ 640 instanceLocation, 641 keyword: 'required', 642 keywordLocation: `${schemaLocation}/required`, 643 error: `Instance does not have required property "${key}".` 644 }); 645 } 646 } 647 } 648 const keys = Object.keys(instance); 649 if ($minProperties !== undefined && keys.length < $minProperties) { 650 errors.push({ 651 instanceLocation, 652 keyword: 'minProperties', 653 keywordLocation: `${schemaLocation}/minProperties`, 654 error: `Instance does not have at least ${$minProperties} properties.` 655 }); 656 } 657 if ($maxProperties !== undefined && keys.length > $maxProperties) { 658 errors.push({ 659 instanceLocation, 660 keyword: 'maxProperties', 661 keywordLocation: `${schemaLocation}/maxProperties`, 662 error: `Instance does not have at least ${$maxProperties} properties.` 663 }); 664 } 665 if ($propertyNames !== undefined) { 666 const keywordLocation = `${schemaLocation}/propertyNames`; 667 for (const key in instance) { 668 const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`; 669 const result = validate(key, $propertyNames, draft, lookup, shortCircuit, recursiveAnchor, subInstancePointer, keywordLocation); 670 if (!result.valid) { 671 errors.push({ 672 instanceLocation, 673 keyword: 'propertyNames', 674 keywordLocation, 675 error: `Property name "${key}" does not match schema.` 676 }, ...result.errors); 677 } 678 } 679 } 680 if ($dependentRequired !== undefined) { 681 const keywordLocation = `${schemaLocation}/dependantRequired`; 682 for (const key in $dependentRequired) { 683 if (key in instance) { 684 const required = $dependentRequired[key]; 685 for (const dependantKey of required) { 686 if (!(dependantKey in instance)) { 687 errors.push({ 688 instanceLocation, 689 keyword: 'dependentRequired', 690 keywordLocation, 691 error: `Instance has "${key}" but does not have "${dependantKey}".` 692 }); 693 } 694 } 695 } 696 } 697 } 698 if ($dependentSchemas !== undefined) { 699 for (const key in $dependentSchemas) { 700 const keywordLocation = `${schemaLocation}/dependentSchemas`; 701 if (key in instance) { 702 const result = validate(instance, $dependentSchemas[key], draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, `${keywordLocation}/${encodePointer(key)}`, evaluated); 703 if (!result.valid) { 704 errors.push({ 705 instanceLocation, 706 keyword: 'dependentSchemas', 707 keywordLocation, 708 error: `Instance has "${key}" but does not match dependant schema.` 709 }, ...result.errors); 710 } 711 } 712 } 713 } 714 if ($dependencies !== undefined) { 715 const keywordLocation = `${schemaLocation}/dependencies`; 716 for (const key in $dependencies) { 717 if (key in instance) { 718 const propsOrSchema = $dependencies[key]; 719 if (Array.isArray(propsOrSchema)) { 720 for (const dependantKey of propsOrSchema) { 721 if (!(dependantKey in instance)) { 722 errors.push({ 723 instanceLocation, 724 keyword: 'dependencies', 725 keywordLocation, 726 error: `Instance has "${key}" but does not have "${dependantKey}".` 727 }); 728 } 729 } 730 } 731 else { 732 const result = validate(instance, propsOrSchema, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, `${keywordLocation}/${encodePointer(key)}`); 733 if (!result.valid) { 734 errors.push({ 735 instanceLocation, 736 keyword: 'dependencies', 737 keywordLocation, 738 error: `Instance has "${key}" but does not match dependant schema.` 739 }, ...result.errors); 740 } 741 } 742 } 743 } 744 } 745 const thisEvaluated = Object.create(null); 746 let stop = false; 747 if ($properties !== undefined) { 748 const keywordLocation = `${schemaLocation}/properties`; 749 for (const key in $properties) { 750 if (!(key in instance)) { 751 continue; 752 } 753 const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`; 754 const result = validate(instance[key], $properties[key], draft, lookup, shortCircuit, recursiveAnchor, subInstancePointer, `${keywordLocation}/${encodePointer(key)}`); 755 if (result.valid) { 756 evaluated[key] = thisEvaluated[key] = true; 757 } 758 else { 759 stop = shortCircuit; 760 errors.push({ 761 instanceLocation, 762 keyword: 'properties', 763 keywordLocation, 764 error: `Property "${key}" does not match schema.` 765 }, ...result.errors); 766 if (stop) 767 break; 768 } 769 } 770 } 771 if (!stop && $patternProperties !== undefined) { 772 const keywordLocation = `${schemaLocation}/patternProperties`; 773 for (const pattern in $patternProperties) { 774 const regex = new RegExp(pattern); 775 const subSchema = $patternProperties[pattern]; 776 for (const key in instance) { 777 if (!regex.test(key)) { 778 continue; 779 } 780 const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`; 781 const result = validate(instance[key], subSchema, draft, lookup, shortCircuit, recursiveAnchor, subInstancePointer, `${keywordLocation}/${encodePointer(pattern)}`); 782 if (result.valid) { 783 evaluated[key] = thisEvaluated[key] = true; 784 } 785 else { 786 stop = shortCircuit; 787 errors.push({ 788 instanceLocation, 789 keyword: 'patternProperties', 790 keywordLocation, 791 error: `Property "${key}" matches pattern "${pattern}" but does not match associated schema.` 792 }, ...result.errors); 793 } 794 } 795 } 796 } 797 if (!stop && $additionalProperties !== undefined) { 798 const keywordLocation = `${schemaLocation}/additionalProperties`; 799 for (const key in instance) { 800 if (thisEvaluated[key]) { 801 continue; 802 } 803 const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`; 804 const result = validate(instance[key], $additionalProperties, draft, lookup, shortCircuit, recursiveAnchor, subInstancePointer, keywordLocation); 805 if (result.valid) { 806 evaluated[key] = true; 807 } 808 else { 809 stop = shortCircuit; 810 errors.push({ 811 instanceLocation, 812 keyword: 'additionalProperties', 813 keywordLocation, 814 error: `Property "${key}" does not match additional properties schema.` 815 }, ...result.errors); 816 } 817 } 818 } 819 else if (!stop && $unevaluatedProperties !== undefined) { 820 const keywordLocation = `${schemaLocation}/unevaluatedProperties`; 821 for (const key in instance) { 822 if (!evaluated[key]) { 823 const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`; 824 const result = validate(instance[key], $unevaluatedProperties, draft, lookup, shortCircuit, recursiveAnchor, subInstancePointer, keywordLocation); 825 if (result.valid) { 826 evaluated[key] = true; 827 } 828 else { 829 errors.push({ 830 instanceLocation, 831 keyword: 'unevaluatedProperties', 832 keywordLocation, 833 error: `Property "${key}" does not match unevaluated properties schema.` 834 }, ...result.errors); 835 } 836 } 837 } 838 } 839 } 840 else if (instanceType === 'array') { 841 if ($maxItems !== undefined && instance.length > $maxItems) { 842 errors.push({ 843 instanceLocation, 844 keyword: 'maxItems', 845 keywordLocation: `${schemaLocation}/maxItems`, 846 error: `Array has too many items (${instance.length} > ${$maxItems}).` 847 }); 848 } 849 if ($minItems !== undefined && instance.length < $minItems) { 850 errors.push({ 851 instanceLocation, 852 keyword: 'minItems', 853 keywordLocation: `${schemaLocation}/minItems`, 854 error: `Array has too few items (${instance.length} < ${$minItems}).` 855 }); 856 } 857 const length = instance.length; 858 let i = 0; 859 let stop = false; 860 if ($prefixItems !== undefined) { 861 const keywordLocation = `${schemaLocation}/prefixItems`; 862 const length2 = Math.min($prefixItems.length, length); 863 for (; i < length2; i++) { 864 const result = validate(instance[i], $prefixItems[i], draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, `${keywordLocation}/${i}`); 865 evaluated[i] = true; 866 if (!result.valid) { 867 stop = shortCircuit; 868 errors.push({ 869 instanceLocation, 870 keyword: 'prefixItems', 871 keywordLocation, 872 error: `Items did not match schema.` 873 }, ...result.errors); 874 if (stop) 875 break; 876 } 877 } 878 } 879 if ($items !== undefined) { 880 const keywordLocation = `${schemaLocation}/items`; 881 if (Array.isArray($items)) { 882 const length2 = Math.min($items.length, length); 883 for (; i < length2; i++) { 884 const result = validate(instance[i], $items[i], draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, `${keywordLocation}/${i}`); 885 evaluated[i] = true; 886 if (!result.valid) { 887 stop = shortCircuit; 888 errors.push({ 889 instanceLocation, 890 keyword: 'items', 891 keywordLocation, 892 error: `Items did not match schema.` 893 }, ...result.errors); 894 if (stop) 895 break; 896 } 897 } 898 } 899 else { 900 for (; i < length; i++) { 901 const result = validate(instance[i], $items, draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, keywordLocation); 902 evaluated[i] = true; 903 if (!result.valid) { 904 stop = shortCircuit; 905 errors.push({ 906 instanceLocation, 907 keyword: 'items', 908 keywordLocation, 909 error: `Items did not match schema.` 910 }, ...result.errors); 911 if (stop) 912 break; 913 } 914 } 915 } 916 if (!stop && $additionalItems !== undefined) { 917 const keywordLocation = `${schemaLocation}/additionalItems`; 918 for (; i < length; i++) { 919 const result = validate(instance[i], $additionalItems, draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, keywordLocation); 920 evaluated[i] = true; 921 if (!result.valid) { 922 stop = shortCircuit; 923 errors.push({ 924 instanceLocation, 925 keyword: 'additionalItems', 926 keywordLocation, 927 error: `Items did not match additional items schema.` 928 }, ...result.errors); 929 } 930 } 931 } 932 } 933 if ($contains !== undefined) { 934 if (length === 0 && $minContains === undefined) { 935 errors.push({ 936 instanceLocation, 937 keyword: 'contains', 938 keywordLocation: `${schemaLocation}/contains`, 939 error: `Array is empty. It must contain at least one item matching the schema.` 940 }); 941 } 942 else if ($minContains !== undefined && length < $minContains) { 943 errors.push({ 944 instanceLocation, 945 keyword: 'minContains', 946 keywordLocation: `${schemaLocation}/minContains`, 947 error: `Array has less items (${length}) than minContains (${$minContains}).` 948 }); 949 } 950 else { 951 const keywordLocation = `${schemaLocation}/contains`; 952 const errorsLength = errors.length; 953 let contained = 0; 954 for (let j = 0; j < length; j++) { 955 const result = validate(instance[j], $contains, draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${j}`, keywordLocation); 956 if (result.valid) { 957 evaluated[j] = true; 958 contained++; 959 } 960 else { 961 errors.push(...result.errors); 962 } 963 } 964 if (contained >= ($minContains || 0)) { 965 errors.length = errorsLength; 966 } 967 if ($minContains === undefined && 968 $maxContains === undefined && 969 contained === 0) { 970 errors.splice(errorsLength, 0, { 971 instanceLocation, 972 keyword: 'contains', 973 keywordLocation, 974 error: `Array does not contain item matching schema.` 975 }); 976 } 977 else if ($minContains !== undefined && contained < $minContains) { 978 errors.push({ 979 instanceLocation, 980 keyword: 'minContains', 981 keywordLocation: `${schemaLocation}/minContains`, 982 error: `Array must contain at least ${$minContains} items matching schema. Only ${contained} items were found.` 983 }); 984 } 985 else if ($maxContains !== undefined && contained > $maxContains) { 986 errors.push({ 987 instanceLocation, 988 keyword: 'maxContains', 989 keywordLocation: `${schemaLocation}/maxContains`, 990 error: `Array may contain at most ${$maxContains} items matching schema. ${contained} items were found.` 991 }); 992 } 993 } 994 } 995 if (!stop && $unevaluatedItems !== undefined) { 996 const keywordLocation = `${schemaLocation}/unevaluatedItems`; 997 for (i; i < length; i++) { 998 if (evaluated[i]) { 999 continue; 1000 } 1001 const result = validate(instance[i], $unevaluatedItems, draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, keywordLocation); 1002 evaluated[i] = true; 1003 if (!result.valid) { 1004 errors.push({ 1005 instanceLocation, 1006 keyword: 'unevaluatedItems', 1007 keywordLocation, 1008 error: `Items did not match unevaluated items schema.` 1009 }, ...result.errors); 1010 } 1011 } 1012 } 1013 if ($uniqueItems) { 1014 for (let j = 0; j < length; j++) { 1015 const a = instance[j]; 1016 const ao = typeof a === 'object' && a !== null; 1017 for (let k = 0; k < length; k++) { 1018 if (j === k) { 1019 continue; 1020 } 1021 const b = instance[k]; 1022 const bo = typeof b === 'object' && b !== null; 1023 if (a === b || (ao && bo && deepCompareStrict(a, b))) { 1024 errors.push({ 1025 instanceLocation, 1026 keyword: 'uniqueItems', 1027 keywordLocation: `${schemaLocation}/uniqueItems`, 1028 error: `Duplicate items at indexes ${j} and ${k}.` 1029 }); 1030 j = Number.MAX_SAFE_INTEGER; 1031 k = Number.MAX_SAFE_INTEGER; 1032 } 1033 } 1034 } 1035 } 1036 } 1037 else if (instanceType === 'number') { 1038 if (draft === '4') { 1039 if ($minimum !== undefined && 1040 (($exclusiveMinimum === true && instance <= $minimum) || 1041 instance < $minimum)) { 1042 errors.push({ 1043 instanceLocation, 1044 keyword: 'minimum', 1045 keywordLocation: `${schemaLocation}/minimum`, 1046 error: `${instance} is less than ${$exclusiveMinimum ? 'or equal to ' : ''} ${$minimum}.` 1047 }); 1048 } 1049 if ($maximum !== undefined && 1050 (($exclusiveMaximum === true && instance >= $maximum) || 1051 instance > $maximum)) { 1052 errors.push({ 1053 instanceLocation, 1054 keyword: 'maximum', 1055 keywordLocation: `${schemaLocation}/maximum`, 1056 error: `${instance} is greater than ${$exclusiveMaximum ? 'or equal to ' : ''} ${$maximum}.` 1057 }); 1058 } 1059 } 1060 else { 1061 if ($minimum !== undefined && instance < $minimum) { 1062 errors.push({ 1063 instanceLocation, 1064 keyword: 'minimum', 1065 keywordLocation: `${schemaLocation}/minimum`, 1066 error: `${instance} is less than ${$minimum}.` 1067 }); 1068 } 1069 if ($maximum !== undefined && instance > $maximum) { 1070 errors.push({ 1071 instanceLocation, 1072 keyword: 'maximum', 1073 keywordLocation: `${schemaLocation}/maximum`, 1074 error: `${instance} is greater than ${$maximum}.` 1075 }); 1076 } 1077 if ($exclusiveMinimum !== undefined && instance <= $exclusiveMinimum) { 1078 errors.push({ 1079 instanceLocation, 1080 keyword: 'exclusiveMinimum', 1081 keywordLocation: `${schemaLocation}/exclusiveMinimum`, 1082 error: `${instance} is less than ${$exclusiveMinimum}.` 1083 }); 1084 } 1085 if ($exclusiveMaximum !== undefined && instance >= $exclusiveMaximum) { 1086 errors.push({ 1087 instanceLocation, 1088 keyword: 'exclusiveMaximum', 1089 keywordLocation: `${schemaLocation}/exclusiveMaximum`, 1090 error: `${instance} is greater than or equal to ${$exclusiveMaximum}.` 1091 }); 1092 } 1093 } 1094 if ($multipleOf !== undefined) { 1095 const remainder = instance % $multipleOf; 1096 if (Math.abs(0 - remainder) >= 1.1920929e-7 && 1097 Math.abs($multipleOf - remainder) >= 1.1920929e-7) { 1098 errors.push({ 1099 instanceLocation, 1100 keyword: 'multipleOf', 1101 keywordLocation: `${schemaLocation}/multipleOf`, 1102 error: `${instance} is not a multiple of ${$multipleOf}.` 1103 }); 1104 } 1105 } 1106 } 1107 else if (instanceType === 'string') { 1108 const length = $minLength === undefined && $maxLength === undefined 1109 ? 0 1110 : ucs2length(instance); 1111 if ($minLength !== undefined && length < $minLength) { 1112 errors.push({ 1113 instanceLocation, 1114 keyword: 'minLength', 1115 keywordLocation: `${schemaLocation}/minLength`, 1116 error: `String is too short (${length} < ${$minLength}).` 1117 }); 1118 } 1119 if ($maxLength !== undefined && length > $maxLength) { 1120 errors.push({ 1121 instanceLocation, 1122 keyword: 'maxLength', 1123 keywordLocation: `${schemaLocation}/maxLength`, 1124 error: `String is too long (${length} > ${$maxLength}).` 1125 }); 1126 } 1127 if ($pattern !== undefined && !new RegExp($pattern).test(instance)) { 1128 errors.push({ 1129 instanceLocation, 1130 keyword: 'pattern', 1131 keywordLocation: `${schemaLocation}/pattern`, 1132 error: `String does not match pattern.` 1133 }); 1134 } 1135 if ($format !== undefined && 1136 fastFormat[$format] && 1137 !fastFormat[$format](instance)) { 1138 errors.push({ 1139 instanceLocation, 1140 keyword: 'format', 1141 keywordLocation: `${schemaLocation}/format`, 1142 error: `String does not match format "${$format}".` 1143 }); 1144 } 1145 } 1146 return { valid: errors.length === 0, errors }; 1147 } 1148 1149 class Validator { 1150 constructor(schema, draft = '2019-09', shortCircuit = true) { 1151 this.schema = schema; 1152 this.draft = draft; 1153 this.shortCircuit = shortCircuit; 1154 this.lookup = dereference(schema); 1155 } 1156 validate(instance) { 1157 return validate(instance, this.schema, this.draft, this.lookup, this.shortCircuit); 1158 } 1159 addSchema(schema, id) { 1160 if (id) { 1161 schema = { ...schema, $id: id }; 1162 } 1163 dereference(schema, this.lookup); 1164 } 1165 } 1166 1167 this.Validator = Validator; 1168 this.deepCompareStrict = deepCompareStrict; 1169 this.dereference = dereference; 1170 this.encodePointer = encodePointer; 1171 this.escapePointer = escapePointer; 1172 this.fastFormat = fastFormat; 1173 this.fullFormat = fullFormat; 1174 this.ignoredKeyword = ignoredKeyword; 1175 this.initialBaseURI = initialBaseURI; 1176 this.schemaArrayKeyword = schemaArrayKeyword; 1177 this.schemaKeyword = schemaKeyword; 1178 this.schemaMapKeyword = schemaMapKeyword; 1179 this.ucs2length = ucs2length; 1180 this.validate = validate;