refactor(api): error formatting (#51595)

This commit is contained in:
Oliver Eyton-Williams
2023-09-19 22:55:35 +02:00
committed by GitHub
parent 0c62b22f1f
commit 5cb22dca84
5 changed files with 84 additions and 123 deletions

View File

@@ -1,72 +0,0 @@
import { ErrorObject } from 'ajv';
import { formatValidationError } from './error-formatting';
const missingSolutionError = {
instancePath: '',
schemaPath: '#/required',
keyword: 'required',
params: { missingProperty: 'solution' },
message: "must have required property 'solution'"
};
const missingIdError = {
instancePath: '',
schemaPath: '#/required',
keyword: 'required',
params: { missingProperty: 'id' },
message: "must have required property 'id'"
};
describe('Error formatting', () => {
describe('formatValidationError', () => {
it('should handle missing solutions', () => {
const formattedError = formatValidationError([missingSolutionError]);
expect(formattedError).toStrictEqual({
type: 'error',
message:
'You have not provided the valid links for us to inspect your work.'
});
});
it('should handle missing ids', () => {
const formattedError = formatValidationError([missingIdError]);
expect(formattedError).toStrictEqual({
type: 'error',
message: 'That does not appear to be a valid challenge submission.'
});
});
it('should return a generic error message for other errors', () => {
const formattedError = formatValidationError([
{
...missingSolutionError,
params: { missingProperty: 'notSolution' }
}
]);
expect(formattedError).toStrictEqual({
type: 'error',
message: 'That does not appear to be a valid challenge submission.'
});
});
it('should throw if passed zero errors', () => {
expect(() => formatValidationError([] as ErrorObject[])).toThrow(
Error(
'Bad Argument: the array of errors must have exactly one element.'
)
);
});
it('should throw if passed more than one error', () => {
expect(() => formatValidationError([{}, {}] as ErrorObject[])).toThrow(
Error(
'Bad Argument: the array of errors must have exactly one element.'
)
);
});
});
});

View File

@@ -1,30 +1,41 @@
import { ErrorObject } from 'ajv';
export type FormattedError = {
type FormattedError = {
type: 'error';
message:
| 'You have not provided the valid links for us to inspect your work.'
| 'That does not appear to be a valid challenge submission.'
// the next isn't generated here, but the type is more general.
| 'You have to complete the project before you can submit a URL.';
message: string;
};
/**
* Format invalid challenge submission errors.
*
* @param errors An array of validation errors.
* @returns Formatted errors that can be used in the response.
*/
export const formatValidationError = (
errors: ErrorObject[]
): FormattedError => {
if (errors.length !== 1) {
// TODO(Post-MVP): Normalize error responses (either msg or messge, not both)
type CodeRoadError = {
type: 'error';
msg: string;
};
const getError = (errors: ErrorObject[]): ErrorObject => {
// This is a guard against accidentally enabling allErrors in ajv and making
// the server more vulnerable to DOS.
const error = errors[0];
if (!error || errors.length !== 1) {
throw new Error(
'Bad Argument: the array of errors must have exactly one element.'
);
}
return error;
};
return errors[0]?.params.missingProperty === 'solution'
/**
* Format validation errors for /project-completed.
*
* @param errors An array of validation errors.
* @returns Formatted errors that can be used in the response.
*/
export const formatProjectCompletedValidation = (
errors: ErrorObject[]
): FormattedError => {
const error = getError(errors);
return error.instancePath === '' &&
error.params.missingProperty === 'solution'
? {
type: 'error',
message:
@@ -35,3 +46,32 @@ export const formatValidationError = (
message: 'That does not appear to be a valid challenge submission.'
};
};
/**
* Format validation errors for /coderoad-challenge-completed.
*
* @param errors An array of validation errors.
* @returns Formatted errors that can be used in the response.
*/
export const formatCoderoadChallengeCompletedValidation = (
errors: ErrorObject[]
): CodeRoadError => {
const error = getError(errors);
// TODO(Post-MVP): Return error saying that the body is not an object.
if (error.instancePath === '' && error.message === 'must be object')
return { type: 'error', msg: `'tutorialId' not found in request body` };
if (
error.instancePath === '' &&
error.params.missingProperty === 'coderoad-user-token'
) {
return {
type: 'error',
msg: `'Coderoad-User-Token' not found in request headers`
};
} else {
// by process of elimination:
return { type: 'error', msg: `'tutorialId' not found in request body` };
}
};