ngx-api-forms 1.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/LICENSE +21 -0
- package/README.md +657 -0
- package/analog/index.d.ts +76 -0
- package/django/index.d.ts +56 -0
- package/express-validator/index.d.ts +60 -0
- package/fesm2022/ngx-api-forms-analog.mjs +195 -0
- package/fesm2022/ngx-api-forms-analog.mjs.map +1 -0
- package/fesm2022/ngx-api-forms-django.mjs +173 -0
- package/fesm2022/ngx-api-forms-django.mjs.map +1 -0
- package/fesm2022/ngx-api-forms-express-validator.mjs +202 -0
- package/fesm2022/ngx-api-forms-express-validator.mjs.map +1 -0
- package/fesm2022/ngx-api-forms-laravel.mjs +167 -0
- package/fesm2022/ngx-api-forms-laravel.mjs.map +1 -0
- package/fesm2022/ngx-api-forms-zod.mjs +226 -0
- package/fesm2022/ngx-api-forms-zod.mjs.map +1 -0
- package/fesm2022/ngx-api-forms.mjs +890 -0
- package/fesm2022/ngx-api-forms.mjs.map +1 -0
- package/index.d.ts +621 -0
- package/laravel/index.d.ts +49 -0
- package/package.json +85 -0
- package/schematics/collection.json +10 -0
- package/schematics/ng-add/index.js +300 -0
- package/schematics/ng-add/index.spec.js +119 -0
- package/schematics/ng-add/schema.json +34 -0
- package/zod/index.d.ts +58 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { GLOBAL_ERROR_FIELD } from 'ngx-api-forms';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* express-validator error preset.
|
|
5
|
+
*
|
|
6
|
+
* Parses the standard express-validator error format (v7+):
|
|
7
|
+
* ```json
|
|
8
|
+
* {
|
|
9
|
+
* "errors": [
|
|
10
|
+
* { "type": "field", "value": "", "msg": "Invalid value", "path": "email", "location": "body" }
|
|
11
|
+
* ]
|
|
12
|
+
* }
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Also handles the legacy v5/v6 format:
|
|
16
|
+
* ```json
|
|
17
|
+
* {
|
|
18
|
+
* "errors": [
|
|
19
|
+
* { "param": "email", "msg": "Invalid value", "location": "body" }
|
|
20
|
+
* ]
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* And the alternative grouped format (from `validationResult().mapped()` or `.array()`):
|
|
25
|
+
* ```json
|
|
26
|
+
* [
|
|
27
|
+
* { "type": "field", "path": "email", "msg": "Invalid email" }
|
|
28
|
+
* ]
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Tries user-provided constraint patterns against a message.
|
|
33
|
+
* Returns the matched constraint key or null.
|
|
34
|
+
*/
|
|
35
|
+
function matchUserPatterns(message, patterns) {
|
|
36
|
+
for (const [constraint, regex] of Object.entries(patterns)) {
|
|
37
|
+
if (regex.test(message))
|
|
38
|
+
return constraint;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Infers a constraint key from an express-validator error message.
|
|
44
|
+
*
|
|
45
|
+
* @remarks
|
|
46
|
+
* Relies on English-language pattern matching. Falls back to 'serverError'
|
|
47
|
+
* for unrecognized messages.
|
|
48
|
+
*/
|
|
49
|
+
function inferConstraint(message) {
|
|
50
|
+
const lower = message.toLowerCase();
|
|
51
|
+
if (lower === 'invalid value')
|
|
52
|
+
return 'invalid';
|
|
53
|
+
if (lower.includes('is required') || lower.includes('must not be empty') || lower.includes('should not be empty'))
|
|
54
|
+
return 'required';
|
|
55
|
+
if (lower.includes('must be an email') || lower.includes('valid email') || lower.includes('is not a valid e-mail'))
|
|
56
|
+
return 'email';
|
|
57
|
+
if (lower.includes('must be at least') && lower.includes('char'))
|
|
58
|
+
return 'minlength';
|
|
59
|
+
if (lower.includes('must be at most') && lower.includes('char'))
|
|
60
|
+
return 'maxlength';
|
|
61
|
+
if (lower.includes('must be at least') || lower.includes('greater than or equal') || lower.includes('minimum'))
|
|
62
|
+
return 'min';
|
|
63
|
+
if (lower.includes('must be at most') || lower.includes('less than or equal') || lower.includes('maximum'))
|
|
64
|
+
return 'max';
|
|
65
|
+
if (lower.includes('must be a number') || lower.includes('must be numeric'))
|
|
66
|
+
return 'number';
|
|
67
|
+
if (lower.includes('must be an integer'))
|
|
68
|
+
return 'integer';
|
|
69
|
+
if (lower.includes('valid date'))
|
|
70
|
+
return 'date';
|
|
71
|
+
if (lower.includes('valid url'))
|
|
72
|
+
return 'url';
|
|
73
|
+
if (lower.includes('already exists') || lower.includes('already in use') || lower.includes('already been taken'))
|
|
74
|
+
return 'unique';
|
|
75
|
+
if (lower.includes('must match') || lower.includes('does not match'))
|
|
76
|
+
return 'pattern';
|
|
77
|
+
if (lower.includes('valid phone'))
|
|
78
|
+
return 'phone';
|
|
79
|
+
if (lower.includes('must be a boolean'))
|
|
80
|
+
return 'boolean';
|
|
81
|
+
if (lower.includes('must be an array'))
|
|
82
|
+
return 'array';
|
|
83
|
+
return 'serverError';
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Default constraint map for express-validator.
|
|
87
|
+
*/
|
|
88
|
+
const EXPRESS_VALIDATOR_CONSTRAINT_MAP = {
|
|
89
|
+
required: 'required',
|
|
90
|
+
email: 'email',
|
|
91
|
+
minlength: 'minlength',
|
|
92
|
+
maxlength: 'maxlength',
|
|
93
|
+
min: 'min',
|
|
94
|
+
max: 'max',
|
|
95
|
+
number: 'number',
|
|
96
|
+
integer: 'integer',
|
|
97
|
+
date: 'date',
|
|
98
|
+
url: 'url',
|
|
99
|
+
unique: 'unique',
|
|
100
|
+
pattern: 'pattern',
|
|
101
|
+
phone: 'phone',
|
|
102
|
+
boolean: 'boolean',
|
|
103
|
+
array: 'array',
|
|
104
|
+
invalid: 'invalid',
|
|
105
|
+
serverError: 'serverError',
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Extracts the field name from an express-validator error.
|
|
109
|
+
* v7 uses `path`, v5/v6 uses `param`. Falls back to the global sentinel.
|
|
110
|
+
*/
|
|
111
|
+
function extractField(err) {
|
|
112
|
+
const raw = err.path ?? err.param;
|
|
113
|
+
if (!raw || raw === '_error')
|
|
114
|
+
return GLOBAL_ERROR_FIELD;
|
|
115
|
+
// express-validator uses dot notation for nested fields (e.g. 'address.city')
|
|
116
|
+
return raw;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Creates an express-validator error preset.
|
|
120
|
+
*
|
|
121
|
+
* @param options.noInference - When true, skips English-language constraint guessing.
|
|
122
|
+
* All errors use `constraint: 'serverError'` with the original message preserved.
|
|
123
|
+
* @param options.constraintPatterns - Custom regex patterns for constraint inference.
|
|
124
|
+
* Keys are constraint names, values are RegExp tested against the raw message.
|
|
125
|
+
* Checked before the built-in English patterns.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* import { expressValidatorPreset } from 'ngx-api-forms/express-validator';
|
|
130
|
+
*
|
|
131
|
+
* const bridge = createFormBridge(form, { preset: expressValidatorPreset() });
|
|
132
|
+
*
|
|
133
|
+
* // No inference: raw messages, no guessing
|
|
134
|
+
* const bridge = createFormBridge(form, { preset: expressValidatorPreset({ noInference: true }) });
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
function expressValidatorPreset(options) {
|
|
138
|
+
const skipInference = options?.noInference ?? false;
|
|
139
|
+
const userPatterns = options?.constraintPatterns;
|
|
140
|
+
return {
|
|
141
|
+
name: 'express-validator',
|
|
142
|
+
constraintMap: EXPRESS_VALIDATOR_CONSTRAINT_MAP,
|
|
143
|
+
parse(error) {
|
|
144
|
+
if (!error || typeof error !== 'object')
|
|
145
|
+
return [];
|
|
146
|
+
let errors;
|
|
147
|
+
// Format 1: { errors: [...] } (standard express-validator output)
|
|
148
|
+
if (!Array.isArray(error)) {
|
|
149
|
+
const obj = error;
|
|
150
|
+
if (Array.isArray(obj['errors'])) {
|
|
151
|
+
const arr = obj['errors'];
|
|
152
|
+
// Verify it looks like express-validator errors (not Laravel / NestJS)
|
|
153
|
+
if (arr.length > 0 && typeof arr[0] === 'object' && arr[0] !== null && 'msg' in arr[0]) {
|
|
154
|
+
errors = arr;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Format 2: Direct array (from validationResult().array())
|
|
159
|
+
if (!errors && Array.isArray(error)) {
|
|
160
|
+
const arr = error;
|
|
161
|
+
if (arr.length > 0 && typeof arr[0] === 'object' && arr[0] !== null && 'msg' in arr[0]) {
|
|
162
|
+
errors = arr;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (!errors || errors.length === 0)
|
|
166
|
+
return [];
|
|
167
|
+
const result = [];
|
|
168
|
+
for (const err of errors) {
|
|
169
|
+
if (typeof err.msg !== 'string')
|
|
170
|
+
continue;
|
|
171
|
+
// express-validator can produce 'alternative' and 'alternative_grouped' types
|
|
172
|
+
// for oneOf() chains. Skip non-field errors unless they are type 'field' or legacy.
|
|
173
|
+
if (err.type && err.type !== 'field' && !err.param)
|
|
174
|
+
continue;
|
|
175
|
+
const field = extractField(err);
|
|
176
|
+
let constraint;
|
|
177
|
+
if (err.code) {
|
|
178
|
+
// Schema-based: use structured code directly (language-independent)
|
|
179
|
+
constraint = err.code;
|
|
180
|
+
}
|
|
181
|
+
else if (skipInference) {
|
|
182
|
+
constraint = 'serverError';
|
|
183
|
+
}
|
|
184
|
+
else if (userPatterns) {
|
|
185
|
+
constraint = matchUserPatterns(err.msg, userPatterns) ?? inferConstraint(err.msg);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
constraint = inferConstraint(err.msg);
|
|
189
|
+
}
|
|
190
|
+
result.push({ field, constraint, message: err.msg });
|
|
191
|
+
}
|
|
192
|
+
return result;
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Generated bundle index. Do not edit.
|
|
199
|
+
*/
|
|
200
|
+
|
|
201
|
+
export { EXPRESS_VALIDATOR_CONSTRAINT_MAP, expressValidatorPreset };
|
|
202
|
+
//# sourceMappingURL=ngx-api-forms-express-validator.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngx-api-forms-express-validator.mjs","sources":["../../../projects/ngx-api-forms/express-validator/src/express-validator.preset.ts","../../../projects/ngx-api-forms/express-validator/src/ngx-api-forms-express-validator.ts"],"sourcesContent":["/**\n * express-validator error preset.\n *\n * Parses the standard express-validator error format (v7+):\n * ```json\n * {\n * \"errors\": [\n * { \"type\": \"field\", \"value\": \"\", \"msg\": \"Invalid value\", \"path\": \"email\", \"location\": \"body\" }\n * ]\n * }\n * ```\n *\n * Also handles the legacy v5/v6 format:\n * ```json\n * {\n * \"errors\": [\n * { \"param\": \"email\", \"msg\": \"Invalid value\", \"location\": \"body\" }\n * ]\n * }\n * ```\n *\n * And the alternative grouped format (from `validationResult().mapped()` or `.array()`):\n * ```json\n * [\n * { \"type\": \"field\", \"path\": \"email\", \"msg\": \"Invalid email\" }\n * ]\n * ```\n */\nimport { ApiFieldError, ConstraintMap, ErrorPreset, GLOBAL_ERROR_FIELD } from 'ngx-api-forms';\n\n/**\n * Shape of a single express-validator v7 error.\n */\ninterface ExpressValidatorError {\n type?: string;\n path?: string;\n param?: string; // legacy v5/v6\n msg: string;\n location?: string;\n value?: unknown;\n code?: string; // custom error code for schema-based inference\n}\n\n/**\n * Tries user-provided constraint patterns against a message.\n * Returns the matched constraint key or null.\n */\nfunction matchUserPatterns(message: string, patterns: Record<string, RegExp>): string | null {\n for (const [constraint, regex] of Object.entries(patterns)) {\n if (regex.test(message)) return constraint;\n }\n return null;\n}\n\n/**\n * Infers a constraint key from an express-validator error message.\n *\n * @remarks\n * Relies on English-language pattern matching. Falls back to 'serverError'\n * for unrecognized messages.\n */\nfunction inferConstraint(message: string): string {\n const lower = message.toLowerCase();\n\n if (lower === 'invalid value') return 'invalid';\n if (lower.includes('is required') || lower.includes('must not be empty') || lower.includes('should not be empty')) return 'required';\n if (lower.includes('must be an email') || lower.includes('valid email') || lower.includes('is not a valid e-mail')) return 'email';\n if (lower.includes('must be at least') && lower.includes('char')) return 'minlength';\n if (lower.includes('must be at most') && lower.includes('char')) return 'maxlength';\n if (lower.includes('must be at least') || lower.includes('greater than or equal') || lower.includes('minimum')) return 'min';\n if (lower.includes('must be at most') || lower.includes('less than or equal') || lower.includes('maximum')) return 'max';\n if (lower.includes('must be a number') || lower.includes('must be numeric')) return 'number';\n if (lower.includes('must be an integer')) return 'integer';\n if (lower.includes('valid date')) return 'date';\n if (lower.includes('valid url')) return 'url';\n if (lower.includes('already exists') || lower.includes('already in use') || lower.includes('already been taken')) return 'unique';\n if (lower.includes('must match') || lower.includes('does not match')) return 'pattern';\n if (lower.includes('valid phone')) return 'phone';\n if (lower.includes('must be a boolean')) return 'boolean';\n if (lower.includes('must be an array')) return 'array';\n\n return 'serverError';\n}\n\n/**\n * Default constraint map for express-validator.\n */\nexport const EXPRESS_VALIDATOR_CONSTRAINT_MAP: ConstraintMap = {\n required: 'required',\n email: 'email',\n minlength: 'minlength',\n maxlength: 'maxlength',\n min: 'min',\n max: 'max',\n number: 'number',\n integer: 'integer',\n date: 'date',\n url: 'url',\n unique: 'unique',\n pattern: 'pattern',\n phone: 'phone',\n boolean: 'boolean',\n array: 'array',\n invalid: 'invalid',\n serverError: 'serverError',\n};\n\n/**\n * Extracts the field name from an express-validator error.\n * v7 uses `path`, v5/v6 uses `param`. Falls back to the global sentinel.\n */\nfunction extractField(err: ExpressValidatorError): string {\n const raw = err.path ?? err.param;\n if (!raw || raw === '_error') return GLOBAL_ERROR_FIELD;\n // express-validator uses dot notation for nested fields (e.g. 'address.city')\n return raw;\n}\n\n/**\n * Creates an express-validator error preset.\n *\n * @param options.noInference - When true, skips English-language constraint guessing.\n * All errors use `constraint: 'serverError'` with the original message preserved.\n * @param options.constraintPatterns - Custom regex patterns for constraint inference.\n * Keys are constraint names, values are RegExp tested against the raw message.\n * Checked before the built-in English patterns.\n *\n * @example\n * ```typescript\n * import { expressValidatorPreset } from 'ngx-api-forms/express-validator';\n *\n * const bridge = createFormBridge(form, { preset: expressValidatorPreset() });\n *\n * // No inference: raw messages, no guessing\n * const bridge = createFormBridge(form, { preset: expressValidatorPreset({ noInference: true }) });\n * ```\n */\nexport function expressValidatorPreset(options?: { noInference?: boolean; constraintPatterns?: Record<string, RegExp> }): ErrorPreset {\n const skipInference = options?.noInference ?? false;\n const userPatterns = options?.constraintPatterns;\n\n return {\n name: 'express-validator',\n constraintMap: EXPRESS_VALIDATOR_CONSTRAINT_MAP,\n parse(error: unknown): ApiFieldError[] {\n if (!error || typeof error !== 'object') return [];\n\n let errors: ExpressValidatorError[] | undefined;\n\n // Format 1: { errors: [...] } (standard express-validator output)\n if (!Array.isArray(error)) {\n const obj = error as Record<string, unknown>;\n if (Array.isArray(obj['errors'])) {\n const arr = obj['errors'] as unknown[];\n // Verify it looks like express-validator errors (not Laravel / NestJS)\n if (arr.length > 0 && typeof arr[0] === 'object' && arr[0] !== null && 'msg' in arr[0]) {\n errors = arr as ExpressValidatorError[];\n }\n }\n }\n\n // Format 2: Direct array (from validationResult().array())\n if (!errors && Array.isArray(error)) {\n const arr = error as unknown[];\n if (arr.length > 0 && typeof arr[0] === 'object' && arr[0] !== null && 'msg' in arr[0]) {\n errors = arr as ExpressValidatorError[];\n }\n }\n\n if (!errors || errors.length === 0) return [];\n\n const result: ApiFieldError[] = [];\n\n for (const err of errors) {\n if (typeof err.msg !== 'string') continue;\n\n // express-validator can produce 'alternative' and 'alternative_grouped' types\n // for oneOf() chains. Skip non-field errors unless they are type 'field' or legacy.\n if (err.type && err.type !== 'field' && !err.param) continue;\n\n const field = extractField(err);\n let constraint: string;\n if (err.code) {\n // Schema-based: use structured code directly (language-independent)\n constraint = err.code;\n } else if (skipInference) {\n constraint = 'serverError';\n } else if (userPatterns) {\n constraint = matchUserPatterns(err.msg, userPatterns) ?? inferConstraint(err.msg);\n } else {\n constraint = inferConstraint(err.msg);\n }\n result.push({ field, constraint, message: err.msg });\n }\n\n return result;\n },\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BG;AAgBH;;;AAGG;AACH,SAAS,iBAAiB,CAAC,OAAe,EAAE,QAAgC,EAAA;AAC1E,IAAA,KAAK,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE;AAC1D,QAAA,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC;AAAE,YAAA,OAAO,UAAU;IAC5C;AACA,IAAA,OAAO,IAAI;AACb;AAEA;;;;;;AAMG;AACH,SAAS,eAAe,CAAC,OAAe,EAAA;AACtC,IAAA,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,EAAE;IAEnC,IAAI,KAAK,KAAK,eAAe;AAAE,QAAA,OAAO,SAAS;AAC/C,IAAA,IAAI,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,mBAAmB,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,qBAAqB,CAAC;AAAE,QAAA,OAAO,UAAU;AACpI,IAAA,IAAI,KAAK,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,uBAAuB,CAAC;AAAE,QAAA,OAAO,OAAO;AAClI,IAAA,IAAI,KAAK,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;AAAE,QAAA,OAAO,WAAW;AACpF,IAAA,IAAI,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;AAAE,QAAA,OAAO,WAAW;AACnF,IAAA,IAAI,KAAK,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,uBAAuB,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC;AAAE,QAAA,OAAO,KAAK;AAC5H,IAAA,IAAI,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,oBAAoB,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC;AAAE,QAAA,OAAO,KAAK;AACxH,IAAA,IAAI,KAAK,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC;AAAE,QAAA,OAAO,QAAQ;AAC5F,IAAA,IAAI,KAAK,CAAC,QAAQ,CAAC,oBAAoB,CAAC;AAAE,QAAA,OAAO,SAAS;AAC1D,IAAA,IAAI,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC;AAAE,QAAA,OAAO,MAAM;AAC/C,IAAA,IAAI,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC;AAAE,QAAA,OAAO,KAAK;AAC7C,IAAA,IAAI,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,oBAAoB,CAAC;AAAE,QAAA,OAAO,QAAQ;AACjI,IAAA,IAAI,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC;AAAE,QAAA,OAAO,SAAS;AACtF,IAAA,IAAI,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC;AAAE,QAAA,OAAO,OAAO;AACjD,IAAA,IAAI,KAAK,CAAC,QAAQ,CAAC,mBAAmB,CAAC;AAAE,QAAA,OAAO,SAAS;AACzD,IAAA,IAAI,KAAK,CAAC,QAAQ,CAAC,kBAAkB,CAAC;AAAE,QAAA,OAAO,OAAO;AAEtD,IAAA,OAAO,aAAa;AACtB;AAEA;;AAEG;AACI,MAAM,gCAAgC,GAAkB;AAC7D,IAAA,QAAQ,EAAE,UAAU;AACpB,IAAA,KAAK,EAAE,OAAO;AACd,IAAA,SAAS,EAAE,WAAW;AACtB,IAAA,SAAS,EAAE,WAAW;AACtB,IAAA,GAAG,EAAE,KAAK;AACV,IAAA,GAAG,EAAE,KAAK;AACV,IAAA,MAAM,EAAE,QAAQ;AAChB,IAAA,OAAO,EAAE,SAAS;AAClB,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,GAAG,EAAE,KAAK;AACV,IAAA,MAAM,EAAE,QAAQ;AAChB,IAAA,OAAO,EAAE,SAAS;AAClB,IAAA,KAAK,EAAE,OAAO;AACd,IAAA,OAAO,EAAE,SAAS;AAClB,IAAA,KAAK,EAAE,OAAO;AACd,IAAA,OAAO,EAAE,SAAS;AAClB,IAAA,WAAW,EAAE,aAAa;;AAG5B;;;AAGG;AACH,SAAS,YAAY,CAAC,GAA0B,EAAA;IAC9C,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,KAAK;AACjC,IAAA,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,QAAQ;AAAE,QAAA,OAAO,kBAAkB;;AAEvD,IAAA,OAAO,GAAG;AACZ;AAEA;;;;;;;;;;;;;;;;;;AAkBG;AACG,SAAU,sBAAsB,CAAC,OAAgF,EAAA;AACrH,IAAA,MAAM,aAAa,GAAG,OAAO,EAAE,WAAW,IAAI,KAAK;AACnD,IAAA,MAAM,YAAY,GAAG,OAAO,EAAE,kBAAkB;IAEhD,OAAO;AACL,QAAA,IAAI,EAAE,mBAAmB;AACzB,QAAA,aAAa,EAAE,gCAAgC;AAC/C,QAAA,KAAK,CAAC,KAAc,EAAA;AAClB,YAAA,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;AAAE,gBAAA,OAAO,EAAE;AAElD,YAAA,IAAI,MAA2C;;YAG/C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;gBACzB,MAAM,GAAG,GAAG,KAAgC;gBAC5C,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE;AAChC,oBAAA,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAc;;AAEtC,oBAAA,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE;wBACtF,MAAM,GAAG,GAA8B;oBACzC;gBACF;YACF;;YAGA,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;gBACnC,MAAM,GAAG,GAAG,KAAkB;AAC9B,gBAAA,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE;oBACtF,MAAM,GAAG,GAA8B;gBACzC;YACF;AAEA,YAAA,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;AAAE,gBAAA,OAAO,EAAE;YAE7C,MAAM,MAAM,GAAoB,EAAE;AAElC,YAAA,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE;AACxB,gBAAA,IAAI,OAAO,GAAG,CAAC,GAAG,KAAK,QAAQ;oBAAE;;;AAIjC,gBAAA,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK;oBAAE;AAEpD,gBAAA,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC;AAC/B,gBAAA,IAAI,UAAkB;AACtB,gBAAA,IAAI,GAAG,CAAC,IAAI,EAAE;;AAEZ,oBAAA,UAAU,GAAG,GAAG,CAAC,IAAI;gBACvB;qBAAO,IAAI,aAAa,EAAE;oBACxB,UAAU,GAAG,aAAa;gBAC5B;qBAAO,IAAI,YAAY,EAAE;AACvB,oBAAA,UAAU,GAAG,iBAAiB,CAAC,GAAG,CAAC,GAAG,EAAE,YAAY,CAAC,IAAI,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC;gBACnF;qBAAO;AACL,oBAAA,UAAU,GAAG,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC;gBACvC;AACA,gBAAA,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC;YACtD;AAEA,YAAA,OAAO,MAAM;QACf,CAAC;KACF;AACH;;ACtMA;;AAEG;;;;"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infers a constraint key from a Laravel validation message.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* This function relies on English-language pattern matching (e.g. "is required",
|
|
6
|
+
* "must be a valid email"). If your Laravel backend returns translated messages,
|
|
7
|
+
* the inference will fall back to 'serverError'. In that case, use structured
|
|
8
|
+
* error codes (`{ message, rule }`) or `constraintPatterns` for reliable i18n.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Tries user-provided constraint patterns against a message.
|
|
12
|
+
* Returns the matched constraint key or null.
|
|
13
|
+
*/
|
|
14
|
+
function matchUserPatterns(message, patterns) {
|
|
15
|
+
for (const [constraint, regex] of Object.entries(patterns)) {
|
|
16
|
+
if (regex.test(message))
|
|
17
|
+
return constraint;
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
function inferConstraint(message) {
|
|
22
|
+
const lower = message.toLowerCase();
|
|
23
|
+
if (lower.includes('is required') || lower.includes('field is required'))
|
|
24
|
+
return 'required';
|
|
25
|
+
if (lower.includes('must be a valid email'))
|
|
26
|
+
return 'email';
|
|
27
|
+
if (lower.includes('must be at least') && lower.includes('character'))
|
|
28
|
+
return 'minlength';
|
|
29
|
+
if (lower.includes('must not be greater than') && lower.includes('character'))
|
|
30
|
+
return 'maxlength';
|
|
31
|
+
if (lower.includes('must be at least'))
|
|
32
|
+
return 'min';
|
|
33
|
+
if (lower.includes('must not be greater than'))
|
|
34
|
+
return 'max';
|
|
35
|
+
if (lower.includes('must be a number'))
|
|
36
|
+
return 'number';
|
|
37
|
+
if (lower.includes('must be an integer'))
|
|
38
|
+
return 'integer';
|
|
39
|
+
if (lower.includes('must be a date'))
|
|
40
|
+
return 'date';
|
|
41
|
+
if (lower.includes('must be a valid url'))
|
|
42
|
+
return 'url';
|
|
43
|
+
if (lower.includes('has already been taken'))
|
|
44
|
+
return 'unique';
|
|
45
|
+
if (lower.includes('must match'))
|
|
46
|
+
return 'pattern';
|
|
47
|
+
if (lower.includes('must be a valid phone'))
|
|
48
|
+
return 'phone';
|
|
49
|
+
if (lower.includes('format is invalid'))
|
|
50
|
+
return 'invalid';
|
|
51
|
+
if (lower.includes('must be accepted'))
|
|
52
|
+
return 'accepted';
|
|
53
|
+
if (lower.includes('confirmation does not match'))
|
|
54
|
+
return 'confirmed';
|
|
55
|
+
if (lower.includes('must be a file'))
|
|
56
|
+
return 'file';
|
|
57
|
+
if (lower.includes('must be an image'))
|
|
58
|
+
return 'image';
|
|
59
|
+
return 'serverError';
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Default constraint map for Laravel.
|
|
63
|
+
*/
|
|
64
|
+
const LARAVEL_CONSTRAINT_MAP = {
|
|
65
|
+
required: 'required',
|
|
66
|
+
email: 'email',
|
|
67
|
+
minlength: 'minlength',
|
|
68
|
+
maxlength: 'maxlength',
|
|
69
|
+
min: 'min',
|
|
70
|
+
max: 'max',
|
|
71
|
+
number: 'number',
|
|
72
|
+
integer: 'integer',
|
|
73
|
+
date: 'date',
|
|
74
|
+
url: 'url',
|
|
75
|
+
unique: 'unique',
|
|
76
|
+
pattern: 'pattern',
|
|
77
|
+
phone: 'phone',
|
|
78
|
+
invalid: 'invalid',
|
|
79
|
+
accepted: 'accepted',
|
|
80
|
+
confirmed: 'confirmed',
|
|
81
|
+
file: 'file',
|
|
82
|
+
image: 'image',
|
|
83
|
+
serverError: 'serverError',
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Creates a Laravel validation error preset.
|
|
87
|
+
*
|
|
88
|
+
* @param options.noInference - When true, skips English-language constraint guessing.
|
|
89
|
+
* The raw error message is used directly and the constraint is set to `'serverError'`.
|
|
90
|
+
* Use this when your backend returns translated or custom messages.
|
|
91
|
+
* @param options.constraintPatterns - Custom regex patterns for constraint inference.
|
|
92
|
+
* Keys are constraint names, values are RegExp tested against the raw message.
|
|
93
|
+
* Checked before the built-in English patterns.
|
|
94
|
+
* Example: `{ required: /obligatoire/i, email: /courriel.*invalide/i }`
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```typescript
|
|
98
|
+
* import { laravelPreset } from 'ngx-api-forms/laravel';
|
|
99
|
+
*
|
|
100
|
+
* // Default: infer constraint from English messages
|
|
101
|
+
* const bridge = createFormBridge(form, { preset: laravelPreset() });
|
|
102
|
+
*
|
|
103
|
+
* // No inference: raw messages, no guessing
|
|
104
|
+
* const bridge = createFormBridge(form, { preset: laravelPreset({ noInference: true }) });
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
function laravelPreset(options) {
|
|
108
|
+
const skipInference = options?.noInference ?? false;
|
|
109
|
+
const userPatterns = options?.constraintPatterns;
|
|
110
|
+
return {
|
|
111
|
+
name: 'laravel',
|
|
112
|
+
constraintMap: LARAVEL_CONSTRAINT_MAP,
|
|
113
|
+
parse(error) {
|
|
114
|
+
if (!error || typeof error !== 'object')
|
|
115
|
+
return [];
|
|
116
|
+
const err = error;
|
|
117
|
+
// Standard Laravel format: { errors: { field: [messages] } }
|
|
118
|
+
let errors;
|
|
119
|
+
if (err['errors'] && typeof err['errors'] === 'object') {
|
|
120
|
+
errors = err['errors'];
|
|
121
|
+
}
|
|
122
|
+
// Direct format: { field: [messages] } (without wrapper)
|
|
123
|
+
else if (!err['statusCode'] && !err['message']) {
|
|
124
|
+
errors = err;
|
|
125
|
+
}
|
|
126
|
+
if (!errors)
|
|
127
|
+
return [];
|
|
128
|
+
const result = [];
|
|
129
|
+
for (const [field, messages] of Object.entries(errors)) {
|
|
130
|
+
if (!Array.isArray(messages))
|
|
131
|
+
continue;
|
|
132
|
+
// Laravel uses dot notation for nested fields (e.g. 'address.city')
|
|
133
|
+
const normalizedField = field.replace(/\.\*/g, '').replace(/\.\d+\./g, '.');
|
|
134
|
+
for (const item of messages) {
|
|
135
|
+
// Structured format: { message: string, rule: string }
|
|
136
|
+
if (typeof item === 'object' && item !== null && 'message' in item && 'rule' in item) {
|
|
137
|
+
const structured = item;
|
|
138
|
+
result.push({ field: normalizedField, constraint: structured.rule, message: structured.message });
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
// Standard format: plain string
|
|
142
|
+
if (typeof item !== 'string')
|
|
143
|
+
continue;
|
|
144
|
+
let constraint;
|
|
145
|
+
if (skipInference) {
|
|
146
|
+
constraint = 'serverError';
|
|
147
|
+
}
|
|
148
|
+
else if (userPatterns) {
|
|
149
|
+
constraint = matchUserPatterns(item, userPatterns) ?? inferConstraint(item);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
constraint = inferConstraint(item);
|
|
153
|
+
}
|
|
154
|
+
result.push({ field: normalizedField, constraint, message: item });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Generated bundle index. Do not edit.
|
|
164
|
+
*/
|
|
165
|
+
|
|
166
|
+
export { LARAVEL_CONSTRAINT_MAP, laravelPreset };
|
|
167
|
+
//# sourceMappingURL=ngx-api-forms-laravel.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngx-api-forms-laravel.mjs","sources":["../../../projects/ngx-api-forms/laravel/src/laravel.preset.ts","../../../projects/ngx-api-forms/laravel/src/ngx-api-forms-laravel.ts"],"sourcesContent":["/**\n * Laravel validation error preset.\n *\n * Parses the standard Laravel validation error format:\n * ```json\n * {\n * \"message\": \"The given data was invalid.\",\n * \"errors\": {\n * \"email\": [\"The email field is required.\", \"The email must be a valid email address.\"],\n * \"name\": [\"The name must be at least 3 characters.\"]\n * }\n * }\n * ```\n */\nimport { ApiFieldError, ConstraintMap, ErrorPreset, LaravelValidationErrors } from 'ngx-api-forms';\n\n/**\n * Shape of a structured Laravel error object (when using a custom exception handler\n * that includes the validation `rule` name).\n */\ninterface LaravelStructuredError {\n message: string;\n rule: string;\n}\n\n/**\n * Infers a constraint key from a Laravel validation message.\n *\n * @remarks\n * This function relies on English-language pattern matching (e.g. \"is required\",\n * \"must be a valid email\"). If your Laravel backend returns translated messages,\n * the inference will fall back to 'serverError'. In that case, use structured\n * error codes (`{ message, rule }`) or `constraintPatterns` for reliable i18n.\n */\n/**\n * Tries user-provided constraint patterns against a message.\n * Returns the matched constraint key or null.\n */\nfunction matchUserPatterns(message: string, patterns: Record<string, RegExp>): string | null {\n for (const [constraint, regex] of Object.entries(patterns)) {\n if (regex.test(message)) return constraint;\n }\n return null;\n}\n\nfunction inferConstraint(message: string): string {\n const lower = message.toLowerCase();\n\n if (lower.includes('is required') || lower.includes('field is required')) return 'required';\n if (lower.includes('must be a valid email')) return 'email';\n if (lower.includes('must be at least') && lower.includes('character')) return 'minlength';\n if (lower.includes('must not be greater than') && lower.includes('character')) return 'maxlength';\n if (lower.includes('must be at least')) return 'min';\n if (lower.includes('must not be greater than')) return 'max';\n if (lower.includes('must be a number')) return 'number';\n if (lower.includes('must be an integer')) return 'integer';\n if (lower.includes('must be a date')) return 'date';\n if (lower.includes('must be a valid url')) return 'url';\n if (lower.includes('has already been taken')) return 'unique';\n if (lower.includes('must match')) return 'pattern';\n if (lower.includes('must be a valid phone')) return 'phone';\n if (lower.includes('format is invalid')) return 'invalid';\n if (lower.includes('must be accepted')) return 'accepted';\n if (lower.includes('confirmation does not match')) return 'confirmed';\n if (lower.includes('must be a file')) return 'file';\n if (lower.includes('must be an image')) return 'image';\n\n return 'serverError';\n}\n\n/**\n * Default constraint map for Laravel.\n */\nexport const LARAVEL_CONSTRAINT_MAP: ConstraintMap = {\n required: 'required',\n email: 'email',\n minlength: 'minlength',\n maxlength: 'maxlength',\n min: 'min',\n max: 'max',\n number: 'number',\n integer: 'integer',\n date: 'date',\n url: 'url',\n unique: 'unique',\n pattern: 'pattern',\n phone: 'phone',\n invalid: 'invalid',\n accepted: 'accepted',\n confirmed: 'confirmed',\n file: 'file',\n image: 'image',\n serverError: 'serverError',\n};\n\n/**\n * Creates a Laravel validation error preset.\n *\n * @param options.noInference - When true, skips English-language constraint guessing.\n * The raw error message is used directly and the constraint is set to `'serverError'`.\n * Use this when your backend returns translated or custom messages.\n * @param options.constraintPatterns - Custom regex patterns for constraint inference.\n * Keys are constraint names, values are RegExp tested against the raw message.\n * Checked before the built-in English patterns.\n * Example: `{ required: /obligatoire/i, email: /courriel.*invalide/i }`\n *\n * @example\n * ```typescript\n * import { laravelPreset } from 'ngx-api-forms/laravel';\n *\n * // Default: infer constraint from English messages\n * const bridge = createFormBridge(form, { preset: laravelPreset() });\n *\n * // No inference: raw messages, no guessing\n * const bridge = createFormBridge(form, { preset: laravelPreset({ noInference: true }) });\n * ```\n */\nexport function laravelPreset(options?: { noInference?: boolean; constraintPatterns?: Record<string, RegExp> }): ErrorPreset {\n const skipInference = options?.noInference ?? false;\n const userPatterns = options?.constraintPatterns;\n return {\n name: 'laravel',\n constraintMap: LARAVEL_CONSTRAINT_MAP,\n parse(error: unknown): ApiFieldError[] {\n if (!error || typeof error !== 'object') return [];\n\n const err = error as Record<string, unknown>;\n\n // Standard Laravel format: { errors: { field: [messages] } }\n let errors: LaravelValidationErrors | undefined;\n\n if (err['errors'] && typeof err['errors'] === 'object') {\n errors = err['errors'] as LaravelValidationErrors;\n }\n // Direct format: { field: [messages] } (without wrapper)\n else if (!err['statusCode'] && !err['message']) {\n errors = err as unknown as LaravelValidationErrors;\n }\n\n if (!errors) return [];\n\n const result: ApiFieldError[] = [];\n\n for (const [field, messages] of Object.entries(errors)) {\n if (!Array.isArray(messages)) continue;\n\n // Laravel uses dot notation for nested fields (e.g. 'address.city')\n const normalizedField = field.replace(/\\.\\*/g, '').replace(/\\.\\d+\\./g, '.');\n\n for (const item of messages) {\n // Structured format: { message: string, rule: string }\n if (typeof item === 'object' && item !== null && 'message' in item && 'rule' in item) {\n const structured = item as LaravelStructuredError;\n result.push({ field: normalizedField, constraint: structured.rule, message: structured.message });\n continue;\n }\n\n // Standard format: plain string\n if (typeof item !== 'string') continue;\n let constraint: string;\n if (skipInference) {\n constraint = 'serverError';\n } else if (userPatterns) {\n constraint = matchUserPatterns(item, userPatterns) ?? inferConstraint(item);\n } else {\n constraint = inferConstraint(item);\n }\n result.push({ field: normalizedField, constraint, message: item });\n }\n }\n\n return result;\n },\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { GLOBAL_ERROR_FIELD } from 'ngx-api-forms';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Zod error preset.
|
|
5
|
+
*
|
|
6
|
+
* Parses errors from `ZodError.flatten()`:
|
|
7
|
+
* ```json
|
|
8
|
+
* {
|
|
9
|
+
* "formErrors": [],
|
|
10
|
+
* "fieldErrors": {
|
|
11
|
+
* "email": ["Invalid email"],
|
|
12
|
+
* "name": ["String must contain at least 3 character(s)"]
|
|
13
|
+
* }
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Also supports raw `ZodError.issues`:
|
|
18
|
+
* ```json
|
|
19
|
+
* [
|
|
20
|
+
* { "code": "too_small", "minimum": 3, "path": ["name"], "message": "..." },
|
|
21
|
+
* { "code": "invalid_string", "validation": "email", "path": ["email"], "message": "..." }
|
|
22
|
+
* ]
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Infers a constraint key from a Zod error message string.
|
|
27
|
+
*
|
|
28
|
+
* @remarks
|
|
29
|
+
* Used only for the flattened format (`ZodError.flatten()`), where the structured
|
|
30
|
+
* `code` field is not available. Relies on English-language pattern matching.
|
|
31
|
+
* The raw issues format uses `zodCodeToConstraint` instead, which is language-independent.
|
|
32
|
+
* If your Zod messages are customized or translated, prefer returning raw issues
|
|
33
|
+
* (`ZodError.issues`) rather than the flattened format.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* Tries user-provided constraint patterns against a message.
|
|
37
|
+
* Returns the matched constraint key or null.
|
|
38
|
+
*/
|
|
39
|
+
function matchUserPatterns(message, patterns) {
|
|
40
|
+
for (const [constraint, regex] of Object.entries(patterns)) {
|
|
41
|
+
if (regex.test(message))
|
|
42
|
+
return constraint;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
function inferConstraintFromMessage(message) {
|
|
47
|
+
const lower = message.toLowerCase();
|
|
48
|
+
if (lower.includes('required') || lower.includes('invalid_type'))
|
|
49
|
+
return 'required';
|
|
50
|
+
if (lower.includes('email'))
|
|
51
|
+
return 'email';
|
|
52
|
+
if (lower.includes('url'))
|
|
53
|
+
return 'url';
|
|
54
|
+
if (lower.includes('at least') || lower.includes('too_small') || lower.includes('must contain at least'))
|
|
55
|
+
return 'minlength';
|
|
56
|
+
if (lower.includes('at most') || lower.includes('too_big') || lower.includes('must contain at most'))
|
|
57
|
+
return 'maxlength';
|
|
58
|
+
if (lower.includes('greater than or equal'))
|
|
59
|
+
return 'min';
|
|
60
|
+
if (lower.includes('less than or equal'))
|
|
61
|
+
return 'max';
|
|
62
|
+
return 'serverError';
|
|
63
|
+
}
|
|
64
|
+
function zodCodeToConstraint(issue) {
|
|
65
|
+
switch (issue.code) {
|
|
66
|
+
case 'too_small':
|
|
67
|
+
return issue.minimum !== undefined ? 'minlength' : 'min';
|
|
68
|
+
case 'too_big':
|
|
69
|
+
return issue.maximum !== undefined ? 'maxlength' : 'max';
|
|
70
|
+
case 'invalid_string':
|
|
71
|
+
return issue.validation ?? 'serverError';
|
|
72
|
+
case 'invalid_type':
|
|
73
|
+
return 'required';
|
|
74
|
+
case 'invalid_enum_value':
|
|
75
|
+
return 'enum';
|
|
76
|
+
case 'invalid_date':
|
|
77
|
+
return 'date';
|
|
78
|
+
case 'custom':
|
|
79
|
+
return 'custom';
|
|
80
|
+
default:
|
|
81
|
+
return issue.code || 'serverError';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Default constraint map for Zod.
|
|
86
|
+
*/
|
|
87
|
+
const ZOD_CONSTRAINT_MAP = {
|
|
88
|
+
required: 'required',
|
|
89
|
+
email: 'email',
|
|
90
|
+
url: 'url',
|
|
91
|
+
uuid: 'uuid',
|
|
92
|
+
minlength: 'minlength',
|
|
93
|
+
maxlength: 'maxlength',
|
|
94
|
+
min: 'min',
|
|
95
|
+
max: 'max',
|
|
96
|
+
regex: 'pattern',
|
|
97
|
+
enum: 'enum',
|
|
98
|
+
date: 'date',
|
|
99
|
+
custom: 'custom',
|
|
100
|
+
invalid: 'invalid',
|
|
101
|
+
serverError: 'serverError',
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Creates a Zod error preset.
|
|
105
|
+
*
|
|
106
|
+
* Supports both `.flatten()` and raw `.issues` formats.
|
|
107
|
+
*
|
|
108
|
+
* @param options.noInference - When true, skips constraint guessing entirely.
|
|
109
|
+
* The raw error message is used directly and the constraint is set to `'serverError'`.
|
|
110
|
+
* Useful for custom or translated Zod error messages.
|
|
111
|
+
* @param options.constraintPatterns - Custom regex patterns for constraint inference.
|
|
112
|
+
* Keys are constraint names, values are RegExp tested against the raw message.
|
|
113
|
+
* Checked before the built-in English patterns (flattened format only;
|
|
114
|
+
* the raw issues format uses structured `code` fields which are language-independent).
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* import { zodPreset } from 'ngx-api-forms/zod';
|
|
119
|
+
*
|
|
120
|
+
* const bridge = createFormBridge(form, { preset: zodPreset() });
|
|
121
|
+
*
|
|
122
|
+
* // No inference: raw messages, no guessing
|
|
123
|
+
* const bridge = createFormBridge(form, { preset: zodPreset({ noInference: true }) });
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
function zodPreset(options) {
|
|
127
|
+
const skipInference = options?.noInference ?? false;
|
|
128
|
+
const userPatterns = options?.constraintPatterns;
|
|
129
|
+
function inferMessage(message) {
|
|
130
|
+
if (userPatterns) {
|
|
131
|
+
const match = matchUserPatterns(message, userPatterns);
|
|
132
|
+
if (match)
|
|
133
|
+
return match;
|
|
134
|
+
}
|
|
135
|
+
return inferConstraintFromMessage(message);
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
name: 'zod',
|
|
139
|
+
constraintMap: ZOD_CONSTRAINT_MAP,
|
|
140
|
+
parse(error) {
|
|
141
|
+
if (!error || typeof error !== 'object')
|
|
142
|
+
return [];
|
|
143
|
+
// Format 1: Flattened error { fieldErrors: { ... }, formErrors: [...] }
|
|
144
|
+
const flat = error;
|
|
145
|
+
if (flat.fieldErrors && typeof flat.fieldErrors === 'object') {
|
|
146
|
+
const result = [];
|
|
147
|
+
// Collect form-level (global) errors
|
|
148
|
+
if (Array.isArray(flat.formErrors)) {
|
|
149
|
+
for (const message of flat.formErrors) {
|
|
150
|
+
if (typeof message !== 'string')
|
|
151
|
+
continue;
|
|
152
|
+
result.push({ field: GLOBAL_ERROR_FIELD, constraint: 'serverError', message });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
for (const [field, messages] of Object.entries(flat.fieldErrors)) {
|
|
156
|
+
if (!Array.isArray(messages))
|
|
157
|
+
continue;
|
|
158
|
+
for (const message of messages) {
|
|
159
|
+
if (typeof message !== 'string')
|
|
160
|
+
continue;
|
|
161
|
+
result.push({ field, constraint: skipInference ? 'serverError' : inferMessage(message), message });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
// Format 2: Raw issues array
|
|
167
|
+
const err = error;
|
|
168
|
+
if (Array.isArray(err['issues'])) {
|
|
169
|
+
const issues = err['issues'];
|
|
170
|
+
return issues.map((issue) => ({
|
|
171
|
+
field: issue.path && issue.path.length > 0
|
|
172
|
+
? issue.path.map(String).join('.')
|
|
173
|
+
: GLOBAL_ERROR_FIELD,
|
|
174
|
+
constraint: skipInference ? 'serverError' : zodCodeToConstraint(issue),
|
|
175
|
+
message: issue.message,
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
// Format 3: Direct array of issues
|
|
179
|
+
if (Array.isArray(error)) {
|
|
180
|
+
const issues = error;
|
|
181
|
+
if (issues.length > 0 && 'code' in issues[0] && 'path' in issues[0]) {
|
|
182
|
+
return issues.map((issue) => ({
|
|
183
|
+
field: issue.path && issue.path.length > 0
|
|
184
|
+
? issue.path.map(String).join('.')
|
|
185
|
+
: GLOBAL_ERROR_FIELD,
|
|
186
|
+
constraint: skipInference ? 'serverError' : zodCodeToConstraint(issue),
|
|
187
|
+
message: issue.message,
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Format 4: Wrapped { errors: { fieldErrors: {...}, formErrors: [...] } } or { error: {...} }
|
|
192
|
+
if (err['errors'] && typeof err['errors'] === 'object') {
|
|
193
|
+
const nested = err['errors'];
|
|
194
|
+
if (nested.fieldErrors && typeof nested.fieldErrors === 'object') {
|
|
195
|
+
const result = [];
|
|
196
|
+
// Collect form-level (global) errors from wrapped format
|
|
197
|
+
if (Array.isArray(nested.formErrors)) {
|
|
198
|
+
for (const message of nested.formErrors) {
|
|
199
|
+
if (typeof message !== 'string')
|
|
200
|
+
continue;
|
|
201
|
+
result.push({ field: GLOBAL_ERROR_FIELD, constraint: 'serverError', message });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
for (const [field, messages] of Object.entries(nested.fieldErrors)) {
|
|
205
|
+
if (!Array.isArray(messages))
|
|
206
|
+
continue;
|
|
207
|
+
for (const message of messages) {
|
|
208
|
+
if (typeof message !== 'string')
|
|
209
|
+
continue;
|
|
210
|
+
result.push({ field, constraint: skipInference ? 'serverError' : inferMessage(message), message });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return [];
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Generated bundle index. Do not edit.
|
|
223
|
+
*/
|
|
224
|
+
|
|
225
|
+
export { ZOD_CONSTRAINT_MAP, zodPreset };
|
|
226
|
+
//# sourceMappingURL=ngx-api-forms-zod.mjs.map
|