Skip to content

Commit 2102073

Browse files
Merge pull request #59 from formio/FIO-7488
FIO 7488: improve error handling
2 parents b9ebc0a + 9d5d1d9 commit 2102073

21 files changed

+262
-190
lines changed

src/error/DereferenceError.ts

-1
This file was deleted.

src/error/ProcessorError.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { ProcessorContext } from "types";
2+
export class ProcessorError extends Error {
3+
context: Omit<ProcessorContext<any>, 'scope'>;
4+
constructor(message: string, context: ProcessorContext<any>, processor: string = 'unknown') {
5+
super(message);
6+
this.message = `${message}\nin ${processor} at ${context.path}`;
7+
const { component, path, data, row } = context;
8+
this.context = {component, path, data, row};
9+
}
10+
};

src/error/ValidatorError.ts

-1
This file was deleted.

src/error/index.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
export * from './FieldError';
2-
export * from './ValidatorError';
3-
export * from './DereferenceError';
2+
export * from './ProcessorError';

src/process/dereference/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DereferenceError } from "error";
1+
import { ProcessorError } from "error";
22
import {
33
ProcessorFn,
44
ProcessorScope,
@@ -37,7 +37,7 @@ export const dereferenceProcess: ProcessorFn<DereferenceScope> = async (context)
3737
return;
3838
}
3939
if (!config?.database) {
40-
throw new DereferenceError('Cannot dereference resource value without a database config object');
40+
throw new ProcessorError('Cannot dereference resource value without a database config object', context, 'dereference');
4141
}
4242

4343
try {
@@ -49,7 +49,7 @@ export const dereferenceProcess: ProcessorFn<DereferenceScope> = async (context)
4949
component.components = vmCompatibleComponents;
5050
}
5151
catch (err: any) {
52-
throw new DereferenceError(err.message || err);
52+
throw new ProcessorError(err.message || err, context, 'dereference');
5353
}
5454
}
5555

src/process/validation/rules/validateAvailableItems.ts

+95-65
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import isEmpty from 'lodash/isEmpty';
2-
import { FieldError, ValidatorError } from 'error';
2+
import { FieldError, ProcessorError } from 'error';
33
import { Evaluator } from 'utils';
44
import { RadioComponent, SelectComponent, RuleFn, RuleFnSync, ValidationContext } from 'types';
55
import { isObject, isPromise } from '../util';
@@ -36,30 +36,36 @@ function mapStaticValues(values: { label: string; value: string }[]) {
3636
return values.map((obj) => obj.value);
3737
}
3838

