tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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;