@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.
- package/LICENSE +21 -0
- package/README.md +143 -2
- package/dist/types/datetime.d.ts +109 -0
- package/dist/types/geo.d.ts +77 -0
- package/dist/types/index.d.ts +23 -0
- package/dist/types/json.d.ts +95 -0
- package/dist/types/number.d.ts +144 -0
- package/dist/types/string.d.ts +355 -0
- package/dist/types/testers.d.ts +39 -0
- package/package.json +28 -8
- package/src/datetime.js +422 -0
- package/src/geo.js +109 -0
- package/src/index.js +33 -0
- package/src/json.js +116 -0
- package/src/number.js +219 -0
- package/src/string.js +517 -0
- package/src/testers.js +232 -0
- package/dist/index.js +0 -679
- package/dist/index.js.map +0 -7
- package/dist/index.min.js +0 -2
- package/dist/index.min.js.map +0 -7
package/src/datetime.js
ADDED
|
@@ -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
|
+
};
|