39-
async function getAvailableSelectValues(component: SelectComponent) {
39+
async function getAvailableSelectValues(component: SelectComponent, context: ValidationContext) {
4040
switch (component.dataSrc) {
4141
case 'values':
4242
if (Array.isArray(component.data.values)) {
4343
return mapStaticValues(component.data.values);
4444
}
45-
throw new ValidatorError(
45+
throw new ProcessorError(
4646
`Failed to validate available values in static values select component '${component.key}': the values are not an array`,
47+
context,
48+
'validate:validateAvailableItems'
4749
);
4850
case 'json': {
4951
if (typeof component.data.json === 'string') {
5052
try {
5153
return mapDynamicValues(component, JSON.parse(component.data.json));
5254
} catch (err) {
53-
throw new ValidatorError(
54-
`Failed to validate available values in JSON select component '${component.key}': ${err}`
55+
throw new ProcessorError(
56+
`Failed to validate available values in JSON select component '${component.key}': ${err}`,
57+
context,
58+
'validate:validateAvailableItems'
5559
);
5660
}
5761
} else if (Array.isArray(component.data.json)) {
5862
// TODO: need to retype this
5963
return mapDynamicValues(component, component.data.json as Record<string, any>[]);
6064
} else {
61-
throw new ValidatorError(
62-
`Failed to validate available values in JSON select component '${component.key}': the values are not an array`
65+
throw new ProcessorError(
66+
`Failed to validate available values in JSON select component '${component.key}': the values are not an array`,
67+
context,
68+
'validate:validateAvailableItems'
6369
);
6470
}
6571
}
@@ -76,48 +82,60 @@ async function getAvailableSelectValues(component: SelectComponent) {
7682
if (Array.isArray(resolvedCustomItems)) {
7783
return resolvedCustomItems;
7884
}
79-
throw new ValidatorError(
80-
`Failed to validate available values in JSON select component '${component.key}': the values are not an array`
85+
throw new ProcessorError(
86+
`Failed to validate available values in JSON select component '${component.key}': the values are not an array`,
87+
context,
88+
'validate:validateAvailableItems'
8189
);
8290
}
8391
if (Array.isArray(customItems)) {
8492
return customItems;
8593
} else {
86-
throw new ValidatorError(
87-
`Failed to validate available values in JSON select component '${component.key}': the values are not an array`
94+
throw new ProcessorError(
95+
`Failed to validate available values in JSON select component '${component.key}': the values are not an array`,
96+
context,
97+
'validate:validateAvailableItems'
8898
);
8999
}
90100
default:
91-
throw new ValidatorError(
92-
`Failed to validate available values in select component '${component.key}': data source ${component.dataSrc} is not valid}`
101+
throw new ProcessorError(
102+
`Failed to validate available values in select component '${component.key}': data source ${component.dataSrc} is not valid}`,
103+
context,
104+
'validate:validateAvailableItems'
93105
);
94106
}
95107
}
96108

97-
function getAvailableSelectValuesSync(component: SelectComponent) {
109+
function getAvailableSelectValuesSync(component: SelectComponent, context: ValidationContext) {
98110
switch (component.dataSrc) {
99111
case 'values':
100112
if (Array.isArray(component.data?.values)) {
101113
return mapStaticValues(component.data.values);
102114
}
103-
throw new ValidatorError(
104-
`Failed to validate available values in static values select component '${component.key}': the values are not an array`
115+
throw new ProcessorError(
116+
`Failed to validate available values in static values select component '${component.key}': the values are not an array`,
117+
context,
118+
'validate:validateAvailableItems'
105119
);
106120
case 'json': {
107121
if (typeof component.data.json === 'string') {
108122
try {
109123
return mapDynamicValues(component, JSON.parse(component.data.json));
110124
} catch (err) {
111-
throw new ValidatorError(
112-
`Failed to validate available values in JSON select component '${component.key}': ${err}`
125+
throw new ProcessorError(
126+
`Failed to validate available values in JSON select component '${component.key}': ${err}`,
127+
context,
128+
'validate:validateAvailableItems'
113129
);
114130
}
115131
} else if (Array.isArray(component.data.json)) {
116132
// TODO: need to retype this
117133
return mapDynamicValues(component, component.data.json as Record<string, any>[]);
118134
} else {
119-
throw new ValidatorError(
120-
`Failed to validate available values in JSON select component '${component.key}': the values are not an array`
135+
throw new ProcessorError(
136+
`Failed to validate available values in JSON select component '${component.key}': the values are not an array`,
137+
context,
138+
'validate:validateAvailableItems'
121139
);
122140
}
123141
}
@@ -132,18 +150,22 @@ function getAvailableSelectValuesSync(component: SelectComponent) {
132150
if (Array.isArray(customItems)) {
133151
return customItems;
134152
} else {
135-
throw new ValidatorError(
136-
`Failed to validate available values in JSON select component '${component.key}': the values are not an array`
153+
throw new ProcessorError(
154+
`Failed to validate available values in JSON select component '${component.key}': the values are not an array`,
155+
context,
156+
'validate:validateAvailableItems'
137157
);
138158
}
139159
default:
140-
throw new ValidatorError(
141-
`Failed to validate available values in select component '${component.key}': data source ${component.dataSrc} is not valid}`
160+
throw new ProcessorError(
161+
`Failed to validate available values in select component '${component.key}': data source ${component.dataSrc} is not valid}`,
162+
context,
163+
'validate:validateAvailableItems'
142164
);
143165
}
144166
}
145167

146-
function compareComplexValues(valueA: unknown, valueB: unknown) {
168+
function compareComplexValues(valueA: unknown, valueB: unknown, context: ValidationContext) {
147169
if (!isObject(valueA) || !isObject(valueB)) {
148170
return false;
149171
}
@@ -153,41 +175,45 @@ function compareComplexValues(valueA: unknown, valueB: unknown) {
153175
// this won't work
154176
return JSON.stringify(valueA) === JSON.stringify(valueB);
155177
} catch (err) {
156-
throw new ValidatorError(`Error while comparing available values: ${err}`);
178+
throw new ProcessorError(`Error while comparing available values: ${err}`, context, 'validate:validateAvailableItems');
157179
}
158180
}
159181

160182
export const validateAvailableItems: RuleFn = async (context: ValidationContext) => {
161183
const { component, value } = context;
162184
const error = new FieldError('invalidOption', context, 'onlyAvailableItems');
163-
if (isValidatableRadioComponent(component)) {
164-
if (value == null || isEmpty(value)) {
165-
return null;
166-
}
167-
168-
const values = component.values;
169-
if (values) {
170-
return values.findIndex(({ value: optionValue }) => optionValue === value) !== -1
171-
? null
172-
: error;
173-
}
185+
try {
186+
if (isValidatableRadioComponent(component)) {
187+
if (value == null || isEmpty(value)) {
188+
return null;
189+
}
174190

175-
return null;
176-
} else if (isValidateableSelectComponent(component)) {
177-
if (value == null || isEmpty(value)) {
178-
return null;
179-
}
180-
const values = await getAvailableSelectValues(component);
181-
if (values) {
182-
if (isObject(value)) {
183-
return values.find((optionValue) => compareComplexValues(optionValue, value)) !==
184-
undefined
191+
const values = component.values;
192+
if (values) {
193+
return values.findIndex(({ value: optionValue }) => optionValue === value) !== -1
185194
? null
186195
: error;
187196
}
188197

189-
return values.find((optionValue) => optionValue === value) !== undefined ? null : error;
198+
return null;
199+
} else if (isValidateableSelectComponent(component)) {
200+
if (value == null || isEmpty(value)) {
201+
return null;
202+
}
203+
const values = await getAvailableSelectValues(component, context);
204+
if (values) {
205+
if (isObject(value)) {
206+
return values.find((optionValue) => compareComplexValues(optionValue, value, context)) !==
207+
undefined
208+
? null
209+
: error;
210+
}
211+
212+
return values.find((optionValue) => optionValue === value) !== undefined ? null : error;
213+
}
190214
}
215+
} catch (err: any) {
216+
throw new ProcessorError(err.message || err, context, 'validate:validateAvailableItems');
191217
}
192218
return null;
193219
};
@@ -209,28 +235,32 @@ export const shouldValidate = (context: any) => {
209235
export const validateAvailableItemsSync: RuleFnSync = (context: ValidationContext) => {
210236
const { component, value } = context;
211237
const error = new FieldError('invalidOption', context, 'onlyAvailableItems');
212-
if (!shouldValidate(context)) {
213-
return null;
214-
}
215-
if (isValidatableRadioComponent(component)) {
216-
const values = component.values;
217-
if (values) {
218-
return values.findIndex(({ value: optionValue }) => optionValue === value) !== -1
219-
? null
220-
: error;
238+
try {
239+
if (!shouldValidate(context)) {
240+
return null;
221241
}
222-
return null;
223-
} else if (isValidateableSelectComponent(component)) {
224-
const values = getAvailableSelectValuesSync(component);
225-
if (values) {
226-
if (isObject(value)) {
227-
return values.find((optionValue) => compareComplexValues(optionValue, value)) !==
228-
undefined
242+
if (isValidatableRadioComponent(component)) {
243+
const values = component.values;
244+
if (values) {
245+
return values.findIndex(({ value: optionValue }) => optionValue === value) !== -1
229246
? null
230247
: error;
231248
}
232-
return values.find((optionValue) => optionValue === value) !== undefined ? null : error;
249+
return null;
250+
} else if (isValidateableSelectComponent(component)) {
251+
const values = getAvailableSelectValuesSync(component, context);
252+
if (values) {
253+
if (isObject(value)) {
254+
return values.find((optionValue) => compareComplexValues(optionValue, value, context)) !==
255+
undefined
256+
? null
257+
: error;
258+
}
259+
return values.find((optionValue) => optionValue === value) !== undefined ? null : error;
260+
}
233261
}
262+
} catch (err: any) {
263+
throw new ProcessorError(err.message || err, context, 'validate:validateAvailableItems');
234264
}
235265
return null;
236266
};

src/process/validation/rules/validateCaptcha.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { FieldError } from '../../../error/FieldError';
22
import { RuleFn, ValidationContext } from '../../../types/index';
3-
import { ValidatorError } from 'error';
3+
import { ProcessorError } from 'error';
44
import { ProcessorInfo } from 'types/process/ProcessorInfo';
55

66
export const shouldValidate = (context: ValidationContext) => {
@@ -18,7 +18,7 @@ export const validateCaptcha: RuleFn = async (context: ValidationContext) => {
1818
}
1919

2020
if (!config || !config.database) {
21-
throw new ValidatorError("Can't test for recaptcha success without a database config object");
21+
throw new ProcessorError("Can't test for recaptcha success without a database config object", context, 'validate:validateCaptcha');
2222
}
2323
try {
2424
if (!value || !value.token) {
@@ -31,7 +31,7 @@ export const validateCaptcha: RuleFn = async (context: ValidationContext) => {
3131
return (captchaResult === true) ? null : new FieldError('captchaFailure', context, 'captcha');
3232
}
3333
catch (err: any) {
34-
throw new ValidatorError(err.message || err);
34+
throw new ProcessorError(err.message || err, context, 'validate:validateCaptcha');
3535
}
3636
};
3737

0 commit comments

Comments
 (0)