mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-20 00:08:02 +01:00
b3054c9277
The `dataPath` was present (but not in the type) in previous versions of the error library that we use. But with the recent major upgrade, it's been removed and the `instancePath` property has finally come into use. This PR removes all the handling for the previous property and replaces it with `instancePath`. Because the `dataPath` used full stops and the `instancePath` uses slashes, we need to change a little bit of the handling too.
199 lines
6.2 KiB
TypeScript
199 lines
6.2 KiB
TypeScript
import { ErrorObject } from 'ajv';
|
|
import { ValidationError } from 'joi';
|
|
import getProp from 'lodash.get';
|
|
import { ApiErrorSchema, UnleashError } from './unleash-error';
|
|
|
|
type ValidationErrorDescription = {
|
|
description?: string;
|
|
message: string;
|
|
path?: string;
|
|
};
|
|
class BadDataError extends UnleashError {
|
|
statusCode = 400;
|
|
|
|
details: ValidationErrorDescription[];
|
|
|
|
constructor(
|
|
message: string,
|
|
errors?: [ValidationErrorDescription, ...ValidationErrorDescription[]],
|
|
) {
|
|
const topLevelMessage = `Request validation failed: your request body or params contain invalid data${
|
|
errors
|
|
? '. Refer to the `details` list for more information.'
|
|
: `: ${message}`
|
|
}`;
|
|
super(topLevelMessage);
|
|
|
|
this.details = errors ?? [{ message: message, description: message }];
|
|
}
|
|
|
|
toJSON(): ApiErrorSchema {
|
|
return {
|
|
...super.toJSON(),
|
|
details: this.details,
|
|
};
|
|
}
|
|
}
|
|
|
|
export default BadDataError;
|
|
|
|
const constructPath = (pathToParent: string, propertyName: string) =>
|
|
[pathToParent, propertyName].filter(Boolean).join('/');
|
|
|
|
const missingRequiredPropertyMessage = (
|
|
pathToParentObject: string,
|
|
missingPropertyName: string,
|
|
) => {
|
|
const path = constructPath(pathToParentObject, missingPropertyName);
|
|
const description = `The \`${path}\` property is required. It was not present on the data you sent.`;
|
|
return {
|
|
path,
|
|
description,
|
|
message: description,
|
|
};
|
|
};
|
|
|
|
const additionalPropertiesMessage = (
|
|
pathToParentObject: string,
|
|
additionalPropertyName: string,
|
|
) => {
|
|
const path = constructPath(pathToParentObject, additionalPropertyName);
|
|
const description = `The ${
|
|
pathToParentObject ? `\`${pathToParentObject}\`` : 'root'
|
|
} object of the request body does not allow additional properties. Your request included the \`${path}\` property.`;
|
|
|
|
return {
|
|
path,
|
|
description,
|
|
message: description,
|
|
};
|
|
};
|
|
|
|
const genericErrorMessage = (
|
|
requestBody: object,
|
|
propertyName: string,
|
|
errorMessage: string = 'is invalid',
|
|
) => {
|
|
const input = getProp(requestBody, propertyName.split('/'));
|
|
|
|
const youSent = JSON.stringify(input);
|
|
const description = `The \`${propertyName}\` property ${errorMessage}. You sent ${youSent}.`;
|
|
return {
|
|
description,
|
|
message: description,
|
|
path: propertyName,
|
|
};
|
|
};
|
|
|
|
const oneOfMessage = (
|
|
propertyName: string,
|
|
errorMessage: string = 'is invalid',
|
|
) => {
|
|
const errorPosition =
|
|
propertyName === '' ? 'root object' : `"${propertyName}" property`;
|
|
|
|
const description = `The ${errorPosition} ${errorMessage}. The data you provided matches more than one option in the schema. These options are mutually exclusive. Please refer back to the schema and remove any excess properties.`;
|
|
|
|
return {
|
|
description,
|
|
message: description,
|
|
path: propertyName,
|
|
};
|
|
};
|
|
|
|
const enumMessage = (
|
|
propertyName: string,
|
|
message: string | undefined,
|
|
allowedValues: string[],
|
|
suppliedValue: string | null | undefined,
|
|
) => {
|
|
const description = `The \`${propertyName}\` property ${
|
|
message ?? 'must match one of the allowed values'
|
|
}: ${allowedValues
|
|
.map((value) => `"${value}"`)
|
|
.join(
|
|
', ',
|
|
)}. You provided "${suppliedValue}", which is not valid. Please use one of the allowed values instead..`;
|
|
|
|
return {
|
|
description,
|
|
message: description,
|
|
path: propertyName,
|
|
};
|
|
};
|
|
|
|
export const fromOpenApiValidationError =
|
|
(requestBody: object) =>
|
|
(validationError: ErrorObject): ValidationErrorDescription => {
|
|
const { instancePath, params, message } = validationError;
|
|
const propertyName = instancePath.substring('/body/'.length);
|
|
|
|
switch (validationError.keyword) {
|
|
case 'required':
|
|
return missingRequiredPropertyMessage(
|
|
propertyName,
|
|
params.missingProperty,
|
|
);
|
|
case 'additionalProperties':
|
|
return additionalPropertiesMessage(
|
|
propertyName,
|
|
params.additionalProperty,
|
|
);
|
|
case 'enum':
|
|
return enumMessage(
|
|
instancePath.substring(instancePath.lastIndexOf('/') + 1),
|
|
message,
|
|
params.allowedValues,
|
|
getProp(
|
|
requestBody,
|
|
instancePath.substring('/body/'.length).split('/'),
|
|
),
|
|
);
|
|
|
|
case 'oneOf':
|
|
return oneOfMessage(propertyName, validationError.message);
|
|
default:
|
|
return genericErrorMessage(requestBody, propertyName, message);
|
|
}
|
|
};
|
|
|
|
export const fromOpenApiValidationErrors = (
|
|
requestBody: object,
|
|
validationErrors: [ErrorObject, ...ErrorObject[]],
|
|
): BadDataError => {
|
|
const [firstDetail, ...remainingDetails] = validationErrors.map(
|
|
fromOpenApiValidationError(requestBody),
|
|
);
|
|
|
|
return new BadDataError(
|
|
"Request validation failed: the payload you provided doesn't conform to the schema. Check the `details` property for a list of errors that we found.",
|
|
[firstDetail, ...remainingDetails],
|
|
);
|
|
};
|
|
|
|
export const fromJoiError = (err: ValidationError): BadDataError => {
|
|
const details = err.details.map((detail) => {
|
|
const messageEnd = detail.context?.value
|
|
? `. You provided ${JSON.stringify(detail.context.value)}.`
|
|
: '.';
|
|
const description = detail.message + messageEnd;
|
|
return {
|
|
description,
|
|
message: description,
|
|
};
|
|
});
|
|
|
|
const [first, ...rest] = details;
|
|
|
|
if (first) {
|
|
return new BadDataError(
|
|
'A validation error occurred while processing your request data. Refer to the `details` property for more information.',
|
|
[first, ...rest],
|
|
);
|
|
} else {
|
|
return new BadDataError(
|
|
'A validation error occurred while processing your request data. Please make sure it conforms to the request data schema.',
|
|
);
|
|
}
|
|
};
|