@codewithagents/api-errors 0.1.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 ADDED
@@ -0,0 +1,113 @@
1
+ # @codewithagents/api-errors
2
+
3
+ Backend API errors don't automatically map to form field errors. Every project writes the same glue code from scratch: catch the error, inspect its shape, figure out which field each message belongs to, then call your form library's error setter. This package does that for you — framework-agnostic core with a first-class React Hook Form adapter.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @codewithagents/api-errors
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### With React Hook Form
14
+
15
+ ```tsx
16
+ import { useForm } from 'react-hook-form'
17
+ import { mapApiErrors } from '@codewithagents/api-errors'
18
+
19
+ function SignupForm() {
20
+ const { register, handleSubmit, setError, formState: { errors } } = useForm()
21
+
22
+ const onSubmit = async (data) => {
23
+ try {
24
+ await api.post('/signup', data)
25
+ } catch (error) {
26
+ // Automatically maps backend field errors to RHF setError calls
27
+ mapApiErrors(error, setError)
28
+ }
29
+ }
30
+
31
+ return (
32
+ <form onSubmit={handleSubmit(onSubmit)}>
33
+ <input {...register('email')} />
34
+ {errors.email && <p>{errors.email.message}</p>}
35
+
36
+ <input {...register('name')} />
37
+ {errors.name && <p>{errors.name.message}</p>}
38
+
39
+ <button type="submit">Sign up</button>
40
+ </form>
41
+ )
42
+ }
43
+ ```
44
+
45
+ ### Standalone with `extractFieldErrors`
46
+
47
+ Use `extractFieldErrors` when you need the normalized error list without a form library:
48
+
49
+ ```ts
50
+ import { extractFieldErrors } from '@codewithagents/api-errors'
51
+
52
+ try {
53
+ await submitData(payload)
54
+ } catch (error) {
55
+ const fieldErrors = extractFieldErrors(error)
56
+ // [{ field: 'email', message: 'must not be blank' }, ...]
57
+
58
+ for (const { field, message } of fieldErrors) {
59
+ console.warn(`${field}: ${message}`)
60
+ }
61
+ }
62
+ ```
63
+
64
+ You can customize the fallback field name and transform field names:
65
+
66
+ ```ts
67
+ const fieldErrors = extractFieldErrors(error, {
68
+ fallbackField: 'root', // used when no field is found (default: 'root')
69
+ transformField: (f) => f.replace(/([A-Z])/g, '.$1').toLowerCase(), // camelCase → dot.path
70
+ })
71
+ ```
72
+
73
+ ## Supported Error Formats
74
+
75
+ **RFC 7807 Problem Details** (Spring Boot 3+, standard):
76
+ ```json
77
+ {
78
+ "type": "...",
79
+ "title": "Validation failed",
80
+ "status": 400,
81
+ "errors": {
82
+ "email": ["must not be blank"],
83
+ "name": ["too short"]
84
+ }
85
+ }
86
+ ```
87
+
88
+ **Spring Boot default validation format** (older):
89
+ ```json
90
+ {
91
+ "status": 400,
92
+ "errors": [
93
+ { "field": "email", "defaultMessage": "must not be blank" }
94
+ ]
95
+ }
96
+ ```
97
+
98
+ **Simple flat format** (custom APIs):
99
+ ```json
100
+ { "field": "email", "message": "Invalid email" }
101
+ ```
102
+ or as an array:
103
+ ```json
104
+ [{ "field": "email", "message": "Invalid email" }]
105
+ ```
106
+
107
+ ## TypeScript
108
+
109
+ This package is written in TypeScript and ships full type declarations. No `@types` package needed.
110
+
111
+ ```ts
112
+ import type { FieldError, MapApiErrorsOptions } from '@codewithagents/api-errors'
113
+ ```
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Normalized representation of a single field-level error.
3
+ */
4
+ export interface FieldError {
5
+ field: string;
6
+ message: string;
7
+ }
8
+ /**
9
+ * Options for mapApiErrors and extractFieldErrors.
10
+ */
11
+ export interface MapApiErrorsOptions {
12
+ /**
13
+ * Fallback field name when no field can be determined from the error.
14
+ * Defaults to 'root'.
15
+ */
16
+ fallbackField?: string;
17
+ /**
18
+ * Optional transform applied to every field name before it is returned.
19
+ * Useful for mapping backend camelCase field names to nested React Hook Form
20
+ * paths, e.g. `"addressCity"` → `"address.city"`.
21
+ */
22
+ transformField?: (field: string) => string;
23
+ }
24
+ /**
25
+ * Extract normalized field errors from an unknown API error response.
26
+ *
27
+ * Supports:
28
+ * - RFC 7807 Problem Details (Spring Boot 3+)
29
+ * - Spring Boot default validation format
30
+ * - Simple flat `{ field, message }` object
31
+ * - Array of `{ field, message }` objects
32
+ *
33
+ * Never throws — returns an empty array for unrecognized shapes.
34
+ */
35
+ export declare function extractFieldErrors(error: unknown, options?: MapApiErrorsOptions): FieldError[];
36
+ /**
37
+ * React Hook Form adapter.
38
+ *
39
+ * Extracts field errors from an unknown API error response and calls
40
+ * React Hook Form's `setError` for each one.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * try {
45
+ * await submitForm(data)
46
+ * } catch (error) {
47
+ * mapApiErrors(error, setError)
48
+ * }
49
+ * ```
50
+ */
51
+ export declare function mapApiErrors(error: unknown, setError: (field: string, error: {
52
+ type: string;
53
+ message: string;
54
+ }) => void, options?: MapApiErrorsOptions): void;
55
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;CAC3C;AAmID;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,mBAAmB,GAC5B,UAAU,EAAE,CAyCd;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,EAC3E,OAAO,CAAC,EAAE,mBAAmB,GAC5B,IAAI,CAKN"}
package/dist/index.js ADDED
@@ -0,0 +1,163 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Internal type guards
3
+ // ---------------------------------------------------------------------------
4
+ function isObject(value) {
5
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
6
+ }
7
+ function isStringArray(value) {
8
+ return Array.isArray(value) && value.every((v) => typeof v === 'string');
9
+ }
10
+ // ---------------------------------------------------------------------------
11
+ // Format detectors + parsers
12
+ // ---------------------------------------------------------------------------
13
+ /**
14
+ * RFC 7807 Problem Details (Spring Boot 3+ default):
15
+ * { "errors": { "email": ["must not be blank"], "name": ["too short"] } }
16
+ */
17
+ function tryParseRfc7807(body, fallbackField, transformField) {
18
+ const { errors } = body;
19
+ if (!isObject(errors))
20
+ return null;
21
+ const result = [];
22
+ for (const [rawField, messages] of Object.entries(errors)) {
23
+ const field = transformField(rawField);
24
+ if (isStringArray(messages)) {
25
+ for (const message of messages) {
26
+ result.push({ field, message });
27
+ }
28
+ }
29
+ else if (typeof messages === 'string') {
30
+ result.push({ field, message: messages });
31
+ }
32
+ else {
33
+ // Unknown value shape — use fallback field with stringified message
34
+ result.push({ field: transformField(fallbackField), message: String(messages) });
35
+ }
36
+ }
37
+ return result.length > 0 ? result : null;
38
+ }
39
+ /**
40
+ * Spring Boot default validation format (pre-3):
41
+ * { "errors": [{ "field": "email", "defaultMessage": "must not be blank" }] }
42
+ */
43
+ function tryParseSpringArray(body, fallbackField, transformField) {
44
+ const { errors } = body;
45
+ if (!Array.isArray(errors))
46
+ return null;
47
+ const result = [];
48
+ for (const item of errors) {
49
+ if (!isObject(item))
50
+ continue;
51
+ const rawField = typeof item['field'] === 'string' ? item['field'] : fallbackField;
52
+ const message = typeof item['defaultMessage'] === 'string'
53
+ ? item['defaultMessage']
54
+ : typeof item['message'] === 'string'
55
+ ? item['message']
56
+ : 'Unknown error';
57
+ result.push({ field: transformField(rawField), message });
58
+ }
59
+ return result.length > 0 ? result : null;
60
+ }
61
+ /**
62
+ * Simple flat object format:
63
+ * { "field": "email", "message": "Invalid email" }
64
+ */
65
+ function tryParseFlatObject(body, fallbackField, transformField) {
66
+ const rawField = typeof body['field'] === 'string' ? body['field'] : null;
67
+ const message = typeof body['message'] === 'string' ? body['message'] : null;
68
+ if (message === null)
69
+ return null;
70
+ const field = rawField !== null ? rawField : fallbackField;
71
+ return [{ field: transformField(field), message }];
72
+ }
73
+ /**
74
+ * Array of simple flat objects:
75
+ * [{ "field": "email", "message": "Invalid email" }]
76
+ */
77
+ function tryParseFlatArray(body, fallbackField, transformField) {
78
+ const result = [];
79
+ for (const item of body) {
80
+ if (!isObject(item))
81
+ continue;
82
+ const rawField = typeof item['field'] === 'string' ? item['field'] : fallbackField;
83
+ const message = typeof item['message'] === 'string' ? item['message'] : null;
84
+ if (message === null)
85
+ continue;
86
+ result.push({ field: transformField(rawField), message });
87
+ }
88
+ return result.length > 0 ? result : null;
89
+ }
90
+ // ---------------------------------------------------------------------------
91
+ // Public API
92
+ // ---------------------------------------------------------------------------
93
+ /**
94
+ * Extract normalized field errors from an unknown API error response.
95
+ *
96
+ * Supports:
97
+ * - RFC 7807 Problem Details (Spring Boot 3+)
98
+ * - Spring Boot default validation format
99
+ * - Simple flat `{ field, message }` object
100
+ * - Array of `{ field, message }` objects
101
+ *
102
+ * Never throws — returns an empty array for unrecognized shapes.
103
+ */
104
+ export function extractFieldErrors(error, options) {
105
+ const fallbackField = options?.fallbackField ?? 'root';
106
+ const transformField = options?.transformField ?? ((f) => f);
107
+ try {
108
+ // Unwrap common error wrapper shapes (e.g. Axios response, fetch response body)
109
+ let body = error;
110
+ // Support objects with a `response.data` shape (Axios-style)
111
+ if (isObject(body) && isObject(body['response']) && body['response']['data'] !== undefined) {
112
+ body = body['response']['data'];
113
+ }
114
+ // Support objects with a `data` property that contains the actual error body
115
+ if (isObject(body) && isObject(body['data']) && !('field' in body) && !('errors' in body)) {
116
+ body = body['data'];
117
+ }
118
+ // Array of flat error objects
119
+ if (Array.isArray(body)) {
120
+ return tryParseFlatArray(body, fallbackField, transformField) ?? [];
121
+ }
122
+ if (!isObject(body))
123
+ return [];
124
+ // Try RFC 7807 / Spring Boot 3 format first (object-style errors map)
125
+ const rfc7807 = tryParseRfc7807(body, fallbackField, transformField);
126
+ if (rfc7807 !== null)
127
+ return rfc7807;
128
+ // Try Spring Boot default format (array-style errors list)
129
+ const springArray = tryParseSpringArray(body, fallbackField, transformField);
130
+ if (springArray !== null)
131
+ return springArray;
132
+ // Try simple flat object
133
+ const flat = tryParseFlatObject(body, fallbackField, transformField);
134
+ if (flat !== null)
135
+ return flat;
136
+ return [];
137
+ }
138
+ catch {
139
+ return [];
140
+ }
141
+ }
142
+ /**
143
+ * React Hook Form adapter.
144
+ *
145
+ * Extracts field errors from an unknown API error response and calls
146
+ * React Hook Form's `setError` for each one.
147
+ *
148
+ * @example
149
+ * ```ts
150
+ * try {
151
+ * await submitForm(data)
152
+ * } catch (error) {
153
+ * mapApiErrors(error, setError)
154
+ * }
155
+ * ```
156
+ */
157
+ export function mapApiErrors(error, setError, options) {
158
+ const fieldErrors = extractFieldErrors(error, options);
159
+ for (const { field, message } of fieldErrors) {
160
+ setError(field, { type: 'server', message });
161
+ }
162
+ }
163
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAyBA,8EAA8E;AAC9E,uBAAuB;AACvB,8EAA8E;AAE9E,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;AAC7E,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAA;AAC1E,CAAC;AAED,8EAA8E;AAC9E,6BAA6B;AAC7B,8EAA8E;AAE9E;;;GAGG;AACH,SAAS,eAAe,CACtB,IAA6B,EAC7B,aAAqB,EACrB,cAAyC;IAEzC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;IACvB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAA;IAElC,MAAM,MAAM,GAAiB,EAAE,CAAA;IAE/B,KAAK,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1D,MAAM,KAAK,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAA;QACtC,IAAI,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5B,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;gBAC/B,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAA;YACjC,CAAC;QACH,CAAC;aAAM,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACxC,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAA;QAC3C,CAAC;aAAM,CAAC;YACN,oEAAoE;YACpE,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;QAClF,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA;AAC1C,CAAC;AAED;;;GAGG;AACH,SAAS,mBAAmB,CAC1B,IAA6B,EAC7B,aAAqB,EACrB,cAAyC;IAEzC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;IACvB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAA;IAEvC,MAAM,MAAM,GAAiB,EAAE,CAAA;IAE/B,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;QAC1B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,SAAQ;QAE7B,MAAM,QAAQ,GACZ,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,aAAa,CAAA;QACnE,MAAM,OAAO,GACX,OAAO,IAAI,CAAC,gBAAgB,CAAC,KAAK,QAAQ;YACxC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC;YACxB,CAAC,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,QAAQ;gBACnC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC;gBACjB,CAAC,CAAC,eAAe,CAAA;QAEvB,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;IAC3D,CAAC;IAED,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA;AAC1C,CAAC;AAED;;;GAGG;AACH,SAAS,kBAAkB,CACzB,IAA6B,EAC7B,aAAqB,EACrB,cAAyC;IAEzC,MAAM,QAAQ,GACZ,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAC1D,MAAM,OAAO,GACX,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAE9D,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAEjC,MAAM,KAAK,GAAG,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAA;IAC1D,OAAO,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;AACpD,CAAC;AAED;;;GAGG;AACH,SAAS,iBAAiB,CACxB,IAAe,EACf,aAAqB,EACrB,cAAyC;IAEzC,MAAM,MAAM,GAAiB,EAAE,CAAA;IAE/B,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;QACxB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,SAAQ;QAE7B,MAAM,QAAQ,GACZ,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,aAAa,CAAA;QACnE,MAAM,OAAO,GACX,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QAE9D,IAAI,OAAO,KAAK,IAAI;YAAE,SAAQ;QAC9B,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;IAC3D,CAAC;IAED,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA;AAC1C,CAAC;AAED,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAc,EACd,OAA6B;IAE7B,MAAM,aAAa,GAAG,OAAO,EAAE,aAAa,IAAI,MAAM,CAAA;IACtD,MAAM,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;IAE5D,IAAI,CAAC;QACH,gFAAgF;QAChF,IAAI,IAAI,GAAY,KAAK,CAAA;QAEzB,6DAA6D;QAC7D,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,KAAK,SAAS,EAAE,CAAC;YAC3F,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAA;QACjC,CAAC;QAED,6EAA6E;QAC7E,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,IAAI,IAAI,CAAC,EAAE,CAAC;YAC1F,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAA;QACrB,CAAC;QAED,8BAA8B;QAC9B,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACxB,OAAO,iBAAiB,CAAC,IAAI,EAAE,aAAa,EAAE,cAAc,CAAC,IAAI,EAAE,CAAA;QACrE,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,CAAA;QAE9B,sEAAsE;QACtE,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,EAAE,aAAa,EAAE,cAAc,CAAC,CAAA;QACpE,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO,OAAO,CAAA;QAEpC,2DAA2D;QAC3D,MAAM,WAAW,GAAG,mBAAmB,CAAC,IAAI,EAAE,aAAa,EAAE,cAAc,CAAC,CAAA;QAC5E,IAAI,WAAW,KAAK,IAAI;YAAE,OAAO,WAAW,CAAA;QAE5C,yBAAyB;QACzB,MAAM,IAAI,GAAG,kBAAkB,CAAC,IAAI,EAAE,aAAa,EAAE,cAAc,CAAC,CAAA;QACpE,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAA;QAE9B,OAAO,EAAE,CAAA;IACX,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAA;IACX,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,YAAY,CAC1B,KAAc,EACd,QAA2E,EAC3E,OAA6B;IAE7B,MAAM,WAAW,GAAG,kBAAkB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IACtD,KAAK,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,WAAW,EAAE,CAAC;QAC7C,QAAQ,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAA;IAC9C,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,161 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { extractFieldErrors, mapApiErrors } from './index.js';
3
+ // ---------------------------------------------------------------------------
4
+ // extractFieldErrors
5
+ // ---------------------------------------------------------------------------
6
+ describe('extractFieldErrors', () => {
7
+ describe('RFC 7807 / Spring Boot 3+ format', () => {
8
+ it('maps a single field with one message', () => {
9
+ const error = { errors: { email: ['must not be blank'] } };
10
+ expect(extractFieldErrors(error)).toEqual([
11
+ { field: 'email', message: 'must not be blank' },
12
+ ]);
13
+ });
14
+ it('maps multiple fields', () => {
15
+ const error = {
16
+ errors: {
17
+ email: ['must not be blank'],
18
+ name: ['too short'],
19
+ },
20
+ };
21
+ const result = extractFieldErrors(error);
22
+ expect(result).toHaveLength(2);
23
+ expect(result).toContainEqual({ field: 'email', message: 'must not be blank' });
24
+ expect(result).toContainEqual({ field: 'name', message: 'too short' });
25
+ });
26
+ it('expands multiple messages per field into separate FieldErrors', () => {
27
+ const error = { errors: { email: ['must not be blank', 'invalid format'] } };
28
+ expect(extractFieldErrors(error)).toEqual([
29
+ { field: 'email', message: 'must not be blank' },
30
+ { field: 'email', message: 'invalid format' },
31
+ ]);
32
+ });
33
+ });
34
+ describe('Spring Boot default validation format (array)', () => {
35
+ it('maps field + defaultMessage', () => {
36
+ const error = {
37
+ status: 400,
38
+ errors: [{ field: 'email', defaultMessage: 'must not be blank' }],
39
+ };
40
+ expect(extractFieldErrors(error)).toEqual([
41
+ { field: 'email', message: 'must not be blank' },
42
+ ]);
43
+ });
44
+ it('falls back to message when defaultMessage is absent', () => {
45
+ const error = {
46
+ errors: [{ field: 'email', message: 'must not be blank' }],
47
+ };
48
+ expect(extractFieldErrors(error)).toEqual([
49
+ { field: 'email', message: 'must not be blank' },
50
+ ]);
51
+ });
52
+ it('maps multiple entries', () => {
53
+ const error = {
54
+ errors: [
55
+ { field: 'email', defaultMessage: 'must not be blank' },
56
+ { field: 'name', defaultMessage: 'too short' },
57
+ ],
58
+ };
59
+ expect(extractFieldErrors(error)).toHaveLength(2);
60
+ });
61
+ });
62
+ describe('flat object format', () => {
63
+ it('maps { field, message } directly', () => {
64
+ const error = { field: 'email', message: 'Invalid email' };
65
+ expect(extractFieldErrors(error)).toEqual([
66
+ { field: 'email', message: 'Invalid email' },
67
+ ]);
68
+ });
69
+ it('uses fallbackField when field is absent', () => {
70
+ const error = { message: 'Something went wrong' };
71
+ expect(extractFieldErrors(error, { fallbackField: 'root' })).toEqual([
72
+ { field: 'root', message: 'Something went wrong' },
73
+ ]);
74
+ });
75
+ });
76
+ describe('flat array format', () => {
77
+ it('maps an array of { field, message } objects', () => {
78
+ const error = [
79
+ { field: 'email', message: 'Invalid' },
80
+ { field: 'name', message: 'Required' },
81
+ ];
82
+ expect(extractFieldErrors(error)).toEqual([
83
+ { field: 'email', message: 'Invalid' },
84
+ { field: 'name', message: 'Required' },
85
+ ]);
86
+ });
87
+ it('skips entries without a message', () => {
88
+ const error = [{ field: 'email' }, { field: 'name', message: 'Required' }];
89
+ expect(extractFieldErrors(error)).toEqual([
90
+ { field: 'name', message: 'Required' },
91
+ ]);
92
+ });
93
+ });
94
+ describe('Axios-style response wrapper', () => {
95
+ it('unwraps response.data before parsing', () => {
96
+ const error = {
97
+ response: {
98
+ data: { errors: { email: ['must not be blank'] } },
99
+ },
100
+ };
101
+ expect(extractFieldErrors(error)).toEqual([
102
+ { field: 'email', message: 'must not be blank' },
103
+ ]);
104
+ });
105
+ });
106
+ describe('unrecognized / unknown shapes', () => {
107
+ it('returns [] for an unrecognized object', () => {
108
+ expect(extractFieldErrors({ foo: 'bar' })).toEqual([]);
109
+ });
110
+ it('returns [] for null', () => {
111
+ expect(extractFieldErrors(null)).toEqual([]);
112
+ });
113
+ it('returns [] for undefined', () => {
114
+ expect(extractFieldErrors(undefined)).toEqual([]);
115
+ });
116
+ it('returns [] for a plain string', () => {
117
+ expect(extractFieldErrors('something went wrong')).toEqual([]);
118
+ });
119
+ it('never throws', () => {
120
+ expect(() => extractFieldErrors({ deeply: { nested: { evil: true } } })).not.toThrow();
121
+ });
122
+ });
123
+ describe('options', () => {
124
+ it('uses fallbackField for errors without a field name', () => {
125
+ const error = { message: 'Unauthorised' };
126
+ const result = extractFieldErrors(error, { fallbackField: 'serverError' });
127
+ expect(result).toEqual([{ field: 'serverError', message: 'Unauthorised' }]);
128
+ });
129
+ it('applies transformField to every resolved field name', () => {
130
+ const error = { errors: { emailAddress: ['invalid'] } };
131
+ const result = extractFieldErrors(error, {
132
+ transformField: (f) => f.replace(/([A-Z])/g, '.$1').toLowerCase(),
133
+ });
134
+ expect(result).toEqual([{ field: 'email.address', message: 'invalid' }]);
135
+ });
136
+ });
137
+ });
138
+ // ---------------------------------------------------------------------------
139
+ // mapApiErrors
140
+ // ---------------------------------------------------------------------------
141
+ describe('mapApiErrors', () => {
142
+ it('calls setError for each field error with type "server"', () => {
143
+ const setError = vi.fn();
144
+ const error = { errors: { email: ['must not be blank'], name: ['too short'] } };
145
+ mapApiErrors(error, setError);
146
+ expect(setError).toHaveBeenCalledTimes(2);
147
+ expect(setError).toHaveBeenCalledWith('email', { type: 'server', message: 'must not be blank' });
148
+ expect(setError).toHaveBeenCalledWith('name', { type: 'server', message: 'too short' });
149
+ });
150
+ it('never calls setError for unrecognized errors', () => {
151
+ const setError = vi.fn();
152
+ mapApiErrors({ foo: 'bar' }, setError);
153
+ expect(setError).not.toHaveBeenCalled();
154
+ });
155
+ it('never calls setError for null', () => {
156
+ const setError = vi.fn();
157
+ mapApiErrors(null, setError);
158
+ expect(setError).not.toHaveBeenCalled();
159
+ });
160
+ });
161
+ //# sourceMappingURL=index.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@codewithagents/api-errors",
3
+ "version": "0.1.0",
4
+ "description": "Map backend API errors to form field errors. Framework-agnostic core with React Hook Form adapter.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "keywords": [
18
+ "api",
19
+ "errors",
20
+ "form",
21
+ "react-hook-form",
22
+ "validation",
23
+ "typescript"
24
+ ],
25
+ "license": "MIT",
26
+ "devDependencies": {
27
+ "typescript": "^5.8.0",
28
+ "vitest": "^3.0.0"
29
+ },
30
+ "scripts": {
31
+ "build": "tsc",
32
+ "test": "vitest run",
33
+ "lint": "tsc --noEmit"
34
+ }
35
+ }