@crewhaus/tool-schema 0.7.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/README.md +127 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +678 -0
- package/dist/lib/assert.d.ts +62 -0
- package/dist/lib/assert.js +343 -0
- package/dist/lib/compare.d.ts +67 -0
- package/dist/lib/compare.js +298 -0
- package/dist/lib/formats.d.ts +115 -0
- package/dist/lib/formats.js +395 -0
- package/dist/lib/infer.d.ts +51 -0
- package/dist/lib/infer.js +201 -0
- package/dist/lib/jsonschema.d.ts +50 -0
- package/dist/lib/jsonschema.js +605 -0
- package/dist/lib/records.d.ts +114 -0
- package/dist/lib/records.js +234 -0
- package/dist/lib/schemadiff.d.ts +26 -0
- package/dist/lib/schemadiff.js +350 -0
- package/dist/lib/suggest.d.ts +25 -0
- package/dist/lib/suggest.js +65 -0
- package/dist/lib/summarize.d.ts +28 -0
- package/dist/lib/summarize.js +212 -0
- package/dist/lib/value.d.ts +84 -0
- package/dist/lib/value.js +241 -0
- package/package.json +45 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comparing a value against an expected value — the primitive under
|
|
3
|
+
* `DeepEqual`, `CompareGolden` and `MatchSubset`.
|
|
4
|
+
*
|
|
5
|
+
* A snapshot comparison is only useful if it can be told to overlook the
|
|
6
|
+
* parts that legitimately move: a generated id, a timestamp, the order of a
|
|
7
|
+
* result set, the last bit of a float. Those three knobs (`ignorePaths`,
|
|
8
|
+
* `ignoreArrayOrder`, `epsilon`) are what separate a golden test that holds
|
|
9
|
+
* for a year from one that is deleted in a week.
|
|
10
|
+
*
|
|
11
|
+
* Every difference is reported with a JSON Pointer, and the walk is
|
|
12
|
+
* deterministic: object keys in sorted order, array items in index order.
|
|
13
|
+
*/
|
|
14
|
+
import { canonicalize, isPlainObject, joinPointer, preview, typeOf } from "./value";
|
|
15
|
+
export const DEFAULT_COMPARE_OPTIONS = {
|
|
16
|
+
ignorePaths: [],
|
|
17
|
+
ignoreArrayOrder: false,
|
|
18
|
+
epsilon: 0,
|
|
19
|
+
subset: false,
|
|
20
|
+
maxDifferences: 50,
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Unordered array matching is O(n*m) comparisons. Past this length the cost
|
|
24
|
+
* stops being worth it and the comparison falls back to positional, saying
|
|
25
|
+
* so in the result rather than silently changing meaning.
|
|
26
|
+
*/
|
|
27
|
+
export const UNORDERED_ARRAY_LIMIT = 200;
|
|
28
|
+
function compilePattern(pointer) {
|
|
29
|
+
const body = pointer.startsWith("/") ? pointer.slice(1) : pointer;
|
|
30
|
+
return body === "" ? [] : body.split("/");
|
|
31
|
+
}
|
|
32
|
+
function pathSegments(pointer) {
|
|
33
|
+
return pointer === "" ? [] : pointer.slice(1).split("/");
|
|
34
|
+
}
|
|
35
|
+
/** True when `pointer` is matched by one of the compiled ignore patterns. */
|
|
36
|
+
export function matchesAnyPattern(pointer, patterns) {
|
|
37
|
+
const segments = pathSegments(pointer);
|
|
38
|
+
return patterns.some((pattern) => matchPattern(segments, pattern));
|
|
39
|
+
}
|
|
40
|
+
function matchPattern(segments, pattern) {
|
|
41
|
+
let s = 0;
|
|
42
|
+
for (let p = 0; p < pattern.length; p++) {
|
|
43
|
+
const token = pattern[p];
|
|
44
|
+
if (token === "**")
|
|
45
|
+
return true;
|
|
46
|
+
if (s >= segments.length)
|
|
47
|
+
return false;
|
|
48
|
+
if (token !== "*" && token !== segments[s])
|
|
49
|
+
return false;
|
|
50
|
+
s += 1;
|
|
51
|
+
}
|
|
52
|
+
return s === segments.length;
|
|
53
|
+
}
|
|
54
|
+
function record(ctx, diff) {
|
|
55
|
+
if (ctx.differences.length >= ctx.opts.maxDifferences) {
|
|
56
|
+
ctx.truncated = true;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
ctx.differences.push(diff);
|
|
60
|
+
}
|
|
61
|
+
function numbersEqual(a, b, epsilon) {
|
|
62
|
+
if (a === b)
|
|
63
|
+
return true;
|
|
64
|
+
if (!Number.isFinite(a) || !Number.isFinite(b))
|
|
65
|
+
return false;
|
|
66
|
+
return epsilon > 0 && Math.abs(a - b) <= epsilon;
|
|
67
|
+
}
|
|
68
|
+
/** Compare without recording anything — used to pair up unordered items. */
|
|
69
|
+
function equalUnder(actual, expected, ctx, path) {
|
|
70
|
+
const probe = {
|
|
71
|
+
opts: { ...ctx.opts, maxDifferences: 1 },
|
|
72
|
+
patterns: ctx.patterns,
|
|
73
|
+
differences: [],
|
|
74
|
+
truncated: false,
|
|
75
|
+
ignored: [],
|
|
76
|
+
orderedFallbacks: [],
|
|
77
|
+
};
|
|
78
|
+
walk(actual, expected, path, probe);
|
|
79
|
+
return probe.differences.length === 0;
|
|
80
|
+
}
|
|
81
|
+
function walk(actual, expected, path, ctx) {
|
|
82
|
+
if (matchesAnyPattern(path, ctx.patterns)) {
|
|
83
|
+
if (!ctx.ignored.includes(path))
|
|
84
|
+
ctx.ignored.push(path);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const expectedType = typeOf(expected);
|
|
88
|
+
const actualType = typeOf(actual);
|
|
89
|
+
if (typeof expected === "number" && typeof actual === "number") {
|
|
90
|
+
if (!numbersEqual(actual, expected, ctx.opts.epsilon)) {
|
|
91
|
+
record(ctx, {
|
|
92
|
+
path,
|
|
93
|
+
kind: "value",
|
|
94
|
+
message: ctx.opts.epsilon > 0
|
|
95
|
+
? `${actual} differs from ${expected} by more than ${ctx.opts.epsilon}`
|
|
96
|
+
: `expected ${expected}, found ${actual}`,
|
|
97
|
+
expected,
|
|
98
|
+
actual,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// `integer` and `number` are the same JSON type for comparison purposes.
|
|
104
|
+
const normalize = (t) => (t === "integer" ? "number" : t);
|
|
105
|
+
if (normalize(expectedType) !== normalize(actualType)) {
|
|
106
|
+
record(ctx, {
|
|
107
|
+
path,
|
|
108
|
+
kind: "type",
|
|
109
|
+
message: `expected ${normalize(expectedType)}, found ${normalize(actualType)} (${preview(actual, 60)})`,
|
|
110
|
+
expected,
|
|
111
|
+
actual,
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (Array.isArray(expected) && Array.isArray(actual)) {
|
|
116
|
+
compareArrays(actual, expected, path, ctx);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (isPlainObject(expected) && isPlainObject(actual)) {
|
|
120
|
+
compareObjects(actual, expected, path, ctx);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (expected !== actual) {
|
|
124
|
+
record(ctx, {
|
|
125
|
+
path,
|
|
126
|
+
kind: "value",
|
|
127
|
+
message: `expected ${preview(expected, 60)}, found ${preview(actual, 60)}`,
|
|
128
|
+
expected,
|
|
129
|
+
actual,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function compareArrays(actual, expected, path, ctx) {
|
|
134
|
+
const tooLong = actual.length > UNORDERED_ARRAY_LIMIT || expected.length > UNORDERED_ARRAY_LIMIT;
|
|
135
|
+
if (ctx.opts.ignoreArrayOrder && tooLong && !ctx.orderedFallbacks.includes(path)) {
|
|
136
|
+
ctx.orderedFallbacks.push(path);
|
|
137
|
+
}
|
|
138
|
+
if (ctx.opts.ignoreArrayOrder && !tooLong) {
|
|
139
|
+
compareArraysUnordered(actual, expected, path, ctx);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (actual.length !== expected.length) {
|
|
143
|
+
record(ctx, {
|
|
144
|
+
path,
|
|
145
|
+
kind: "length",
|
|
146
|
+
message: `expected ${expected.length} items, found ${actual.length}`,
|
|
147
|
+
expected: expected.length,
|
|
148
|
+
actual: actual.length,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const shared = Math.min(actual.length, expected.length);
|
|
152
|
+
for (let i = 0; i < shared; i++) {
|
|
153
|
+
walk(actual[i], expected[i], joinPointer(path, i), ctx);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Pair the two arrays up greedily: each expected item takes the first
|
|
158
|
+
* unclaimed actual item it equals.
|
|
159
|
+
*
|
|
160
|
+
* With `epsilon` at 0, equality is an equivalence relation, so greedy pairing
|
|
161
|
+
* finds a complete matching whenever one exists. With a non-zero `epsilon` it
|
|
162
|
+
* is not — `1.0` and `1.2` are each within 0.15 of `1.1` but not of each
|
|
163
|
+
* other — and greedy can claim an item a later one needed, reporting a
|
|
164
|
+
* difference where a perfect pairing existed. That is the deliberate trade
|
|
165
|
+
* for keeping this O(n*m) rather than running a full bipartite matching; the
|
|
166
|
+
* report errs towards naming a difference, never towards hiding one.
|
|
167
|
+
*/
|
|
168
|
+
function compareArraysUnordered(actual, expected, path, ctx) {
|
|
169
|
+
const taken = new Set();
|
|
170
|
+
const unmatchedExpected = [];
|
|
171
|
+
for (let e = 0; e < expected.length; e++) {
|
|
172
|
+
let matched = false;
|
|
173
|
+
for (let a = 0; a < actual.length; a++) {
|
|
174
|
+
if (taken.has(a))
|
|
175
|
+
continue;
|
|
176
|
+
if (!equalUnder(actual[a], expected[e], ctx, path))
|
|
177
|
+
continue;
|
|
178
|
+
taken.add(a);
|
|
179
|
+
matched = true;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
if (!matched)
|
|
183
|
+
unmatchedExpected.push(e);
|
|
184
|
+
}
|
|
185
|
+
for (const e of unmatchedExpected) {
|
|
186
|
+
record(ctx, {
|
|
187
|
+
path: joinPointer(path, e),
|
|
188
|
+
kind: "missing",
|
|
189
|
+
message: `no item in the actual array matches expected[${e}] (${preview(expected[e], 60)})`,
|
|
190
|
+
expected: expected[e],
|
|
191
|
+
actual: undefined,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
for (let a = 0; a < actual.length; a++) {
|
|
195
|
+
if (taken.has(a))
|
|
196
|
+
continue;
|
|
197
|
+
record(ctx, {
|
|
198
|
+
path: joinPointer(path, a),
|
|
199
|
+
kind: "unexpected",
|
|
200
|
+
message: `actual[${a}] (${preview(actual[a], 60)}) has no counterpart in expected`,
|
|
201
|
+
expected: undefined,
|
|
202
|
+
actual: actual[a],
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function compareObjects(actual, expected, path, ctx) {
|
|
207
|
+
for (const key of Object.keys(expected).sort()) {
|
|
208
|
+
const childPath = joinPointer(path, key);
|
|
209
|
+
if (!Object.hasOwn(actual, key)) {
|
|
210
|
+
if (matchesAnyPattern(childPath, ctx.patterns)) {
|
|
211
|
+
if (!ctx.ignored.includes(childPath))
|
|
212
|
+
ctx.ignored.push(childPath);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
record(ctx, {
|
|
216
|
+
path: childPath,
|
|
217
|
+
kind: "missing",
|
|
218
|
+
message: `expected property "${key}" is absent`,
|
|
219
|
+
expected: expected[key],
|
|
220
|
+
actual: undefined,
|
|
221
|
+
});
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
walk(actual[key], expected[key], childPath, ctx);
|
|
225
|
+
}
|
|
226
|
+
if (ctx.opts.subset)
|
|
227
|
+
return;
|
|
228
|
+
for (const key of Object.keys(actual).sort()) {
|
|
229
|
+
if (Object.hasOwn(expected, key))
|
|
230
|
+
continue;
|
|
231
|
+
const childPath = joinPointer(path, key);
|
|
232
|
+
if (matchesAnyPattern(childPath, ctx.patterns)) {
|
|
233
|
+
if (!ctx.ignored.includes(childPath))
|
|
234
|
+
ctx.ignored.push(childPath);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
record(ctx, {
|
|
238
|
+
path: childPath,
|
|
239
|
+
kind: "unexpected",
|
|
240
|
+
message: `unexpected property "${key}" (${preview(actual[key], 60)})`,
|
|
241
|
+
expected: undefined,
|
|
242
|
+
actual: actual[key],
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Compare `actual` against `expected` under the given options and return
|
|
248
|
+
* every difference found, each addressed by JSON Pointer.
|
|
249
|
+
*
|
|
250
|
+
* With `ignoreArrayOrder`, item paths refer to positions in the array they
|
|
251
|
+
* came from — an unmatched expected item is reported at its index in
|
|
252
|
+
* *expected*, an unmatched actual item at its index in *actual* — because
|
|
253
|
+
* with order ignored there is no single index that means both.
|
|
254
|
+
*/
|
|
255
|
+
export function compareValues(actual, expected, options = {}) {
|
|
256
|
+
const opts = { ...DEFAULT_COMPARE_OPTIONS, ...options };
|
|
257
|
+
const ctx = {
|
|
258
|
+
opts,
|
|
259
|
+
patterns: opts.ignorePaths.map(compilePattern),
|
|
260
|
+
differences: [],
|
|
261
|
+
truncated: false,
|
|
262
|
+
ignored: [],
|
|
263
|
+
orderedFallbacks: [],
|
|
264
|
+
};
|
|
265
|
+
walk(actual, expected, "", ctx);
|
|
266
|
+
return {
|
|
267
|
+
equal: ctx.differences.length === 0,
|
|
268
|
+
differences: ctx.differences,
|
|
269
|
+
truncated: ctx.truncated,
|
|
270
|
+
ignored: ctx.ignored,
|
|
271
|
+
orderedFallbacks: ctx.orderedFallbacks,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* The first place two values differ, walking objects in sorted key order and
|
|
276
|
+
* arrays by index, or `null` when they are structurally equal. This is the
|
|
277
|
+
* one line a failing equality check should print.
|
|
278
|
+
*/
|
|
279
|
+
export function firstDifference(actual, expected) {
|
|
280
|
+
const result = compareValues(actual, expected, { maxDifferences: 1 });
|
|
281
|
+
return result.differences[0] ?? null;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Group values that canonicalize identically. Used for duplicate detection,
|
|
285
|
+
* where "identical" must ignore key order.
|
|
286
|
+
*/
|
|
287
|
+
export function groupByCanonical(items, key) {
|
|
288
|
+
const groups = new Map();
|
|
289
|
+
items.forEach((item, index) => {
|
|
290
|
+
const id = canonicalize(key(item));
|
|
291
|
+
const bucket = groups.get(id);
|
|
292
|
+
if (bucket === undefined)
|
|
293
|
+
groups.set(id, [index]);
|
|
294
|
+
else
|
|
295
|
+
bucket.push(index);
|
|
296
|
+
});
|
|
297
|
+
return groups;
|
|
298
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String format checks, written out rather than borrowed.
|
|
3
|
+
*
|
|
4
|
+
* Every one of these is a *stated subset*, not a full grammar. The rule this
|
|
5
|
+
* file follows: reject what is definitely wrong, accept what is definitely
|
|
6
|
+
* right, and say in the doc comment which grey areas fall which way — so a
|
|
7
|
+
* harness gating on `ValidateFormat` knows exactly what it bought. A format
|
|
8
|
+
* check that quietly disagrees with the reader downstream is worse than no
|
|
9
|
+
* check at all.
|
|
10
|
+
*/
|
|
11
|
+
/** Every format name {@link checkFormat} understands. */
|
|
12
|
+
export declare const FORMAT_NAMES: readonly ["email", "uri", "uri-reference", "hostname", "ipv4", "ipv6", "uuid", "date", "time", "date-time", "json-pointer", "regex"];
|
|
13
|
+
export type FormatName = (typeof FORMAT_NAMES)[number];
|
|
14
|
+
/** Result of one format check: valid, or invalid with the reason why. */
|
|
15
|
+
export type FormatResult = {
|
|
16
|
+
valid: boolean;
|
|
17
|
+
reason: string | null;
|
|
18
|
+
};
|
|
19
|
+
/** True when `name` is one this module implements. */
|
|
20
|
+
export declare function isFormatName(name: string): name is FormatName;
|
|
21
|
+
/**
|
|
22
|
+
* A DNS hostname: dot-separated labels, each 1-63 characters of letters,
|
|
23
|
+
* digits and hyphens, not starting or ending with a hyphen; 253 characters
|
|
24
|
+
* total at most.
|
|
25
|
+
*
|
|
26
|
+
* Deliberate choices: a trailing dot (the DNS root) is rejected, underscores
|
|
27
|
+
* are rejected (legal in some DNS records, not in a hostname), and no
|
|
28
|
+
* internationalized form is decoded — a Unicode domain must arrive already
|
|
29
|
+
* punycoded.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isHostname(value: string): FormatResult;
|
|
32
|
+
/**
|
|
33
|
+
* Dotted-quad IPv4. Four decimal octets 0-255, no leading zeros — `01.2.3.4`
|
|
34
|
+
* is rejected, because a leading zero means octal to some parsers and
|
|
35
|
+
* decimal to others, and that ambiguity has been a source of SSRF bugs.
|
|
36
|
+
*/
|
|
37
|
+
export declare function isIpv4(value: string): FormatResult;
|
|
38
|
+
/**
|
|
39
|
+
* IPv6, including `::` compression and a dotted-quad IPv4 tail
|
|
40
|
+
* (`::ffff:192.0.2.1`). A zone identifier (`fe80::1%eth0`) is rejected: it
|
|
41
|
+
* addresses an interface, not a host, and is not part of the `ipv6` format.
|
|
42
|
+
*/
|
|
43
|
+
export declare function isIpv6(value: string): FormatResult;
|
|
44
|
+
/**
|
|
45
|
+
* A UUID in the 8-4-4-4-12 hex form, any case.
|
|
46
|
+
*
|
|
47
|
+
* Any version and variant is accepted, including the nil UUID — this checks
|
|
48
|
+
* the shape, not that the value came from a v4 generator. Braces and a
|
|
49
|
+
* `urn:uuid:` prefix are rejected.
|
|
50
|
+
*/
|
|
51
|
+
export declare function isUuid(value: string): FormatResult;
|
|
52
|
+
/** Days in a month, honouring the Gregorian leap-year rule. */
|
|
53
|
+
export declare function daysInMonth(year: number, month: number): number;
|
|
54
|
+
/**
|
|
55
|
+
* RFC 3339 `full-date`: `YYYY-MM-DD`, calendar-checked, so `2023-02-30` and
|
|
56
|
+
* `2023-13-01` are rejected and `2024-02-29` is accepted. No other layout is
|
|
57
|
+
* accepted; `2023-1-1` fails.
|
|
58
|
+
*/
|
|
59
|
+
export declare function isDate(value: string): FormatResult;
|
|
60
|
+
/**
|
|
61
|
+
* RFC 3339 `full-time`: `HH:MM:SS`, an optional fractional part, and a
|
|
62
|
+
* mandatory offset of `Z`, `z`, or `+HH:MM` / `-HH:MM`.
|
|
63
|
+
*
|
|
64
|
+
* A seconds value of 60 is accepted, because RFC 3339 allows it for leap
|
|
65
|
+
* seconds. An offset with minutes over 59 is rejected.
|
|
66
|
+
*/
|
|
67
|
+
export declare function isTime(value: string): FormatResult;
|
|
68
|
+
/**
|
|
69
|
+
* RFC 3339 `date-time`: a date, a `T` (or lowercase `t`) separator, and a
|
|
70
|
+
* time with an offset. A space separator, as SQL writes it, is rejected —
|
|
71
|
+
* this is the strict interchange form.
|
|
72
|
+
*/
|
|
73
|
+
export declare function isDateTime(value: string): FormatResult;
|
|
74
|
+
/**
|
|
75
|
+
* An email address, in the form services actually accept: a dot-separated
|
|
76
|
+
* local part of unquoted ASCII characters and a hostname domain.
|
|
77
|
+
*
|
|
78
|
+
* Deliberately narrower than RFC 5322: quoted local parts, comments,
|
|
79
|
+
* IP-literal domains (`a@[192.0.2.1]`) and non-ASCII are all rejected. A
|
|
80
|
+
* domain with no dot (`a@localhost`) is rejected too, since a bare host is
|
|
81
|
+
* never a deliverable address in practice.
|
|
82
|
+
*/
|
|
83
|
+
export declare function isEmail(value: string): FormatResult;
|
|
84
|
+
/**
|
|
85
|
+
* An absolute URI: a scheme (`[A-Za-z][A-Za-z0-9+.-]*`), a colon, and a
|
|
86
|
+
* non-empty remainder with no whitespace or control characters.
|
|
87
|
+
*
|
|
88
|
+
* This is the absolute-URI shape check, not the full RFC 3986 grammar: the
|
|
89
|
+
* authority, path, query and fragment are not parsed. `/path` and
|
|
90
|
+
* `page.html` are rejected — use `uri-reference` for those.
|
|
91
|
+
*/
|
|
92
|
+
export declare function isUri(value: string): FormatResult;
|
|
93
|
+
/**
|
|
94
|
+
* A URI reference: either an absolute URI or a relative one. Only whitespace,
|
|
95
|
+
* control characters and a second `#` are rejected; anything else a browser
|
|
96
|
+
* would resolve is accepted.
|
|
97
|
+
*/
|
|
98
|
+
export declare function isUriReference(value: string): FormatResult;
|
|
99
|
+
/** RFC 6901 JSON Pointer: empty, or `/`-led tokens where `~` is followed by 0 or 1. */
|
|
100
|
+
export declare function isJsonPointer(value: string): FormatResult;
|
|
101
|
+
/** A string that compiles as a JavaScript regular expression. */
|
|
102
|
+
export declare function isRegex(value: string): FormatResult;
|
|
103
|
+
/**
|
|
104
|
+
* Check one string against one format name. An unknown format name is a
|
|
105
|
+
* caller error and throws; every implemented format returns a result.
|
|
106
|
+
*/
|
|
107
|
+
export declare function checkFormat(value: string, format: FormatName): FormatResult;
|
|
108
|
+
/**
|
|
109
|
+
* The formats a value matches, narrowest first — see
|
|
110
|
+
* {@link FORMAT_SPECIFICITY}. Used by `ValidateFormat` to tell a caller what
|
|
111
|
+
* their value *does* look like when it fails the format they asked for, and
|
|
112
|
+
* by schema inference, which narrows the list further before labelling a
|
|
113
|
+
* field.
|
|
114
|
+
*/
|
|
115
|
+
export declare function matchingFormats(value: string): FormatName[];
|