Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIO 7488: improve error handling #59

Merged
merged 8 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/error/DereferenceError.ts

This file was deleted.

10 changes: 10 additions & 0 deletions src/error/ProcessorError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ProcessorContext } from "types";
export class ProcessorError extends Error {
context: Omit<ProcessorContext<any>, 'scope'>;
constructor(message: string, context: ProcessorContext<any>, processor: string = 'unknown') {
super(message);
this.message = `${message}\nin ${processor} at ${context.path}`;
const { component, path, data, row } = context;
this.context = {component, path, data, row};
}
};
1 change: 0 additions & 1 deletion src/error/ValidatorError.ts

This file was deleted.

3 changes: 1 addition & 2 deletions src/error/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from './FieldError';
export * from './ValidatorError';
export * from './DereferenceError';
export * from './ProcessorError';
6 changes: 3 additions & 3 deletions src/process/dereference/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DereferenceError } from "error";
import { ProcessorError } from "error";
import {
ProcessorFn,
ProcessorScope,
Expand Down Expand Up @@ -37,7 +37,7 @@ export const dereferenceProcess: ProcessorFn<DereferenceScope> = async (context)
return;
}
if (!config?.database) {
throw new DereferenceError('Cannot dereference resource value without a database config object');
throw new ProcessorError('Cannot dereference resource value without a database config object', context, 'dereference');
}

try {
Expand All @@ -49,7 +49,7 @@ export const dereferenceProcess: ProcessorFn<DereferenceScope> = async (context)
component.components = vmCompatibleComponents;
}
catch (err: any) {
throw new DereferenceError(err.message || err);
throw new ProcessorError(err.message || err, context, 'dereference');
}
}

