Skip to content

Commit 87aefe7

Browse files
committed
Add support for the hostname format
1 parent 2050042 commit 87aefe7

File tree

8 files changed

+106
-13
lines changed

8 files changed

+106
-13
lines changed

draft-04/format.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const annotation = (format) => format;
2222
const formats = {
2323
"date-time": "https://json-schema.org/format/date-time",
2424
"email": "https://json-schema.org/format/email",
25-
"hostname": "https://json-schema.org/format/hostname",
25+
"hostname": "https://json-schema.org/format/draft-04/hostname",
2626
"ipv4": "https://json-schema.org/format/ipv4",
2727
"ipv6": "https://json-schema.org/format/ipv6",
2828
"uri": "https://json-schema.org/format/uri"

draft-04/hostname.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const label = `(?!-)[A-Za-z0-9-]{1,63}(?<!-)`;
2+
const domain = `${label}(?:\\.${label})*`;
3+
4+
const domainPattern = new RegExp(`^${domain}$`);
5+
6+
export const isHostname = (hostname) => {
7+
return domainPattern.test(hostname) && hostname.length < 256;
8+
};
9+
10+
export default {
11+
id: "https://json-schema.org/format/draft-04/hostname",
12+
handler: (hostname) => typeof hostname !== "string" || isHostname(hostname)
13+
};

draft-04/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { addKeyword, defineVocabulary, loadDialect } from "../lib/keywords.js";
1+
import { addFormat, addKeyword, defineVocabulary, loadDialect } from "../lib/keywords.js";
22
import { registerSchema } from "../lib/index.js";
33
import metaSchema from "./schema.js";
44
import additionalItems from "./additionalItems.js";
@@ -11,6 +11,7 @@ import format from "./format.js";
1111
import maximum from "./maximum.js";
1212
import minimum from "./minimum.js";
1313
import ref from "./ref.js";
14+
import hostname from "./hostname.js";
1415

1516

1617
addKeyword(additionalItems);
@@ -24,6 +25,8 @@ addKeyword(items);
2425
addKeyword(format);
2526
addKeyword(ref);
2627

28+
addFormat(hostname);
29+
2730
const jsonSchemaVersion = "http://json-schema.org/draft-04/schema";
2831

2932
defineVocabulary(jsonSchemaVersion, {

formats/formats-test-suite.spec.ts

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,51 @@ const skip = new Set<string>([
3232
"|draft6|email.json",
3333
"|draft4|email.json",
3434

35-
// Not supported
36-
"|draft2020-12|hostname.json",
37-
"|draft2019-09|hostname.json",
38-
"|draft7|hostname.json",
39-
"|draft6|hostname.json",
40-
"|draft4|hostname.json",
41-
4235
// Not supported
4336
"|draft2020-12|idn-email.json",
4437
"|draft2019-09|idn-email.json",
4538
"|draft7|idn-email.json",
4639

47-
// Not supported
48-
"|draft2020-12|idn-hostname.json",
49-
"|draft2019-09|idn-hostname.json",
50-
"|draft7|idn-hostname.json",
40+
// Uses UTS #46 rather than RFC 589[0-5]
41+
"|draft2020-12|idn-hostname.json|validation of internationalized host names|contains illegal char U+302E Hangul single dot tone mark",
42+
"|draft2019-09|idn-hostname.json|validation of internationalized host names|contains illegal char U+302E Hangul single dot tone mark",
43+
"|draft7|idn-hostname.json|validation of internationalized host names|contains illegal char U+302E Hangul single dot tone mark",
44+
"|draft2020-12|idn-hostname.json|validation of internationalized host names|Exceptions that are DISALLOWED, right-to-left chars",
45+
"|draft2019-09|idn-hostname.json|validation of internationalized host names|Exceptions that are DISALLOWED, right-to-left chars",
46+
"|draft7|idn-hostname.json|validation of internationalized host names|Exceptions that are DISALLOWED, right-to-left chars",
47+
"|draft2020-12|idn-hostname.json|validation of internationalized host names|Exceptions that are DISALLOWED, left-to-right chars",
48+
"|draft2019-09|idn-hostname.json|validation of internationalized host names|Exceptions that are DISALLOWED, left-to-right chars",
49+
"|draft7|idn-hostname.json|validation of internationalized host names|Exceptions that are DISALLOWED, left-to-right chars",
50+
"|draft2020-12|idn-hostname.json|validation of internationalized host names|MIDDLE DOT with no preceding 'l'",
51+
"|draft2019-09|idn-hostname.json|validation of internationalized host names|MIDDLE DOT with no preceding 'l'",
52+
"|draft7|idn-hostname.json|validation of internationalized host names|MIDDLE DOT with no preceding 'l'",
53+
"|draft2020-12|idn-hostname.json|validation of internationalized host names|MIDDLE DOT with nothing preceding",
54+
"|draft2019-09|idn-hostname.json|validation of internationalized host names|MIDDLE DOT with nothing preceding",
55+
"|draft7|idn-hostname.json|validation of internationalized host names|MIDDLE DOT with nothing preceding",
56+
"|draft2020-12|idn-hostname.json|validation of internationalized host names|MIDDLE DOT with no following 'l'",
57+
"|draft2019-09|idn-hostname.json|validation of internationalized host names|MIDDLE DOT with no following 'l'",
58+
"|draft7|idn-hostname.json|validation of internationalized host names|MIDDLE DOT with no following 'l'",
59+
"|draft2020-12|idn-hostname.json|validation of internationalized host names|MIDDLE DOT with nothing following",
60+
"|draft2019-09|idn-hostname.json|validation of internationalized host names|MIDDLE DOT with nothing following",
61+
"|draft7|idn-hostname.json|validation of internationalized host names|MIDDLE DOT with nothing following",
62+
"|draft2020-12|idn-hostname.json|validation of internationalized host names|Greek KERAIA not followed by Greek",
63+
"|draft2019-09|idn-hostname.json|validation of internationalized host names|Greek KERAIA not followed by Greek",
64+
"|draft7|idn-hostname.json|validation of internationalized host names|Greek KERAIA not followed by Greek",
65+
"|draft2020-12|idn-hostname.json|validation of internationalized host names|Greek KERAIA not followed by anything",
66+
"|draft2019-09|idn-hostname.json|validation of internationalized host names|Greek KERAIA not followed by anything",
67+
"|draft7|idn-hostname.json|validation of internationalized host names|Greek KERAIA not followed by anything",
68+
"|draft2020-12|idn-hostname.json|validation of internationalized host names|Hebrew GERESH not preceded by anything",
69+
"|draft2019-09|idn-hostname.json|validation of internationalized host names|Hebrew GERESH not preceded by anything",
70+
"|draft7|idn-hostname.json|validation of internationalized host names|Hebrew GERESH not preceded by anything",
71+
"|draft2020-12|idn-hostname.json|validation of internationalized host names|Hebrew GERSHAYIM not preceded by anything",
72+
"|draft2019-09|idn-hostname.json|validation of internationalized host names|Hebrew GERSHAYIM not preceded by anything",
73+
"|draft7|idn-hostname.json|validation of internationalized host names|Hebrew GERSHAYIM not preceded by anything",
74+
"|draft2020-12|idn-hostname.json|validation of internationalized host names|KATAKANA MIDDLE DOT with no Hiragana, Katakana, or Han",
75+
"|draft2019-09|idn-hostname.json|validation of internationalized host names|KATAKANA MIDDLE DOT with no Hiragana, Katakana, or Han",
76+
"|draft7|idn-hostname.json|validation of internationalized host names|KATAKANA MIDDLE DOT with no Hiragana, Katakana, or Han",
77+
"|draft2020-12|idn-hostname.json|validation of internationalized host names|KATAKANA MIDDLE DOT with no other characters",
78+
"|draft2019-09|idn-hostname.json|validation of internationalized host names|KATAKANA MIDDLE DOT with no other characters",
79+
"|draft7|idn-hostname.json|validation of internationalized host names|KATAKANA MIDDLE DOT with no other characters",
5180

5281
// Leap seconds don't make sense without a date
5382
"|draft2020-12|time.json|validation of time strings|a valid time string with leap second, Zulu",

formats/handlers/hostname.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import tr46 from "tr46";
2+
3+
4+
const label = `[A-Za-z0-9-]{1,63}`;
5+
const domain = `${label}(?:\\.${label})*`;
6+
7+
const domainPattern = new RegExp(`^${domain}$`);
8+
9+
const parserOptions = {
10+
checkBidi: true,
11+
checkJoiners: true,
12+
checkHyphens: true
13+
};
14+
15+
export const isHostname = (hostname) => {
16+
return domainPattern.test(hostname)
17+
&& hostname.length < 256
18+
&& !tr46.toUnicode(hostname, parserOptions).error;
19+
};
20+
21+
export default {
22+
id: "https://json-schema.org/format/hostname",
23+
handler: (hostname) => typeof hostname !== "string" || isHostname(hostname)
24+
};

formats/handlers/idn-hostname.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import tr46 from "tr46";
2+
import { isHostname } from "./hostname.js";
3+
4+
5+
export const isIdnHostname = (hostname) => {
6+
const asciiHostname = tr46.toASCII(hostname);
7+
8+
if (!asciiHostname) {
9+
return false;
10+
}
11+
12+
return isHostname(asciiHostname);
13+
};
14+
15+
export default {
16+
id: "https://json-schema.org/format/idn-hostname",
17+
handler: (hostname) => typeof hostname !== "string" || isIdnHostname(hostname)
18+
};

formats/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import dateTime from "./handlers/date-time.js";
44
import date from "./handlers/date.js";
55
import time from "./handlers/time.js";
66
import duration from "./handlers/duration.js";
7+
import hostname from "./handlers/hostname.js";
8+
import idnHostname from "./handlers/idn-hostname.js";
79
import ipv4 from "./handlers/ipv4.js";
810
import ipv6 from "./handlers/ipv6.js";
911
import uri from "./handlers/uri.js";
@@ -21,6 +23,8 @@ addFormat(dateTime);
2123
addFormat(date);
2224
addFormat(time);
2325
addFormat(duration);
26+
addFormat(hostname);
27+
addFormat(idnHostname);
2428
addFormat(ipv4);
2529
addFormat(ipv6);
2630
addFormat(uri);

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"@stylistic/eslint-plugin": "*",
5555
"@types/content-type": "*",
5656
"@types/node": "*",
57+
"@types/tr46": "^5.0.1",
5758
"@types/uuid": "*",
5859
"@vitest/coverage-v8": "*",
5960
"eslint-import-resolver-typescript": "*",
@@ -72,6 +73,7 @@
7273
"content-type": "^1.0.4",
7374
"json-stringify-deterministic": "^1.0.12",
7475
"just-curry-it": "^5.3.0",
76+
"tr46": "^6.0.0",
7577
"uuid": "^9.0.0"
7678
},
7779
"peerDependencies": {

0 commit comments

Comments
 (0)