@jarenjs/formats 0.8.3 → 0.9.2

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.
@@ -0,0 +1,373 @@
1
+ //@ts-check
2
+
3
+ import {
4
+ isStringType,
5
+ getInclusiveExclusiveBounds,
6
+ } from '@jarenjs/core';
7
+
8
+ import {
9
+ isDateType,
10
+ getDateTypeOfDateTimeRFC3339,
11
+ getDateTypeOfDateOnlyRFC3339,
12
+ getDateTypeOfTimeOnlyRFC3339,
13
+ isValidDuration,
14
+ isValidISODateTime,
15
+ isValidISOTime,
16
+ getDateTypeOfISODateTime,
17
+ getDateTypeOfISOTime,
18
+ } from '@jarenjs/core/dates';
19
+
20
+ /**
21
+ * @typedef {{format?: string, formatMinimum?: string, formatExclusiveMinimum?: string, formatMaximum?: string, formatExclusiveMaximum?: string}} JSONSchema
22
+ * @typedef {{
23
+ * options: {skipErrors: boolean},
24
+ * createErrorHandler: (expected: any, key: string, ...details: any[]) => (data: any, dataPath?: string) => boolean
25
+ * }} ValidationObject
26
+ */
27
+
28
+ /**
29
+ * Compiles a format minimum validator function for date/time types.
30
+ * Supports both inclusive (formatMinimum) and exclusive (formatExclusiveMinimum) bounds.
31
+ *
32
+ * @param {(value: string) => Date | undefined} parseType - Function to parse string into Date
33
+ * @param {ValidationObject} schemaObj - The validation object for error handling and options
34
+ * @param {JSONSchema} jsonSchema - The JSON schema containing format constraints
35
+ * @returns {((date: Date, dataPath?: string) => boolean) | undefined} A validator function or undefined if no minimum constraint
36
+ */
37
+ function compileFormatMinimumByType(parseType, schemaObj, jsonSchema) {
38
+ const [min, emin] = getInclusiveExclusiveBounds(
39
+ parseType,
40
+ jsonSchema.formatMinimum,
41
+ jsonSchema.formatExclusiveMinimum,
42
+ );
43
+
44
+ if (emin != null) {
45
+ const addError = schemaObj.createErrorHandler(emin, 'formatExclusiveMinimum');
46
+
47
+ return function isFormatExclusiveMinimum(date, dataPath) {
48
+ return date > emin
49
+ || addError(date, dataPath);
50
+ };
51
+ }
52
+ else if (min) {
53
+ const addError = schemaObj.createErrorHandler(min, 'formatMinimum');
54
+
55
+ return function isFormatMinimum(date, dataPath) {
56
+ return date >= min
57
+ || addError(date, dataPath);
58
+ };
59
+ }
60
+
61
+ return undefined;
62
+ }
63
+
64
+ /**
65
+ * Compiles a format maximum validator function for date/time types.
66
+ * Supports both inclusive (formatMaximum) and exclusive (formatExclusiveMaximum) bounds.
67
+ *
68
+ * @param {(value: string) => Date | undefined} parseType - Function to parse string into Date
69
+ * @param {ValidationObject} schemaObj - The validation object for error handling and options
70
+ * @param {JSONSchema} jsonSchema - The JSON schema containing format constraints
71
+ * @returns {((date: Date, dataPath?: string) => boolean) | undefined} A validator function or undefined if no maximum constraint
72
+ */
73
+ function compileFormatMaximumByType(parseType, schemaObj, jsonSchema) {
74
+ const [max, emax] = getInclusiveExclusiveBounds(
75
+ parseType,
76
+ jsonSchema.formatMaximum,
77
+ jsonSchema.formatExclusiveMaximum,
78
+ );
79
+
80
+ if (emax != null) {
81
+ const addError = schemaObj.createErrorHandler(emax, 'formatExclusiveMaximum');
82
+
83
+ return function isFormatExclusiveMaximum(date, dataPath) {
84
+ return date < emax
85
+ || addError(date, dataPath);
86
+ };
87
+ }
88
+ else if (max) {
89
+ const addError = schemaObj.createErrorHandler(max, 'formatMaximum');
90
+
91
+ return function isFormatMaximum(date, dataPath) {
92
+ return date <= max
93
+ || addError(date, dataPath);
94
+ };
95
+ }
96
+
97
+ return undefined;
98
+ }
99
+
100
+ /**
101
+ * Creates a date/time format compiler with range validation support.
102
+ * Combines format validation with optional minimum and maximum bounds.
103
+ *
104
+ * @param {string} name - The name of the format (e.g., 'date-time', 'date', 'time')
105
+ * @param {(value: string) => Date | undefined} parseType - Function to parse string into Date
106
+ * @param {ValidationObject} schemaObj - The validation object for error handling and options
107
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
108
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
109
+ * @example
110
+ * // Basic format validation
111
+ * compileFormatByType('date', getDateTypeOfDateOnlyRFC3339, schemaObj, { format: 'date' })('2024-01-15'); // true
112
+ *
113
+ * // With range constraints
114
+ * compileFormatByType('date-time', getDateTypeOfDateTimeRFC3339, schemaObj, {
115
+ * format: 'date-time',
116
+ * formatMinimum: '2024-01-01T00:00:00Z'
117
+ * })('2024-06-15T12:00:00Z'); // true
118
+ */
119
+ function compileFormatByType(name, parseType, schemaObj, jsonSchema) {
120
+ if (jsonSchema.format !== name)
121
+ throw new Error('ERROR: This should not happen!');
122
+
123
+ const addError = schemaObj.createErrorHandler(jsonSchema.format, 'format');
124
+
125
+ const validateMin = compileFormatMinimumByType(
126
+ parseType,
127
+ schemaObj,
128
+ jsonSchema,
129
+ );
130
+
131
+ const validateMax = compileFormatMaximumByType(
132
+ parseType,
133
+ schemaObj,
134
+ jsonSchema,
135
+ );
136
+
137
+ if (validateMin != null && validateMax != null) {
138
+ return function validateFormatBetween(data, dataPath) {
139
+ if (isStringType(data)) {
140
+ const date = parseType(data);
141
+ return date == null
142
+ ? addError(data, dataPath)
143
+ : validateMin(date, dataPath)
144
+ && validateMax(date, dataPath);
145
+ }
146
+ else if (isDateType(data))
147
+ // @ts-ignore
148
+ return validateMin(data, dataPath)
149
+ // @ts-ignore
150
+ && validateMax(data, dataPath);
151
+ else
152
+ return true;
153
+ };
154
+ }
155
+ if (validateMin != null) {
156
+ return function validateFormatMinimum(data, dataPath) {
157
+ if (isStringType(data)) {
158
+ const date = parseType(data);
159
+ return date == null
160
+ ? addError(data, dataPath)
161
+ : validateMin(date, dataPath);
162
+ }
163
+ else if (isDateType(data))
164
+ // @ts-ignore
165
+ return validateMin(data, dataPath);
166
+ else
167
+ return true;
168
+ };
169
+ }
170
+ if (validateMax != null) {
171
+ return function validateFormatMaximum(data, dataPath) {
172
+ if (isStringType(data)) {
173
+ const date = parseType(data);
174
+ return date == null
175
+ ? addError(data, dataPath)
176
+ : validateMax(date);
177
+ }
178
+ else if (isDateType(data))
179
+ // @ts-ignore
180
+ return validateMax(data, dataPath);
181
+ else
182
+ return true;
183
+ };
184
+ }
185
+
186
+ return function validateDateTime(data, dataPath) {
187
+ if (isStringType(data)) {
188
+ const date = parseType(data);
189
+ return date == null
190
+ ? addError(data, dataPath)
191
+ : true;
192
+ }
193
+ else
194
+ return true;
195
+ };
196
+ }
197
+
198
+ // =============================================================================
199
+ // Date-Time Format Compilers
200
+ // =============================================================================
201
+
202
+ /**
203
+ * Compiles a validator for the 'date-time' format.
204
+ * Validates date-time strings per RFC 3339 (ISO 8601 profile).
205
+ * Supports formatMinimum, formatMaximum, formatExclusiveMinimum, formatExclusiveMaximum.
206
+ *
207
+ * @param {ValidationObject} schemaObj - The validation object for error handling and options
208
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
209
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
210
+ * @example
211
+ * compileDateTimeFormat(schemaObj, { format: 'date-time' })('2024-01-15T12:30:00Z'); // true
212
+ * compileDateTimeFormat(schemaObj, { format: 'date-time' })('2024-01-15T12:30:00+01:00'); // true
213
+ * compileDateTimeFormat(schemaObj, { format: 'date-time' })('invalid'); // false (with error)
214
+ */
215
+ export function compileDateTimeFormat(schemaObj, jsonSchema) {
216
+ return compileFormatByType(
217
+ 'date-time',
218
+ getDateTypeOfDateTimeRFC3339,
219
+ schemaObj,
220
+ jsonSchema,
221
+ );
222
+ }
223
+
224
+ /**
225
+ * Compiles a validator for the 'date' format.
226
+ * Validates date-only strings (YYYY-MM-DD) per RFC 3339.
227
+ * Supports formatMinimum, formatMaximum, formatExclusiveMinimum, formatExclusiveMaximum.
228
+ *
229
+ * @param {ValidationObject} schemaObj - The validation object for error handling and options
230
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
231
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
232
+ * @example
233
+ * compileDateOnlyFormat(schemaObj, { format: 'date' })('2024-01-15'); // true
234
+ * compileDateOnlyFormat(schemaObj, { format: 'date' })('2024-13-45'); // false (with error)
235
+ */
236
+ export function compileDateOnlyFormat(schemaObj, jsonSchema) {
237
+ return compileFormatByType(
238
+ 'date',
239
+ getDateTypeOfDateOnlyRFC3339,
240
+ schemaObj,
241
+ jsonSchema,
242
+ );
243
+ }
244
+
245
+ /**
246
+ * Compiles a validator for the 'time' format.
247
+ * Validates time-only strings (HH:MM:SS or HH:MM:SS.sss) per RFC 3339.
248
+ * Supports formatMinimum, formatMaximum, formatExclusiveMinimum, formatExclusiveMaximum.
249
+ *
250
+ * @param {ValidationObject} schemaObj - The validation object for error handling and options
251
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
252
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
253
+ * @example
254
+ * compileTimeOnlyFormat(schemaObj, { format: 'time' })('12:30:00'); // true
255
+ * compileTimeOnlyFormat(schemaObj, { format: 'time' })('12:30:00.123'); // true
256
+ * compileTimeOnlyFormat(schemaObj, { format: 'time' })('25:00:00'); // false (with error)
257
+ */
258
+ export function compileTimeOnlyFormat(schemaObj, jsonSchema) {
259
+ return compileFormatByType(
260
+ 'time',
261
+ getDateTypeOfTimeOnlyRFC3339,
262
+ schemaObj,
263
+ jsonSchema,
264
+ );
265
+ }
266
+
267
+ // =============================================================================
268
+ // Duration Format Compiler (RFC 3339)
269
+ // =============================================================================
270
+
271
+ /**
272
+ * Compiles a validator for the 'duration' format.
273
+ * Validates duration strings per RFC 3339.
274
+ * Format: P[n]Y[n]M[n]DT[n]H[n]M[n]S or P[n]W
275
+ * Examples: P1Y2M3DT4H5M6S, P1W, PT1H
276
+ *
277
+ * @param {ValidationObject} schemaObj - The validation object for error handling and options
278
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
279
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
280
+ * @example
281
+ * compileDurationFormat(schemaObj, { format: 'duration' })('P1Y2M3DT4H5M6S'); // true
282
+ * compileDurationFormat(schemaObj, { format: 'duration' })('P1W'); // true
283
+ * compileDurationFormat(schemaObj, { format: 'duration' })('PT1H30M'); // true
284
+ * compileDurationFormat(schemaObj, { format: 'duration' })('P'); // false (with error)
285
+ */
286
+ export function compileDurationFormat(schemaObj, jsonSchema) {
287
+ if (jsonSchema.format !== 'duration')
288
+ throw new Error('ERROR: This should not happen!');
289
+
290
+ // when skipErrors is true, we don't need to create error objects
291
+ if (schemaObj.options.skipErrors) {
292
+ return function validateDurationFast(data, dataPath) {
293
+ return isStringType(data)
294
+ ? isValidDuration(data)
295
+ : true;
296
+ };
297
+ }
298
+
299
+ const addError = schemaObj.createErrorHandler('duration', 'format');
300
+
301
+ return function validateDuration(data, dataPath) {
302
+ return isStringType(data)
303
+ ? isValidDuration(data) || addError(data, dataPath)
304
+ : true;
305
+ };
306
+ }
307
+
308
+ // =============================================================================
309
+ // ISO Date-Time and ISO Time Format Compilers
310
+ // =============================================================================
311
+
312
+ /**
313
+ * Compiles a validator for the 'iso-date-time' format.
314
+ * Validates ISO 8601 date-time strings with optional timezone.
315
+ * Unlike RFC 3339 date-time, the timezone is optional.
316
+ * Supports formatMinimum, formatMaximum, formatExclusiveMinimum, formatExclusiveMaximum.
317
+ *
318
+ * @param {ValidationObject} schemaObj - The validation object for error handling and options
319
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
320
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
321
+ * @example
322
+ * compileISODateTimeFormat(schemaObj, { format: 'iso-date-time' })('2024-01-15T12:30:00Z'); // true
323
+ * compileISODateTimeFormat(schemaObj, { format: 'iso-date-time' })('2024-01-15T12:30:00+01:00'); // true
324
+ * compileISODateTimeFormat(schemaObj, { format: 'iso-date-time' })('2024-01-15T12:30:00'); // true (no timezone)
325
+ * compileISODateTimeFormat(schemaObj, { format: 'iso-date-time' })('2024-13-15T12:30:00'); // false (with error)
326
+ */
327
+ export function compileISODateTimeFormat(schemaObj, jsonSchema) {
328
+ return compileFormatByType(
329
+ 'iso-date-time',
330
+ getDateTypeOfISODateTime,
331
+ schemaObj,
332
+ jsonSchema,
333
+ );
334
+ }
335
+
336
+ /**
337
+ * Compiles a validator for the 'iso-time' format.
338
+ * Validates ISO 8601 time strings with optional timezone.
339
+ * Unlike RFC 3339 time, the timezone is optional.
340
+ * Supports formatMinimum, formatMaximum, formatExclusiveMinimum, formatExclusiveMaximum.
341
+ *
342
+ * @param {ValidationObject} schemaObj - The validation object for error handling and options
343
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
344
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
345
+ * @example
346
+ * compileISOTimeFormat(schemaObj, { format: 'iso-time' })('12:30:00Z'); // true
347
+ * compileISOTimeFormat(schemaObj, { format: 'iso-time' })('12:30:00+01:00'); // true
348
+ * compileISOTimeFormat(schemaObj, { format: 'iso-time' })('12:30:00'); // true (no timezone)
349
+ * compileISOTimeFormat(schemaObj, { format: 'iso-time' })('25:00:00'); // false (with error)
350
+ */
351
+ export function compileISOTimeFormat(schemaObj, jsonSchema) {
352
+ return compileFormatByType(
353
+ 'iso-time',
354
+ getDateTypeOfISOTime,
355
+ schemaObj,
356
+ jsonSchema,
357
+ );
358
+ }
359
+
360
+ /**
361
+ * Object mapping date/time format names to their compiler functions.
362
+ * Used for backward compatibility and aggregate imports.
363
+ *
364
+ * @type {Record<string, (schemaObj: ValidationObject, jsonSchema: JSONSchema) => (data: unknown, dataPath?: string) => boolean>}
365
+ */
366
+ export const formatValidators = {
367
+ 'date-time': compileDateTimeFormat,
368
+ date: compileDateOnlyFormat,
369
+ time: compileTimeOnlyFormat,
370
+ duration: compileDurationFormat,
371
+ 'iso-date-time': compileISODateTimeFormat,
372
+ 'iso-time': compileISOTimeFormat,
373
+ };
package/src/index.js ADDED
@@ -0,0 +1,31 @@
1
+ //@ts-check
2
+
3
+ /**
4
+ * A compiled format validator: tests one instance value against the
5
+ * format. `dataPath` is the value's RFC 6901 location, used in error
6
+ * reporting when error collection is enabled.
7
+ * @typedef {(data: unknown, dataPath?: string) => boolean} FormatValidator
8
+ */
9
+
10
+ /**
11
+ * A format compiler as consumed by the JarenValidator's addFormat and
12
+ * addFormats methods of @jarenjs/validate: called once per schema
13
+ * location at compile time with the compiling validation object and the
14
+ * schema declaring the format, it returns the FormatValidator invoked for
15
+ * each instance value. Typed structurally so @jarenjs/formats stays free
16
+ * of a dependency on @jarenjs/validate.
17
+ * @typedef {(schemaObj: import('./string.js').ValidationObject, jsonSchema: import('./string.js').JSONSchema) => FormatValidator} FormatCompiler
18
+ */
19
+
20
+ export { formatValidators as dateTimeFormats } from './datetime.js';
21
+ export { formatValidators as stringFormats } from './string.js';
22
+ export { formatValidators as numberFormats } from './number.js';
23
+ export { formatValidators as jsonFormats } from './json.js';
24
+
25
+ export {
26
+ formatTesters,
27
+ stringFormatTesters,
28
+ jsonFormatTesters,
29
+ dateTimeFormatTesters,
30
+ numberFormatTesters,
31
+ } from './testers.js';
package/src/json.js ADDED
@@ -0,0 +1,90 @@
1
+ //@ts-check
2
+
3
+ // The name -> predicate bindings live in ONE place: testers.js. This
4
+ // module only wraps them in the validator's compiler contract.
5
+ import { jsonFormatTesters } from './testers.js';
6
+
7
+ import { createStringFormatCompiler } from './string.js';
8
+
9
+ /**
10
+ * @typedef {{format?: string, formatMinimum?: string, formatExclusiveMinimum?: string, formatMaximum?: string, formatExclusiveMaximum?: string}} JSONSchema
11
+ * @typedef {{
12
+ * options: {skipErrors: boolean},
13
+ * createErrorHandler: (expected: any, key: string, ...details: any[]) => (data: any, dataPath?: string) => boolean
14
+ * }} ValidationObject
15
+ */
16
+
17
+ // =============================================================================
18
+ // JSON Pointer Format Compilers
19
+ // =============================================================================
20
+
21
+ /**
22
+ * Compiles a validator for the 'json-pointer' format.
23
+ * Validates JSON Pointer strings per RFC 6901.
24
+ *
25
+ * @param {ValidationObject} schemaObj - The validation JSONSchema for error handling and options
26
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
27
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
28
+ */
29
+ export const compileJsonPointerFormat = createStringFormatCompiler('json-pointer', jsonFormatTesters['json-pointer']);
30
+
31
+ /**
32
+ * Compiles a validator for the 'json-pointer-uri-fragment' format.
33
+ * Validates JSON Pointer URI fragment strings (e.g., #/foo/bar).
34
+ *
35
+ * @param {ValidationObject} schemaObj - The validation JSONSchema for error handling and options
36
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
37
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
38
+ */
39
+ export const compileJsonPointerUriFragmentFormat = createStringFormatCompiler('json-pointer-uri-fragment', jsonFormatTesters['json-pointer-uri-fragment']);
40
+
41
+ /**
42
+ * Compiles a validator for the 'relative-json-pointer' format.
43
+ * Validates Relative JSON Pointer strings.
44
+ *
45
+ * @param {ValidationObject} schemaObj - The validation JSONSchema for error handling and options
46
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
47
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
48
+ */
49
+ export const compileRelativeJsonPointerFormat = createStringFormatCompiler('relative-json-pointer', jsonFormatTesters['relative-json-pointer']);
50
+
51
+ // =============================================================================
52
+ // JSONPath Format Compiler (RFC 9535)
53
+ // =============================================================================
54
+
55
+ /**
56
+ * Compiles a validator for the 'json-path' format.
57
+ * Validates JSONPath query expressions strictly against the complete
58
+ * RFC 9535 grammar, using the parser of the JSONPath compiler in
59
+ * `@jarenjs/json`. This includes the well-typedness rules for
60
+ * function expressions, so queries like `$[?length(@)]` or comparisons
61
+ * against non-singular queries are rejected.
62
+ *
63
+ * @param {ValidationObject} schemaObj - The validation JSONSchema for error handling and options
64
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
65
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
66
+ * @example
67
+ * compileJsonPathFormat(schemaObj, { format: 'json-path' })('$.store.book[0].title'); // true
68
+ * compileJsonPathFormat(schemaObj, { format: 'json-path' })("$..book[?@.price < 10]"); // true
69
+ * compileJsonPathFormat(schemaObj, { format: 'json-path' })('@.name'); // false (queries start at $)
70
+ * compileJsonPathFormat(schemaObj, { format: 'json-path' })('$.foo '); // false (trailing whitespace)
71
+ */
72
+ export const compileJsonPathFormat = createStringFormatCompiler('json-path', jsonFormatTesters['json-path']);
73
+
74
+ // =============================================================================
75
+ // Aggregated Format Validators Object
76
+ // =============================================================================
77
+
78
+ /**
79
+ * Object mapping JSON-related format names to their compiler functions.
80
+ *
81
+ * @type {Record<string, (schemaObj: ValidationObject, jsonSchema: JSONSchema) => (data: unknown, dataPath?: string) => boolean>}
82
+ */
83
+ export const formatValidators = {
84
+ // JSON Pointer
85
+ 'json-pointer': compileJsonPointerFormat,
86
+ 'json-pointer-uri-fragment': compileJsonPointerUriFragmentFormat,
87
+ 'relative-json-pointer': compileRelativeJsonPointerFormat,
88
+ // JSONPath
89
+ 'json-path': compileJsonPathFormat,
90
+ };