mirror of
https://github.com/directus/directus.git
synced 2026-01-29 18:27:55 -05:00
Replace old errors with new exceptions
This commit is contained in:
123
src/error.ts
123
src/error.ts
@@ -1,123 +0,0 @@
|
||||
import { ErrorRequestHandler } from 'express';
|
||||
import { ValidationError } from '@hapi/joi';
|
||||
import { TokenExpiredError } from 'jsonwebtoken';
|
||||
import logger from './logger';
|
||||
|
||||
export enum ErrorCode {
|
||||
NOT_FOUND = 'NOT_FOUND',
|
||||
FIELD_NOT_FOUND = 'FIELD_NOT_FOUND',
|
||||
INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR',
|
||||
ENOENT = 'ENOENT',
|
||||
EXTENSION_ILLEGAL_TYPE = 'EXTENSION_ILLEGAL_TYPE',
|
||||
INVALID_QUERY = 'INVALID_QUERY',
|
||||
INVALID_USER_CREDENTIALS = 'INVALID_USER_CREDENTIALS',
|
||||
USER_NOT_FOUND = 'USER_NOT_FOUND',
|
||||
FAILED_VALIDATION = 'FAILED_VALIDATION',
|
||||
MALFORMED_JSON = 'MALFORMED_JSON',
|
||||
TOKEN_EXPIRED = 'TOKEN_EXPIRED',
|
||||
TOO_MANY_ITEMS = 'TOO_MANY_ITEMS',
|
||||
}
|
||||
|
||||
enum HTTPStatus {
|
||||
NOT_FOUND = 404,
|
||||
FIELD_NOT_FOUND = 400,
|
||||
INTERNAL_SERVER_ERROR = 500,
|
||||
ENOENT = 501,
|
||||
EXTENSION_ILLEGAL_TYPE = 400,
|
||||
INVALID_QUERY = 400,
|
||||
INVALID_USER_CREDENTIALS = 401,
|
||||
USER_NOT_FOUND = 401,
|
||||
FAILED_VALIDATION = 422,
|
||||
MALFORMED_JSON = 400,
|
||||
TOKEN_EXPIRED = 401,
|
||||
TOO_MANY_ITEMS = 400,
|
||||
}
|
||||
|
||||
export const errorHandler: ErrorRequestHandler = (
|
||||
error: APIError | ValidationError | Error,
|
||||
req,
|
||||
res,
|
||||
next
|
||||
) => {
|
||||
let response: any = {};
|
||||
|
||||
if (error instanceof APIError) {
|
||||
logger.debug(error);
|
||||
|
||||
res.status(error.status);
|
||||
|
||||
response = {
|
||||
error: {
|
||||
code: error.code,
|
||||
message: error.message,
|
||||
},
|
||||
};
|
||||
} else if (error instanceof ValidationError) {
|
||||
logger.debug(error);
|
||||
|
||||
res.status(HTTPStatus.FAILED_VALIDATION);
|
||||
|
||||
response = {
|
||||
error: {
|
||||
code: ErrorCode.FAILED_VALIDATION,
|
||||
message: error.message,
|
||||
},
|
||||
};
|
||||
} else if (error instanceof TokenExpiredError) {
|
||||
logger.debug(error);
|
||||
res.status(HTTPStatus.TOKEN_EXPIRED);
|
||||
response = {
|
||||
error: {
|
||||
code: ErrorCode.TOKEN_EXPIRED,
|
||||
message: 'The provided token is expired.',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Syntax errors are most likely thrown by Body Parser when misaligned JSON is sent to the API
|
||||
else if (error instanceof SyntaxError && 'entity.parse.failed') {
|
||||
logger.debug(error);
|
||||
|
||||
res.status(HTTPStatus.MALFORMED_JSON);
|
||||
|
||||
response = {
|
||||
error: {
|
||||
code: ErrorCode.MALFORMED_JSON,
|
||||
message: error.message,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
logger.error(error);
|
||||
|
||||
res.status(500);
|
||||
|
||||
response = {
|
||||
error: {
|
||||
code: ErrorCode.INTERNAL_SERVER_ERROR,
|
||||
},
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
response.error.message = error.message;
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
response.error.stack = error.stack;
|
||||
}
|
||||
|
||||
return res.json(response);
|
||||
};
|
||||
|
||||
export default class APIError extends Error {
|
||||
status: HTTPStatus;
|
||||
code: ErrorCode;
|
||||
|
||||
constructor(code: ErrorCode, message: string) {
|
||||
super(message);
|
||||
this.status = HTTPStatus[code] || 500;
|
||||
this.code = code;
|
||||
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
import { Code } from './types';
|
||||
|
||||
export class BaseException extends Error {
|
||||
status: number;
|
||||
code: Code;
|
||||
code: string;
|
||||
|
||||
constructor(message: string, status: number, code: Code) {
|
||||
constructor(message: string, status: number, code: string) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
this.code = code;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
export * from './not-found';
|
||||
export * from './base';
|
||||
export * from './invalid-credentials';
|
||||
export * from './invalid-payload';
|
||||
export * from './invalid-query';
|
||||
export * from './item-limit';
|
||||
export * from './route-not-found';
|
||||
|
||||
7
src/exceptions/invalid-credentials.ts
Normal file
7
src/exceptions/invalid-credentials.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { BaseException } from './base';
|
||||
|
||||
export class InvalidCredentialsException extends BaseException {
|
||||
constructor(message = 'Invalid user credentials.') {
|
||||
super(message, 401, 'INVALID_CREDENTIALS');
|
||||
}
|
||||
}
|
||||
7
src/exceptions/invalid-payload.ts
Normal file
7
src/exceptions/invalid-payload.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { BaseException } from './base';
|
||||
|
||||
export class InvalidPayloadException extends BaseException {
|
||||
constructor(message: string) {
|
||||
super(message, 400, 'INVALID_PAYLOAD');
|
||||
}
|
||||
}
|
||||
7
src/exceptions/item-limit.ts
Normal file
7
src/exceptions/item-limit.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { BaseException } from './base';
|
||||
|
||||
export class ItemLimitException extends BaseException {
|
||||
constructor(message: string) {
|
||||
super(message, 400, 'ITEM_LIMIT_REACHED');
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { BaseException } from './base';
|
||||
|
||||
export class RouteNotFound extends BaseException {
|
||||
constructor(path: string) {
|
||||
super(`Route ${path} doesn't exist.`, 404, 'ROUTE_NOT_FOUND');
|
||||
}
|
||||
}
|
||||
|
||||
export class CollectionNotFoundException extends BaseException {
|
||||
constructor(collection: string) {
|
||||
super(`Collection ${collection} can't be found.`, 404, 'COLLECTION_NOT_FOUND');
|
||||
}
|
||||
}
|
||||
|
||||
export class FieldNotFoundException extends BaseException {
|
||||
constructor(field: string) {
|
||||
super(`Field ${field} can't be found.`, 404, 'FIELD_NOT_FOUND');
|
||||
}
|
||||
}
|
||||
7
src/exceptions/route-not-found.ts
Normal file
7
src/exceptions/route-not-found.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { BaseException } from './base';
|
||||
|
||||
export class RouteNotFoundException extends BaseException {
|
||||
constructor(path: string) {
|
||||
super(`Route ${path} doesn't exist.`, 404, 'ROUTE_NOT_FOUND');
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
export type Code =
|
||||
| 'COLLECTION_NOT_FOUND'
|
||||
| 'FIELD_NOT_FOUND'
|
||||
| 'ROUTE_NOT_FOUND'
|
||||
| 'NOT_FOUND'
|
||||
| 'INVALID_QUERY';
|
||||
@@ -5,7 +5,7 @@
|
||||
import { RequestHandler } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import database from '../database';
|
||||
import APIError, { ErrorCode } from '../error';
|
||||
import { RouteNotFoundException } from '../exceptions';
|
||||
|
||||
const validateCollection: RequestHandler = asyncHandler(async (req, res, next) => {
|
||||
if (!req.params.collection) return next();
|
||||
@@ -17,7 +17,7 @@ const validateCollection: RequestHandler = asyncHandler(async (req, res, next) =
|
||||
return next();
|
||||
}
|
||||
|
||||
throw new APIError(ErrorCode.NOT_FOUND, `Collection "${req.params.collection}" doesn't exist.`);
|
||||
throw new RouteNotFoundException(req.path);
|
||||
});
|
||||
|
||||
export default validateCollection;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { RequestHandler } from 'express';
|
||||
import { Query } from '../types/query';
|
||||
import { hasFields } from '../services/schema';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import APIError, { ErrorCode } from '../error';
|
||||
import { InvalidQueryException } from '../exceptions';
|
||||
|
||||
const validateQuery: RequestHandler = asyncHandler(async (req, res, next) => {
|
||||
if (!req.params.collection) return next();
|
||||
@@ -28,8 +28,7 @@ const validateQuery: RequestHandler = asyncHandler(async (req, res, next) => {
|
||||
|
||||
async function validateParams(collection: string, query: Query) {
|
||||
if (query.offset && query.page) {
|
||||
throw new APIError(
|
||||
ErrorCode.INVALID_QUERY,
|
||||
throw new InvalidQueryException(
|
||||
`You can't have both the offset and page query parameters enabled.`
|
||||
);
|
||||
}
|
||||
@@ -51,10 +50,7 @@ async function validateFields(collection: string, query: Query) {
|
||||
Array.from(fieldsToCheck).forEach((field, index) => {
|
||||
const exists = fieldsExist[index];
|
||||
if (exists === false)
|
||||
throw new APIError(
|
||||
ErrorCode.FIELD_NOT_FOUND,
|
||||
`Field ${field} doesn't exist in ${collection}.`
|
||||
);
|
||||
throw new InvalidQueryException(`Field ${field} doesn't exist in ${collection}.`);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { RequestHandler } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import database from '../database';
|
||||
import APIError, { ErrorCode } from '../error';
|
||||
import { ItemLimitException } from '../exceptions';
|
||||
|
||||
const validateCollection: RequestHandler = asyncHandler(async (req, res, next) => {
|
||||
if (!req.collection) return next();
|
||||
@@ -24,8 +24,7 @@ const validateCollection: RequestHandler = asyncHandler(async (req, res, next) =
|
||||
|
||||
if (Number(count) === 0) return next();
|
||||
|
||||
throw new APIError(
|
||||
ErrorCode.TOO_MANY_ITEMS,
|
||||
throw new ItemLimitException(
|
||||
`You can only create a single item in singleton "${req.collection}"`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import * as ExtensionsService from '../services/extensions';
|
||||
import APIError, { ErrorCode } from '../error';
|
||||
import { RouteNotFoundException } from '../exceptions';
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -11,10 +11,7 @@ router.get(
|
||||
const typeAllowList = ['interfaces', 'layouts', 'displays', 'modules'];
|
||||
|
||||
if (typeAllowList.includes(req.params.type) === false) {
|
||||
throw new APIError(
|
||||
ErrorCode.EXTENSION_ILLEGAL_TYPE,
|
||||
`${req.params.type} is not an extension type.`
|
||||
);
|
||||
throw new RouteNotFoundException(req.path);
|
||||
}
|
||||
|
||||
const interfaces = await ExtensionsService.listExtensions(req.params.type);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { RequestHandler } from 'express';
|
||||
import APIError, { ErrorCode } from '../error';
|
||||
import { RouteNotFoundException } from '../exceptions';
|
||||
|
||||
const notFound: RequestHandler = (req, res, next) => {
|
||||
throw new APIError(ErrorCode.NOT_FOUND, `Route ${req.path} not found.`);
|
||||
throw new RouteNotFoundException(req.path);
|
||||
};
|
||||
|
||||
export default notFound;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import database from '../database';
|
||||
import APIError, { ErrorCode } from '../error';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import bcrypt from 'bcrypt';
|
||||
import { InvalidCredentialsException } from '../exceptions';
|
||||
|
||||
export const authenticate = async (email: string, password?: string) => {
|
||||
const user = await database
|
||||
@@ -11,7 +11,7 @@ export const authenticate = async (email: string, password?: string) => {
|
||||
.first();
|
||||
|
||||
if (!user) {
|
||||
throw new APIError(ErrorCode.INVALID_USER_CREDENTIALS, 'Invalid user credentials');
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,7 +22,7 @@ export const authenticate = async (email: string, password?: string) => {
|
||||
* signal the difference
|
||||
*/
|
||||
if (password !== undefined && (await bcrypt.compare(password, user.password)) === false) {
|
||||
throw new APIError(ErrorCode.INVALID_USER_CREDENTIALS, 'Invalid user credentials');
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
|
||||
const payload = {
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
import listFolders from '../utils/list-folders';
|
||||
import path from 'path';
|
||||
import APIError, { ErrorCode } from '../error';
|
||||
|
||||
export async function listExtensions(type: string) {
|
||||
const extensionsPath = process.env.EXTENSIONS_PATH;
|
||||
const location = path.join(extensionsPath, type);
|
||||
|
||||
try {
|
||||
return await listFolders(location);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
throw new APIError(ErrorCode.ENOENT, `The ${type} folder doesn't exist.`);
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
return await listFolders(location);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import * as ItemsService from './items';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { sendInviteMail } from '../mail';
|
||||
import database from '../database';
|
||||
import APIError, { ErrorCode } from '../error';
|
||||
import bcrypt from 'bcrypt';
|
||||
import * as PayloadService from '../services/payload';
|
||||
import { InvalidPayloadException } from '../exceptions';
|
||||
|
||||
export const createUser = async (data: Record<string, any>, query?: Query) => {
|
||||
return await ItemsService.createItem('directus_users', data, query);
|
||||
@@ -50,12 +50,8 @@ export const acceptInvite = async (token: string, password: string) => {
|
||||
.where({ email })
|
||||
.first();
|
||||
|
||||
if (!user) {
|
||||
throw new APIError(ErrorCode.USER_NOT_FOUND, `Email address ${email} hasn't been invited.`);
|
||||
}
|
||||
|
||||
if (user.status !== 'invited') {
|
||||
throw new APIError(ErrorCode.USER_NOT_FOUND, `Email address ${email} hasn't been invited.`);
|
||||
if (!user || user.status !== 'invited') {
|
||||
throw new InvalidPayloadException(`Email address ${email} hasn't been invited.`);
|
||||
}
|
||||
|
||||
const passwordHashed = await bcrypt.hash(password, Number(process.env.SALT_ROUNDS));
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { promisify } from 'util';
|
||||
import APIError, { ErrorCode } from '../error';
|
||||
|
||||
const readdir = promisify(fs.readdir);
|
||||
const stat = promisify(fs.stat);
|
||||
|
||||
Reference in New Issue
Block a user