@optique/core 1.0.0-dev.788 → 1.0.0-dev.885
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/dist/facade.cjs +129 -2
- package/dist/facade.js +129 -2
- package/dist/valueparser.cjs +65 -45
- package/dist/valueparser.d.cts +7 -0
- package/dist/valueparser.d.ts +7 -0
- package/dist/valueparser.js +65 -45
- package/package.json +1 -1
package/dist/facade.cjs
CHANGED
|
@@ -132,16 +132,143 @@ function finalizeParsedForContext(context, parsed) {
|
|
|
132
132
|
if (parsed !== void 0 || !Reflect.has(context, phase1ConfigAnnotationsKey)) return parsed;
|
|
133
133
|
return { [phase2UndefinedParsedValueKey]: true };
|
|
134
134
|
}
|
|
135
|
+
const SANITIZE_FAILED = Symbol("sanitizeFailed");
|
|
136
|
+
const activeSanitizations = /* @__PURE__ */ new WeakMap();
|
|
137
|
+
function callWithSanitizedOwnProperties(target, fn, args, strip, seen) {
|
|
138
|
+
let active = activeSanitizations.get(target);
|
|
139
|
+
if (active != null) active.count++;
|
|
140
|
+
else {
|
|
141
|
+
const saved = /* @__PURE__ */ new Map();
|
|
142
|
+
const sanitizedValues = /* @__PURE__ */ new Map();
|
|
143
|
+
for (const key of Reflect.ownKeys(target)) {
|
|
144
|
+
const desc = Object.getOwnPropertyDescriptor(target, key);
|
|
145
|
+
if (desc != null && "value" in desc) {
|
|
146
|
+
let stripped;
|
|
147
|
+
try {
|
|
148
|
+
stripped = strip(desc.value, seen);
|
|
149
|
+
} catch {
|
|
150
|
+
for (const [k, d] of saved) try {
|
|
151
|
+
Object.defineProperty(target, k, d);
|
|
152
|
+
} catch {}
|
|
153
|
+
return SANITIZE_FAILED;
|
|
154
|
+
}
|
|
155
|
+
if (stripped !== desc.value) try {
|
|
156
|
+
Object.defineProperty(target, key, {
|
|
157
|
+
...desc,
|
|
158
|
+
value: stripped
|
|
159
|
+
});
|
|
160
|
+
saved.set(key, desc);
|
|
161
|
+
sanitizedValues.set(key, stripped);
|
|
162
|
+
} catch {
|
|
163
|
+
for (const [k, d] of saved) try {
|
|
164
|
+
Object.defineProperty(target, k, d);
|
|
165
|
+
} catch {}
|
|
166
|
+
return SANITIZE_FAILED;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
active = {
|
|
171
|
+
saved,
|
|
172
|
+
sanitizedValues,
|
|
173
|
+
count: 1
|
|
174
|
+
};
|
|
175
|
+
activeSanitizations.set(target, active);
|
|
176
|
+
}
|
|
177
|
+
function release() {
|
|
178
|
+
active.count--;
|
|
179
|
+
if (active.count === 0) {
|
|
180
|
+
activeSanitizations.delete(target);
|
|
181
|
+
for (const [key, desc] of active.saved) try {
|
|
182
|
+
const current = Object.getOwnPropertyDescriptor(target, key);
|
|
183
|
+
if (current == null) continue;
|
|
184
|
+
if ("value" in current && current.value !== active.sanitizedValues.get(key)) continue;
|
|
185
|
+
Object.defineProperty(target, key, desc);
|
|
186
|
+
} catch {}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
let result;
|
|
190
|
+
try {
|
|
191
|
+
result = fn.apply(target, args);
|
|
192
|
+
} catch (e) {
|
|
193
|
+
release();
|
|
194
|
+
throw e;
|
|
195
|
+
}
|
|
196
|
+
if (result instanceof Promise) return result.then((v) => {
|
|
197
|
+
release();
|
|
198
|
+
return strip(v, seen);
|
|
199
|
+
}, (e) => {
|
|
200
|
+
release();
|
|
201
|
+
throw e;
|
|
202
|
+
});
|
|
203
|
+
release();
|
|
204
|
+
return strip(result, seen);
|
|
205
|
+
}
|
|
206
|
+
function callMethodOnSanitizedTarget(fn, proxy, target, args, strip, seen) {
|
|
207
|
+
const result = callWithSanitizedOwnProperties(target, fn, args, strip, seen);
|
|
208
|
+
if (result !== SANITIZE_FAILED) return result;
|
|
209
|
+
const fallback = fn.apply(proxy, args);
|
|
210
|
+
if (fallback instanceof Promise) return fallback.then((v) => strip(v, seen));
|
|
211
|
+
return strip(fallback, seen);
|
|
212
|
+
}
|
|
135
213
|
function createSanitizedNonPlainContextView(value$1, seen) {
|
|
214
|
+
const methodCache = /* @__PURE__ */ new Map();
|
|
136
215
|
const proxy = new Proxy(value$1, {
|
|
137
216
|
get(target, key, receiver) {
|
|
138
217
|
const descriptor = Object.getOwnPropertyDescriptor(target, key);
|
|
139
|
-
if (descriptor != null && "value" in descriptor)
|
|
140
|
-
|
|
218
|
+
if (descriptor != null && "value" in descriptor) {
|
|
219
|
+
if (!descriptor.configurable && !descriptor.writable) return descriptor.value;
|
|
220
|
+
const val = stripDeferredPromptValuesForContexts(descriptor.value, seen);
|
|
221
|
+
if (typeof val === "function") {
|
|
222
|
+
if (!descriptor.configurable && !descriptor.writable || /^class[\s{]/.test(Function.prototype.toString.call(val))) return val;
|
|
223
|
+
const cached = methodCache.get(key);
|
|
224
|
+
if (cached != null && cached.fn === val) return cached.wrapper;
|
|
225
|
+
const wrapper = function(...args) {
|
|
226
|
+
if (this !== proxy) return stripDeferredPromptValuesForContexts(val.apply(this, args), seen);
|
|
227
|
+
return callMethodOnSanitizedTarget(val, proxy, target, args, stripDeferredPromptValuesForContexts, seen);
|
|
228
|
+
};
|
|
229
|
+
methodCache.set(key, {
|
|
230
|
+
fn: val,
|
|
231
|
+
wrapper
|
|
232
|
+
});
|
|
233
|
+
return wrapper;
|
|
234
|
+
}
|
|
235
|
+
return val;
|
|
236
|
+
}
|
|
237
|
+
let isAccessor = false;
|
|
238
|
+
for (let proto = target; proto != null; proto = Object.getPrototypeOf(proto)) {
|
|
239
|
+
const d = Object.getOwnPropertyDescriptor(proto, key);
|
|
240
|
+
if (d != null) {
|
|
241
|
+
isAccessor = "get" in d;
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const result = Reflect.get(target, key, receiver);
|
|
246
|
+
if (typeof result === "function") {
|
|
247
|
+
if (/^class[\s{]/.test(Function.prototype.toString.call(result))) return result;
|
|
248
|
+
if (!isAccessor) {
|
|
249
|
+
const cached = methodCache.get(key);
|
|
250
|
+
if (cached != null && cached.fn === result) return cached.wrapper;
|
|
251
|
+
const wrapper = function(...args) {
|
|
252
|
+
if (this !== proxy) return stripDeferredPromptValuesForContexts(result.apply(this, args), seen);
|
|
253
|
+
return callMethodOnSanitizedTarget(result, proxy, target, args, stripDeferredPromptValuesForContexts, seen);
|
|
254
|
+
};
|
|
255
|
+
methodCache.set(key, {
|
|
256
|
+
fn: result,
|
|
257
|
+
wrapper
|
|
258
|
+
});
|
|
259
|
+
return wrapper;
|
|
260
|
+
}
|
|
261
|
+
return function(...args) {
|
|
262
|
+
if (this !== proxy) return stripDeferredPromptValuesForContexts(result.apply(this, args), seen);
|
|
263
|
+
return callMethodOnSanitizedTarget(result, proxy, target, args, stripDeferredPromptValuesForContexts, seen);
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
return stripDeferredPromptValuesForContexts(result, seen);
|
|
141
267
|
},
|
|
142
268
|
getOwnPropertyDescriptor(target, key) {
|
|
143
269
|
const descriptor = Object.getOwnPropertyDescriptor(target, key);
|
|
144
270
|
if (descriptor == null || !("value" in descriptor)) return descriptor;
|
|
271
|
+
if (!descriptor.configurable && !descriptor.writable) return descriptor;
|
|
145
272
|
return {
|
|
146
273
|
...descriptor,
|
|
147
274
|
value: stripDeferredPromptValuesForContexts(descriptor.value, seen)
|
package/dist/facade.js
CHANGED
|
@@ -132,16 +132,143 @@ function finalizeParsedForContext(context, parsed) {
|
|
|
132
132
|
if (parsed !== void 0 || !Reflect.has(context, phase1ConfigAnnotationsKey)) return parsed;
|
|
133
133
|
return { [phase2UndefinedParsedValueKey]: true };
|
|
134
134
|
}
|
|
135
|
+
const SANITIZE_FAILED = Symbol("sanitizeFailed");
|
|
136
|
+
const activeSanitizations = /* @__PURE__ */ new WeakMap();
|
|
137
|
+
function callWithSanitizedOwnProperties(target, fn, args, strip, seen) {
|
|
138
|
+
let active = activeSanitizations.get(target);
|
|
139
|
+
if (active != null) active.count++;
|
|
140
|
+
else {
|
|
141
|
+
const saved = /* @__PURE__ */ new Map();
|
|
142
|
+
const sanitizedValues = /* @__PURE__ */ new Map();
|
|
143
|
+
for (const key of Reflect.ownKeys(target)) {
|
|
144
|
+
const desc = Object.getOwnPropertyDescriptor(target, key);
|
|
145
|
+
if (desc != null && "value" in desc) {
|
|
146
|
+
let stripped;
|
|
147
|
+
try {
|
|
148
|
+
stripped = strip(desc.value, seen);
|
|
149
|
+
} catch {
|
|
150
|
+
for (const [k, d] of saved) try {
|
|
151
|
+
Object.defineProperty(target, k, d);
|
|
152
|
+
} catch {}
|
|
153
|
+
return SANITIZE_FAILED;
|
|
154
|
+
}
|
|
155
|
+
if (stripped !== desc.value) try {
|
|
156
|
+
Object.defineProperty(target, key, {
|
|
157
|
+
...desc,
|
|
158
|
+
value: stripped
|
|
159
|
+
});
|
|
160
|
+
saved.set(key, desc);
|
|
161
|
+
sanitizedValues.set(key, stripped);
|
|
162
|
+
} catch {
|
|
163
|
+
for (const [k, d] of saved) try {
|
|
164
|
+
Object.defineProperty(target, k, d);
|
|
165
|
+
} catch {}
|
|
166
|
+
return SANITIZE_FAILED;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
active = {
|
|
171
|
+
saved,
|
|
172
|
+
sanitizedValues,
|
|
173
|
+
count: 1
|
|
174
|
+
};
|
|
175
|
+
activeSanitizations.set(target, active);
|
|
176
|
+
}
|
|
177
|
+
function release() {
|
|
178
|
+
active.count--;
|
|
179
|
+
if (active.count === 0) {
|
|
180
|
+
activeSanitizations.delete(target);
|
|
181
|
+
for (const [key, desc] of active.saved) try {
|
|
182
|
+
const current = Object.getOwnPropertyDescriptor(target, key);
|
|
183
|
+
if (current == null) continue;
|
|
184
|
+
if ("value" in current && current.value !== active.sanitizedValues.get(key)) continue;
|
|
185
|
+
Object.defineProperty(target, key, desc);
|
|
186
|
+
} catch {}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
let result;
|
|
190
|
+
try {
|
|
191
|
+
result = fn.apply(target, args);
|
|
192
|
+
} catch (e) {
|
|
193
|
+
release();
|
|
194
|
+
throw e;
|
|
195
|
+
}
|
|
196
|
+
if (result instanceof Promise) return result.then((v) => {
|
|
197
|
+
release();
|
|
198
|
+
return strip(v, seen);
|
|
199
|
+
}, (e) => {
|
|
200
|
+
release();
|
|
201
|
+
throw e;
|
|
202
|
+
});
|
|
203
|
+
release();
|
|
204
|
+
return strip(result, seen);
|
|
205
|
+
}
|
|
206
|
+
function callMethodOnSanitizedTarget(fn, proxy, target, args, strip, seen) {
|
|
207
|
+
const result = callWithSanitizedOwnProperties(target, fn, args, strip, seen);
|
|
208
|
+
if (result !== SANITIZE_FAILED) return result;
|
|
209
|
+
const fallback = fn.apply(proxy, args);
|
|
210
|
+
if (fallback instanceof Promise) return fallback.then((v) => strip(v, seen));
|
|
211
|
+
return strip(fallback, seen);
|
|
212
|
+
}
|
|
135
213
|
function createSanitizedNonPlainContextView(value$1, seen) {
|
|
214
|
+
const methodCache = /* @__PURE__ */ new Map();
|
|
136
215
|
const proxy = new Proxy(value$1, {
|
|
137
216
|
get(target, key, receiver) {
|
|
138
217
|
const descriptor = Object.getOwnPropertyDescriptor(target, key);
|
|
139
|
-
if (descriptor != null && "value" in descriptor)
|
|
140
|
-
|
|
218
|
+
if (descriptor != null && "value" in descriptor) {
|
|
219
|
+
if (!descriptor.configurable && !descriptor.writable) return descriptor.value;
|
|
220
|
+
const val = stripDeferredPromptValuesForContexts(descriptor.value, seen);
|
|
221
|
+
if (typeof val === "function") {
|
|
222
|
+
if (!descriptor.configurable && !descriptor.writable || /^class[\s{]/.test(Function.prototype.toString.call(val))) return val;
|
|
223
|
+
const cached = methodCache.get(key);
|
|
224
|
+
if (cached != null && cached.fn === val) return cached.wrapper;
|
|
225
|
+
const wrapper = function(...args) {
|
|
226
|
+
if (this !== proxy) return stripDeferredPromptValuesForContexts(val.apply(this, args), seen);
|
|
227
|
+
return callMethodOnSanitizedTarget(val, proxy, target, args, stripDeferredPromptValuesForContexts, seen);
|
|
228
|
+
};
|
|
229
|
+
methodCache.set(key, {
|
|
230
|
+
fn: val,
|
|
231
|
+
wrapper
|
|
232
|
+
});
|
|
233
|
+
return wrapper;
|
|
234
|
+
}
|
|
235
|
+
return val;
|
|
236
|
+
}
|
|
237
|
+
let isAccessor = false;
|
|
238
|
+
for (let proto = target; proto != null; proto = Object.getPrototypeOf(proto)) {
|
|
239
|
+
const d = Object.getOwnPropertyDescriptor(proto, key);
|
|
240
|
+
if (d != null) {
|
|
241
|
+
isAccessor = "get" in d;
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const result = Reflect.get(target, key, receiver);
|
|
246
|
+
if (typeof result === "function") {
|
|
247
|
+
if (/^class[\s{]/.test(Function.prototype.toString.call(result))) return result;
|
|
248
|
+
if (!isAccessor) {
|
|
249
|
+
const cached = methodCache.get(key);
|
|
250
|
+
if (cached != null && cached.fn === result) return cached.wrapper;
|
|
251
|
+
const wrapper = function(...args) {
|
|
252
|
+
if (this !== proxy) return stripDeferredPromptValuesForContexts(result.apply(this, args), seen);
|
|
253
|
+
return callMethodOnSanitizedTarget(result, proxy, target, args, stripDeferredPromptValuesForContexts, seen);
|
|
254
|
+
};
|
|
255
|
+
methodCache.set(key, {
|
|
256
|
+
fn: result,
|
|
257
|
+
wrapper
|
|
258
|
+
});
|
|
259
|
+
return wrapper;
|
|
260
|
+
}
|
|
261
|
+
return function(...args) {
|
|
262
|
+
if (this !== proxy) return stripDeferredPromptValuesForContexts(result.apply(this, args), seen);
|
|
263
|
+
return callMethodOnSanitizedTarget(result, proxy, target, args, stripDeferredPromptValuesForContexts, seen);
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
return stripDeferredPromptValuesForContexts(result, seen);
|
|
141
267
|
},
|
|
142
268
|
getOwnPropertyDescriptor(target, key) {
|
|
143
269
|
const descriptor = Object.getOwnPropertyDescriptor(target, key);
|
|
144
270
|
if (descriptor == null || !("value" in descriptor)) return descriptor;
|
|
271
|
+
if (!descriptor.configurable && !descriptor.writable) return descriptor;
|
|
145
272
|
return {
|
|
146
273
|
...descriptor,
|
|
147
274
|
value: stripDeferredPromptValuesForContexts(descriptor.value, seen)
|
package/dist/valueparser.cjs
CHANGED
|
@@ -15,10 +15,14 @@ function isValueParser(object) {
|
|
|
15
15
|
*/
|
|
16
16
|
function choice(choices, options = {}) {
|
|
17
17
|
if (choices.length < 1) throw new TypeError("Expected at least one choice, but got an empty array.");
|
|
18
|
+
if (choices.some((c) => c === "")) throw new TypeError("Empty strings are not allowed as choices.");
|
|
19
|
+
for (const c of choices) if (typeof c !== "string" && typeof c !== "number") throw new TypeError(`Expected every choice to be a string or number, but got ${typeof c}.`);
|
|
20
|
+
const isNumber = typeof choices[0] === "number";
|
|
21
|
+
for (const c of choices) if (isNumber ? typeof c !== "number" : typeof c !== "string") throw new TypeError(`Expected every choice to be the same type, but got both ${isNumber ? "number" : "string"} and ${typeof c}.`);
|
|
22
|
+
if (isNumber && choices.some((v) => Number.isNaN(v))) throw new TypeError("NaN is not allowed in number choices.");
|
|
18
23
|
const metavar = options.metavar ?? "TYPE";
|
|
19
24
|
require_nonempty.ensureNonEmptyString(metavar);
|
|
20
|
-
|
|
21
|
-
if (isNumberChoice) {
|
|
25
|
+
if (isNumber) {
|
|
22
26
|
const numberChoices = (() => {
|
|
23
27
|
const seen = /* @__PURE__ */ new Set();
|
|
24
28
|
let hasPositiveZero = false;
|
|
@@ -39,17 +43,16 @@ function choice(choices, options = {}) {
|
|
|
39
43
|
}
|
|
40
44
|
return result;
|
|
41
45
|
})();
|
|
42
|
-
const
|
|
43
|
-
const hasNaN = numberChoices.some((v) => Number.isNaN(v));
|
|
44
|
-
const validNumberChoices = hasNaN ? numberChoices.filter((v) => !Number.isNaN(v)) : numberChoices;
|
|
46
|
+
const numberInvalidChoice = options.errors?.invalidChoice;
|
|
45
47
|
const numberStrings = numberChoices.map((v) => Object.is(v, -0) ? "-0" : String(v));
|
|
48
|
+
const frozenNumberChoices = Object.freeze(numberChoices);
|
|
46
49
|
return {
|
|
47
50
|
$mode: "sync",
|
|
48
51
|
metavar,
|
|
49
|
-
choices:
|
|
52
|
+
choices: frozenNumberChoices,
|
|
50
53
|
parse(input) {
|
|
51
54
|
const index = numberStrings.indexOf(input);
|
|
52
|
-
if (index >= 0
|
|
55
|
+
if (index >= 0) return {
|
|
53
56
|
success: true,
|
|
54
57
|
value: numberChoices[index]
|
|
55
58
|
};
|
|
@@ -94,7 +97,7 @@ function choice(choices, options = {}) {
|
|
|
94
97
|
}
|
|
95
98
|
return {
|
|
96
99
|
success: false,
|
|
97
|
-
error: formatNumberChoiceError(input,
|
|
100
|
+
error: formatNumberChoiceError(input, numberChoices, numberChoices, numberInvalidChoice)
|
|
98
101
|
};
|
|
99
102
|
},
|
|
100
103
|
format(value) {
|
|
@@ -108,10 +111,12 @@ function choice(choices, options = {}) {
|
|
|
108
111
|
}
|
|
109
112
|
};
|
|
110
113
|
}
|
|
111
|
-
const stringChoices = [...new Set(choices)];
|
|
114
|
+
const stringChoices = Object.freeze([...new Set(choices)]);
|
|
112
115
|
const stringOptions = options;
|
|
113
|
-
|
|
114
|
-
|
|
116
|
+
if (stringOptions.caseInsensitive !== void 0 && typeof stringOptions.caseInsensitive !== "boolean") throw new TypeError(`Expected caseInsensitive to be a boolean, but got ${typeof stringOptions.caseInsensitive}: ${String(stringOptions.caseInsensitive)}.`);
|
|
117
|
+
const caseInsensitive = stringOptions.caseInsensitive ?? false;
|
|
118
|
+
const normalizedValues = caseInsensitive ? stringChoices.map((v) => v.toLowerCase()) : stringChoices;
|
|
119
|
+
if (caseInsensitive) {
|
|
115
120
|
const seen = /* @__PURE__ */ new Map();
|
|
116
121
|
for (let i = 0; i < stringChoices.length; i++) {
|
|
117
122
|
const nv = normalizedValues[i];
|
|
@@ -121,16 +126,17 @@ function choice(choices, options = {}) {
|
|
|
121
126
|
seen.set(nv, original);
|
|
122
127
|
}
|
|
123
128
|
}
|
|
129
|
+
const stringInvalidChoice = stringOptions.errors?.invalidChoice;
|
|
124
130
|
return {
|
|
125
131
|
$mode: "sync",
|
|
126
132
|
metavar,
|
|
127
133
|
choices: stringChoices,
|
|
128
134
|
parse(input) {
|
|
129
|
-
const normalizedInput =
|
|
135
|
+
const normalizedInput = caseInsensitive ? input.toLowerCase() : input;
|
|
130
136
|
const index = normalizedValues.indexOf(normalizedInput);
|
|
131
137
|
if (index < 0) return {
|
|
132
138
|
success: false,
|
|
133
|
-
error: formatStringChoiceError(input, stringChoices,
|
|
139
|
+
error: formatStringChoiceError(input, stringChoices, stringInvalidChoice)
|
|
134
140
|
};
|
|
135
141
|
return {
|
|
136
142
|
success: true,
|
|
@@ -141,9 +147,9 @@ function choice(choices, options = {}) {
|
|
|
141
147
|
return String(value);
|
|
142
148
|
},
|
|
143
149
|
suggest(prefix) {
|
|
144
|
-
const normalizedPrefix =
|
|
150
|
+
const normalizedPrefix = caseInsensitive ? prefix.toLowerCase() : prefix;
|
|
145
151
|
return stringChoices.filter((value) => {
|
|
146
|
-
const normalizedValue =
|
|
152
|
+
const normalizedValue = caseInsensitive ? value.toLowerCase() : value;
|
|
147
153
|
return normalizedValue.startsWith(normalizedPrefix);
|
|
148
154
|
}).map((value) => ({
|
|
149
155
|
kind: "literal",
|
|
@@ -203,15 +209,15 @@ function normalizeDecimal(s) {
|
|
|
203
209
|
/**
|
|
204
210
|
* Formats error message for string choice parser.
|
|
205
211
|
*/
|
|
206
|
-
function formatStringChoiceError(input, choices,
|
|
207
|
-
if (
|
|
212
|
+
function formatStringChoiceError(input, choices, invalidChoice) {
|
|
213
|
+
if (invalidChoice) return typeof invalidChoice === "function" ? invalidChoice(input, choices) : invalidChoice;
|
|
208
214
|
return formatDefaultChoiceError(input, choices);
|
|
209
215
|
}
|
|
210
216
|
/**
|
|
211
217
|
* Formats error message for number choice parser.
|
|
212
218
|
*/
|
|
213
|
-
function formatNumberChoiceError(input, validChoices, allChoices,
|
|
214
|
-
if (
|
|
219
|
+
function formatNumberChoiceError(input, validChoices, allChoices, invalidChoice) {
|
|
220
|
+
if (invalidChoice) return typeof invalidChoice === "function" ? invalidChoice(input, validChoices) : invalidChoice;
|
|
215
221
|
return formatDefaultChoiceError(input, allChoices);
|
|
216
222
|
}
|
|
217
223
|
/**
|
|
@@ -242,19 +248,22 @@ function string(options = {}) {
|
|
|
242
248
|
if (options.pattern != null && !(options.pattern instanceof RegExp)) throw new TypeError(`Expected pattern to be a RegExp, but got: ${Object.prototype.toString.call(options.pattern)}`);
|
|
243
249
|
const metavar = options.metavar ?? "STRING";
|
|
244
250
|
require_nonempty.ensureNonEmptyString(metavar);
|
|
251
|
+
const patternSource = options.pattern?.source ?? null;
|
|
252
|
+
const patternFlags = options.pattern?.flags ?? null;
|
|
253
|
+
const patternMismatch = options.errors?.patternMismatch;
|
|
245
254
|
return {
|
|
246
255
|
$mode: "sync",
|
|
247
256
|
metavar,
|
|
248
257
|
parse(input) {
|
|
249
|
-
if (
|
|
250
|
-
const pattern = new RegExp(
|
|
258
|
+
if (patternSource != null && patternFlags != null) {
|
|
259
|
+
const pattern = new RegExp(patternSource, patternFlags);
|
|
251
260
|
if (pattern.test(input)) return {
|
|
252
261
|
success: true,
|
|
253
262
|
value: input
|
|
254
263
|
};
|
|
255
264
|
return {
|
|
256
265
|
success: false,
|
|
257
|
-
error:
|
|
266
|
+
error: patternMismatch ? typeof patternMismatch === "function" ? patternMismatch(input, pattern) : patternMismatch : require_message.message`Expected a string matching pattern ${require_message.text(patternSource)}, but got ${input}.`
|
|
258
267
|
};
|
|
259
268
|
}
|
|
260
269
|
return {
|
|
@@ -440,21 +449,24 @@ function float(options = {}) {
|
|
|
440
449
|
* @returns A {@link ValueParser} that converts string input to `URL` objects.
|
|
441
450
|
*/
|
|
442
451
|
function url(options = {}) {
|
|
443
|
-
const
|
|
452
|
+
const originalProtocols = options.allowedProtocols != null ? Object.freeze([...options.allowedProtocols]) : void 0;
|
|
453
|
+
const allowedProtocols = options.allowedProtocols != null ? Object.freeze(options.allowedProtocols.map((p) => p.toLowerCase())) : void 0;
|
|
444
454
|
const metavar = options.metavar ?? "URL";
|
|
445
455
|
require_nonempty.ensureNonEmptyString(metavar);
|
|
456
|
+
const invalidUrl = options.errors?.invalidUrl;
|
|
457
|
+
const disallowedProtocol = options.errors?.disallowedProtocol;
|
|
446
458
|
return {
|
|
447
459
|
$mode: "sync",
|
|
448
460
|
metavar,
|
|
449
461
|
parse(input) {
|
|
450
462
|
if (!URL.canParse(input)) return {
|
|
451
463
|
success: false,
|
|
452
|
-
error:
|
|
464
|
+
error: invalidUrl ? typeof invalidUrl === "function" ? invalidUrl(input) : invalidUrl : require_message.message`Invalid URL: ${input}.`
|
|
453
465
|
};
|
|
454
466
|
const url$1 = new URL(input);
|
|
455
467
|
if (allowedProtocols != null && !allowedProtocols.includes(url$1.protocol)) return {
|
|
456
468
|
success: false,
|
|
457
|
-
error:
|
|
469
|
+
error: disallowedProtocol ? typeof disallowedProtocol === "function" ? disallowedProtocol(url$1.protocol, originalProtocols) : disallowedProtocol : require_message.message`URL protocol ${url$1.protocol} is not allowed. Allowed protocols: ${allowedProtocols.join(", ")}.`
|
|
458
470
|
};
|
|
459
471
|
return {
|
|
460
472
|
success: true,
|
|
@@ -508,7 +520,7 @@ function locale(options = {}) {
|
|
|
508
520
|
};
|
|
509
521
|
},
|
|
510
522
|
format(value) {
|
|
511
|
-
return value.
|
|
523
|
+
return value.toString();
|
|
512
524
|
},
|
|
513
525
|
*suggest(prefix) {
|
|
514
526
|
const commonLocales = [
|
|
@@ -758,24 +770,27 @@ function uuid(options = {}) {
|
|
|
758
770
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
759
771
|
const metavar = options.metavar ?? "UUID";
|
|
760
772
|
require_nonempty.ensureNonEmptyString(metavar);
|
|
773
|
+
const allowedVersions = options.allowedVersions != null ? Object.freeze([...options.allowedVersions]) : null;
|
|
774
|
+
const invalidUuid = options.errors?.invalidUuid;
|
|
775
|
+
const disallowedVersion = options.errors?.disallowedVersion;
|
|
761
776
|
return {
|
|
762
777
|
$mode: "sync",
|
|
763
778
|
metavar,
|
|
764
779
|
parse(input) {
|
|
765
780
|
if (!uuidRegex.test(input)) return {
|
|
766
781
|
success: false,
|
|
767
|
-
error:
|
|
782
|
+
error: invalidUuid ? typeof invalidUuid === "function" ? invalidUuid(input) : invalidUuid : require_message.message`Expected a valid UUID in format ${"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}, but got ${input}.`
|
|
768
783
|
};
|
|
769
|
-
if (
|
|
784
|
+
if (allowedVersions != null && allowedVersions.length > 0) {
|
|
770
785
|
const versionChar = input.charAt(14);
|
|
771
786
|
const version = parseInt(versionChar, 16);
|
|
772
|
-
if (!
|
|
787
|
+
if (!allowedVersions.includes(version)) return {
|
|
773
788
|
success: false,
|
|
774
|
-
error:
|
|
789
|
+
error: disallowedVersion ? typeof disallowedVersion === "function" ? disallowedVersion(version, allowedVersions) : disallowedVersion : (() => {
|
|
775
790
|
let expectedVersions = require_message.message``;
|
|
776
791
|
let i = 0;
|
|
777
|
-
for (const v of
|
|
778
|
-
expectedVersions = i < 1 ? require_message.message`${expectedVersions}${v.toLocaleString("en")}` : i + 1 >=
|
|
792
|
+
for (const v of allowedVersions) {
|
|
793
|
+
expectedVersions = i < 1 ? require_message.message`${expectedVersions}${v.toLocaleString("en")}` : i + 1 >= allowedVersions.length ? require_message.message`${expectedVersions}, or ${v.toLocaleString("en")}` : require_message.message`${expectedVersions}, ${v.toLocaleString("en")}`;
|
|
779
794
|
i++;
|
|
780
795
|
}
|
|
781
796
|
return require_message.message`Expected UUID version ${expectedVersions}, but got version ${version.toLocaleString("en")}.`;
|
|
@@ -1220,7 +1235,9 @@ function email(options) {
|
|
|
1220
1235
|
const allowMultiple = options?.allowMultiple ?? false;
|
|
1221
1236
|
const allowDisplayName = options?.allowDisplayName ?? false;
|
|
1222
1237
|
const lowercase = options?.lowercase ?? false;
|
|
1223
|
-
const allowedDomains = options?.allowedDomains;
|
|
1238
|
+
const allowedDomains = options?.allowedDomains != null ? Object.freeze([...options.allowedDomains]) : void 0;
|
|
1239
|
+
const invalidEmail = options?.errors?.invalidEmail;
|
|
1240
|
+
const domainNotAllowed = options?.errors?.domainNotAllowed;
|
|
1224
1241
|
const atextRegex = /^[a-zA-Z0-9._+-]+$/;
|
|
1225
1242
|
function validateEmail(input) {
|
|
1226
1243
|
const trimmed = input.trim();
|
|
@@ -1271,7 +1288,7 @@ function email(options) {
|
|
|
1271
1288
|
for (const email$1 of emails) {
|
|
1272
1289
|
const validated = validateEmail(email$1);
|
|
1273
1290
|
if (validated === null) {
|
|
1274
|
-
const errorMsg =
|
|
1291
|
+
const errorMsg = invalidEmail;
|
|
1275
1292
|
const msg = typeof errorMsg === "function" ? errorMsg(email$1) : errorMsg ?? require_message.message`Expected a valid email address, but got ${email$1}.`;
|
|
1276
1293
|
return {
|
|
1277
1294
|
success: false,
|
|
@@ -1283,7 +1300,7 @@ function email(options) {
|
|
|
1283
1300
|
const domain$1 = validated.substring(atIndex + 1).toLowerCase();
|
|
1284
1301
|
const isAllowed = allowedDomains.some((allowed) => domain$1 === allowed.toLowerCase());
|
|
1285
1302
|
if (!isAllowed) {
|
|
1286
|
-
const errorMsg =
|
|
1303
|
+
const errorMsg = domainNotAllowed;
|
|
1287
1304
|
if (typeof errorMsg === "function") return {
|
|
1288
1305
|
success: false,
|
|
1289
1306
|
error: errorMsg(validated, allowedDomains)
|
|
@@ -1317,7 +1334,7 @@ function email(options) {
|
|
|
1317
1334
|
} else {
|
|
1318
1335
|
const validated = validateEmail(input);
|
|
1319
1336
|
if (validated === null) {
|
|
1320
|
-
const errorMsg =
|
|
1337
|
+
const errorMsg = invalidEmail;
|
|
1321
1338
|
const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg ?? require_message.message`Expected a valid email address, but got ${input}.`;
|
|
1322
1339
|
return {
|
|
1323
1340
|
success: false,
|
|
@@ -1329,7 +1346,7 @@ function email(options) {
|
|
|
1329
1346
|
const domain$1 = validated.substring(atIndex + 1).toLowerCase();
|
|
1330
1347
|
const isAllowed = allowedDomains.some((allowed) => domain$1 === allowed.toLowerCase());
|
|
1331
1348
|
if (!isAllowed) {
|
|
1332
|
-
const errorMsg =
|
|
1349
|
+
const errorMsg = domainNotAllowed;
|
|
1333
1350
|
if (typeof errorMsg === "function") return {
|
|
1334
1351
|
success: false,
|
|
1335
1352
|
error: errorMsg(validated, allowedDomains)
|
|
@@ -1774,17 +1791,20 @@ function macAddress(options) {
|
|
|
1774
1791
|
function domain(options) {
|
|
1775
1792
|
const metavar = options?.metavar ?? "DOMAIN";
|
|
1776
1793
|
const allowSubdomains = options?.allowSubdomains ?? true;
|
|
1777
|
-
const allowedTLDs = options?.allowedTLDs;
|
|
1794
|
+
const allowedTLDs = options?.allowedTLDs != null ? Object.freeze([...options.allowedTLDs]) : void 0;
|
|
1778
1795
|
const minLabels = options?.minLabels ?? 2;
|
|
1779
1796
|
const lowercase = options?.lowercase ?? false;
|
|
1780
|
-
const
|
|
1797
|
+
const invalidDomain = options?.errors?.invalidDomain;
|
|
1798
|
+
const tooFewLabels = options?.errors?.tooFewLabels;
|
|
1799
|
+
const subdomainsNotAllowed = options?.errors?.subdomainsNotAllowed;
|
|
1800
|
+
const tldNotAllowed = options?.errors?.tldNotAllowed;
|
|
1781
1801
|
const labelRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
|
|
1782
1802
|
return {
|
|
1783
1803
|
$mode: "sync",
|
|
1784
1804
|
metavar,
|
|
1785
1805
|
parse(input) {
|
|
1786
1806
|
if (input.length === 0 || input.startsWith(".") || input.endsWith(".")) {
|
|
1787
|
-
const errorMsg =
|
|
1807
|
+
const errorMsg = invalidDomain;
|
|
1788
1808
|
if (typeof errorMsg === "function") return {
|
|
1789
1809
|
success: false,
|
|
1790
1810
|
error: errorMsg(input)
|
|
@@ -1809,7 +1829,7 @@ function domain(options) {
|
|
|
1809
1829
|
};
|
|
1810
1830
|
}
|
|
1811
1831
|
if (input.includes("..")) {
|
|
1812
|
-
const errorMsg =
|
|
1832
|
+
const errorMsg = invalidDomain;
|
|
1813
1833
|
if (typeof errorMsg === "function") return {
|
|
1814
1834
|
success: false,
|
|
1815
1835
|
error: errorMsg(input)
|
|
@@ -1835,7 +1855,7 @@ function domain(options) {
|
|
|
1835
1855
|
}
|
|
1836
1856
|
const labels = input.split(".");
|
|
1837
1857
|
for (const label of labels) if (!labelRegex.test(label)) {
|
|
1838
|
-
const errorMsg =
|
|
1858
|
+
const errorMsg = invalidDomain;
|
|
1839
1859
|
if (typeof errorMsg === "function") return {
|
|
1840
1860
|
success: false,
|
|
1841
1861
|
error: errorMsg(input)
|
|
@@ -1860,7 +1880,7 @@ function domain(options) {
|
|
|
1860
1880
|
};
|
|
1861
1881
|
}
|
|
1862
1882
|
if (labels.length < minLabels) {
|
|
1863
|
-
const errorMsg =
|
|
1883
|
+
const errorMsg = tooFewLabels;
|
|
1864
1884
|
if (typeof errorMsg === "function") return {
|
|
1865
1885
|
success: false,
|
|
1866
1886
|
error: errorMsg(input, minLabels)
|
|
@@ -1885,7 +1905,7 @@ function domain(options) {
|
|
|
1885
1905
|
};
|
|
1886
1906
|
}
|
|
1887
1907
|
if (!allowSubdomains && labels.length > 2) {
|
|
1888
|
-
const errorMsg =
|
|
1908
|
+
const errorMsg = subdomainsNotAllowed;
|
|
1889
1909
|
if (typeof errorMsg === "function") return {
|
|
1890
1910
|
success: false,
|
|
1891
1911
|
error: errorMsg(input)
|
|
@@ -1914,7 +1934,7 @@ function domain(options) {
|
|
|
1914
1934
|
const tldLower = tld.toLowerCase();
|
|
1915
1935
|
const allowedTLDsLower = allowedTLDs.map((t) => t.toLowerCase());
|
|
1916
1936
|
if (!allowedTLDsLower.includes(tldLower)) {
|
|
1917
|
-
const errorMsg =
|
|
1937
|
+
const errorMsg = tldNotAllowed;
|
|
1918
1938
|
if (typeof errorMsg === "function") return {
|
|
1919
1939
|
success: false,
|
|
1920
1940
|
error: errorMsg(tld, allowedTLDs)
|
package/dist/valueparser.d.cts
CHANGED
|
@@ -207,6 +207,10 @@ declare function isValueParser<M extends Mode, T>(object: unknown): object is Va
|
|
|
207
207
|
* @returns A {@link ValueParser} that checks if the input matches one of the
|
|
208
208
|
* specified values.
|
|
209
209
|
* @throws {TypeError} If the choices array is empty.
|
|
210
|
+
* @throws {TypeError} If any choice is an empty string.
|
|
211
|
+
* @throws {TypeError} If any choice is not a string.
|
|
212
|
+
* @throws {TypeError} If choices contain a mix of strings and numbers.
|
|
213
|
+
* @throws {TypeError} If `caseInsensitive` is not a boolean.
|
|
210
214
|
* @throws {TypeError} If `caseInsensitive` is `true` and multiple choices
|
|
211
215
|
* normalize to the same lowercase value.
|
|
212
216
|
*/
|
|
@@ -223,6 +227,9 @@ declare function choice<const T extends string>(choices: readonly T[], options?:
|
|
|
223
227
|
* @returns A {@link ValueParser} that checks if the input matches one of the
|
|
224
228
|
* specified values.
|
|
225
229
|
* @throws {TypeError} If the choices array is empty.
|
|
230
|
+
* @throws {TypeError} If any choice is not a number.
|
|
231
|
+
* @throws {TypeError} If any choice is `NaN`.
|
|
232
|
+
* @throws {TypeError} If choices contain a mix of strings and numbers.
|
|
226
233
|
* @since 0.9.0
|
|
227
234
|
*/
|
|
228
235
|
declare function choice<const T extends number>(choices: readonly T[], options?: ChoiceOptionsNumber): ValueParser<"sync", T>;
|
package/dist/valueparser.d.ts
CHANGED
|
@@ -207,6 +207,10 @@ declare function isValueParser<M extends Mode, T>(object: unknown): object is Va
|
|
|
207
207
|
* @returns A {@link ValueParser} that checks if the input matches one of the
|
|
208
208
|
* specified values.
|
|
209
209
|
* @throws {TypeError} If the choices array is empty.
|
|
210
|
+
* @throws {TypeError} If any choice is an empty string.
|
|
211
|
+
* @throws {TypeError} If any choice is not a string.
|
|
212
|
+
* @throws {TypeError} If choices contain a mix of strings and numbers.
|
|
213
|
+
* @throws {TypeError} If `caseInsensitive` is not a boolean.
|
|
210
214
|
* @throws {TypeError} If `caseInsensitive` is `true` and multiple choices
|
|
211
215
|
* normalize to the same lowercase value.
|
|
212
216
|
*/
|
|
@@ -223,6 +227,9 @@ declare function choice<const T extends string>(choices: readonly T[], options?:
|
|
|
223
227
|
* @returns A {@link ValueParser} that checks if the input matches one of the
|
|
224
228
|
* specified values.
|
|
225
229
|
* @throws {TypeError} If the choices array is empty.
|
|
230
|
+
* @throws {TypeError} If any choice is not a number.
|
|
231
|
+
* @throws {TypeError} If any choice is `NaN`.
|
|
232
|
+
* @throws {TypeError} If choices contain a mix of strings and numbers.
|
|
226
233
|
* @since 0.9.0
|
|
227
234
|
*/
|
|
228
235
|
declare function choice<const T extends number>(choices: readonly T[], options?: ChoiceOptionsNumber): ValueParser<"sync", T>;
|
package/dist/valueparser.js
CHANGED
|
@@ -15,10 +15,14 @@ function isValueParser(object) {
|
|
|
15
15
|
*/
|
|
16
16
|
function choice(choices, options = {}) {
|
|
17
17
|
if (choices.length < 1) throw new TypeError("Expected at least one choice, but got an empty array.");
|
|
18
|
+
if (choices.some((c) => c === "")) throw new TypeError("Empty strings are not allowed as choices.");
|
|
19
|
+
for (const c of choices) if (typeof c !== "string" && typeof c !== "number") throw new TypeError(`Expected every choice to be a string or number, but got ${typeof c}.`);
|
|
20
|
+
const isNumber = typeof choices[0] === "number";
|
|
21
|
+
for (const c of choices) if (isNumber ? typeof c !== "number" : typeof c !== "string") throw new TypeError(`Expected every choice to be the same type, but got both ${isNumber ? "number" : "string"} and ${typeof c}.`);
|
|
22
|
+
if (isNumber && choices.some((v) => Number.isNaN(v))) throw new TypeError("NaN is not allowed in number choices.");
|
|
18
23
|
const metavar = options.metavar ?? "TYPE";
|
|
19
24
|
ensureNonEmptyString(metavar);
|
|
20
|
-
|
|
21
|
-
if (isNumberChoice) {
|
|
25
|
+
if (isNumber) {
|
|
22
26
|
const numberChoices = (() => {
|
|
23
27
|
const seen = /* @__PURE__ */ new Set();
|
|
24
28
|
let hasPositiveZero = false;
|
|
@@ -39,17 +43,16 @@ function choice(choices, options = {}) {
|
|
|
39
43
|
}
|
|
40
44
|
return result;
|
|
41
45
|
})();
|
|
42
|
-
const
|
|
43
|
-
const hasNaN = numberChoices.some((v) => Number.isNaN(v));
|
|
44
|
-
const validNumberChoices = hasNaN ? numberChoices.filter((v) => !Number.isNaN(v)) : numberChoices;
|
|
46
|
+
const numberInvalidChoice = options.errors?.invalidChoice;
|
|
45
47
|
const numberStrings = numberChoices.map((v) => Object.is(v, -0) ? "-0" : String(v));
|
|
48
|
+
const frozenNumberChoices = Object.freeze(numberChoices);
|
|
46
49
|
return {
|
|
47
50
|
$mode: "sync",
|
|
48
51
|
metavar,
|
|
49
|
-
choices:
|
|
52
|
+
choices: frozenNumberChoices,
|
|
50
53
|
parse(input) {
|
|
51
54
|
const index = numberStrings.indexOf(input);
|
|
52
|
-
if (index >= 0
|
|
55
|
+
if (index >= 0) return {
|
|
53
56
|
success: true,
|
|
54
57
|
value: numberChoices[index]
|
|
55
58
|
};
|
|
@@ -94,7 +97,7 @@ function choice(choices, options = {}) {
|
|
|
94
97
|
}
|
|
95
98
|
return {
|
|
96
99
|
success: false,
|
|
97
|
-
error: formatNumberChoiceError(input,
|
|
100
|
+
error: formatNumberChoiceError(input, numberChoices, numberChoices, numberInvalidChoice)
|
|
98
101
|
};
|
|
99
102
|
},
|
|
100
103
|
format(value) {
|
|
@@ -108,10 +111,12 @@ function choice(choices, options = {}) {
|
|
|
108
111
|
}
|
|
109
112
|
};
|
|
110
113
|
}
|
|
111
|
-
const stringChoices = [...new Set(choices)];
|
|
114
|
+
const stringChoices = Object.freeze([...new Set(choices)]);
|
|
112
115
|
const stringOptions = options;
|
|
113
|
-
|
|
114
|
-
|
|
116
|
+
if (stringOptions.caseInsensitive !== void 0 && typeof stringOptions.caseInsensitive !== "boolean") throw new TypeError(`Expected caseInsensitive to be a boolean, but got ${typeof stringOptions.caseInsensitive}: ${String(stringOptions.caseInsensitive)}.`);
|
|
117
|
+
const caseInsensitive = stringOptions.caseInsensitive ?? false;
|
|
118
|
+
const normalizedValues = caseInsensitive ? stringChoices.map((v) => v.toLowerCase()) : stringChoices;
|
|
119
|
+
if (caseInsensitive) {
|
|
115
120
|
const seen = /* @__PURE__ */ new Map();
|
|
116
121
|
for (let i = 0; i < stringChoices.length; i++) {
|
|
117
122
|
const nv = normalizedValues[i];
|
|
@@ -121,16 +126,17 @@ function choice(choices, options = {}) {
|
|
|
121
126
|
seen.set(nv, original);
|
|
122
127
|
}
|
|
123
128
|
}
|
|
129
|
+
const stringInvalidChoice = stringOptions.errors?.invalidChoice;
|
|
124
130
|
return {
|
|
125
131
|
$mode: "sync",
|
|
126
132
|
metavar,
|
|
127
133
|
choices: stringChoices,
|
|
128
134
|
parse(input) {
|
|
129
|
-
const normalizedInput =
|
|
135
|
+
const normalizedInput = caseInsensitive ? input.toLowerCase() : input;
|
|
130
136
|
const index = normalizedValues.indexOf(normalizedInput);
|
|
131
137
|
if (index < 0) return {
|
|
132
138
|
success: false,
|
|
133
|
-
error: formatStringChoiceError(input, stringChoices,
|
|
139
|
+
error: formatStringChoiceError(input, stringChoices, stringInvalidChoice)
|
|
134
140
|
};
|
|
135
141
|
return {
|
|
136
142
|
success: true,
|
|
@@ -141,9 +147,9 @@ function choice(choices, options = {}) {
|
|
|
141
147
|
return String(value);
|
|
142
148
|
},
|
|
143
149
|
suggest(prefix) {
|
|
144
|
-
const normalizedPrefix =
|
|
150
|
+
const normalizedPrefix = caseInsensitive ? prefix.toLowerCase() : prefix;
|
|
145
151
|
return stringChoices.filter((value) => {
|
|
146
|
-
const normalizedValue =
|
|
152
|
+
const normalizedValue = caseInsensitive ? value.toLowerCase() : value;
|
|
147
153
|
return normalizedValue.startsWith(normalizedPrefix);
|
|
148
154
|
}).map((value) => ({
|
|
149
155
|
kind: "literal",
|
|
@@ -203,15 +209,15 @@ function normalizeDecimal(s) {
|
|
|
203
209
|
/**
|
|
204
210
|
* Formats error message for string choice parser.
|
|
205
211
|
*/
|
|
206
|
-
function formatStringChoiceError(input, choices,
|
|
207
|
-
if (
|
|
212
|
+
function formatStringChoiceError(input, choices, invalidChoice) {
|
|
213
|
+
if (invalidChoice) return typeof invalidChoice === "function" ? invalidChoice(input, choices) : invalidChoice;
|
|
208
214
|
return formatDefaultChoiceError(input, choices);
|
|
209
215
|
}
|
|
210
216
|
/**
|
|
211
217
|
* Formats error message for number choice parser.
|
|
212
218
|
*/
|
|
213
|
-
function formatNumberChoiceError(input, validChoices, allChoices,
|
|
214
|
-
if (
|
|
219
|
+
function formatNumberChoiceError(input, validChoices, allChoices, invalidChoice) {
|
|
220
|
+
if (invalidChoice) return typeof invalidChoice === "function" ? invalidChoice(input, validChoices) : invalidChoice;
|
|
215
221
|
return formatDefaultChoiceError(input, allChoices);
|
|
216
222
|
}
|
|
217
223
|
/**
|
|
@@ -242,19 +248,22 @@ function string(options = {}) {
|
|
|
242
248
|
if (options.pattern != null && !(options.pattern instanceof RegExp)) throw new TypeError(`Expected pattern to be a RegExp, but got: ${Object.prototype.toString.call(options.pattern)}`);
|
|
243
249
|
const metavar = options.metavar ?? "STRING";
|
|
244
250
|
ensureNonEmptyString(metavar);
|
|
251
|
+
const patternSource = options.pattern?.source ?? null;
|
|
252
|
+
const patternFlags = options.pattern?.flags ?? null;
|
|
253
|
+
const patternMismatch = options.errors?.patternMismatch;
|
|
245
254
|
return {
|
|
246
255
|
$mode: "sync",
|
|
247
256
|
metavar,
|
|
248
257
|
parse(input) {
|
|
249
|
-
if (
|
|
250
|
-
const pattern = new RegExp(
|
|
258
|
+
if (patternSource != null && patternFlags != null) {
|
|
259
|
+
const pattern = new RegExp(patternSource, patternFlags);
|
|
251
260
|
if (pattern.test(input)) return {
|
|
252
261
|
success: true,
|
|
253
262
|
value: input
|
|
254
263
|
};
|
|
255
264
|
return {
|
|
256
265
|
success: false,
|
|
257
|
-
error:
|
|
266
|
+
error: patternMismatch ? typeof patternMismatch === "function" ? patternMismatch(input, pattern) : patternMismatch : message`Expected a string matching pattern ${text(patternSource)}, but got ${input}.`
|
|
258
267
|
};
|
|
259
268
|
}
|
|
260
269
|
return {
|
|
@@ -440,21 +449,24 @@ function float(options = {}) {
|
|
|
440
449
|
* @returns A {@link ValueParser} that converts string input to `URL` objects.
|
|
441
450
|
*/
|
|
442
451
|
function url(options = {}) {
|
|
443
|
-
const
|
|
452
|
+
const originalProtocols = options.allowedProtocols != null ? Object.freeze([...options.allowedProtocols]) : void 0;
|
|
453
|
+
const allowedProtocols = options.allowedProtocols != null ? Object.freeze(options.allowedProtocols.map((p) => p.toLowerCase())) : void 0;
|
|
444
454
|
const metavar = options.metavar ?? "URL";
|
|
445
455
|
ensureNonEmptyString(metavar);
|
|
456
|
+
const invalidUrl = options.errors?.invalidUrl;
|
|
457
|
+
const disallowedProtocol = options.errors?.disallowedProtocol;
|
|
446
458
|
return {
|
|
447
459
|
$mode: "sync",
|
|
448
460
|
metavar,
|
|
449
461
|
parse(input) {
|
|
450
462
|
if (!URL.canParse(input)) return {
|
|
451
463
|
success: false,
|
|
452
|
-
error:
|
|
464
|
+
error: invalidUrl ? typeof invalidUrl === "function" ? invalidUrl(input) : invalidUrl : message`Invalid URL: ${input}.`
|
|
453
465
|
};
|
|
454
466
|
const url$1 = new URL(input);
|
|
455
467
|
if (allowedProtocols != null && !allowedProtocols.includes(url$1.protocol)) return {
|
|
456
468
|
success: false,
|
|
457
|
-
error:
|
|
469
|
+
error: disallowedProtocol ? typeof disallowedProtocol === "function" ? disallowedProtocol(url$1.protocol, originalProtocols) : disallowedProtocol : message`URL protocol ${url$1.protocol} is not allowed. Allowed protocols: ${allowedProtocols.join(", ")}.`
|
|
458
470
|
};
|
|
459
471
|
return {
|
|
460
472
|
success: true,
|
|
@@ -508,7 +520,7 @@ function locale(options = {}) {
|
|
|
508
520
|
};
|
|
509
521
|
},
|
|
510
522
|
format(value) {
|
|
511
|
-
return value.
|
|
523
|
+
return value.toString();
|
|
512
524
|
},
|
|
513
525
|
*suggest(prefix) {
|
|
514
526
|
const commonLocales = [
|
|
@@ -758,24 +770,27 @@ function uuid(options = {}) {
|
|
|
758
770
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
759
771
|
const metavar = options.metavar ?? "UUID";
|
|
760
772
|
ensureNonEmptyString(metavar);
|
|
773
|
+
const allowedVersions = options.allowedVersions != null ? Object.freeze([...options.allowedVersions]) : null;
|
|
774
|
+
const invalidUuid = options.errors?.invalidUuid;
|
|
775
|
+
const disallowedVersion = options.errors?.disallowedVersion;
|
|
761
776
|
return {
|
|
762
777
|
$mode: "sync",
|
|
763
778
|
metavar,
|
|
764
779
|
parse(input) {
|
|
765
780
|
if (!uuidRegex.test(input)) return {
|
|
766
781
|
success: false,
|
|
767
|
-
error:
|
|
782
|
+
error: invalidUuid ? typeof invalidUuid === "function" ? invalidUuid(input) : invalidUuid : message`Expected a valid UUID in format ${"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}, but got ${input}.`
|
|
768
783
|
};
|
|
769
|
-
if (
|
|
784
|
+
if (allowedVersions != null && allowedVersions.length > 0) {
|
|
770
785
|
const versionChar = input.charAt(14);
|
|
771
786
|
const version = parseInt(versionChar, 16);
|
|
772
|
-
if (!
|
|
787
|
+
if (!allowedVersions.includes(version)) return {
|
|
773
788
|
success: false,
|
|
774
|
-
error:
|
|
789
|
+
error: disallowedVersion ? typeof disallowedVersion === "function" ? disallowedVersion(version, allowedVersions) : disallowedVersion : (() => {
|
|
775
790
|
let expectedVersions = message``;
|
|
776
791
|
let i = 0;
|
|
777
|
-
for (const v of
|
|
778
|
-
expectedVersions = i < 1 ? message`${expectedVersions}${v.toLocaleString("en")}` : i + 1 >=
|
|
792
|
+
for (const v of allowedVersions) {
|
|
793
|
+
expectedVersions = i < 1 ? message`${expectedVersions}${v.toLocaleString("en")}` : i + 1 >= allowedVersions.length ? message`${expectedVersions}, or ${v.toLocaleString("en")}` : message`${expectedVersions}, ${v.toLocaleString("en")}`;
|
|
779
794
|
i++;
|
|
780
795
|
}
|
|
781
796
|
return message`Expected UUID version ${expectedVersions}, but got version ${version.toLocaleString("en")}.`;
|
|
@@ -1220,7 +1235,9 @@ function email(options) {
|
|
|
1220
1235
|
const allowMultiple = options?.allowMultiple ?? false;
|
|
1221
1236
|
const allowDisplayName = options?.allowDisplayName ?? false;
|
|
1222
1237
|
const lowercase = options?.lowercase ?? false;
|
|
1223
|
-
const allowedDomains = options?.allowedDomains;
|
|
1238
|
+
const allowedDomains = options?.allowedDomains != null ? Object.freeze([...options.allowedDomains]) : void 0;
|
|
1239
|
+
const invalidEmail = options?.errors?.invalidEmail;
|
|
1240
|
+
const domainNotAllowed = options?.errors?.domainNotAllowed;
|
|
1224
1241
|
const atextRegex = /^[a-zA-Z0-9._+-]+$/;
|
|
1225
1242
|
function validateEmail(input) {
|
|
1226
1243
|
const trimmed = input.trim();
|
|
@@ -1271,7 +1288,7 @@ function email(options) {
|
|
|
1271
1288
|
for (const email$1 of emails) {
|
|
1272
1289
|
const validated = validateEmail(email$1);
|
|
1273
1290
|
if (validated === null) {
|
|
1274
|
-
const errorMsg =
|
|
1291
|
+
const errorMsg = invalidEmail;
|
|
1275
1292
|
const msg = typeof errorMsg === "function" ? errorMsg(email$1) : errorMsg ?? message`Expected a valid email address, but got ${email$1}.`;
|
|
1276
1293
|
return {
|
|
1277
1294
|
success: false,
|
|
@@ -1283,7 +1300,7 @@ function email(options) {
|
|
|
1283
1300
|
const domain$1 = validated.substring(atIndex + 1).toLowerCase();
|
|
1284
1301
|
const isAllowed = allowedDomains.some((allowed) => domain$1 === allowed.toLowerCase());
|
|
1285
1302
|
if (!isAllowed) {
|
|
1286
|
-
const errorMsg =
|
|
1303
|
+
const errorMsg = domainNotAllowed;
|
|
1287
1304
|
if (typeof errorMsg === "function") return {
|
|
1288
1305
|
success: false,
|
|
1289
1306
|
error: errorMsg(validated, allowedDomains)
|
|
@@ -1317,7 +1334,7 @@ function email(options) {
|
|
|
1317
1334
|
} else {
|
|
1318
1335
|
const validated = validateEmail(input);
|
|
1319
1336
|
if (validated === null) {
|
|
1320
|
-
const errorMsg =
|
|
1337
|
+
const errorMsg = invalidEmail;
|
|
1321
1338
|
const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg ?? message`Expected a valid email address, but got ${input}.`;
|
|
1322
1339
|
return {
|
|
1323
1340
|
success: false,
|
|
@@ -1329,7 +1346,7 @@ function email(options) {
|
|
|
1329
1346
|
const domain$1 = validated.substring(atIndex + 1).toLowerCase();
|
|
1330
1347
|
const isAllowed = allowedDomains.some((allowed) => domain$1 === allowed.toLowerCase());
|
|
1331
1348
|
if (!isAllowed) {
|
|
1332
|
-
const errorMsg =
|
|
1349
|
+
const errorMsg = domainNotAllowed;
|
|
1333
1350
|
if (typeof errorMsg === "function") return {
|
|
1334
1351
|
success: false,
|
|
1335
1352
|
error: errorMsg(validated, allowedDomains)
|
|
@@ -1774,17 +1791,20 @@ function macAddress(options) {
|
|
|
1774
1791
|
function domain(options) {
|
|
1775
1792
|
const metavar = options?.metavar ?? "DOMAIN";
|
|
1776
1793
|
const allowSubdomains = options?.allowSubdomains ?? true;
|
|
1777
|
-
const allowedTLDs = options?.allowedTLDs;
|
|
1794
|
+
const allowedTLDs = options?.allowedTLDs != null ? Object.freeze([...options.allowedTLDs]) : void 0;
|
|
1778
1795
|
const minLabels = options?.minLabels ?? 2;
|
|
1779
1796
|
const lowercase = options?.lowercase ?? false;
|
|
1780
|
-
const
|
|
1797
|
+
const invalidDomain = options?.errors?.invalidDomain;
|
|
1798
|
+
const tooFewLabels = options?.errors?.tooFewLabels;
|
|
1799
|
+
const subdomainsNotAllowed = options?.errors?.subdomainsNotAllowed;
|
|
1800
|
+
const tldNotAllowed = options?.errors?.tldNotAllowed;
|
|
1781
1801
|
const labelRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
|
|
1782
1802
|
return {
|
|
1783
1803
|
$mode: "sync",
|
|
1784
1804
|
metavar,
|
|
1785
1805
|
parse(input) {
|
|
1786
1806
|
if (input.length === 0 || input.startsWith(".") || input.endsWith(".")) {
|
|
1787
|
-
const errorMsg =
|
|
1807
|
+
const errorMsg = invalidDomain;
|
|
1788
1808
|
if (typeof errorMsg === "function") return {
|
|
1789
1809
|
success: false,
|
|
1790
1810
|
error: errorMsg(input)
|
|
@@ -1809,7 +1829,7 @@ function domain(options) {
|
|
|
1809
1829
|
};
|
|
1810
1830
|
}
|
|
1811
1831
|
if (input.includes("..")) {
|
|
1812
|
-
const errorMsg =
|
|
1832
|
+
const errorMsg = invalidDomain;
|
|
1813
1833
|
if (typeof errorMsg === "function") return {
|
|
1814
1834
|
success: false,
|
|
1815
1835
|
error: errorMsg(input)
|
|
@@ -1835,7 +1855,7 @@ function domain(options) {
|
|
|
1835
1855
|
}
|
|
1836
1856
|
const labels = input.split(".");
|
|
1837
1857
|
for (const label of labels) if (!labelRegex.test(label)) {
|
|
1838
|
-
const errorMsg =
|
|
1858
|
+
const errorMsg = invalidDomain;
|
|
1839
1859
|
if (typeof errorMsg === "function") return {
|
|
1840
1860
|
success: false,
|
|
1841
1861
|
error: errorMsg(input)
|
|
@@ -1860,7 +1880,7 @@ function domain(options) {
|
|
|
1860
1880
|
};
|
|
1861
1881
|
}
|
|
1862
1882
|
if (labels.length < minLabels) {
|
|
1863
|
-
const errorMsg =
|
|
1883
|
+
const errorMsg = tooFewLabels;
|
|
1864
1884
|
if (typeof errorMsg === "function") return {
|
|
1865
1885
|
success: false,
|
|
1866
1886
|
error: errorMsg(input, minLabels)
|
|
@@ -1885,7 +1905,7 @@ function domain(options) {
|
|
|
1885
1905
|
};
|
|
1886
1906
|
}
|
|
1887
1907
|
if (!allowSubdomains && labels.length > 2) {
|
|
1888
|
-
const errorMsg =
|
|
1908
|
+
const errorMsg = subdomainsNotAllowed;
|
|
1889
1909
|
if (typeof errorMsg === "function") return {
|
|
1890
1910
|
success: false,
|
|
1891
1911
|
error: errorMsg(input)
|
|
@@ -1914,7 +1934,7 @@ function domain(options) {
|
|
|
1914
1934
|
const tldLower = tld.toLowerCase();
|
|
1915
1935
|
const allowedTLDsLower = allowedTLDs.map((t) => t.toLowerCase());
|
|
1916
1936
|
if (!allowedTLDsLower.includes(tldLower)) {
|
|
1917
|
-
const errorMsg =
|
|
1937
|
+
const errorMsg = tldNotAllowed;
|
|
1918
1938
|
if (typeof errorMsg === "function") return {
|
|
1919
1939
|
success: false,
|
|
1920
1940
|
error: errorMsg(tld, allowedTLDs)
|