@ultimat3/schema 4.0.0 → 5.0.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/package.json +1 -1
- package/src/errors.ts +35 -6
package/package.json
CHANGED
package/src/errors.ts
CHANGED
|
@@ -4,6 +4,31 @@
|
|
|
4
4
|
|
|
5
5
|
import { formatIssues } from './standard';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* The same escape `@ultimat3/core`'s `singleLine` performs. **Keep in sync**, and read the reason
|
|
9
|
+
* there — briefly: the 3-line format is line-oriented, and a `cause` may hold a value the caller
|
|
10
|
+
* chose, so a newline in one writes a line an operator reads as a genuine framework message.
|
|
11
|
+
*
|
|
12
|
+
* Copied rather than imported for the same reason the brand symbol above is: `schema` and `core`
|
|
13
|
+
* are both tier 0, so `schema` may not import `core` (imports go DOWN, never sideways). A schema
|
|
14
|
+
* cause is the one most likely to carry a hostile string — it describes the value that failed
|
|
15
|
+
* validation, which is the request body.
|
|
16
|
+
*/
|
|
17
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: escaping them is the point.
|
|
18
|
+
const CONTROL = /[\u0000-\u001f\u007f\u2028\u2029]/g;
|
|
19
|
+
const ESCAPES: Readonly<Record<string, string>> = {
|
|
20
|
+
'\n': String.raw`\n`,
|
|
21
|
+
'\r': String.raw`\r`,
|
|
22
|
+
'\t': String.raw`\t`,
|
|
23
|
+
'\b': String.raw`\b`,
|
|
24
|
+
'\f': String.raw`\f`,
|
|
25
|
+
};
|
|
26
|
+
const singleLine = (text: string): string =>
|
|
27
|
+
text.replace(
|
|
28
|
+
CONTROL,
|
|
29
|
+
(char) => ESCAPES[char] ?? `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`,
|
|
30
|
+
);
|
|
31
|
+
|
|
7
32
|
/** Same well-known symbol `@ultimat3/core` brands with. Keep in sync, never rename. */
|
|
8
33
|
export const ULTIMATE_ERROR_BRAND: unique symbol = Symbol.for('ultimate.error');
|
|
9
34
|
|
|
@@ -71,16 +96,20 @@ export class SchemaError extends Error {
|
|
|
71
96
|
readonly meta: Readonly<Record<string, unknown>> | undefined;
|
|
72
97
|
|
|
73
98
|
constructor(init: SchemaErrorInit) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
99
|
+
// Escaped HERE, once, exactly as `UltimateError`'s constructor does — read the reason there.
|
|
100
|
+
// A schema cause is the one most likely to carry a hostile string: it describes the value that
|
|
101
|
+
// failed validation, which is the request body.
|
|
102
|
+
const code = singleLine(init.code);
|
|
103
|
+
const title = singleLine(TITLES[init.code] ?? humanize(init.code));
|
|
104
|
+
super(`${code}: ${title}`, { cause: singleLine(init.cause) });
|
|
105
|
+
this.code = code;
|
|
77
106
|
this.title = title;
|
|
78
|
-
this.fix = init.fix;
|
|
79
|
-
this.docs = init.docs ?? `https://ultimate.dev/errors/${init.code}
|
|
107
|
+
this.fix = singleLine(init.fix);
|
|
108
|
+
this.docs = singleLine(init.docs ?? `https://ultimate.dev/errors/${init.code}`);
|
|
80
109
|
this.meta = init.meta;
|
|
81
110
|
}
|
|
82
111
|
|
|
83
|
-
/** The same 3-line rendering as `UltimateError.format()
|
|
112
|
+
/** The same 3-line rendering as `UltimateError.format()`, escaped in the same place: neither. */
|
|
84
113
|
format(): string {
|
|
85
114
|
return [`${this.code}: ${this.title}`, ` cause: ${this.cause}`, ` fix: ${this.fix}`].join(
|
|
86
115
|
'\n',
|