z-schema 12.3.1 → 12.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/z-schema +0 -0
- package/cjs/index.cjs +151 -29
- package/dist/format-validators.js +20 -10
- package/dist/json-schema.js +9 -3
- package/dist/json-validation.js +3 -2
- package/dist/report.d.ts +1 -1
- package/dist/schema-cache.d.ts +1 -1
- package/dist/schema-compiler.js +8 -4
- package/dist/utils/hostname.js +109 -5
- package/dist/utils/json.js +2 -1
- package/dist/utils/time.js +3 -3
- package/dist/validation/ref.js +1 -1
- package/dist/validation/shared.js +4 -2
- package/dist/validation/string.js +4 -4
- package/dist/z-schema-options.js +1 -1
- package/dist/z-schema.js +1 -1
- package/package.json +5 -5
- package/src/format-validators.ts +36 -10
- package/src/json-validation.ts +7 -7
- package/src/utils/base64.ts +1 -1
- package/src/utils/hostname.ts +230 -4
- package/src/utils/time.ts +3 -2
- package/src/validation/string.ts +7 -3
- package/umd/ZSchema.js +151 -29
- package/umd/ZSchema.min.js +2 -2
package/bin/z-schema
CHANGED
|
File without changes
|
package/cjs/index.cjs
CHANGED
|
@@ -80,7 +80,7 @@ function isObject(value) {
|
|
|
80
80
|
function isInteger(value) {
|
|
81
81
|
return typeof value === "number" && Number.isFinite(value) && value % 1 === 0;
|
|
82
82
|
}
|
|
83
|
-
const NON_SCHEMA_KEYWORDS_SET = new Set([
|
|
83
|
+
const NON_SCHEMA_KEYWORDS_SET = /* @__PURE__ */ new Set([
|
|
84
84
|
"enum",
|
|
85
85
|
"const",
|
|
86
86
|
"default",
|
|
@@ -255,7 +255,8 @@ const areEqual = (json1, json2, options, _depth = 0) => {
|
|
|
255
255
|
}
|
|
256
256
|
if (isObject(json1) && isObject(json2)) {
|
|
257
257
|
const keys1 = sortedKeys(json1);
|
|
258
|
-
|
|
258
|
+
const keys2 = sortedKeys(json2);
|
|
259
|
+
if (!areEqual(keys1, keys2, options, _depth + 1)) return false;
|
|
259
260
|
len = keys1.length;
|
|
260
261
|
for (i = 0; i < len; i++) if (!areEqual(json1[keys1[i]], json2[keys1[i]], options, _depth + 1)) return false;
|
|
261
262
|
return true;
|
|
@@ -1680,6 +1681,94 @@ const isValidRfc3339Date = (year, month, day) => {
|
|
|
1680
1681
|
//#region src/utils/hostname.ts
|
|
1681
1682
|
const IDN_SEPARATOR_REGEX = /[\u3002\uFF0E\uFF61]/g;
|
|
1682
1683
|
const IDN_SEPARATOR_TEST_REGEX = /[\u3002\uFF0E\uFF61]/;
|
|
1684
|
+
const XN_LABEL_REGEX = /^xn--/i;
|
|
1685
|
+
const DISALLOWED_CATEGORY_REGEX = /[\p{P}\p{S}\p{Z}\p{C}]/u;
|
|
1686
|
+
const DISALLOWED_CATEGORY_WHITELIST = /* @__PURE__ */ new Set([
|
|
1687
|
+
"-",
|
|
1688
|
+
"·",
|
|
1689
|
+
"͵",
|
|
1690
|
+
"׳",
|
|
1691
|
+
"״",
|
|
1692
|
+
"・",
|
|
1693
|
+
"۽",
|
|
1694
|
+
"۾",
|
|
1695
|
+
"་",
|
|
1696
|
+
"",
|
|
1697
|
+
""
|
|
1698
|
+
]);
|
|
1699
|
+
const isDisallowedCodePoint = (char) => DISALLOWED_CATEGORY_REGEX.test(char) && !DISALLOWED_CATEGORY_WHITELIST.has(char);
|
|
1700
|
+
const TRANSPARENT_MARK_REGEX = /\p{Mn}/u;
|
|
1701
|
+
const ARABIC_SCRIPT_REGEX = /\p{Script=Arabic}/u;
|
|
1702
|
+
const ARABIC_INDIC_DIGITS_REGEX = /[\u0660-\u0669\u06F0-\u06F9]/;
|
|
1703
|
+
const isArabicJoiningLetter = (char) => char !== void 0 && ARABIC_SCRIPT_REGEX.test(char) && !ARABIC_INDIC_DIGITS_REGEX.test(char);
|
|
1704
|
+
const hasZwnjJoiningContext = (label, idx) => {
|
|
1705
|
+
let before = idx - 1;
|
|
1706
|
+
while (before >= 0 && TRANSPARENT_MARK_REGEX.test(label[before])) before--;
|
|
1707
|
+
let after = idx + 1;
|
|
1708
|
+
while (after < label.length && TRANSPARENT_MARK_REGEX.test(label[after])) after++;
|
|
1709
|
+
return isArabicJoiningLetter(label[before]) && isArabicJoiningLetter(label[after]);
|
|
1710
|
+
};
|
|
1711
|
+
const BIDI_NSM_REGEX = /[\p{Mn}\p{Me}]/u;
|
|
1712
|
+
const BIDI_ARABIC_INDIC_DIGIT_REGEX = /[\u0660-\u0669]/;
|
|
1713
|
+
const BIDI_EXTENDED_ARABIC_INDIC_DIGIT_REGEX = /[\u06F0-\u06F9]/;
|
|
1714
|
+
const BIDI_HEBREW_SCRIPT_REGEX = /\p{Script=Hebrew}/u;
|
|
1715
|
+
const BIDI_AL_SCRIPT_REGEX = /[\p{Script=Arabic}\p{Script=Syriac}\p{Script=Thaana}]/u;
|
|
1716
|
+
const BIDI_ASCII_DIGIT_REGEX = /[0-9]/;
|
|
1717
|
+
const BIDI_LETTER_REGEX = /\p{L}/u;
|
|
1718
|
+
const classifyBidi = (char) => {
|
|
1719
|
+
if (BIDI_NSM_REGEX.test(char)) return "NSM";
|
|
1720
|
+
if (BIDI_ARABIC_INDIC_DIGIT_REGEX.test(char)) return "AN";
|
|
1721
|
+
if (BIDI_EXTENDED_ARABIC_INDIC_DIGIT_REGEX.test(char)) return "EN";
|
|
1722
|
+
if (BIDI_HEBREW_SCRIPT_REGEX.test(char)) return "R";
|
|
1723
|
+
if (BIDI_AL_SCRIPT_REGEX.test(char)) return "AL";
|
|
1724
|
+
if (BIDI_ASCII_DIGIT_REGEX.test(char)) return "EN";
|
|
1725
|
+
if (BIDI_LETTER_REGEX.test(char)) return "L";
|
|
1726
|
+
return "ON";
|
|
1727
|
+
};
|
|
1728
|
+
const isLabelBidiTriggering = (label) => {
|
|
1729
|
+
for (const char of label) {
|
|
1730
|
+
const bidiClass = classifyBidi(char);
|
|
1731
|
+
if (bidiClass === "R" || bidiClass === "AL" || bidiClass === "AN") return true;
|
|
1732
|
+
}
|
|
1733
|
+
return false;
|
|
1734
|
+
};
|
|
1735
|
+
const isLabelBidiRuleValid = (label) => {
|
|
1736
|
+
const classes = [];
|
|
1737
|
+
for (const char of label) classes.push(classifyBidi(char));
|
|
1738
|
+
if (classes.length === 0) return false;
|
|
1739
|
+
const first = classes[0];
|
|
1740
|
+
if (first !== "L" && first !== "R" && first !== "AL") return false;
|
|
1741
|
+
let lastIdx = classes.length - 1;
|
|
1742
|
+
while (lastIdx > 0 && classes[lastIdx] === "NSM") lastIdx--;
|
|
1743
|
+
const last = classes[lastIdx];
|
|
1744
|
+
if (first === "R" || first === "AL") {
|
|
1745
|
+
let hasEN = false;
|
|
1746
|
+
let hasAN = false;
|
|
1747
|
+
for (let i = 0; i < classes.length; i++) {
|
|
1748
|
+
const c = classes[i];
|
|
1749
|
+
if (c !== "R" && c !== "AL" && c !== "AN" && c !== "EN" && c !== "NSM" && c !== "ON") return false;
|
|
1750
|
+
if (c === "EN") hasEN = true;
|
|
1751
|
+
else if (c === "AN") hasAN = true;
|
|
1752
|
+
}
|
|
1753
|
+
if (hasEN && hasAN) return false;
|
|
1754
|
+
return last === "R" || last === "AL" || last === "EN" || last === "AN";
|
|
1755
|
+
}
|
|
1756
|
+
for (let i = 0; i < classes.length; i++) {
|
|
1757
|
+
const c = classes[i];
|
|
1758
|
+
if (c !== "L" && c !== "EN" && c !== "NSM" && c !== "ON") return false;
|
|
1759
|
+
}
|
|
1760
|
+
return last === "L" || last === "EN";
|
|
1761
|
+
};
|
|
1762
|
+
const isDomainBidiValid = (unicodeLabels) => {
|
|
1763
|
+
let isBidiDomain = false;
|
|
1764
|
+
for (let i = 0; i < unicodeLabels.length; i++) if (isLabelBidiTriggering(unicodeLabels[i])) {
|
|
1765
|
+
isBidiDomain = true;
|
|
1766
|
+
break;
|
|
1767
|
+
}
|
|
1768
|
+
if (!isBidiDomain) return true;
|
|
1769
|
+
for (let i = 0; i < unicodeLabels.length; i++) if (!isLabelBidiRuleValid(unicodeLabels[i])) return false;
|
|
1770
|
+
return true;
|
|
1771
|
+
};
|
|
1683
1772
|
const splitHostnameLabels = (hostname) => {
|
|
1684
1773
|
if (hostname.length === 0 || hostname.length > 255) return null;
|
|
1685
1774
|
if (hostname.startsWith(".") || hostname.endsWith(".")) return null;
|
|
@@ -1695,7 +1784,7 @@ const isGreek = (char) => /\p{Script=Greek}/u.test(char);
|
|
|
1695
1784
|
const isHebrew = (char) => /\p{Script=Hebrew}/u.test(char);
|
|
1696
1785
|
const hasCjkKanaOrHan = (input) => /[\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Han}]/u.test(input);
|
|
1697
1786
|
const toUnicodeLabel = (label) => {
|
|
1698
|
-
if (
|
|
1787
|
+
if (!XN_LABEL_REGEX.test(label)) return label;
|
|
1699
1788
|
try {
|
|
1700
1789
|
return punycode_punycode_js.default.toUnicode(label.toLowerCase());
|
|
1701
1790
|
} catch {
|
|
@@ -1704,7 +1793,7 @@ const toUnicodeLabel = (label) => {
|
|
|
1704
1793
|
};
|
|
1705
1794
|
const isValidIdnUnicodeLabel = (label) => {
|
|
1706
1795
|
if (label.startsWith("-") || label.endsWith("-")) return false;
|
|
1707
|
-
if (label.length >= 4 && label[2] === "-" && label[3] === "-" &&
|
|
1796
|
+
if (label.length >= 4 && label[2] === "-" && label[3] === "-" && !XN_LABEL_REGEX.test(label)) return false;
|
|
1708
1797
|
if (/^\p{M}/u.test(label)) return false;
|
|
1709
1798
|
if (/[\u302E\u302F\u0640\u07FA]/u.test(label)) return false;
|
|
1710
1799
|
for (let idx = 0; idx < label.length; idx++) {
|
|
@@ -1713,7 +1802,9 @@ const isValidIdnUnicodeLabel = (label) => {
|
|
|
1713
1802
|
if (char === "͵" && (idx === label.length - 1 || !isGreek(label[idx + 1]))) return false;
|
|
1714
1803
|
if ((char === "׳" || char === "״") && (idx === 0 || !isHebrew(label[idx - 1]))) return false;
|
|
1715
1804
|
if (char === "" && (idx === 0 || label[idx - 1] !== "्")) return false;
|
|
1805
|
+
if (char === "" && (idx === 0 || label[idx - 1] !== "्" && !hasZwnjJoiningContext(label, idx))) return false;
|
|
1716
1806
|
}
|
|
1807
|
+
for (const char of label) if (isDisallowedCodePoint(char)) return false;
|
|
1717
1808
|
if (label.includes("・") && !hasCjkKanaOrHan(label.replaceAll("・", ""))) return false;
|
|
1718
1809
|
const hasArabicIndic = /[\u0660-\u0669]/.test(label);
|
|
1719
1810
|
const hasExtendedArabicIndic = /[\u06F0-\u06F9]/.test(label);
|
|
@@ -1728,7 +1819,7 @@ const isValidHostname = (hostname) => {
|
|
|
1728
1819
|
for (let i = 0; i < labels.length; i++) {
|
|
1729
1820
|
const label = labels[i];
|
|
1730
1821
|
if (!isAsciiHostnameLabel(label)) return false;
|
|
1731
|
-
if (
|
|
1822
|
+
if (XN_LABEL_REGEX.test(label)) {
|
|
1732
1823
|
const unicodeLabel = toUnicodeLabel(label);
|
|
1733
1824
|
if (unicodeLabel === null || !isValidIdnUnicodeLabel(unicodeLabel)) return false;
|
|
1734
1825
|
}
|
|
@@ -1736,14 +1827,28 @@ const isValidHostname = (hostname) => {
|
|
|
1736
1827
|
return true;
|
|
1737
1828
|
};
|
|
1738
1829
|
const isValidIdnHostname = (hostname) => {
|
|
1739
|
-
const
|
|
1830
|
+
const normalizedHostname = hostname.replace(IDN_SEPARATOR_REGEX, ".");
|
|
1831
|
+
const labels = splitHostnameLabels(normalizedHostname);
|
|
1740
1832
|
if (labels === null) return false;
|
|
1833
|
+
const unicodeLabels = [];
|
|
1834
|
+
let totalLength = labels.length - 1;
|
|
1741
1835
|
for (let i = 0; i < labels.length; i++) {
|
|
1742
1836
|
const label = labels[i];
|
|
1743
1837
|
const unicodeLabel = toUnicodeLabel(label);
|
|
1744
1838
|
if (unicodeLabel === null || !isValidIdnUnicodeLabel(unicodeLabel)) return false;
|
|
1839
|
+
let aLabel;
|
|
1840
|
+
try {
|
|
1841
|
+
aLabel = punycode_punycode_js.default.toASCII(unicodeLabel);
|
|
1842
|
+
} catch {
|
|
1843
|
+
return false;
|
|
1844
|
+
}
|
|
1845
|
+
if (XN_LABEL_REGEX.test(label) && aLabel.toLowerCase() !== label.toLowerCase()) return false;
|
|
1846
|
+
if (aLabel.length > 63) return false;
|
|
1847
|
+
totalLength += aLabel.length;
|
|
1848
|
+
unicodeLabels.push(unicodeLabel);
|
|
1745
1849
|
}
|
|
1746
|
-
return
|
|
1850
|
+
if (totalLength > 253) return false;
|
|
1851
|
+
return isDomainBidiValid(unicodeLabels);
|
|
1747
1852
|
};
|
|
1748
1853
|
//#endregion
|
|
1749
1854
|
//#region src/utils/time.ts
|
|
@@ -1756,7 +1861,7 @@ const toUtcTime = (hour, minute, offsetSign, offsetHour, offsetMinute) => {
|
|
|
1756
1861
|
minute: normalizedUtcTotalMinutes % 60
|
|
1757
1862
|
};
|
|
1758
1863
|
};
|
|
1759
|
-
const RFC3339_TIME_REGEX = /^([0-9]{2}):([0-9]{2}):([0-9]{2})(
|
|
1864
|
+
const RFC3339_TIME_REGEX = /^(?<hour>[0-9]{2}):(?<minute>[0-9]{2}):(?<second>[0-9]{2})(?<fraction>\.[0-9]+)?(?<offset>z|(?<offsetValue>[+-][0-9]{2}:[0-9]{2}))$/i;
|
|
1760
1865
|
const parseRfc3339Time = (time) => {
|
|
1761
1866
|
const matches = RFC3339_TIME_REGEX.exec(time);
|
|
1762
1867
|
if (matches === null) return null;
|
|
@@ -1767,7 +1872,7 @@ const parseRfc3339Time = (time) => {
|
|
|
1767
1872
|
let utcHour = hour;
|
|
1768
1873
|
let utcMinute = minute;
|
|
1769
1874
|
if (matches[5].toLowerCase() !== "z") {
|
|
1770
|
-
const offsetMatches = /^([+-])([0-9]{2}):([0-9]{2})$/.exec(matches[5]);
|
|
1875
|
+
const offsetMatches = /^(?<sign>[+-])(?<hour>[0-9]{2}):(?<minute>[0-9]{2})$/.exec(matches[5]);
|
|
1771
1876
|
if (offsetMatches === null) return null;
|
|
1772
1877
|
const offsetSign = offsetMatches[1];
|
|
1773
1878
|
const offsetHour = Number.parseInt(offsetMatches[2], 10);
|
|
@@ -1790,7 +1895,7 @@ const parseRfc3339Time = (time) => {
|
|
|
1790
1895
|
//#region src/format-validators.ts
|
|
1791
1896
|
const dateValidator = (date) => {
|
|
1792
1897
|
if (typeof date !== "string") return true;
|
|
1793
|
-
const matches = /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.exec(date);
|
|
1898
|
+
const matches = /^(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})$/.exec(date);
|
|
1794
1899
|
if (matches === null) return false;
|
|
1795
1900
|
return isValidRfc3339Date(Number.parseInt(matches[1], 10), Number.parseInt(matches[2], 10), Number.parseInt(matches[3], 10));
|
|
1796
1901
|
};
|
|
@@ -1801,7 +1906,7 @@ const dateTimeValidator = (dateTime) => {
|
|
|
1801
1906
|
if (tIdx === -1) return false;
|
|
1802
1907
|
const datePart = dateTime.slice(0, tIdx);
|
|
1803
1908
|
const timePart = dateTime.slice(tIdx + 1);
|
|
1804
|
-
const dateMatches = /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.exec(datePart);
|
|
1909
|
+
const dateMatches = /^(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})$/.exec(datePart);
|
|
1805
1910
|
if (dateMatches === null) return false;
|
|
1806
1911
|
if (!isValidRfc3339Date(Number.parseInt(dateMatches[1], 10), Number.parseInt(dateMatches[2], 10), Number.parseInt(dateMatches[3], 10))) return false;
|
|
1807
1912
|
return parseRfc3339Time(timePart) !== null;
|
|
@@ -1812,7 +1917,7 @@ const emailValidator = (email) => {
|
|
|
1812
1917
|
require_tld: true,
|
|
1813
1918
|
allow_ip_domain: true
|
|
1814
1919
|
})) return true;
|
|
1815
|
-
const ipv6Literal = /^(
|
|
1920
|
+
const ipv6Literal = /^(?<localPart>.+)@\[IPv6:(?<address>[^\]]+)\]$/i.exec(email);
|
|
1816
1921
|
if (!ipv6Literal) return false;
|
|
1817
1922
|
const localPart = ipv6Literal[1];
|
|
1818
1923
|
const addressPart = ipv6Literal[2];
|
|
@@ -1832,7 +1937,7 @@ const ipv6Validator = (ipv6) => {
|
|
|
1832
1937
|
if (ipv6.includes("%")) return false;
|
|
1833
1938
|
return validator_lib_isIP_js.default.default(ipv6, 6);
|
|
1834
1939
|
};
|
|
1835
|
-
const INVALID_REGEX_ESCAPES = new Set(["a"]);
|
|
1940
|
+
const INVALID_REGEX_ESCAPES = /* @__PURE__ */ new Set(["a"]);
|
|
1836
1941
|
const regexValidator = (input) => {
|
|
1837
1942
|
if (typeof input !== "string") return true;
|
|
1838
1943
|
for (let idx = 0; idx < input.length; idx++) {
|
|
@@ -1885,7 +1990,7 @@ const uriValidator = (uri) => {
|
|
|
1885
1990
|
if (typeof uri !== "string") return true;
|
|
1886
1991
|
if (/[^\u0000-\u007F]/.test(uri)) return false;
|
|
1887
1992
|
if (!hasValidPercentEncoding(uri)) return false;
|
|
1888
|
-
const match = /^([a-zA-Z][a-zA-Z0-9+.-]*):\/\/([^/?#]*)/.exec(uri);
|
|
1993
|
+
const match = /^(?<scheme>[a-zA-Z][a-zA-Z0-9+.-]*):\/\/(?<authority>[^/?#]*)/.exec(uri);
|
|
1889
1994
|
if (match) {
|
|
1890
1995
|
const authority = match[2];
|
|
1891
1996
|
const atIndex = authority.indexOf("@");
|
|
@@ -1909,11 +2014,11 @@ const uriValidator = (uri) => {
|
|
|
1909
2014
|
const uriReferenceValidator = (uri) => {
|
|
1910
2015
|
if (typeof uri !== "string") return true;
|
|
1911
2016
|
if (/[^\u0000-\u007F]/.test(uri)) return false;
|
|
1912
|
-
return /^([a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^{}^`| ]*$/.test(uri);
|
|
2017
|
+
return /^(?:[a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^{}^`| ]*$/.test(uri);
|
|
1913
2018
|
};
|
|
1914
2019
|
const uriTemplateValidator = (uri) => {
|
|
1915
2020
|
if (typeof uri !== "string") return true;
|
|
1916
|
-
if (!/^([a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^`| ]*$/.test(uri)) return false;
|
|
2021
|
+
if (!/^(?:[a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^`| ]*$/.test(uri)) return false;
|
|
1917
2022
|
let inExpression = false;
|
|
1918
2023
|
for (let idx = 0; idx < uri.length; idx++) {
|
|
1919
2024
|
const ch = uri[idx];
|
|
@@ -1945,7 +2050,7 @@ const jsonPointerValidator = (pointer) => {
|
|
|
1945
2050
|
};
|
|
1946
2051
|
const relativeJsonPointerValidator = (pointer) => {
|
|
1947
2052
|
if (typeof pointer !== "string") return true;
|
|
1948
|
-
const match = /^(0|[1-9]\d*)(
|
|
2053
|
+
const match = /^(?<int>0|[1-9]\d*)(?<suffix>.*)$/.exec(pointer);
|
|
1949
2054
|
if (!match) return false;
|
|
1950
2055
|
const suffix = match[2];
|
|
1951
2056
|
if (suffix === "" || suffix === "#") return true;
|
|
@@ -1959,9 +2064,19 @@ const timeValidator = (time) => {
|
|
|
1959
2064
|
if (typeof time !== "string") return true;
|
|
1960
2065
|
return parseRfc3339Time(time) !== null;
|
|
1961
2066
|
};
|
|
2067
|
+
const LONE_SURROGATE_REGEX = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:^|[^\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
2068
|
+
const QUOTED_LOCAL_PART_REGEX = /^"(?:[^"\\]|\\.)*"$/u;
|
|
2069
|
+
const UNQUOTED_LOCAL_PART_REGEX = /^[^\s@]+$/u;
|
|
2070
|
+
const isValidIdnEmailLocalPart = (localPart) => {
|
|
2071
|
+
if (localPart.length === 0 || LONE_SURROGATE_REGEX.test(localPart)) return false;
|
|
2072
|
+
if (localPart.length >= 2 && localPart.startsWith("\"") && localPart.endsWith("\"")) return QUOTED_LOCAL_PART_REGEX.test(localPart);
|
|
2073
|
+
return UNQUOTED_LOCAL_PART_REGEX.test(localPart);
|
|
2074
|
+
};
|
|
1962
2075
|
const idnEmailValidator = (email) => {
|
|
1963
2076
|
if (typeof email !== "string") return true;
|
|
1964
|
-
|
|
2077
|
+
const atIdx = email.lastIndexOf("@");
|
|
2078
|
+
if (atIdx <= 0 || atIdx === email.length - 1) return false;
|
|
2079
|
+
return isValidIdnEmailLocalPart(email.slice(0, atIdx)) && isValidIdnHostname(email.slice(atIdx + 1));
|
|
1965
2080
|
};
|
|
1966
2081
|
const idnHostnameValidator = (hostname) => {
|
|
1967
2082
|
if (typeof hostname !== "string") return true;
|
|
@@ -1979,7 +2094,7 @@ const iriValidator = (iri) => {
|
|
|
1979
2094
|
};
|
|
1980
2095
|
const iriReferenceValidator = (iriReference) => {
|
|
1981
2096
|
if (typeof iriReference !== "string") return true;
|
|
1982
|
-
return /^([a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^{}^`| ]*$/u.test(iriReference);
|
|
2097
|
+
return /^(?:[a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^{}^`| ]*$/u.test(iriReference);
|
|
1983
2098
|
};
|
|
1984
2099
|
const inbuiltValidators = {
|
|
1985
2100
|
date: dateValidator,
|
|
@@ -2156,7 +2271,7 @@ const VOCAB_VALIDATION_2019_09 = "https://json-schema.org/draft/2019-09/vocab/va
|
|
|
2156
2271
|
const VOCAB_VALIDATION_2020_12 = "https://json-schema.org/draft/2020-12/vocab/validation";
|
|
2157
2272
|
const VOCAB_FORMAT_2019_09 = "https://json-schema.org/draft/2019-09/vocab/format";
|
|
2158
2273
|
const VOCAB_FORMAT_ASSERTION_2020_12 = "https://json-schema.org/draft/2020-12/vocab/format-assertion";
|
|
2159
|
-
const VALIDATION_VOCAB_KEYWORDS = new Set([
|
|
2274
|
+
const VALIDATION_VOCAB_KEYWORDS = /* @__PURE__ */ new Set([
|
|
2160
2275
|
"type",
|
|
2161
2276
|
"multipleOf",
|
|
2162
2277
|
"maximum",
|
|
@@ -2187,7 +2302,9 @@ const isValidationVocabularyEnabled = (schema, report, version) => {
|
|
|
2187
2302
|
const metaSchema = currentSchemaMeta || rootSchemaMeta;
|
|
2188
2303
|
if (!metaSchema || typeof metaSchema !== "object" || !isObject(metaSchema.$vocabulary)) return true;
|
|
2189
2304
|
const vocabulary = metaSchema.$vocabulary;
|
|
2190
|
-
|
|
2305
|
+
const has2019 = Object.hasOwn(vocabulary, VOCAB_VALIDATION_2019_09);
|
|
2306
|
+
const has2020 = Object.hasOwn(vocabulary, VOCAB_VALIDATION_2020_12);
|
|
2307
|
+
if (has2019 || has2020) return vocabulary[VOCAB_VALIDATION_2019_09] || vocabulary[VOCAB_VALIDATION_2020_12];
|
|
2191
2308
|
return false;
|
|
2192
2309
|
};
|
|
2193
2310
|
/**
|
|
@@ -2623,11 +2740,11 @@ function formatValidator(ctx, report, schema, json) {
|
|
|
2623
2740
|
if (result instanceof Promise) {
|
|
2624
2741
|
const promiseResult = result;
|
|
2625
2742
|
report.addAsyncTaskWithPath(async (callback) => {
|
|
2743
|
+
let resolved = false;
|
|
2626
2744
|
try {
|
|
2627
|
-
|
|
2628
|
-
} catch {
|
|
2629
|
-
|
|
2630
|
-
}
|
|
2745
|
+
resolved = await promiseResult;
|
|
2746
|
+
} catch {}
|
|
2747
|
+
callback(resolved);
|
|
2631
2748
|
}, [], (resolvedResult) => {
|
|
2632
2749
|
if (resolvedResult !== true) report.addError("INVALID_FORMAT", [schema.format, JSON.stringify(json)], void 0, schema, "format");
|
|
2633
2750
|
});
|
|
@@ -3120,7 +3237,8 @@ function validate(ctx, report, schema, json) {
|
|
|
3120
3237
|
}
|
|
3121
3238
|
}
|
|
3122
3239
|
if (deferredUnevaluatedKeys.length > 0 && !(report.errors.length > 0 && ctx.options.breakOnFirstError)) for (let i = 0; i < deferredUnevaluatedKeys.length; i++) {
|
|
3123
|
-
const
|
|
3240
|
+
const key = deferredUnevaluatedKeys[i];
|
|
3241
|
+
const validator = JsonValidators[key];
|
|
3124
3242
|
if (validator) {
|
|
3125
3243
|
validator(ctx, report, schema, json);
|
|
3126
3244
|
if (report.errors.length && ctx.options.breakOnFirstError) break;
|
|
@@ -3147,7 +3265,7 @@ function setSchemaReader(schemaReader) {
|
|
|
3147
3265
|
}
|
|
3148
3266
|
//#endregion
|
|
3149
3267
|
//#region src/schema-compiler.ts
|
|
3150
|
-
const UNSAFE_TARGETS = new Set([
|
|
3268
|
+
const UNSAFE_TARGETS = /* @__PURE__ */ new Set([
|
|
3151
3269
|
Object.prototype,
|
|
3152
3270
|
Function.prototype,
|
|
3153
3271
|
Array.prototype
|
|
@@ -3229,7 +3347,10 @@ const collectIds = (obj, maxDepth = 100) => {
|
|
|
3229
3347
|
}
|
|
3230
3348
|
}
|
|
3231
3349
|
id.absoluteParent = absoluteParent;
|
|
3232
|
-
if (id.absoluteParent)
|
|
3350
|
+
if (id.absoluteParent) {
|
|
3351
|
+
const parentUri = id.absoluteParent.absoluteUri || id.absoluteParent.id;
|
|
3352
|
+
id.absoluteUri = resolveSchemaScopeId(parentUri, schemaNode, id.id);
|
|
3353
|
+
}
|
|
3233
3354
|
}
|
|
3234
3355
|
ids.push(id);
|
|
3235
3356
|
scope.push(id);
|
|
@@ -3313,7 +3434,8 @@ var SchemaCompiler = class {
|
|
|
3313
3434
|
for (const item of ids) if (item.absoluteUri) {
|
|
3314
3435
|
this.validator.scache.cacheSchemaByUri(item.absoluteUri, item.obj);
|
|
3315
3436
|
if (item.type === "relative" && item.absoluteParent && isSimpleIdentifier(item.id)) {
|
|
3316
|
-
const
|
|
3437
|
+
const parentUri = item.absoluteParent.absoluteUri || item.absoluteParent.id;
|
|
3438
|
+
const altAbsoluteUri = resolveReference(parentUri, item.id);
|
|
3317
3439
|
this.validator.scache.cacheSchemaByUri(altAbsoluteUri, item.obj);
|
|
3318
3440
|
}
|
|
3319
3441
|
} else if (item.type === "root") this.validator.scache.cacheSchemaByUri(item.id, item.obj);
|
|
@@ -8,7 +8,7 @@ import isURLModule from "validator/lib/isURL.js";
|
|
|
8
8
|
//#region src/format-validators.ts
|
|
9
9
|
const dateValidator = (date) => {
|
|
10
10
|
if (typeof date !== "string") return true;
|
|
11
|
-
const matches = /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.exec(date);
|
|
11
|
+
const matches = /^(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})$/.exec(date);
|
|
12
12
|
if (matches === null) return false;
|
|
13
13
|
return isValidRfc3339Date(Number.parseInt(matches[1], 10), Number.parseInt(matches[2], 10), Number.parseInt(matches[3], 10));
|
|
14
14
|
};
|
|
@@ -19,7 +19,7 @@ const dateTimeValidator = (dateTime) => {
|
|
|
19
19
|
if (tIdx === -1) return false;
|
|
20
20
|
const datePart = dateTime.slice(0, tIdx);
|
|
21
21
|
const timePart = dateTime.slice(tIdx + 1);
|
|
22
|
-
const dateMatches = /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.exec(datePart);
|
|
22
|
+
const dateMatches = /^(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})$/.exec(datePart);
|
|
23
23
|
if (dateMatches === null) return false;
|
|
24
24
|
if (!isValidRfc3339Date(Number.parseInt(dateMatches[1], 10), Number.parseInt(dateMatches[2], 10), Number.parseInt(dateMatches[3], 10))) return false;
|
|
25
25
|
return parseRfc3339Time(timePart) !== null;
|
|
@@ -30,7 +30,7 @@ const emailValidator = (email) => {
|
|
|
30
30
|
require_tld: true,
|
|
31
31
|
allow_ip_domain: true
|
|
32
32
|
})) return true;
|
|
33
|
-
const ipv6Literal = /^(
|
|
33
|
+
const ipv6Literal = /^(?<localPart>.+)@\[IPv6:(?<address>[^\]]+)\]$/i.exec(email);
|
|
34
34
|
if (!ipv6Literal) return false;
|
|
35
35
|
const localPart = ipv6Literal[1];
|
|
36
36
|
const addressPart = ipv6Literal[2];
|
|
@@ -50,7 +50,7 @@ const ipv6Validator = (ipv6) => {
|
|
|
50
50
|
if (ipv6.includes("%")) return false;
|
|
51
51
|
return isIPModule.default(ipv6, 6);
|
|
52
52
|
};
|
|
53
|
-
const INVALID_REGEX_ESCAPES = new Set(["a"]);
|
|
53
|
+
const INVALID_REGEX_ESCAPES = /* @__PURE__ */ new Set(["a"]);
|
|
54
54
|
const regexValidator = (input) => {
|
|
55
55
|
if (typeof input !== "string") return true;
|
|
56
56
|
for (let idx = 0; idx < input.length; idx++) {
|
|
@@ -103,7 +103,7 @@ const uriValidator = (uri) => {
|
|
|
103
103
|
if (typeof uri !== "string") return true;
|
|
104
104
|
if (/[^\u0000-\u007F]/.test(uri)) return false;
|
|
105
105
|
if (!hasValidPercentEncoding(uri)) return false;
|
|
106
|
-
const match = /^([a-zA-Z][a-zA-Z0-9+.-]*):\/\/([^/?#]*)/.exec(uri);
|
|
106
|
+
const match = /^(?<scheme>[a-zA-Z][a-zA-Z0-9+.-]*):\/\/(?<authority>[^/?#]*)/.exec(uri);
|
|
107
107
|
if (match) {
|
|
108
108
|
const authority = match[2];
|
|
109
109
|
const atIndex = authority.indexOf("@");
|
|
@@ -127,11 +127,11 @@ const uriValidator = (uri) => {
|
|
|
127
127
|
const uriReferenceValidator = (uri) => {
|
|
128
128
|
if (typeof uri !== "string") return true;
|
|
129
129
|
if (/[^\u0000-\u007F]/.test(uri)) return false;
|
|
130
|
-
return /^([a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^{}^`| ]*$/.test(uri);
|
|
130
|
+
return /^(?:[a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^{}^`| ]*$/.test(uri);
|
|
131
131
|
};
|
|
132
132
|
const uriTemplateValidator = (uri) => {
|
|
133
133
|
if (typeof uri !== "string") return true;
|
|
134
|
-
if (!/^([a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^`| ]*$/.test(uri)) return false;
|
|
134
|
+
if (!/^(?:[a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^`| ]*$/.test(uri)) return false;
|
|
135
135
|
let inExpression = false;
|
|
136
136
|
for (let idx = 0; idx < uri.length; idx++) {
|
|
137
137
|
const ch = uri[idx];
|
|
@@ -163,7 +163,7 @@ const jsonPointerValidator = (pointer) => {
|
|
|
163
163
|
};
|
|
164
164
|
const relativeJsonPointerValidator = (pointer) => {
|
|
165
165
|
if (typeof pointer !== "string") return true;
|
|
166
|
-
const match = /^(0|[1-9]\d*)(
|
|
166
|
+
const match = /^(?<int>0|[1-9]\d*)(?<suffix>.*)$/.exec(pointer);
|
|
167
167
|
if (!match) return false;
|
|
168
168
|
const suffix = match[2];
|
|
169
169
|
if (suffix === "" || suffix === "#") return true;
|
|
@@ -177,9 +177,19 @@ const timeValidator = (time) => {
|
|
|
177
177
|
if (typeof time !== "string") return true;
|
|
178
178
|
return parseRfc3339Time(time) !== null;
|
|
179
179
|
};
|
|
180
|
+
const LONE_SURROGATE_REGEX = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:^|[^\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
181
|
+
const QUOTED_LOCAL_PART_REGEX = /^"(?:[^"\\]|\\.)*"$/u;
|
|
182
|
+
const UNQUOTED_LOCAL_PART_REGEX = /^[^\s@]+$/u;
|
|
183
|
+
const isValidIdnEmailLocalPart = (localPart) => {
|
|
184
|
+
if (localPart.length === 0 || LONE_SURROGATE_REGEX.test(localPart)) return false;
|
|
185
|
+
if (localPart.length >= 2 && localPart.startsWith("\"") && localPart.endsWith("\"")) return QUOTED_LOCAL_PART_REGEX.test(localPart);
|
|
186
|
+
return UNQUOTED_LOCAL_PART_REGEX.test(localPart);
|
|
187
|
+
};
|
|
180
188
|
const idnEmailValidator = (email) => {
|
|
181
189
|
if (typeof email !== "string") return true;
|
|
182
|
-
|
|
190
|
+
const atIdx = email.lastIndexOf("@");
|
|
191
|
+
if (atIdx <= 0 || atIdx === email.length - 1) return false;
|
|
192
|
+
return isValidIdnEmailLocalPart(email.slice(0, atIdx)) && isValidIdnHostname(email.slice(atIdx + 1));
|
|
183
193
|
};
|
|
184
194
|
const idnHostnameValidator = (hostname) => {
|
|
185
195
|
if (typeof hostname !== "string") return true;
|
|
@@ -197,7 +207,7 @@ const iriValidator = (iri) => {
|
|
|
197
207
|
};
|
|
198
208
|
const iriReferenceValidator = (iriReference) => {
|
|
199
209
|
if (typeof iriReference !== "string") return true;
|
|
200
|
-
return /^([a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^{}^`| ]*$/u.test(iriReference);
|
|
210
|
+
return /^(?:[a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^{}^`| ]*$/u.test(iriReference);
|
|
201
211
|
};
|
|
202
212
|
const inbuiltValidators = {
|
|
203
213
|
date: dateValidator,
|
package/dist/json-schema.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { getRemotePath, isAbsoluteUri } from "./utils/uri.js";
|
|
2
2
|
import { isObject } from "./utils/what-is.js";
|
|
3
|
-
|
|
3
|
+
//#region src/json-schema.ts
|
|
4
|
+
/**
|
|
5
|
+
* Keywords whose values are not JSON Schema sub-schemas and must not be
|
|
6
|
+
* traversed during schema walking (id collection, reference collection, etc.).
|
|
7
|
+
*/
|
|
8
|
+
const NON_SCHEMA_KEYWORDS = [
|
|
4
9
|
"enum",
|
|
5
10
|
"const",
|
|
6
11
|
"default",
|
|
7
12
|
"examples"
|
|
8
|
-
]
|
|
13
|
+
];
|
|
14
|
+
const NON_SCHEMA_KEYWORDS_SET = new Set(NON_SCHEMA_KEYWORDS);
|
|
9
15
|
/** Returns true if the key is an internal z-schema property (prefixed with `__$`). */
|
|
10
16
|
const isInternalKey = (key) => key.startsWith("__$");
|
|
11
17
|
const getId = (schema) => {
|
|
@@ -45,4 +51,4 @@ const findId = (schema, id, targetBaseUri, currentBaseUri, maxDepth = 100, _dept
|
|
|
45
51
|
}
|
|
46
52
|
};
|
|
47
53
|
//#endregion
|
|
48
|
-
export { NON_SCHEMA_KEYWORDS_SET, findId, getId, isInternalKey };
|
|
54
|
+
export { NON_SCHEMA_KEYWORDS, NON_SCHEMA_KEYWORDS_SET, findId, getId, isInternalKey };
|
package/dist/json-validation.js
CHANGED
|
@@ -437,7 +437,8 @@ function validate(ctx, report, schema, json) {
|
|
|
437
437
|
}
|
|
438
438
|
}
|
|
439
439
|
if (deferredUnevaluatedKeys.length > 0 && !(report.errors.length > 0 && ctx.options.breakOnFirstError)) for (let i = 0; i < deferredUnevaluatedKeys.length; i++) {
|
|
440
|
-
const
|
|
440
|
+
const key = deferredUnevaluatedKeys[i];
|
|
441
|
+
const validator = JsonValidators[key];
|
|
441
442
|
if (validator) {
|
|
442
443
|
validator(ctx, report, schema, json);
|
|
443
444
|
if (report.errors.length && ctx.options.breakOnFirstError) break;
|
|
@@ -454,4 +455,4 @@ function validate(ctx, report, schema, json) {
|
|
|
454
455
|
return report.errors.length === 0;
|
|
455
456
|
}
|
|
456
457
|
//#endregion
|
|
457
|
-
export { validate };
|
|
458
|
+
export { JsonValidators, validate };
|
package/dist/report.d.ts
CHANGED
|
@@ -93,4 +93,4 @@ declare class Report {
|
|
|
93
93
|
addCustomError(errorCode: ErrorCode, errorMessage: string, params?: ErrorParam[], subReports?: Report | Report[], schema?: JsonSchema | boolean, keyword?: keyof JsonSchemaAll): void;
|
|
94
94
|
}
|
|
95
95
|
//#endregion
|
|
96
|
-
export { Report, SchemaErrorDetail };
|
|
96
|
+
export { Report, ReportOptions, SchemaErrorDetail };
|
package/dist/schema-cache.d.ts
CHANGED
package/dist/schema-compiler.js
CHANGED
|
@@ -3,7 +3,7 @@ import { NON_SCHEMA_KEYWORDS_SET, getId, isInternalKey } from "./json-schema.js"
|
|
|
3
3
|
import { Report } from "./report.js";
|
|
4
4
|
import { getSchemaReader } from "./z-schema-reader.js";
|
|
5
5
|
//#region src/schema-compiler.ts
|
|
6
|
-
const UNSAFE_TARGETS = new Set([
|
|
6
|
+
const UNSAFE_TARGETS = /* @__PURE__ */ new Set([
|
|
7
7
|
Object.prototype,
|
|
8
8
|
Function.prototype,
|
|
9
9
|
Array.prototype
|
|
@@ -85,7 +85,10 @@ const collectIds = (obj, maxDepth = 100) => {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
id.absoluteParent = absoluteParent;
|
|
88
|
-
if (id.absoluteParent)
|
|
88
|
+
if (id.absoluteParent) {
|
|
89
|
+
const parentUri = id.absoluteParent.absoluteUri || id.absoluteParent.id;
|
|
90
|
+
id.absoluteUri = resolveSchemaScopeId(parentUri, schemaNode, id.id);
|
|
91
|
+
}
|
|
89
92
|
}
|
|
90
93
|
ids.push(id);
|
|
91
94
|
scope.push(id);
|
|
@@ -169,7 +172,8 @@ var SchemaCompiler = class {
|
|
|
169
172
|
for (const item of ids) if (item.absoluteUri) {
|
|
170
173
|
this.validator.scache.cacheSchemaByUri(item.absoluteUri, item.obj);
|
|
171
174
|
if (item.type === "relative" && item.absoluteParent && isSimpleIdentifier(item.id)) {
|
|
172
|
-
const
|
|
175
|
+
const parentUri = item.absoluteParent.absoluteUri || item.absoluteParent.id;
|
|
176
|
+
const altAbsoluteUri = resolveReference(parentUri, item.id);
|
|
173
177
|
this.validator.scache.cacheSchemaByUri(altAbsoluteUri, item.obj);
|
|
174
178
|
}
|
|
175
179
|
} else if (item.type === "root") this.validator.scache.cacheSchemaByUri(item.id, item.obj);
|
|
@@ -280,4 +284,4 @@ var SchemaCompiler = class {
|
|
|
280
284
|
}
|
|
281
285
|
};
|
|
282
286
|
//#endregion
|
|
283
|
-
export { SchemaCompiler };
|
|
287
|
+
export { SchemaCompiler, collectIds, collectReferences };
|