Error handler for API, separated schemas

This commit is contained in:
DarkCat09 2023-08-23 13:46:19 +04:00
parent e106509c45
commit 66ce1caab0
No known key found for this signature in database
GPG Key ID: 0A26CD5B3345D6E3
10 changed files with 205 additions and 114 deletions

34
src/errors/api.ts Normal file
View File

@ -0,0 +1,34 @@
export interface IApiError {
code: number;
name: string;
message: string;
}
export const errorSchema = {
type: "object",
properties: {
code: {
type: "number",
description: "HTTP error code",
},
name: {
type: "string",
description: "Exception class name",
},
message: {
type: "string",
description: "Exception message",
},
},
};
export const errorResponseSchema = {
type: "object",
properties: {
data: {
type: "object",
nullable: true,
},
error: errorSchema,
},
};

View File

@ -4,14 +4,42 @@ import { getFastifyError } from "./validation";
export default function errorHandler(
error: Error,
_: FastifyRequest,
req: FastifyRequest,
reply: FastifyReply
) {
// TODO: check if req.url starts with "/api/" and return JSON
if (req.originalUrl.startsWith("/api/")) {
return apiErrorHandler(error, reply);
}
return htmlErrorHandler(error, reply);
}
function apiErrorHandler(error: Error, reply: FastifyReply) {
function generateResponse(code: number) {
return reply.code(code).send({
data: null,
error: {
code: code,
name: error.name,
message: error.message,
},
});
}
if (error instanceof NotHtmlMimetypeError) {
return generateResponse(501);
}
if (getFastifyError(error)?.statusCode === 400) {
return generateResponse(400);
}
if (error instanceof TxtDotError) {
return generateResponse(error.code);
}
return generateResponse(500);
}
function htmlErrorHandler(error: Error, reply: FastifyReply) {
if (error instanceof NotHtmlMimetypeError) {
return reply.redirect(301, error.url);

View File

@ -1,27 +1,30 @@
export class TxtDotError extends Error {
export abstract class TxtDotError extends Error {
code: number;
name: string;
description: string;
constructor(code: number, description: string) {
constructor(code: number, name: string, description: string) {
super(description);
this.code = code;
this.name = name;
this.description = description;
}
}
export class EngineParseError extends TxtDotError {
constructor(message: string) {
super(500, `Parse error: ${message}`);
super(422, "EngineParseError", `Parse error: ${message}`);
}
}
export class LocalResourceError extends TxtDotError {
constructor() {
super(403, "Proxying local resources is forbidden.");
super(403, "LocalResourceError", "Proxying local resources is forbidden.");
}
}
export class NotHtmlMimetypeError extends Error {
name: string = "NotHtmlMimetypeError";
url: string;
constructor(url: string) {

View File

@ -1,6 +1,6 @@
import { FastifyInstance } from "fastify";
import { GetSchema, IGetSchema } from "../types/requests";
import { GetSchema, IGetSchema } from "../types/requests/browser";
import handlePage from "../handlers/main";
import { generateRequestUrl } from "../utils/generate";

View File

@ -1,6 +1,6 @@
import { FastifyInstance } from "fastify";
import { engineList } from "../handlers/main";
import { indexSchema } from "../types/requests";
import { indexSchema } from "../types/requests/browser";
export default async function indexRoute(fastify: FastifyInstance) {
fastify.get("/", { schema: indexSchema }, async (_, reply) => {

View File

@ -1,5 +1,7 @@
import { EngineRequest, IParseSchema, parseSchema } from "../types/requests";
import { FastifyInstance } from "fastify";
import { EngineRequest, IParseSchema, parseSchema } from "../types/requests/api";
import handlePage from "../handlers/main";
import { generateRequestUrl } from "../utils/generate";
@ -8,15 +10,18 @@ export default async function parseRoute(fastify: FastifyInstance) {
"/api/parse",
{ schema: parseSchema },
async (request: EngineRequest) => {
return await handlePage(
request.query.url,
generateRequestUrl(
request.protocol,
request.hostname,
request.originalUrl
return {
data: await handlePage(
request.query.url,
generateRequestUrl(
request.protocol,
request.hostname,
request.originalUrl
),
request.query.engine
),
request.query.engine
);
error: null,
};
}
);
}

View File

@ -1,6 +1,8 @@
import { FastifyInstance } from "fastify";
import { GetRequest, IParseSchema, rawHtmlSchema } from "../types/requests";
import { IParseSchema, rawHtmlSchema } from "../types/requests/api";
import { GetRequest } from "../types/requests/browser";
import handlePage from "../handlers/main";
import { generateRequestUrl } from "../utils/generate";

View File

@ -1,95 +0,0 @@
import { FastifyRequest, FastifySchema } from "fastify";
import { handlerSchema } from "../handlers/handler.interface";
import { engineList } from "../handlers/main";
export type GetRequest = FastifyRequest<{
Querystring: IGetQuery;
}>;
export interface IGetQuery {
url: string;
format?: string;
engine?: string;
}
export interface IParseQuery {
url: string;
engine?: string;
}
export interface IGetSchema {
Querystring: IGetQuery;
}
export interface IParseSchema {
Querystring: IParseQuery;
}
export const indexSchema = {
produces: ["text/html"],
hide: true
};
export const getQuerySchema = {
type: "object",
required: ["url"],
properties: {
url: {
type: "string",
description: "URL",
},
format: {
type: "string",
enum: ["text", "html", ""],
default: "html",
},
engine: {
type: "string",
enum: [...engineList, ""],
},
},
};
export const parseQuerySchema = {
type: "object",
required: ["url"],
properties: {
url: {
type: "string",
description: "URL",
},
engine: {
type: "string",
enum: [...engineList, ""],
},
},
};
export const GetSchema: FastifySchema = {
description: "Get page",
hide: true,
querystring: getQuerySchema,
produces: ["text/html", "text/plain"],
};
export const parseSchema: FastifySchema = {
description: "Parse page",
querystring: parseQuerySchema,
response: {
"2xx": handlerSchema,
},
produces: ["text/json"],
};
export const rawHtmlSchema: FastifySchema = {
description: "Get raw HTML",
querystring: parseQuerySchema,
produces: ["text/html"],
};
export type EngineRequest = FastifyRequest<{
Querystring: {
url: string;
engine?: string;
};
}>;

66
src/types/requests/api.ts Normal file
View File

@ -0,0 +1,66 @@
import { FastifySchema, FastifyRequest } from "fastify";
import { IApiError, errorResponseSchema } from "../../errors/api";
import { handlerSchema } from "../../handlers/handler.interface";
import { engineList } from "../../handlers/main";
export interface IApiResponse<T> {
data?: T;
error?: IApiError;
}
export interface IParseQuery {
url: string;
engine?: string;
}
export interface IParseSchema {
Querystring: IParseQuery;
}
export const parseQuerySchema = {
type: "object",
required: ["url"],
properties: {
url: {
type: "string",
description: "URL",
},
engine: {
type: "string",
enum: [...engineList, ""],
},
},
};
export const parseSchema: FastifySchema = {
description: "Parse the page and get all data from the engine",
querystring: parseQuerySchema,
response: {
"2xx": {
type: "object",
properties: {
data: handlerSchema,
error: {
type: "object",
nullable: true,
},
},
},
"4xx": errorResponseSchema,
"5xx": errorResponseSchema,
},
produces: ["text/json"],
};
export const rawHtmlSchema: FastifySchema = {
description: "Parse the page and get raw HTML from the engine",
querystring: parseQuerySchema,
produces: ["text/html"],
};
export type EngineRequest = FastifyRequest<{
Querystring: {
url: string;
engine?: string;
};
}>;

View File

@ -0,0 +1,48 @@
import { FastifyRequest, FastifySchema } from "fastify";
import { engineList } from "../../handlers/main";
export type GetRequest = FastifyRequest<{
Querystring: IGetQuery;
}>;
export interface IGetQuery {
url: string;
format?: string;
engine?: string;
}
export interface IGetSchema {
Querystring: IGetQuery;
}
export const indexSchema = {
produces: ["text/html"],
hide: true
};
export const getQuerySchema = {
type: "object",
required: ["url"],
properties: {
url: {
type: "string",
description: "URL",
},
format: {
type: "string",
enum: ["text", "html", ""],
default: "html",
},
engine: {
type: "string",
enum: [...engineList, ""],
},
},
};
export const GetSchema: FastifySchema = {
description: "Get page",
hide: true,
querystring: getQuerySchema,
produces: ["text/html", "text/plain"],
};