diff --git a/src/formats.ts b/src/formats.ts index 46f1192..3ab44f8 100644 --- a/src/formats.ts +++ b/src/formats.ts @@ -62,7 +62,7 @@ export const fullFormats: DefinedFormats = { 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, // optimized https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9780596802837/ch07s16.html - ipv4: /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/, + ipv4: /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/, 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, regex, // uuid: http://tools.ietf.org/html/rfc4122 @@ -149,7 +149,7 @@ function compareDate(d1: string, d2: string): number | undefined { return 0 } -const TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-]\d\d)(?::?(\d\d))?)?$/i +const TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i function time(str: string, withTimeZone?: boolean, strictTime?: boolean): boolean { const matches: string[] | null = TIME.exec(str) @@ -158,14 +158,15 @@ function time(str: string, withTimeZone?: boolean, strictTime?: boolean): boolea const min: number = +matches[2] const sec: number = +matches[3] const tz: string | undefined = matches[4] - const tzH: number = +(matches[5] || 0) - const tzM: number = +(matches[6] || 0) - return ( - ((hr <= 23 && min <= 59 && sec < 60 && tzH <= 24 && tzM < 60) || - // leap second - (hr - tzH === 23 && min - tzM === 59 && sec < 61 && tzH <= 24 && tzM < 60)) && - (!withTimeZone || (tz !== "" && (!strictTime || !!tz))) - ) + const tzSign: number = matches[5] === "-" ? -1 : 1 + const tzH: number = +(matches[6] || 0) + const tzM: number = +(matches[7] || 0) + if (tzH > 23 || tzM > 59 || (withTimeZone && (tz === "" || (strictTime && !tz)))) return false + if (hr <= 23 && min <= 59 && sec < 60) return true + // leap second + const utcMin = min - tzM * tzSign + const utcHr = hr - tzH * tzSign - (utcMin < 0 ? 1 : 0) + return (utcHr === 23 || utcHr === -1) && (utcMin === 59 || utcMin === -1) && sec < 61 } function strict_time(str: string): boolean { diff --git a/tests/JSON-Schema-Test-Suite b/tests/JSON-Schema-Test-Suite index 21555a8..e82bfdf 160000 --- a/tests/JSON-Schema-Test-Suite +++ b/tests/JSON-Schema-Test-Suite @@ -1 +1 @@ -Subproject commit 21555a8540584447a9f8ea659accd0ce79bd36e5 +Subproject commit e82bfdfa59c63cc74175d35fac81bb95e61db24b diff --git a/tests/json-schema.spec.js b/tests/json-schema.spec.js index 74af2ee..1cc4c5c 100644 --- a/tests/json-schema.spec.js +++ b/tests/json-schema.spec.js @@ -2,30 +2,47 @@ const jsonSchemaTest = require("json-schema-test") const Ajv = require("ajv").default const addFormats = require("../dist") -const ajv = new Ajv({$data: true, strictTypes: false, formats: {allowedUnknown: true}}) -addFormats(ajv) - -jsonSchemaTest(ajv, { - description: `JSON-Schema Test Suite draft-07 formats + extras`, +jsonSchemaTest(getAjv(true), { + description: `JSON-Schema Test Suite formats`, suites: { "draft-07 formats": "./JSON-Schema-Test-Suite/tests/draft7/optional/format/*.json", "draft-07 regex": "./JSON-Schema-Test-Suite/tests/draft7/optional/ecmascript-regex.json", "draft-2019-09 formats": "./JSON-Schema-Test-Suite/tests/draft2019-09/optional/format/*.json", "draft-2019-09 regex": "./JSON-Schema-Test-Suite/tests/draft2019-09/optional/ecmascript-regex.json", - extras: "./extras/{**/,}*.json", + "draft-2020-12 formats": "./JSON-Schema-Test-Suite/tests/draft2020-12/optional/format/*.json", + "draft-2020-12 regex": + "./JSON-Schema-Test-Suite/tests/draft2020-12/optional/ecmascript-regex.json", }, only: [], skip: ["format/idn-email", "format/idn-hostname", "format/iri", "format/iri-reference"], - afterEach({valid, errors}) { - expect(typeof valid).toBe("boolean") - if (valid === true) { - expect(errors).toBe(null) - } else { - expect(Array.isArray(errors)).toBe(true) - errors.every((err) => expect(typeof err).toBe("object")) - } - }, + afterEach, cwd: __dirname, hideFolder: "draft7/", }) + +jsonSchemaTest(getAjv(), { + description: `Extra tests`, + suites: { + extras: "./extras/{**/,}*.json", + }, + only: [], + afterEach, + cwd: __dirname, +}) + +function getAjv(strictTime) { + const ajv = new Ajv({$data: true, strictTypes: false, formats: {allowedUnknown: true}}) + addFormats(ajv, {mode: "full", keywords: true, strictTime}) + return ajv +} + +function afterEach({valid, errors}) { + expect(typeof valid).toBe("boolean") + if (valid === true) { + expect(errors).toBe(null) + } else { + expect(Array.isArray(errors)).toBe(true) + errors.every((err) => expect(typeof err).toBe("object")) + } +}