@jarenjs/formats 0.8.4 → 0.34.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.
@@ -0,0 +1,422 @@
1
+ //@ts-check
2
+
3
+ import {
4
+ isStringType,
5
+ getInclusiveExclusiveBounds,
6
+ } from '@jarenjs/core';
7
+
8
+ import {
9
+ isDateType,
10
+ isDateTimeRFC3339,
11
+ isDateOnlyRFC3339,
12
+ isTimeOnlyRFC3339,
13
+ getDateTypeOfDateTimeRFC3339,
14
+ getDateTypeOfDateOnlyRFC3339,
15
+ getDateTypeOfTimeOnlyRFC3339,
16
+ getEpochOfDateTimeRFC3339,
17
+ getEpochOfDateOnlyRFC3339,
18
+ getEpochOfTimeOnlyRFC3339,
19
+ isValidDuration,
20
+ getDateTypeOfISODateTime,
21
+ getDateTypeOfISOTime,
22
+ getEpochOfISODateTime,
23
+ getEpochOfISOTime,
24
+ } from '@jarenjs/core/dates';
25
+
26
+ /**
27
+ * @typedef {{format?: string, formatMinimum?: string, formatExclusiveMinimum?: string, formatMaximum?: string, formatExclusiveMaximum?: string}} JSONSchema
28
+ * @typedef {{
29
+ * options: {skipErrors: boolean},
30
+ * createErrorHandler: (expected: any, key: string, ...details: any[]) => (data: any, dataPath?: string) => boolean
31
+ * }} ValidationObject
32
+ */
33
+
34
+ // The bound validators compare epoch milliseconds: the bound is folded
35
+ // to a number at compile time and the value arrives as either an epoch
36
+ // number (the string path, which then allocates nothing) or a raw Date
37
+ // instance (which the relational operators coerce numerically, keeping
38
+ // that door open). Only the error path materializes a Date, so the
39
+ // reported error value stays what it always was.
40
+ function asDateValue(value) {
41
+ return typeof value === 'number' ? new Date(value) : value;
42
+ }
43
+
44
+ /**
45
+ * Compiles a format minimum validator function for date/time types.
46
+ * Supports both inclusive (formatMinimum) and exclusive (formatExclusiveMinimum) bounds.
47
+ *
48
+ * @param {(value: string) => Date | undefined} parseType - Function to parse string into Date
49
+ * @param {ValidationObject} schemaObj - The validation object for error handling and options
50
+ * @param {JSONSchema} jsonSchema - The JSON schema containing format constraints
51
+ * @returns {((date: number | Date, dataPath?: string) => boolean) | undefined} A validator function or undefined if no minimum constraint
52
+ */
53
+ function compileFormatMinimumByType(parseType, schemaObj, jsonSchema) {
54
+ const [min, emin] = getInclusiveExclusiveBounds(
55
+ parseType,
56
+ jsonSchema.formatMinimum,
57
+ jsonSchema.formatExclusiveMinimum,
58
+ );
59
+
60
+ if (emin != null) {
61
+ const addError = schemaObj.createErrorHandler(emin, 'formatExclusiveMinimum');
62
+ const bound = emin.valueOf();
63
+
64
+ return function isFormatExclusiveMinimum(date, dataPath) {
65
+ return date > bound
66
+ || addError(asDateValue(date), dataPath);
67
+ };
68
+ }
69
+ else if (min) {
70
+ const addError = schemaObj.createErrorHandler(min, 'formatMinimum');
71
+ const bound = min.valueOf();
72
+
73
+ return function isFormatMinimum(date, dataPath) {
74
+ return date >= bound
75
+ || addError(asDateValue(date), dataPath);
76
+ };
77
+ }
78
+
79
+ return undefined;
80
+ }
81
+
82
+ /**
83
+ * Compiles a format maximum validator function for date/time types.
84
+ * Supports both inclusive (formatMaximum) and exclusive (formatExclusiveMaximum) bounds.
85
+ *
86
+ * @param {(value: string) => Date | undefined} parseType - Function to parse string into Date
87
+ * @param {ValidationObject} schemaObj - The validation object for error handling and options
88
+ * @param {JSONSchema} jsonSchema - The JSON schema containing format constraints
89
+ * @returns {((date: number | Date, dataPath?: string) => boolean) | undefined} A validator function or undefined if no maximum constraint
90
+ */
91
+ function compileFormatMaximumByType(parseType, schemaObj, jsonSchema) {
92
+ const [max, emax] = getInclusiveExclusiveBounds(
93
+ parseType,
94
+ jsonSchema.formatMaximum,
95
+ jsonSchema.formatExclusiveMaximum,
96
+ );
97
+
98
+ if (emax != null) {
99
+ const addError = schemaObj.createErrorHandler(emax, 'formatExclusiveMaximum');
100
+ const bound = emax.valueOf();
101
+
102
+ return function isFormatExclusiveMaximum(date, dataPath) {
103
+ return date < bound
104
+ || addError(asDateValue(date), dataPath);
105
+ };
106
+ }
107
+ else if (max) {
108
+ const addError = schemaObj.createErrorHandler(max, 'formatMaximum');
109
+ const bound = max.valueOf();
110
+
111
+ return function isFormatMaximum(date, dataPath) {
112
+ return date <= bound
113
+ || addError(asDateValue(date), dataPath);
114
+ };
115
+ }
116
+
117
+ return undefined;
118
+ }
119
+
120
+ /**
121
+ * Creates a date/time format compiler with range validation support.
122
+ * Combines format validation with optional minimum and maximum bounds.
123
+ *
124
+ * @param {string} name - The name of the format (e.g., 'date-time', 'date', 'time')
125
+ * @param {(value: string) => Date | undefined} parseType - Function to parse string into Date
126
+ * @param {ValidationObject} schemaObj - The validation object for error handling and options
127
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
128
+ * @param {(value: string) => boolean} [isType] - Boolean tester matching parseType's
129
+ * accepted grammar; used on the boundless path so no Date is constructed
130
+ * @param {(value: string) => number | undefined} [parseEpoch] - Epoch twin of
131
+ * parseType; used on the bounded path so string values compare as numbers
132
+ * without constructing a Date
133
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
134
+ * @example
135
+ * // Basic format validation
136
+ * compileFormatByType('date', getDateTypeOfDateOnlyRFC3339, schemaObj, { format: 'date' })('2024-01-15'); // true
137
+ *
138
+ * // With range constraints
139
+ * compileFormatByType('date-time', getDateTypeOfDateTimeRFC3339, schemaObj, {
140
+ * format: 'date-time',
141
+ * formatMinimum: '2024-01-01T00:00:00Z'
142
+ * })('2024-06-15T12:00:00Z'); // true
143
+ */
144
+ function compileFormatByType(name, parseType, schemaObj, jsonSchema, isType = undefined, parseEpoch = undefined) {
145
+ if (jsonSchema.format !== name)
146
+ throw new Error('ERROR: This should not happen!');
147
+
148
+ const addError = schemaObj.createErrorHandler(jsonSchema.format, 'format');
149
+
150
+ const validateMin = compileFormatMinimumByType(
151
+ parseType,
152
+ schemaObj,
153
+ jsonSchema,
154
+ );
155
+
156
+ const validateMax = compileFormatMaximumByType(
157
+ parseType,
158
+ schemaObj,
159
+ jsonSchema,
160
+ );
161
+
162
+ // the bounded paths parse string values with the epoch twin where one
163
+ // exists, so a passing validation allocates nothing
164
+ const parseValue = parseEpoch != null ? parseEpoch : parseType;
165
+
166
+ if (validateMin != null && validateMax != null) {
167
+ return function validateFormatBetween(data, dataPath) {
168
+ if (isStringType(data)) {
169
+ const date = parseValue(data);
170
+ return date == null
171
+ ? addError(data, dataPath)
172
+ : validateMin(date, dataPath)
173
+ && validateMax(date, dataPath);
174
+ }
175
+ else if (isDateType(data))
176
+ // @ts-ignore
177
+ return validateMin(data, dataPath)
178
+ // @ts-ignore
179
+ && validateMax(data, dataPath);
180
+ else
181
+ return true;
182
+ };
183
+ }
184
+ if (validateMin != null) {
185
+ return function validateFormatMinimum(data, dataPath) {
186
+ if (isStringType(data)) {
187
+ const date = parseValue(data);
188
+ return date == null
189
+ ? addError(data, dataPath)
190
+ : validateMin(date, dataPath);
191
+ }
192
+ else if (isDateType(data))
193
+ // @ts-ignore
194
+ return validateMin(data, dataPath);
195
+ else
196
+ return true;
197
+ };
198
+ }
199
+ if (validateMax != null) {
200
+ return function validateFormatMaximum(data, dataPath) {
201
+ if (isStringType(data)) {
202
+ const date = parseValue(data);
203
+ return date == null
204
+ ? addError(data, dataPath)
205
+ : validateMax(date, dataPath);
206
+ }
207
+ else if (isDateType(data))
208
+ // @ts-ignore
209
+ return validateMax(data, dataPath);
210
+ else
211
+ return true;
212
+ };
213
+ }
214
+
215
+ // Without bounds only the format assertion remains; the boolean tester
216
+ // avoids parsing the string into a Date that would be discarded.
217
+ if (isType != null) {
218
+ return function validateDateTimeFormatOnly(data, dataPath) {
219
+ return isStringType(data)
220
+ ? isType(data) || addError(data, dataPath)
221
+ : true;
222
+ };
223
+ }
224
+
225
+ return function validateDateTime(data, dataPath) {
226
+ if (isStringType(data)) {
227
+ const date = parseType(data);
228
+ return date == null
229
+ ? addError(data, dataPath)
230
+ : true;
231
+ }
232
+ else
233
+ return true;
234
+ };
235
+ }
236
+
237
+ // =============================================================================
238
+ // Date-Time Format Compilers
239
+ // =============================================================================
240
+
241
+ /**
242
+ * Compiles a validator for the 'date-time' format.
243
+ * Validates date-time strings per RFC 3339 (ISO 8601 profile).
244
+ * Supports formatMinimum, formatMaximum, formatExclusiveMinimum, formatExclusiveMaximum.
245
+ *
246
+ * @param {ValidationObject} schemaObj - The validation object for error handling and options
247
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
248
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
249
+ * @example
250
+ * compileDateTimeFormat(schemaObj, { format: 'date-time' })('2024-01-15T12:30:00Z'); // true
251
+ * compileDateTimeFormat(schemaObj, { format: 'date-time' })('2024-01-15T12:30:00+01:00'); // true
252
+ * compileDateTimeFormat(schemaObj, { format: 'date-time' })('invalid'); // false (with error)
253
+ */
254
+ export function compileDateTimeFormat(schemaObj, jsonSchema) {
255
+ return compileFormatByType(
256
+ 'date-time',
257
+ getDateTypeOfDateTimeRFC3339,
258
+ schemaObj,
259
+ jsonSchema,
260
+ isDateTimeRFC3339,
261
+ getEpochOfDateTimeRFC3339,
262
+ );
263
+ }
264
+
265
+ /**
266
+ * Compiles a validator for the 'date' format.
267
+ * Validates date-only strings (YYYY-MM-DD) per RFC 3339.
268
+ * Supports formatMinimum, formatMaximum, formatExclusiveMinimum, formatExclusiveMaximum.
269
+ *
270
+ * @param {ValidationObject} schemaObj - The validation object for error handling and options
271
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
272
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
273
+ * @example
274
+ * compileDateOnlyFormat(schemaObj, { format: 'date' })('2024-01-15'); // true
275
+ * compileDateOnlyFormat(schemaObj, { format: 'date' })('2024-13-45'); // false (with error)
276
+ */
277
+ export function compileDateOnlyFormat(schemaObj, jsonSchema) {
278
+ return compileFormatByType(
279
+ 'date',
280
+ getDateTypeOfDateOnlyRFC3339,
281
+ schemaObj,
282
+ jsonSchema,
283
+ isDateOnlyRFC3339,
284
+ getEpochOfDateOnlyRFC3339,
285
+ );
286
+ }
287
+
288
+ /**
289
+ * Compiles a validator for the 'time' format.
290
+ * Validates time-only strings (HH:MM:SS or HH:MM:SS.sss) per RFC 3339.
291
+ * Supports formatMinimum, formatMaximum, formatExclusiveMinimum, formatExclusiveMaximum.
292
+ *
293
+ * @param {ValidationObject} schemaObj - The validation object for error handling and options
294
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
295
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
296
+ * @example
297
+ * compileTimeOnlyFormat(schemaObj, { format: 'time' })('12:30:00'); // true
298
+ * compileTimeOnlyFormat(schemaObj, { format: 'time' })('12:30:00.123'); // true
299
+ * compileTimeOnlyFormat(schemaObj, { format: 'time' })('25:00:00'); // false (with error)
300
+ */
301
+ export function compileTimeOnlyFormat(schemaObj, jsonSchema) {
302
+ return compileFormatByType(
303
+ 'time',
304
+ getDateTypeOfTimeOnlyRFC3339,
305
+ schemaObj,
306
+ jsonSchema,
307
+ isTimeOnlyRFC3339,
308
+ getEpochOfTimeOnlyRFC3339,
309
+ );
310
+ }
311
+
312
+ // =============================================================================
313
+ // Duration Format Compiler (RFC 3339)
314
+ // =============================================================================
315
+
316
+ /**
317
+ * Compiles a validator for the 'duration' format.
318
+ * Validates duration strings per RFC 3339.
319
+ * Format: P[n]Y[n]M[n]DT[n]H[n]M[n]S or P[n]W
320
+ * Examples: P1Y2M3DT4H5M6S, P1W, PT1H
321
+ *
322
+ * @param {ValidationObject} schemaObj - The validation object for error handling and options
323
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
324
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
325
+ * @example
326
+ * compileDurationFormat(schemaObj, { format: 'duration' })('P1Y2M3DT4H5M6S'); // true
327
+ * compileDurationFormat(schemaObj, { format: 'duration' })('P1W'); // true
328
+ * compileDurationFormat(schemaObj, { format: 'duration' })('PT1H30M'); // true
329
+ * compileDurationFormat(schemaObj, { format: 'duration' })('P'); // false (with error)
330
+ */
331
+ export function compileDurationFormat(schemaObj, jsonSchema) {
332
+ if (jsonSchema.format !== 'duration')
333
+ throw new Error('ERROR: This should not happen!');
334
+
335
+ // when skipErrors is true, we don't need to create error objects
336
+ if (schemaObj.options.skipErrors) {
337
+ return function validateDurationFast(data, _dataPath) {
338
+ return isStringType(data)
339
+ ? isValidDuration(data)
340
+ : true;
341
+ };
342
+ }
343
+
344
+ const addError = schemaObj.createErrorHandler('duration', 'format');
345
+
346
+ return function validateDuration(data, dataPath) {
347
+ return isStringType(data)
348
+ ? isValidDuration(data) || addError(data, dataPath)
349
+ : true;
350
+ };
351
+ }
352
+
353
+ // =============================================================================
354
+ // ISO Date-Time and ISO Time Format Compilers
355
+ // =============================================================================
356
+
357
+ /**
358
+ * Compiles a validator for the 'iso-date-time' format.
359
+ * Validates ISO 8601 date-time strings with optional timezone.
360
+ * Unlike RFC 3339 date-time, the timezone is optional.
361
+ * Supports formatMinimum, formatMaximum, formatExclusiveMinimum, formatExclusiveMaximum.
362
+ *
363
+ * @param {ValidationObject} schemaObj - The validation object for error handling and options
364
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
365
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
366
+ * @example
367
+ * compileISODateTimeFormat(schemaObj, { format: 'iso-date-time' })('2024-01-15T12:30:00Z'); // true
368
+ * compileISODateTimeFormat(schemaObj, { format: 'iso-date-time' })('2024-01-15T12:30:00+01:00'); // true
369
+ * compileISODateTimeFormat(schemaObj, { format: 'iso-date-time' })('2024-01-15T12:30:00'); // true (no timezone)
370
+ * compileISODateTimeFormat(schemaObj, { format: 'iso-date-time' })('2024-13-15T12:30:00'); // false (with error)
371
+ */
372
+ export function compileISODateTimeFormat(schemaObj, jsonSchema) {
373
+ return compileFormatByType(
374
+ 'iso-date-time',
375
+ getDateTypeOfISODateTime,
376
+ schemaObj,
377
+ jsonSchema,
378
+ undefined,
379
+ getEpochOfISODateTime,
380
+ );
381
+ }
382
+
383
+ /**
384
+ * Compiles a validator for the 'iso-time' format.
385
+ * Validates ISO 8601 time strings with optional timezone.
386
+ * Unlike RFC 3339 time, the timezone is optional.
387
+ * Supports formatMinimum, formatMaximum, formatExclusiveMinimum, formatExclusiveMaximum.
388
+ *
389
+ * @param {ValidationObject} schemaObj - The validation object for error handling and options
390
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
391
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
392
+ * @example
393
+ * compileISOTimeFormat(schemaObj, { format: 'iso-time' })('12:30:00Z'); // true
394
+ * compileISOTimeFormat(schemaObj, { format: 'iso-time' })('12:30:00+01:00'); // true
395
+ * compileISOTimeFormat(schemaObj, { format: 'iso-time' })('12:30:00'); // true (no timezone)
396
+ * compileISOTimeFormat(schemaObj, { format: 'iso-time' })('25:00:00'); // false (with error)
397
+ */
398
+ export function compileISOTimeFormat(schemaObj, jsonSchema) {
399
+ return compileFormatByType(
400
+ 'iso-time',
401
+ getDateTypeOfISOTime,
402
+ schemaObj,
403
+ jsonSchema,
404
+ undefined,
405
+ getEpochOfISOTime,
406
+ );
407
+ }
408
+
409
+ /**
410
+ * Object mapping date/time format names to their compiler functions.
411
+ * Used for backward compatibility and aggregate imports.
412
+ *
413
+ * @type {Record<string, (schemaObj: ValidationObject, jsonSchema: JSONSchema) => (data: unknown, dataPath?: string) => boolean>}
414
+ */
415
+ export const formatValidators = {
416
+ 'date-time': compileDateTimeFormat,
417
+ date: compileDateOnlyFormat,
418
+ time: compileTimeOnlyFormat,
419
+ duration: compileDurationFormat,
420
+ 'iso-date-time': compileISODateTimeFormat,
421
+ 'iso-time': compileISOTimeFormat,
422
+ };
package/src/geo.js ADDED
@@ -0,0 +1,109 @@
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 { geoFormatTesters } 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
+ // Geospatial Format Compilers
19
+ // =============================================================================
20
+
21
+ /**
22
+ * Compiles a validator for the 'geohash' format.
23
+ * Validates geohash strings: any length, every character in the
24
+ * base-32 geohash alphabet.
25
+ *
26
+ * @param {ValidationObject} schemaObj - The validation object for error handling and options
27
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
28
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
29
+ * @example
30
+ * compileGeohashFormat(schemaObj, { format: 'geohash' })('u173z'); // true
31
+ * compileGeohashFormat(schemaObj, { format: 'geohash' })('u17a'); // false ('a' is not in the alphabet)
32
+ */
33
+ export const compileGeohashFormat = createStringFormatCompiler('geohash', geoFormatTesters['geohash']);
34
+
35
+ /**
36
+ * Compiles a validator for the 'wkt' format.
37
+ * Validates Well-Known Text geometry strings (ISO 19125 / OGC Simple
38
+ * Features): the seven tagged geometry types with optional Z/M/ZM
39
+ * modifiers, consistent coordinate counts, and closed polygon rings.
40
+ *
41
+ * @param {ValidationObject} schemaObj - The validation object for error handling and options
42
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
43
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
44
+ * @example
45
+ * compileWktFormat(schemaObj, { format: 'wkt' })('POINT (4.9041 52.3676)'); // true
46
+ * compileWktFormat(schemaObj, { format: 'wkt' })('POLYGON ((0 0, 4 0, 4 4, 1 1))'); // false (open ring)
47
+ */
48
+ export const compileWktFormat = createStringFormatCompiler('wkt', geoFormatTesters['wkt']);
49
+
50
+ /**
51
+ * Compiles a validator for the 'geojson' format. Unlike the string
52
+ * formats, this one applies to **objects**: any non-array object must be
53
+ * a structurally valid GeoJSON object (RFC 7946) — the coordinate
54
+ * nesting its `type` requires, positions inside the WGS 84 bounds, and
55
+ * every linear ring closed. Non-object values pass, following the rule
56
+ * that a format constrains only its own type.
57
+ *
58
+ * This is the quick, shallow judgment; the GeoJSON meta-schema artifacts
59
+ * in `@jarenjs/json` validate the same grammar more thoroughly (locating
60
+ * the failure, and — in the Jaren-extended variant — checking ring
61
+ * winding through `$query`). Reach for the schema when you want to know
62
+ * *what* is wrong; reach for the format when a one-keyword annotation is
63
+ * worth more than a diagnosis.
64
+ *
65
+ * @param {ValidationObject} schemaObj - The validation object for error handling and options
66
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
67
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
68
+ * @example
69
+ * compileGeoJsonFormat(schemaObj, { format: 'geojson' })({ type: 'Point', coordinates: [4.9, 52.4] }); // true
70
+ * compileGeoJsonFormat(schemaObj, { format: 'geojson' })({ type: 'Polygon', coordinates: [[[0,0],[1,0],[1,1],[2,2]]] }); // false (open ring)
71
+ * compileGeoJsonFormat(schemaObj, { format: 'geojson' })('not an object'); // true (wrong type is not this format's business)
72
+ */
73
+ export function compileGeoJsonFormat(schemaObj, jsonSchema) {
74
+ if (jsonSchema.format !== 'geojson')
75
+ throw new Error('Format is not equal to jsonSchema (should not happen!)');
76
+
77
+ const isGeoJson = geoFormatTesters['geojson'];
78
+
79
+ if (schemaObj.options.skipErrors) {
80
+ return function validateGeoJsonFormatFast(data, _dataPath) {
81
+ return data === null || typeof data !== 'object' || Array.isArray(data)
82
+ ? true
83
+ : isGeoJson(data);
84
+ };
85
+ }
86
+
87
+ const addError = schemaObj.createErrorHandler('geojson', 'format', isGeoJson.constructor.name);
88
+
89
+ return function validateGeoJsonFormat(data, dataPath) {
90
+ return data === null || typeof data !== 'object' || Array.isArray(data)
91
+ ? true
92
+ : isGeoJson(data) || addError(data, dataPath);
93
+ };
94
+ }
95
+
96
+ // =============================================================================
97
+ // Aggregated Format Validators Object
98
+ // =============================================================================
99
+
100
+ /**
101
+ * Object mapping geospatial format names to their compiler functions.
102
+ *
103
+ * @type {Record<string, (schemaObj: ValidationObject, jsonSchema: JSONSchema) => (data: unknown, dataPath?: string) => boolean>}
104
+ */
105
+ export const formatValidators = {
106
+ 'geohash': compileGeohashFormat,
107
+ 'wkt': compileWktFormat,
108
+ 'geojson': compileGeoJsonFormat,
109
+ };
package/src/index.js ADDED
@@ -0,0 +1,33 @@
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
+ export { formatValidators as geoFormats } from './geo.js';
25
+
26
+ export {
27
+ formatTesters,
28
+ stringFormatTesters,
29
+ jsonFormatTesters,
30
+ geoFormatTesters,
31
+ dateTimeFormatTesters,
32
+ numberFormatTesters,
33
+ } from './testers.js';
package/src/json.js ADDED
@@ -0,0 +1,116 @@
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
+ * Compiles a validator for the 'json-path-segments' format.
76
+ * Validates a *variable-rooted* path string: `$name` followed by
77
+ * optional RFC 9535 segments (`$book.price[?@.isbn]`). Such a string is
78
+ * not a valid RFC 9535 query — the RFC's root identifier is `$` alone —
79
+ * so `json-path` would reject it; this format is what gives the Jaren
80
+ * query format's variable-rooted paths the same schema-time
81
+ * well-formedness that absolute paths get from `json-path`.
82
+ *
83
+ * Both formats recognize the five built-in function extensions and no
84
+ * others: a format is a property of the string itself, so it must mean
85
+ * the same thing in every schema regardless of which custom extensions
86
+ * a particular host registered.
87
+ *
88
+ * @param {ValidationObject} schemaObj - The validation JSONSchema for error handling and options
89
+ * @param {JSONSchema} jsonSchema - The JSON schema containing the format definition
90
+ * @returns {(data: unknown, dataPath?: string) => boolean} A validator function
91
+ * @example
92
+ * compileJsonPathSegmentsFormat(schemaObj, { format: 'json-path-segments' })('$book.price'); // true
93
+ * compileJsonPathSegmentsFormat(schemaObj, { format: 'json-path-segments' })('$book'); // true
94
+ * compileJsonPathSegmentsFormat(schemaObj, { format: 'json-path-segments' })('$.price'); // false (that is json-path)
95
+ * compileJsonPathSegmentsFormat(schemaObj, { format: 'json-path-segments' })('$book.price['); // false
96
+ */
97
+ export const compileJsonPathSegmentsFormat = createStringFormatCompiler('json-path-segments', jsonFormatTesters['json-path-segments']);
98
+
99
+ // =============================================================================
100
+ // Aggregated Format Validators Object
101
+ // =============================================================================
102
+
103
+ /**
104
+ * Object mapping JSON-related format names to their compiler functions.
105
+ *
106
+ * @type {Record<string, (schemaObj: ValidationObject, jsonSchema: JSONSchema) => (data: unknown, dataPath?: string) => boolean>}
107
+ */
108
+ export const formatValidators = {
109
+ // JSON Pointer
110
+ 'json-pointer': compileJsonPointerFormat,
111
+ 'json-pointer-uri-fragment': compileJsonPointerUriFragmentFormat,
112
+ 'relative-json-pointer': compileRelativeJsonPointerFormat,
113
+ // JSONPath
114
+ 'json-path': compileJsonPathFormat,
115
+ 'json-path-segments': compileJsonPathSegmentsFormat,
116
+ };