@nextrush/errors 3.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.
@@ -0,0 +1,210 @@
1
+ /**
2
+ * @nextrush/errors - Validation Error Classes
3
+ *
4
+ * Specialized errors for input validation.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+
9
+ import { NextRushError } from './base';
10
+
11
+ /**
12
+ * Single validation issue
13
+ */
14
+ export interface ValidationIssue {
15
+ /** Field path (e.g., 'user.email' or 'items[0].name') */
16
+ path: string;
17
+ /** Error message for this field */
18
+ message: string;
19
+ /** Validation rule that failed */
20
+ rule?: string;
21
+ /** Expected value or constraint */
22
+ expected?: unknown;
23
+ /** Actual value received */
24
+ received?: unknown;
25
+ }
26
+
27
+ /**
28
+ * Validation error with multiple issues
29
+ */
30
+ export class ValidationError extends NextRushError {
31
+ /** List of validation issues */
32
+ readonly issues: ValidationIssue[];
33
+
34
+ constructor(issues: ValidationIssue[], message = 'Validation failed') {
35
+ super(message, {
36
+ status: 400,
37
+ code: 'VALIDATION_ERROR',
38
+ expose: true,
39
+ });
40
+ this.issues = issues;
41
+ }
42
+
43
+ /**
44
+ * Create from a single field error
45
+ */
46
+ static fromField(path: string, message: string, rule?: string): ValidationError {
47
+ return new ValidationError([{ path, message, rule }]);
48
+ }
49
+
50
+ /**
51
+ * Create from multiple field errors
52
+ */
53
+ static fromFields(errors: Record<string, string>): ValidationError {
54
+ const issues = Object.entries(errors).map(([path, message]) => ({
55
+ path,
56
+ message,
57
+ }));
58
+ return new ValidationError(issues);
59
+ }
60
+
61
+ /**
62
+ * Check if a specific field has errors
63
+ */
64
+ hasErrorFor(path: string): boolean {
65
+ return this.issues.some((issue) => issue.path === path);
66
+ }
67
+
68
+ /**
69
+ * Get errors for a specific field
70
+ */
71
+ getErrorsFor(path: string): ValidationIssue[] {
72
+ return this.issues.filter((issue) => issue.path === path);
73
+ }
74
+
75
+ /**
76
+ * Get first error message for a field
77
+ */
78
+ getFirstError(path: string): string | undefined {
79
+ return this.issues.find((issue) => issue.path === path)?.message;
80
+ }
81
+
82
+ /**
83
+ * Convert to flat error object
84
+ */
85
+ toFlatObject(): Record<string, string> {
86
+ const result: Record<string, string> = {};
87
+ for (const issue of this.issues) {
88
+ if (!result[issue.path]) {
89
+ result[issue.path] = issue.message;
90
+ }
91
+ }
92
+ return result;
93
+ }
94
+
95
+ override toJSON(): Record<string, unknown> {
96
+ return {
97
+ error: this.name,
98
+ message: this.message,
99
+ code: this.code,
100
+ status: this.status,
101
+ // Strip `received` to prevent leaking sensitive input values (passwords, tokens)
102
+ issues: this.issues.map(({ path, message, rule, expected }) => ({
103
+ path,
104
+ message,
105
+ ...(rule !== undefined && { rule }),
106
+ ...(expected !== undefined && { expected }),
107
+ })),
108
+ };
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Required field missing
114
+ */
115
+ export class RequiredFieldError extends ValidationError {
116
+ constructor(field: string) {
117
+ super(
118
+ [{ path: field, message: `${field} is required`, rule: 'required' }],
119
+ `${field} is required`
120
+ );
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Field type mismatch
126
+ */
127
+ export class TypeMismatchError extends ValidationError {
128
+ constructor(field: string, expected: string, received: string) {
129
+ super(
130
+ [
131
+ {
132
+ path: field,
133
+ message: `Expected ${expected}, received ${received}`,
134
+ rule: 'type',
135
+ expected,
136
+ received,
137
+ },
138
+ ],
139
+ `${field} must be of type ${expected}`
140
+ );
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Value out of range
146
+ */
147
+ export class RangeValidationError extends ValidationError {
148
+ constructor(field: string, min?: number, max?: number) {
149
+ const parts: string[] = [];
150
+ if (min !== undefined) parts.push(`at least ${min}`);
151
+ if (max !== undefined) parts.push(`at most ${max}`);
152
+ const message = `${field} must be ${parts.join(' and ')}`;
153
+
154
+ super([{ path: field, message, rule: 'range', expected: { min, max } }], message);
155
+ }
156
+ }
157
+
158
+ /**
159
+ * String length violation
160
+ */
161
+ export class LengthError extends ValidationError {
162
+ constructor(field: string, min?: number, max?: number) {
163
+ const parts: string[] = [];
164
+ if (min !== undefined) parts.push(`at least ${min} characters`);
165
+ if (max !== undefined) parts.push(`at most ${max} characters`);
166
+ const message = `${field} must be ${parts.join(' and ')}`;
167
+
168
+ super([{ path: field, message, rule: 'length', expected: { min, max } }], message);
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Pattern mismatch
174
+ */
175
+ export class PatternError extends ValidationError {
176
+ constructor(field: string, pattern: string, message?: string) {
177
+ super(
178
+ [
179
+ {
180
+ path: field,
181
+ message: message ?? `${field} does not match required pattern`,
182
+ rule: 'pattern',
183
+ expected: pattern,
184
+ },
185
+ ],
186
+ message ?? `${field} does not match required pattern`
187
+ );
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Invalid email format
193
+ */
194
+ export class InvalidEmailError extends ValidationError {
195
+ constructor(field = 'email') {
196
+ super(
197
+ [{ path: field, message: 'Invalid email address', rule: 'email' }],
198
+ 'Invalid email address'
199
+ );
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Invalid URL format
205
+ */
206
+ export class InvalidUrlError extends ValidationError {
207
+ constructor(field = 'url') {
208
+ super([{ path: field, message: 'Invalid URL', rule: 'url' }], 'Invalid URL');
209
+ }
210
+ }