Expand Down
113 changes: 61 additions & 52 deletions src/process/validation/rules/validateAvailableItems.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import isEmpty from 'lodash/isEmpty';
import { FieldError, ValidatorError } from 'error';
import { FieldError, ProcessorError } from 'error';
import { Evaluator } from 'utils';
import { RadioComponent, SelectComponent, RuleFn, RuleFnSync, ValidationContext } from 'types';
import { isObject, isPromise } from '../util';
Expand Down Expand Up @@ -42,23 +42,23 @@ async function getAvailableSelectValues(component: SelectComponent) {
if (Array.isArray(component.data.values)) {
return mapStaticValues(component.data.values);
}
throw new ValidatorError(
throw new Error(
brendanbond marked this conversation as resolved.
Show resolved Hide resolved
`Failed to validate available values in static values select component '${component.key}': the values are not an array`,
);
case 'json': {
if (typeof component.data.json === 'string') {
try {
return mapDynamicValues(component, JSON.parse(component.data.json));
} catch (err) {
throw new ValidatorError(
throw new Error(
`Failed to validate available values in JSON select component '${component.key}': ${err}`
);
}
} else if (Array.isArray(component.data.json)) {
// TODO: need to retype this
return mapDynamicValues(component, component.data.json as Record<string, any>[]);
} else {
throw new ValidatorError(
throw new Error(
`Failed to validate available values in JSON select component '${component.key}': the values are not an array`
);
}
Expand All @@ -76,19 +76,19 @@ async function getAvailableSelectValues(component: SelectComponent) {
if (Array.isArray(resolvedCustomItems)) {
return resolvedCustomItems;
}
throw new ValidatorError(
throw new Error(
`Failed to validate available values in JSON select component '${component.key}': the values are not an array`
);
}
if (Array.isArray(customItems)) {
return customItems;
} else {
throw new ValidatorError(
throw new Error(
`Failed to validate available values in JSON select component '${component.key}': the values are not an array`
);
}
default:
throw new ValidatorError(
throw new Error(
`Failed to validate available values in select component '${component.key}': data source ${component.dataSrc} is not valid}`
);
}
Expand All @@ -100,23 +100,23 @@ function getAvailableSelectValuesSync(component: SelectComponent) {
if (Array.isArray(component.data?.values)) {
return mapStaticValues(component.data.values);
}
throw new ValidatorError(
throw new Error(
`Failed to validate available values in static values select component '${component.key}': the values are not an array`
);
case 'json': {
if (typeof component.data.json === 'string') {
try {
return mapDynamicValues(component, JSON.parse(component.data.json));
} catch (err) {
throw new ValidatorError(
throw new Error(
`Failed to validate available values in JSON select component '${component.key}': ${err}`
);
}
} else if (Array.isArray(component.data.json)) {
// TODO: need to retype this
return mapDynamicValues(component, component.data.json as Record<string, any>[]);
} else {
throw new ValidatorError(
throw new Error(
`Failed to validate available values in JSON select component '${component.key}': the values are not an array`
);
}
Expand All @@ -132,12 +132,12 @@ function getAvailableSelectValuesSync(component: SelectComponent) {
if (Array.isArray(customItems)) {
return customItems;
} else {
throw new ValidatorError(
throw new Error(
`Failed to validate available values in JSON select component '${component.key}': the values are not an array`
);
}
default:
throw new ValidatorError(
throw new Error(
`Failed to validate available values in select component '${component.key}': data source ${component.dataSrc} is not valid}`
);
}
Expand All @@ -153,41 +153,45 @@ function compareComplexValues(valueA: unknown, valueB: unknown) {
// this won't work
return JSON.stringify(valueA) === JSON.stringify(valueB);
} catch (err) {
throw new ValidatorError(`Error while comparing available values: ${err}`);
throw new Error(`Error while comparing available values: ${err}`);
}
}

export const validateAvailableItems: RuleFn = async (context: ValidationContext) => {
const { component, value } = context;
const error = new FieldError('invalidOption', context);
if (isValidatableRadioComponent(component)) {
if (value == null || isEmpty(value)) {
return null;
}

const values = component.values;
if (values) {
return values.findIndex(({ value: optionValue }) => optionValue === value) !== -1
? null
: error;
}
try {
if (isValidatableRadioComponent(component)) {
if (value == null || isEmpty(value)) {
return null;
}

return null;
} else if (isValidateableSelectComponent(component)) {
if (value == null || isEmpty(value)) {
return null;
}
const values = await getAvailableSelectValues(component);
if (values) {
if (isObject(value)) {
return values.find((optionValue) => compareComplexValues(optionValue, value)) !==
undefined
const values = component.values;
if (values) {
return values.findIndex(({ value: optionValue }) => optionValue === value) !== -1
? null
: error;
}

return values.find((optionValue) => optionValue === value) !== undefined ? null : error;
return null;
} else if (isValidateableSelectComponent(component)) {
if (value == null || isEmpty(value)) {
return null;
}
const values = await getAvailableSelectValues(component);
if (values) {
if (isObject(value)) {
return values.find((optionValue) => compareComplexValues(optionValue, value)) !==
undefined
? null
: error;
}

return values.find((optionValue) => optionValue === value) !== undefined ? null : error;
}
}
} catch (err: any) {
throw new ProcessorError(err.message || err, context, 'validate:validateAvailableItems');
}
return null;
};
Expand All @@ -209,28 +213,33 @@ export const shouldValidate = (context: any) => {
export const validateAvailableItemsSync: RuleFnSync = (context: ValidationContext) => {
const { component, value } = context;
const error = new FieldError('invalidOption', context);
if (!shouldValidate(context)) {
return null;
}
if (isValidatableRadioComponent(component)) {
const values = component.values;
if (values) {
return values.findIndex(({ value: optionValue }) => optionValue === value) !== -1
? null
: error;
try {

if (!shouldValidate(context)) {
return null;
}
return null;
} else if (isValidateableSelectComponent(component)) {
const values = getAvailableSelectValuesSync(component);
if (values) {
if (isObject(value)) {
return values.find((optionValue) => compareComplexValues(optionValue, value)) !==
undefined
if (isValidatableRadioComponent(component)) {
const values = component.values;
if (values) {
return values.findIndex(({ value: optionValue }) => optionValue === value) !== -1
? null
: error;
}
return values.find((optionValue) => optionValue === value) !== undefined ? null : error;
return null;
} else if (isValidateableSelectComponent(component)) {
const values = getAvailableSelectValuesSync(component);
if (values) {
if (isObject(value)) {
return values.find((optionValue) => compareComplexValues(optionValue, value)) !==
undefined
? null
: error;
}
return values.find((optionValue) => optionValue === value) !== undefined ? null : error;
}
}
} catch (err: any) {
throw new ProcessorError(err.message || err, context, 'validate:validateAvailableItems');
}
return null;
};
Expand Down
6 changes: 3 additions & 3 deletions src/process/validation/rules/validateCaptcha.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FieldError } from '../../../error/FieldError';
import { RuleFn, ValidationContext } from '../../../types/index';
import { ValidatorError } from 'error';
import { ProcessorError } from 'error';
import { ProcessorInfo } from 'types/process/ProcessorInfo';

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

if (!config || !config.database) {
throw new ValidatorError("Can't test for recaptcha success without a database config object");
throw new ProcessorError("Can't test for recaptcha success without a database config object", context, 'validate:validateCaptcha');
}
try {
if (!value || !value.token) {
Expand All @@ -31,7 +31,7 @@ export const validateCaptcha: RuleFn = async (context: ValidationContext) => {
return (captchaResult === true) ? null : new FieldError('captchaFailure', context);
}
catch (err: any) {
throw new ValidatorError(err.message || err);
throw new ProcessorError(err.message || err, context, 'validate:validateCaptcha');
}
};

Expand Down
68 changes: 35 additions & 33 deletions src/process/validation/rules/validateCustom.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { isEmpty } from 'lodash';
import { RuleFn, RuleFnSync } from 'types/RuleFn';
import { FieldError } from 'error/FieldError';
import { RuleFn, RuleFnSync, ProcessorInfo, ValidationContext } from 'types';
import { FieldError, ProcessorError } from 'error';
import { Evaluator } from 'utils';
import { ValidationContext } from 'types';
import { ProcessorInfo } from 'types/process/ProcessorInfo';

export const validateCustom: RuleFn = async (context: ValidationContext) => {
return validateCustomSync(context);
};

export const shouldValidate = (context: ValidationContext) => {
const { component, value } = context;
const { component } = context;
const customValidation = component.validate?.custom;
if (!customValidation) {
return false;
Expand All @@ -21,35 +19,39 @@ export const shouldValidate = (context: ValidationContext) => {
export const validateCustomSync: RuleFnSync = (context: ValidationContext) => {
const { component, data, row, value, index, instance, evalContext } = context;
const customValidation = component.validate?.custom;
if (!shouldValidate(context)) {
return null;
try {
if (!shouldValidate(context)) {
return null;
}

const evalContextValue = {
...(instance?.evalContext ? instance.evalContext() : (evalContext ? evalContext(context) : context)),
component,
data,
row,
rowIndex: index,
instance,
valid: true,
input: value,
}

const isValid = Evaluator.evaluate(
customValidation,
evalContextValue,
'valid',
true,
{},
{}
);

if (isValid === null || isValid === true) {
return null;
}

return new FieldError(typeof isValid === 'string' ? isValid : 'custom', {...context, hasLabel: false });
} catch (err: any) {
throw new ProcessorError(err.message || err, context, 'validate:validateCustom');
}

const evalContextValue = {
...(instance?.evalContext ? instance.evalContext() : (evalContext ? evalContext(context) : context)),
component,
data,
row,
rowIndex: index,
instance,
valid: true,
input: value,
}

const isValid = Evaluator.evaluate(
customValidation,
evalContextValue,
'valid',
true,
{},
{}
);

if (isValid === null || isValid === true) {
return null;
}

return new FieldError(typeof isValid === 'string' ? isValid : 'custom', {...context, hasLabel: false });
};


Expand Down
6 changes: 3 additions & 3 deletions src/process/validation/rules/validateMaximumDay.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ValidatorError, FieldError } from 'error';
import { ProcessorError, FieldError } from 'error';
import { DayComponent, RuleFn, RuleFnSync, ValidationContext } from 'types';
import { dayjs, isPartialDay, getDateValidationFormat, getDateSetting } from 'utils/date';
import { ProcessorInfo } from 'types/process/ProcessorInfo';
Expand Down Expand Up @@ -31,7 +31,7 @@ export const validateMaximumDaySync: RuleFnSync = (context: ValidationContext) =
return null;
}
if (typeof value !== 'string') {
throw new ValidatorError(`Cannot validate day value ${value} because it is not a string`);
throw new ProcessorError(`Cannot validate day value ${value} because it is not a string`, context, 'validate:validateMaximumDay');
}
// TODO: this validation probably goes for dates and days
const format = getDateValidationFormat(component as DayComponent);
Expand All @@ -52,4 +52,4 @@ export const validateMaximumDayInfo: ProcessorInfo<ValidationContext, FieldError
process: validateMaximumDay,
processSync: validateMaximumDaySync,
shouldProcess: shouldValidate,
};
};
Loading
Loading