Skip to content

Commit 5a14033

Browse files
Merge pull request #1430 from open-circle/enhance-to-json-schema
Enhance to-json-schema with new validation actions
2 parents 7981523 + 35592eb commit 5a14033

File tree

14 files changed

+1256
-197
lines changed

14 files changed

+1256
-197
lines changed

library/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
All notable changes to the library will be documented in this file.
44

5+
## vX.X.X (Month DD, YYYY)
6+
7+
- Change `BASE64_REGEX`, `EMAIL_REGEX`, `IPV6_REGEX`, `IP_REGEX`, `MAC48_REGEX`, `MAC64_REGEX`, `MAC_REGEX` and `UUID_REGEX` to drop the `i` flag for better JSON Schema compatibility (pull request #1430)
8+
- Change `hash` action to use case-expanded character classes instead of the `i` flag (pull request #1430)
9+
510
## v1.3.0 (March 17, 2026)
611

712
- Add `guard` transformation action to narrow types using type predicates (pull request #1204)

library/src/actions/hash/hash.test.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ describe('hash', () => {
214214
test('for invalid md4 hashes', () => {
215215
expectActionIssue(
216216
hash(['md4'], 'message'),
217-
{ ...baseIssue, requirement: /^[a-f0-9]{32}$/iu },
217+
{ ...baseIssue, requirement: /^[a-fA-F0-9]{32}$/u },
218218
[
219219
'',
220220
' ',
@@ -228,7 +228,7 @@ describe('hash', () => {
228228
test('for invalid md5 hashes', () => {
229229
expectActionIssue(
230230
hash(['md5'], 'message'),
231-
{ ...baseIssue, requirement: /^[a-f0-9]{32}$/iu },
231+
{ ...baseIssue, requirement: /^[a-fA-F0-9]{32}$/u },
232232
[
233233
'',
234234
' ',
@@ -242,7 +242,7 @@ describe('hash', () => {
242242
test('for invalid sha1 hashes', () => {
243243
expectActionIssue(
244244
hash(['sha1'], 'message'),
245-
{ ...baseIssue, requirement: /^[a-f0-9]{40}$/iu },
245+
{ ...baseIssue, requirement: /^[a-fA-F0-9]{40}$/u },
246246
[
247247
'',
248248
' ',
@@ -256,7 +256,7 @@ describe('hash', () => {
256256
test('for invalid sha256 hashes', () => {
257257
expectActionIssue(
258258
hash(['sha256'], 'message'),
259-
{ ...baseIssue, requirement: /^[a-f0-9]{64}$/iu },
259+
{ ...baseIssue, requirement: /^[a-fA-F0-9]{64}$/u },
260260
[
261261
'',
262262
' ',
@@ -270,7 +270,7 @@ describe('hash', () => {
270270
test('for invalid sha384 hashes', () => {
271271
expectActionIssue(
272272
hash(['sha384'], 'message'),
273-
{ ...baseIssue, requirement: /^[a-f0-9]{96}$/iu },
273+
{ ...baseIssue, requirement: /^[a-fA-F0-9]{96}$/u },
274274
[
275275
'',
276276
' ',
@@ -284,7 +284,7 @@ describe('hash', () => {
284284
test('for invalid sha512 hashes', () => {
285285
expectActionIssue(
286286
hash(['sha512'], 'message'),
287-
{ ...baseIssue, requirement: /^[a-f0-9]{128}$/iu },
287+
{ ...baseIssue, requirement: /^[a-fA-F0-9]{128}$/u },
288288
[
289289
'',
290290
' ',
@@ -298,7 +298,7 @@ describe('hash', () => {
298298
test('for invalid ripemd128 hashes', () => {
299299
expectActionIssue(
300300
hash(['ripemd128'], 'message'),
301-
{ ...baseIssue, requirement: /^[a-f0-9]{32}$/iu },
301+
{ ...baseIssue, requirement: /^[a-fA-F0-9]{32}$/u },
302302
[
303303
'',
304304
' ',
@@ -312,7 +312,7 @@ describe('hash', () => {
312312
test('for invalid ripemd160 hashes', () => {
313313
expectActionIssue(
314314
hash(['ripemd160'], 'message'),
315-
{ ...baseIssue, requirement: /^[a-f0-9]{40}$/iu },
315+
{ ...baseIssue, requirement: /^[a-fA-F0-9]{40}$/u },
316316
[
317317
'',
318318
' ',
@@ -326,7 +326,7 @@ describe('hash', () => {
326326
test('for invalid tiger128 hashes', () => {
327327
expectActionIssue(
328328
hash(['tiger128'], 'message'),
329-
{ ...baseIssue, requirement: /^[a-f0-9]{32}$/iu },
329+
{ ...baseIssue, requirement: /^[a-fA-F0-9]{32}$/u },
330330
[
331331
'',
332332
' ',
@@ -340,7 +340,7 @@ describe('hash', () => {
340340
test('for invalid tiger160 hashes', () => {
341341
expectActionIssue(
342342
hash(['tiger160'], 'message'),
343-
{ ...baseIssue, requirement: /^[a-f0-9]{40}$/iu },
343+
{ ...baseIssue, requirement: /^[a-fA-F0-9]{40}$/u },
344344
[
345345
'',
346346
' ',
@@ -354,7 +354,7 @@ describe('hash', () => {
354354
test('for invalid tiger192 hashes', () => {
355355
expectActionIssue(
356356
hash(['tiger192'], 'message'),
357-
{ ...baseIssue, requirement: /^[a-f0-9]{48}$/iu },
357+
{ ...baseIssue, requirement: /^[a-fA-F0-9]{48}$/u },
358358
[
359359
'',
360360
' ',
@@ -368,31 +368,31 @@ describe('hash', () => {
368368
test('for invalid crc32 hashes', () => {
369369
expectActionIssue(
370370
hash(['crc32'], 'message'),
371-
{ ...baseIssue, requirement: /^[a-f0-9]{8}$/iu },
371+
{ ...baseIssue, requirement: /^[a-fA-F0-9]{8}$/u },
372372
['', ' ', '12345', '3d08bq77', 'c93d3bf7a7c4afe94b64e30c2ce39f4f']
373373
);
374374
});
375375

376376
test('for invalid crc32b hashes', () => {
377377
expectActionIssue(
378378
hash(['crc32b'], 'message'),
379-
{ ...baseIssue, requirement: /^[a-f0-9]{8}$/iu },
379+
{ ...baseIssue, requirement: /^[a-fA-F0-9]{8}$/u },
380380
['', ' ', '12345', '35cer956', 'fbe550055d737a0cd3a4325df5dfdc79']
381381
);
382382
});
383383

384384
test('for invalid adler32 hashes', () => {
385385
expectActionIssue(
386386
hash(['adler32'], 'message'),
387-
{ ...baseIssue, requirement: /^[a-f0-9]{8}$/iu },
387+
{ ...baseIssue, requirement: /^[a-fA-F0-9]{8}$/u },
388388
['', ' ', '12345', '045dx1c1', '16360e57b209fb9d07f725aa796789ac']
389389
);
390390
});
391391

392392
test('for invalid md5 and adler32 hashes', () => {
393393
expectActionIssue(
394394
hash(['adler32', 'md5'], 'message'),
395-
{ ...baseIssue, requirement: /^[a-f0-9]{8}$|^[a-f0-9]{32}$/iu },
395+
{ ...baseIssue, requirement: /^[a-fA-F0-9]{8}$|^[a-fA-F0-9]{32}$/u },
396396
[
397397
'',
398398
' ',
@@ -410,7 +410,7 @@ describe('hash', () => {
410410
hash(['crc32', 'ripemd128', 'sha1'], 'message'),
411411
{
412412
...baseIssue,
413-
requirement: /^[a-f0-9]{8}$|^[a-f0-9]{32}$|^[a-f0-9]{40}$/iu,
413+
requirement: /^[a-fA-F0-9]{8}$|^[a-fA-F0-9]{32}$|^[a-fA-F0-9]{40}$/u,
414414
},
415415
[
416416
'',

library/src/actions/hash/hash.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,8 @@ export function hash(
124124
expects: null,
125125
async: false,
126126
requirement: RegExp(
127-
types.map((type) => `^[a-f0-9]{${HASH_LENGTHS[type]}}$`).join('|'),
128-
'iu'
127+
types.map((type) => `^[a-fA-F0-9]{${HASH_LENGTHS[type]}}$`).join('|'),
128+
'u'
129129
),
130130
message,
131131
'~run'(dataset, config) {

library/src/regex.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
/**
22
* [Base64](https://en.wikipedia.org/wiki/Base64) regex.
3+
*
4+
* Hint: We decided against the `i` flag for better JSON Schema compatibility.
35
*/
46
export const BASE64_REGEX: RegExp =
5-
/^(?:[\da-z+/]{4})*(?:[\da-z+/]{2}==|[\da-z+/]{3}=)?$/iu;
7+
/^(?:[\da-zA-Z+/]{4})*(?:[\da-zA-Z+/]{2}==|[\da-zA-Z+/]{3}=)?$/u;
68

79
/**
810
* [BIC](https://en.wikipedia.org/wiki/ISO_9362) regex.
@@ -37,9 +39,12 @@ export const DOMAIN_REGEX: RegExp =
3739

3840
/**
3941
* [Email address](https://en.wikipedia.org/wiki/Email_address) regex.
42+
*
43+
* Hint: We decided against the `i` flag for better JSON Schema compatibility.
4044
*/
4145
export const EMAIL_REGEX: RegExp =
42-
/^[\w+-]+(?:\.[\w+-]+)*@[\da-z]+(?:[.-][\da-z]+)*\.[a-z]{2,}$/iu;
46+
// eslint-disable-next-line redos-detector/no-unsafe-regex -- false positive
47+
/^[\w+-]+(?:\.[\w+-]+)*@[\da-zA-Z]+(?:[.-][\da-zA-Z]+)*\.[a-zA-Z]{2,}$/u;
4348

4449
/**
4550
* Emoji regex from [emoji-regex-xs](https://github.com/slevithan/emoji-regex-xs) v1.0.0 (MIT license).
@@ -80,15 +85,21 @@ export const IPV4_REGEX: RegExp =
8085

8186
/**
8287
* [IPv6](https://en.wikipedia.org/wiki/IPv6) regex.
88+
*
89+
* Hint: We decided against the `i` flag for better JSON Schema compatibility.
8390
*/
8491
export const IPV6_REGEX: RegExp =
85-
/^(?:(?:[\da-f]{1,4}:){7}[\da-f]{1,4}|(?:[\da-f]{1,4}:){1,7}:|(?:[\da-f]{1,4}:){1,6}:[\da-f]{1,4}|(?:[\da-f]{1,4}:){1,5}(?::[\da-f]{1,4}){1,2}|(?:[\da-f]{1,4}:){1,4}(?::[\da-f]{1,4}){1,3}|(?:[\da-f]{1,4}:){1,3}(?::[\da-f]{1,4}){1,4}|(?:[\da-f]{1,4}:){1,2}(?::[\da-f]{1,4}){1,5}|[\da-f]{1,4}:(?::[\da-f]{1,4}){1,6}|:(?:(?::[\da-f]{1,4}){1,7}|:)|fe80:(?::[\da-f]{0,4}){0,4}%[\da-z]+|::(?:f{4}(?::0{1,4})?:)?(?:(?:25[0-5]|(?:2[0-4]|1?\d)?\d)\.){3}(?:25[0-5]|(?:2[0-4]|1?\d)?\d)|(?:[\da-f]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1?\d)?\d)\.){3}(?:25[0-5]|(?:2[0-4]|1?\d)?\d))$/iu;
92+
// eslint-disable-next-line redos-detector/no-unsafe-regex -- false positive
93+
/^(?:(?:[\da-fA-F]{1,4}:){7}[\da-fA-F]{1,4}|(?:[\da-fA-F]{1,4}:){1,7}:|(?:[\da-fA-F]{1,4}:){1,6}:[\da-fA-F]{1,4}|(?:[\da-fA-F]{1,4}:){1,5}(?::[\da-fA-F]{1,4}){1,2}|(?:[\da-fA-F]{1,4}:){1,4}(?::[\da-fA-F]{1,4}){1,3}|(?:[\da-fA-F]{1,4}:){1,3}(?::[\da-fA-F]{1,4}){1,4}|(?:[\da-fA-F]{1,4}:){1,2}(?::[\da-fA-F]{1,4}){1,5}|[\da-fA-F]{1,4}:(?::[\da-fA-F]{1,4}){1,6}|:(?:(?::[\da-fA-F]{1,4}){1,7}|:)|[Ff][Ee]80:(?::[\da-fA-F]{0,4}){0,4}%[\da-zA-Z]+|::(?:[Ff]{4}(?::0{1,4})?:)?(?:(?:25[0-5]|(?:2[0-4]|1?\d)?\d)\.){3}(?:25[0-5]|(?:2[0-4]|1?\d)?\d)|(?:[\da-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1?\d)?\d)\.){3}(?:25[0-5]|(?:2[0-4]|1?\d)?\d))$/u;
8694

8795
/**
8896
* [IP](https://en.wikipedia.org/wiki/IP_address) regex.
97+
*
98+
* Hint: We decided against the `i` flag for better JSON Schema compatibility.
8999
*/
90100
export const IP_REGEX: RegExp =
91-
/^(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])(?:\.(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])){3}$|^(?:(?:[\da-f]{1,4}:){7}[\da-f]{1,4}|(?:[\da-f]{1,4}:){1,7}:|(?:[\da-f]{1,4}:){1,6}:[\da-f]{1,4}|(?:[\da-f]{1,4}:){1,5}(?::[\da-f]{1,4}){1,2}|(?:[\da-f]{1,4}:){1,4}(?::[\da-f]{1,4}){1,3}|(?:[\da-f]{1,4}:){1,3}(?::[\da-f]{1,4}){1,4}|(?:[\da-f]{1,4}:){1,2}(?::[\da-f]{1,4}){1,5}|[\da-f]{1,4}:(?::[\da-f]{1,4}){1,6}|:(?:(?::[\da-f]{1,4}){1,7}|:)|fe80:(?::[\da-f]{0,4}){0,4}%[\da-z]+|::(?:f{4}(?::0{1,4})?:)?(?:(?:25[0-5]|(?:2[0-4]|1?\d)?\d)\.){3}(?:25[0-5]|(?:2[0-4]|1?\d)?\d)|(?:[\da-f]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1?\d)?\d)\.){3}(?:25[0-5]|(?:2[0-4]|1?\d)?\d))$/iu;
101+
// eslint-disable-next-line redos-detector/no-unsafe-regex -- false positive
102+
/^(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])(?:\.(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])){3}$|^(?:(?:[\da-fA-F]{1,4}:){7}[\da-fA-F]{1,4}|(?:[\da-fA-F]{1,4}:){1,7}:|(?:[\da-fA-F]{1,4}:){1,6}:[\da-fA-F]{1,4}|(?:[\da-fA-F]{1,4}:){1,5}(?::[\da-fA-F]{1,4}){1,2}|(?:[\da-fA-F]{1,4}:){1,4}(?::[\da-fA-F]{1,4}){1,3}|(?:[\da-fA-F]{1,4}:){1,3}(?::[\da-fA-F]{1,4}){1,4}|(?:[\da-fA-F]{1,4}:){1,2}(?::[\da-fA-F]{1,4}){1,5}|[\da-fA-F]{1,4}:(?::[\da-fA-F]{1,4}){1,6}|:(?:(?::[\da-fA-F]{1,4}){1,7}|:)|[Ff][Ee]80:(?::[\da-fA-F]{0,4}){0,4}%[\da-zA-Z]+|::(?:[Ff]{4}(?::0{1,4})?:)?(?:(?:25[0-5]|(?:2[0-4]|1?\d)?\d)\.){3}(?:25[0-5]|(?:2[0-4]|1?\d)?\d)|(?:[\da-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1?\d)?\d)\.){3}(?:25[0-5]|(?:2[0-4]|1?\d)?\d))$/u;
92103

93104
/**
94105
* [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) date regex.
@@ -144,21 +155,27 @@ export const ISRC_REGEX: RegExp =
144155

145156
/**
146157
* [MAC](https://en.wikipedia.org/wiki/MAC_address) 48 bit regex.
158+
*
159+
* Hint: We decided against the `i` flag for better JSON Schema compatibility.
147160
*/
148161
export const MAC48_REGEX: RegExp =
149-
/^(?:[\da-f]{2}:){5}[\da-f]{2}$|^(?:[\da-f]{2}-){5}[\da-f]{2}$|^(?:[\da-f]{4}\.){2}[\da-f]{4}$/iu;
162+
/^(?:[\da-fA-F]{2}:){5}[\da-fA-F]{2}$|^(?:[\da-fA-F]{2}-){5}[\da-fA-F]{2}$|^(?:[\da-fA-F]{4}\.){2}[\da-fA-F]{4}$/u;
150163

151164
/**
152165
* [MAC](https://en.wikipedia.org/wiki/MAC_address) 64 bit regex.
166+
*
167+
* Hint: We decided against the `i` flag for better JSON Schema compatibility.
153168
*/
154169
export const MAC64_REGEX: RegExp =
155-
/^(?:[\da-f]{2}:){7}[\da-f]{2}$|^(?:[\da-f]{2}-){7}[\da-f]{2}$|^(?:[\da-f]{4}\.){3}[\da-f]{4}$|^(?:[\da-f]{4}:){3}[\da-f]{4}$/iu;
170+
/^(?:[\da-fA-F]{2}:){7}[\da-fA-F]{2}$|^(?:[\da-fA-F]{2}-){7}[\da-fA-F]{2}$|^(?:[\da-fA-F]{4}\.){3}[\da-fA-F]{4}$|^(?:[\da-fA-F]{4}:){3}[\da-fA-F]{4}$/u;
156171

157172
/**
158173
* [MAC](https://en.wikipedia.org/wiki/MAC_address) regex.
174+
*
175+
* Hint: We decided against the `i` flag for better JSON Schema compatibility.
159176
*/
160177
export const MAC_REGEX: RegExp =
161-
/^(?:[\da-f]{2}:){5}[\da-f]{2}$|^(?:[\da-f]{2}-){5}[\da-f]{2}$|^(?:[\da-f]{4}\.){2}[\da-f]{4}$|^(?:[\da-f]{2}:){7}[\da-f]{2}$|^(?:[\da-f]{2}-){7}[\da-f]{2}$|^(?:[\da-f]{4}\.){3}[\da-f]{4}$|^(?:[\da-f]{4}:){3}[\da-f]{4}$/iu;
178+
/^(?:[\da-fA-F]{2}:){5}[\da-fA-F]{2}$|^(?:[\da-fA-F]{2}-){5}[\da-fA-F]{2}$|^(?:[\da-fA-F]{4}\.){2}[\da-fA-F]{4}$|^(?:[\da-fA-F]{2}:){7}[\da-fA-F]{2}$|^(?:[\da-fA-F]{2}-){7}[\da-fA-F]{2}$|^(?:[\da-fA-F]{4}\.){3}[\da-fA-F]{4}$|^(?:[\da-fA-F]{4}:){3}[\da-fA-F]{4}$/u;
162179

163180
/**
164181
* [Nano ID](https://github.com/ai/nanoid) regex.
@@ -193,6 +210,8 @@ export const ULID_REGEX: RegExp = /^[\da-hjkmnp-tv-zA-HJKMNP-TV-Z]{26}$/u;
193210

194211
/**
195212
* [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) regex.
213+
*
214+
* Hint: We decided against the `i` flag for better JSON Schema compatibility.
196215
*/
197216
export const UUID_REGEX: RegExp =
198-
/^[\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12}$/iu;
217+
/^[\da-fA-F]{8}(?:-[\da-fA-F]{4}){3}-[\da-fA-F]{12}$/u;

packages/to-json-schema/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
All notable changes to the library will be documented in this file.
44

5+
## vX.X.X (Month DD, YYYY)
6+
7+
- Add support for `never` schema (pull request #1430)
8+
- Add support for `endsWith`, `gtValue`, `hash`, `includes`, `isoTimeSecond`, `isoWeek`, `isrc`, `ltValue`, `mac`, `mac48`, `mac64`, `notValue`, `notValues`, `rfcEmail`, `safeInteger`, `slug`, `startsWith` and `values` actions (pull request #1430)
9+
- Add JSON compatibility validation for the requirements of `value`, `values`, `notValue` and `notValues` actions (pull request #1430)
10+
- Add inferred `type` for `enum` and `picklist` schemas (pull request #1430)
11+
- Change Valibot peer dependency to v1.3.0
12+
513
## v1.5.0 (December 11, 2025)
614

715
- Add support for JSON Schema draft-2020-12 and OpenAPI 3.0 Schema Object format

0 commit comments

Comments
 (0)