@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/errors.ts +35 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/schema",
3
- "version": "4.0.0",
3
+ "version": "5.0.0",
4
4
  "description": "Ultimate's validation seam: Standard Schema interface, the t namespace, JSON Schema output",
5
5
  "license": "MIT",
6
6
  "type": "module",
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
- const title = TITLES[init.code] ?? humanize(init.code);
75
- super(`${init.code}: ${title}`, { cause: init.cause });
76
- this.code = init.code;
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',