diff --git a/src/errors/api.ts b/src/errors/api.ts new file mode 100644 index 0000000..4d0728c --- /dev/null +++ b/src/errors/api.ts @@ -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, + }, +}; diff --git a/src/errors/handler.ts b/src/errors/handler.ts index 34782b6..b23560b 100644 --- a/src/errors/handler.ts +++ b/src/errors/handler.ts @@ -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); diff --git a/src/errors/main.ts b/src/errors/main.ts index 7a07f65..29190c9 100644 --- a/src/errors/main.ts +++ b/src/errors/main.ts @@ -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) { diff --git a/src/routes/get.ts b/src/routes/get.ts index dbd1e59..795b7e2 100644 --- a/src/routes/get.ts +++ b/src/routes/get.ts @@ -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"; diff --git a/src/routes/index.ts b/src/routes/index.ts index ea3ff2d..00d6dba 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -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) => { diff --git a/src/routes/parse.ts b/src/routes/parse.ts index 24b5738..bca7d1d 100644 --- a/src/routes/parse.ts +++ b/src/routes/parse.ts @@ -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, + }; } ); } diff --git a/src/routes/raw-html.ts b/src/routes/raw-html.ts index 83ac62f..4665fe2 100644 --- a/src/routes/raw-html.ts +++ b/src/routes/raw-html.ts @@ -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"; diff --git a/src/types/requests.ts b/src/types/requests.ts deleted file mode 100644 index 37271f9..0000000 --- a/src/types/requests.ts +++ /dev/null @@ -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; - }; -}>; diff --git a/src/types/requests/api.ts b/src/types/requests/api.ts new file mode 100644 index 0000000..9e13803 --- /dev/null +++ b/src/types/requests/api.ts @@ -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 { + 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; + }; +}>; diff --git a/src/types/requests/browser.ts b/src/types/requests/browser.ts new file mode 100644 index 0000000..df526d9 --- /dev/null +++ b/src/types/requests/browser.ts @@ -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"], +};