mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-01-06 06:01:31 -05:00
refactor(api): error formatting (#51595)
This commit is contained in:
committed by
GitHub
parent
0c62b22f1f
commit
5cb22dca84
@@ -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.'
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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` };
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user