diff --git a/README.md b/README.md index 8d0a12b..ece8299 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ However it's not correct, because you don't know that before I registered this h ```js app.use((req, res) => { res.sendStatus(400); -}) +}); ``` So to avoid confusion like this, there is no middleware support in HNDL. @@ -48,7 +48,7 @@ In HNDL any object that looks like this is a valid endpoint: type Endpoint = { accept: (request: Request) => Optional | Promise>; handle: (payload: T) => Response | Promise; -} +}; ``` The most important thing to notice is the relationship between `accept` and `handle`: @@ -62,11 +62,7 @@ A router in HNDL is defined using the function `router`, it takes a variadic lis endpoints like this: ```ts -const myRouter = router( - firstEndpoint, - secondEndpoint, - thirdEndpoint -) +const myRouter = router(firstEndpoint, secondEndpoint, thirdEndpoint); ``` The main job of the router is to choose one of the passed endpoints to handle the @@ -95,8 +91,10 @@ This endpoint will respond to any request with a 200 OK: ```ts const everythingIsOK = { accept: () => true, - handle: () => { status: OK } -} + handle: () => { + status: HttpStatusCode.OK; + }, +}; ``` This router will respond with `200 OK` if the URL starts with `/ok`, @@ -106,21 +104,17 @@ otherwise with `404 Not Found`: const myRouter = router( { accept: request => request.url.startsWith("/ok"), - handle: () => { status: OK } + handle: () => { status: HttpStatusCode.OK } }, { accept: () => true, - handle: () { status: NOT_FOUND } + handle: () { status: HttpStatusCode.NOT_FOUND } } ) ``` - - ## The Service When developing a web service it's necessary to perform additional tasks such as logging, error handling, etc. These do not fall into the area of responsibility of endpoints, and for this we use the `service`. - - diff --git a/src/example/hello-world.ts b/src/example/hello-world.ts index e988afe..11b7984 100644 --- a/src/example/hello-world.ts +++ b/src/example/hello-world.ts @@ -1,9 +1,5 @@ import { createServer } from "http"; -import { acceptPath } from "../accept-path"; -import { endpoint } from "../endpoint"; -import { listener } from "../listener"; -import { json } from "../response"; -import { service } from "../service"; +import { acceptPath, endpoint, json, listener, service } from ".."; const port = process.env.PORT || "3000"; diff --git a/src/index.ts b/src/index.ts index bd06513..b91d1a2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,7 @@ -export { listener } from "./listener"; -export { endpoint, router } from "./endpoint"; -export { service } from "./service"; +export * from './accept-path'; +export * from './endpoint'; +export * from './types'; +export * from './status'; +export * from './service'; +export * from './listener'; +export * from './response' diff --git a/src/listener.ts b/src/listener.ts index 6e34e84..bc74433 100644 --- a/src/listener.ts +++ b/src/listener.ts @@ -1,5 +1,5 @@ import { IncomingMessage, ServerResponse } from "http"; -import { INTERNAL_SERVER_ERROR } from "./status"; +import { HttpStatusCode } from "./status"; import { Endpoint, Request, Response } from "./types"; export const listener = (def: Endpoint) => { @@ -15,7 +15,7 @@ const acceptAndHandle = async (def: Endpoint, req: Request) => { } const listenerErrorResponse: Response = { - status: INTERNAL_SERVER_ERROR, + status: HttpStatusCode.INTERNAL_SERVER_ERROR, body: "Endpoint did not produce a response", headers: { "Content-Type": "text/plain" } } diff --git a/src/response.ts b/src/response.ts index 7e64611..2885b03 100644 --- a/src/response.ts +++ b/src/response.ts @@ -1,9 +1,9 @@ -import { OK } from "./status"; +import { HttpStatusCode } from "./status"; import { Response } from "./types"; export const json = (data: any): Response => { return { - status: OK, + status: HttpStatusCode.OK, headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) } diff --git a/src/service.ts b/src/service.ts index 380e7c5..c6eb033 100644 --- a/src/service.ts +++ b/src/service.ts @@ -1,5 +1,5 @@ import { endpoint, router } from "./endpoint"; -import { INTERNAL_SERVER_ERROR, NOT_FOUND } from "./status"; +import { HttpStatusCode } from "./status"; import { Endpoint, ErrorHandler, Logger, Request, Response } from "./types"; type ServiceProps = { @@ -35,7 +35,7 @@ const produceResponse = async (request: Request, endpoint: Endpoint, errorH const catchAllEndpoint = endpoint({ accept: () => true, - handle: () => ({ status: NOT_FOUND }) + handle: () => ({ status: HttpStatusCode.NOT_FOUND }) }); @@ -53,5 +53,5 @@ const defaultErrorHandler = (error: any): Response => { const getErrorStatus = (error: any) => { if (typeof error?.status?.code === "number" && typeof error?.status?.phrase === "string") return error.status; - return INTERNAL_SERVER_ERROR; + return HttpStatusCode.INTERNAL_SERVER_ERROR; } diff --git a/src/status.ts b/src/status.ts index 8ef543d..2a1913d 100644 --- a/src/status.ts +++ b/src/status.ts @@ -1,58 +1,60 @@ import { HttpStatus } from "./types"; -export const CONTINUE: HttpStatus = { code: 100, phrase: "Continue" }; -export const SWITCHING_PROTOCOLS: HttpStatus = { code: 101, phrase: "Switching Protocols" }; -export const PROCESSING: HttpStatus = { code: 102, phrase: "Processin" }; -export const OK: HttpStatus = { code: 200, phrase: "OK" }; -export const CREATED: HttpStatus = { code: 201, phrase: "Created" }; -export const ACCEPTED: HttpStatus = { code: 202, phrase: "Accepted" }; -export const NON_AUTHORITATIVE_INFORMATION: HttpStatus = { code: 203, phrase: "Non Authoritative Information" }; -export const NO_CONTENT: HttpStatus = { code: 204, phrase: "No Content" }; -export const RESET_CONTENT: HttpStatus = { code: 205, phrase: "Reset Content" }; -export const PARTIAL_CONTENT: HttpStatus = { code: 206, phrase: "Partial Content" }; -export const MULTI_STATUS: HttpStatus = { code: 207, phrase: "Multi-Status" }; -export const MULTIPLE_CHOICES: HttpStatus = { code: 300, phrase: "Multiple Choices" }; -export const MOVED_PERMANENTLY: HttpStatus = { code: 301, phrase: "Moved Permanently" }; -export const MOVED_TEMPORARILY: HttpStatus = { code: 302, phrase: "Moved Temporarily" }; -export const SEE_OTHER: HttpStatus = { code: 303, phrase: "See Other" }; -export const NOT_MODIFIED: HttpStatus = { code: 304, phrase: "Not Modified" }; -export const USE_PROXY: HttpStatus = { code: 305, phrase: "Use Proxy" }; -export const TEMPORARY_REDIRECT: HttpStatus = { code: 307, phrase: "Temporary Redirect" }; -export const PERMANENT_REDIRECT: HttpStatus = { code: 308, phrase: "Permanent Redirect" }; -export const BAD_REQUEST: HttpStatus = { code: 400, phrase: "Bad Request" }; -export const UNAUTHORIZED: HttpStatus = { code: 401, phrase: "Unauthorized" }; -export const PAYMENT_REQUIRED: HttpStatus = { code: 402, phrase: "Payment Required" }; -export const FORBIDDEN: HttpStatus = { code: 403, phrase: "Forbidden" }; -export const NOT_FOUND: HttpStatus = { code: 404, phrase: "Not Found" }; -export const METHOD_NOT_ALLOWED: HttpStatus = { code: 405, phrase: "Method Not Allowed" }; -export const NOT_ACCEPTABLE: HttpStatus = { code: 406, phrase: "Not Acceptable" }; -export const PROXY_AUTHENTICATION_REQUIRED: HttpStatus = { code: 407, phrase: "Proxy Authentication Required" }; -export const REQUEST_TIMEOUT: HttpStatus = { code: 408, phrase: "Request Timeout" }; -export const CONFLICT: HttpStatus = { code: 409, phrase: "Conflict" }; -export const GONE: HttpStatus = { code: 410, phrase: "Gone" }; -export const LENGTH_REQUIRED: HttpStatus = { code: 411, phrase: "Length Required" }; -export const PRECONDITION_FAILED: HttpStatus = { code: 412, phrase: "Precondition Failed" }; -export const REQUEST_TOO_LONG: HttpStatus = { code: 413, phrase: "Request Entity Too Large" }; -export const REQUEST_URI_TOO_LONG: HttpStatus = { code: 414, phrase: "Request-URI Too Long" }; -export const UNSUPPORTED_MEDIA_TYPE: HttpStatus = { code: 415, phrase: "Unsupported Media Type" }; -export const REQUESTED_RANGE_NOT_SATISFIABLE: HttpStatus = { code: 416, phrase: "Requested Range Not Satisfiable" }; -export const EXPECTATION_FAILED: HttpStatus = { code: 417, phrase: "Expectation Failed" }; -export const IM_A_TEAPOT: HttpStatus = { code: 418, phrase: "I'm a teapot" }; -export const INSUFFICIENT_SPACE_ON_RESOURCE: HttpStatus = { code: 419, phrase: "Insufficient Space on Resource" }; -export const METHOD_FAILURE: HttpStatus = { code: 420, phrase: "Method Failure" }; -export const MISDIRECTED_REQUEST: HttpStatus = { code: 421, phrase: "Misdirected Request" }; -export const UNPROCESSABLE_ENTITY: HttpStatus = { code: 422, phrase: "Unprocessable Entity" }; -export const LOCKED: HttpStatus = { code: 423, phrase: "Locked" }; -export const FAILED_DEPENDENCY: HttpStatus = { code: 424, phrase: "Failed Dependency" }; -export const PRECONDITION_REQUIRED: HttpStatus = { code: 428, phrase: "Precondition Required" }; -export const TOO_MANY_REQUESTS: HttpStatus = { code: 429, phrase: "Too Many Requests" }; -export const REQUEST_HEADER_FIELDS_TOO_LARGE: HttpStatus = { code: 431, phrase: "Request Header Fields Too Large" }; -export const UNAVAILABLE_FOR_LEGAL_REASONS: HttpStatus = { code: 451, phrase: "Unavailable For Legal Reasons" }; -export const INTERNAL_SERVER_ERROR: HttpStatus = { code: 500, phrase: "Internal Server Error" }; -export const NOT_IMPLEMENTED: HttpStatus = { code: 501, phrase: "Not Implemented" }; -export const BAD_GATEWAY: HttpStatus = { code: 502, phrase: "Bad Gateway" }; -export const SERVICE_UNAVAILABLE: HttpStatus = { code: 503, phrase: "Service Unavailable" }; -export const GATEWAY_TIMEOUT: HttpStatus = { code: 504, phrase: "Gateway Timeout" }; -export const HTTP_VERSION_NOT_SUPPORTED: HttpStatus = { code: 505, phrase: "HTTP Version Not Supported" }; -export const INSUFFICIENT_STORAGE: HttpStatus = { code: 507, phrase: "Insufficient Storage" }; -export const NETWORK_AUTHENTICATION_REQUIRED: HttpStatus = { code: 511, phrase: "Network Authentication Required" }; +export abstract class HttpStatusCode { + public static CONTINUE: HttpStatus = { code: 100, phrase: "Continue" }; + public static SWITCHING_PROTOCOLS: HttpStatus = { code: 101, phrase: "Switching Protocols" }; + public static PROCESSING: HttpStatus = { code: 102, phrase: "Processin" }; + public static OK: HttpStatus = { code: 200, phrase: "OK" }; + public static CREATED: HttpStatus = { code: 201, phrase: "Created" }; + public static ACCEPTED: HttpStatus = { code: 202, phrase: "Accepted" }; + public static NON_AUTHORITATIVE_INFORMATION: HttpStatus = { code: 203, phrase: "Non Authoritative Information" }; + public static NO_CONTENT: HttpStatus = { code: 204, phrase: "No Content" }; + public static RESET_CONTENT: HttpStatus = { code: 205, phrase: "Reset Content" }; + public static PARTIAL_CONTENT: HttpStatus = { code: 206, phrase: "Partial Content" }; + public static MULTI_STATUS: HttpStatus = { code: 207, phrase: "Multi-Status" }; + public static MULTIPLE_CHOICES: HttpStatus = { code: 300, phrase: "Multiple Choices" }; + public static MOVED_PERMANENTLY: HttpStatus = { code: 301, phrase: "Moved Permanently" }; + public static MOVED_TEMPORARILY: HttpStatus = { code: 302, phrase: "Moved Temporarily" }; + public static SEE_OTHER: HttpStatus = { code: 303, phrase: "See Other" }; + public static NOT_MODIFIED: HttpStatus = { code: 304, phrase: "Not Modified" }; + public static USE_PROXY: HttpStatus = { code: 305, phrase: "Use Proxy" }; + public static TEMPORARY_REDIRECT: HttpStatus = { code: 307, phrase: "Temporary Redirect" }; + public static PERMANENT_REDIRECT: HttpStatus = { code: 308, phrase: "Permanent Redirect" }; + public static BAD_REQUEST: HttpStatus = { code: 400, phrase: "Bad Request" }; + public static UNAUTHORIZED: HttpStatus = { code: 401, phrase: "Unauthorized" }; + public static PAYMENT_REQUIRED: HttpStatus = { code: 402, phrase: "Payment Required" }; + public static FORBIDDEN: HttpStatus = { code: 403, phrase: "Forbidden" }; + public static NOT_FOUND: HttpStatus = { code: 404, phrase: "Not Found" }; + public static METHOD_NOT_ALLOWED: HttpStatus = { code: 405, phrase: "Method Not Allowed" }; + public static NOT_ACCEPTABLE: HttpStatus = { code: 406, phrase: "Not Acceptable" }; + public static PROXY_AUTHENTICATION_REQUIRED: HttpStatus = { code: 407, phrase: "Proxy Authentication Required" }; + public static REQUEST_TIMEOUT: HttpStatus = { code: 408, phrase: "Request Timeout" }; + public static CONFLICT: HttpStatus = { code: 409, phrase: "Conflict" }; + public static GONE: HttpStatus = { code: 410, phrase: "Gone" }; + public static LENGTH_REQUIRED: HttpStatus = { code: 411, phrase: "Length Required" }; + public static PRECONDITION_FAILED: HttpStatus = { code: 412, phrase: "Precondition Failed" }; + public static REQUEST_TOO_LONG: HttpStatus = { code: 413, phrase: "Request Entity Too Large" }; + public static REQUEST_URI_TOO_LONG: HttpStatus = { code: 414, phrase: "Request-URI Too Long" }; + public static UNSUPPORTED_MEDIA_TYPE: HttpStatus = { code: 415, phrase: "Unsupported Media Type" }; + public static REQUESTED_RANGE_NOT_SATISFIABLE: HttpStatus = { code: 416, phrase: "Requested Range Not Satisfiable" }; + public static EXPECTATION_FAILED: HttpStatus = { code: 417, phrase: "Expectation Failed" }; + public static IM_A_TEAPOT: HttpStatus = { code: 418, phrase: "I'm a teapot" }; + public static INSUFFICIENT_SPACE_ON_RESOURCE: HttpStatus = { code: 419, phrase: "Insufficient Space on Resource" }; + public static METHOD_FAILURE: HttpStatus = { code: 420, phrase: "Method Failure" }; + public static MISDIRECTED_REQUEST: HttpStatus = { code: 421, phrase: "Misdirected Request" }; + public static UNPROCESSABLE_ENTITY: HttpStatus = { code: 422, phrase: "Unprocessable Entity" }; + public static LOCKED: HttpStatus = { code: 423, phrase: "Locked" }; + public static FAILED_DEPENDENCY: HttpStatus = { code: 424, phrase: "Failed Dependency" }; + public static PRECONDITION_REQUIRED: HttpStatus = { code: 428, phrase: "Precondition Required" }; + public static TOO_MANY_REQUESTS: HttpStatus = { code: 429, phrase: "Too Many Requests" }; + public static REQUEST_HEADER_FIELDS_TOO_LARGE: HttpStatus = { code: 431, phrase: "Request Header Fields Too Large" }; + public static UNAVAILABLE_FOR_LEGAL_REASONS: HttpStatus = { code: 451, phrase: "Unavailable For Legal Reasons" }; + public static INTERNAL_SERVER_ERROR: HttpStatus = { code: 500, phrase: "Internal Server Error" }; + public static NOT_IMPLEMENTED: HttpStatus = { code: 501, phrase: "Not Implemented" }; + public static BAD_GATEWAY: HttpStatus = { code: 502, phrase: "Bad Gateway" }; + public static SERVICE_UNAVAILABLE: HttpStatus = { code: 503, phrase: "Service Unavailable" }; + public static GATEWAY_TIMEOUT: HttpStatus = { code: 504, phrase: "Gateway Timeout" }; + public static HTTP_VERSION_NOT_SUPPORTED: HttpStatus = { code: 505, phrase: "HTTP Version Not Supported" }; + public static INSUFFICIENT_STORAGE: HttpStatus = { code: 507, phrase: "Insufficient Storage" }; + public static NETWORK_AUTHENTICATION_REQUIRED: HttpStatus = { code: 511, phrase: "Network Authentication Required" }; +} \ No newline at end of file diff --git a/tests/listener.ts b/tests/listener.ts index 646cbbb..461792c 100644 --- a/tests/listener.ts +++ b/tests/listener.ts @@ -1,12 +1,12 @@ import { deepStrictEqual } from "assert"; import { listener } from "../src/listener"; -import { INTERNAL_SERVER_ERROR, OK } from "../src/status"; +import { HttpStatusCode } from "../src/status"; import { Response } from "../src/types"; export const listenerTests = async () => { { const response: Response = { - status: OK, + status: HttpStatusCode.OK, headers: { "Content-Type": "text/plain" }, body: "Hello World" } @@ -39,7 +39,7 @@ export const listenerTests = async () => { { const testListener = listener({ accept: () => false, - handle: () => ({ status: OK }) + handle: () => ({ status: HttpStatusCode.OK }) }); const actual = { @@ -63,7 +63,7 @@ export const listenerTests = async () => { deepStrictEqual( actual, { - status: INTERNAL_SERVER_ERROR, + status: HttpStatusCode.INTERNAL_SERVER_ERROR, body: "Endpoint did not produce a response", headers: { "Content-Type": "text/plain" } }, diff --git a/tests/response.ts b/tests/response.ts index 103595a..2405fdc 100644 --- a/tests/response.ts +++ b/tests/response.ts @@ -1,6 +1,6 @@ import { deepStrictEqual } from "assert"; import { json } from "../src/response"; -import { OK } from "../src/status"; +import { HttpStatusCode } from "../src/status"; export const responseTests = () => { @@ -10,7 +10,7 @@ export const responseTests = () => { deepStrictEqual( response, { - status: OK, + status: HttpStatusCode.OK, headers: { "Content-Type": "application/json" }, body: '{"hello":"World"}' } diff --git a/tests/router.ts b/tests/router.ts index 47dad3b..fc462be 100644 --- a/tests/router.ts +++ b/tests/router.ts @@ -1,26 +1,26 @@ import { deepStrictEqual } from "assert"; import { router } from "../src/endpoint"; -import { BAD_REQUEST, MOVED_TEMPORARILY, OK } from "../src/status"; +import { HttpStatusCode } from "../src/status"; export const routerTests = async () => { { const testRouter = router( { accept: () => false, - handle: () => ({ status: OK }) + handle: () => ({ status: HttpStatusCode.OK }) }, { accept: () => true, - handle: () => ({ status: BAD_REQUEST }) + handle: () => ({ status: HttpStatusCode.BAD_REQUEST }) }, { accept: () => true, - handle: () => ({ status: MOVED_TEMPORARILY }) + handle: () => ({ status: HttpStatusCode.MOVED_TEMPORARILY }) } ) const response = await testRouter.handle(await testRouter.accept({} as any)); - deepStrictEqual(response.status, BAD_REQUEST, "Router should choose the first endpoint that accepts"); + deepStrictEqual(response.status, HttpStatusCode.BAD_REQUEST, "Router should choose the first endpoint that accepts"); } { @@ -30,24 +30,24 @@ export const routerTests = async () => { const testRouter = router( { accept: () => false, - handle: () => ({ status: MOVED_TEMPORARILY }) + handle: () => ({ status: HttpStatusCode.MOVED_TEMPORARILY }) }, { accept: () => truthyPayload, handle: payload => { handledPayload = payload; - return { status: OK } + return { status: HttpStatusCode.OK } } }, { accept: () => true, - handle: () => ({ status: BAD_REQUEST }) + handle: () => ({ status: HttpStatusCode.BAD_REQUEST }) } ) const response = await testRouter.handle(await testRouter.accept({} as any)); - deepStrictEqual(response.status, OK, "Router should accept any truthy value"); + deepStrictEqual(response.status, HttpStatusCode.OK, "Router should accept any truthy value"); deepStrictEqual(handledPayload, truthyPayload, "Router should pass the correct payload to handle") } } diff --git a/tests/service.ts b/tests/service.ts index 88fd1f9..ebb05d0 100644 --- a/tests/service.ts +++ b/tests/service.ts @@ -1,6 +1,6 @@ import { deepStrictEqual } from "assert"; import { service } from "../src/service"; -import { BAD_REQUEST, NOT_FOUND, OK, UNAUTHORIZED } from "../src/status"; +import { HttpStatusCode } from "../src/status"; import { Request, Response } from "../src/types"; export const serviceTests = async () => { @@ -9,29 +9,29 @@ export const serviceTests = async () => { logger: () => { }, endpoint: { accept: () => false, - handle: () => ({ status: OK }) + handle: () => ({ status: HttpStatusCode.OK }) }, }); const payload = await testService.accept({} as any); const response = await testService.handle(payload as any); - deepStrictEqual(response.status, NOT_FOUND, "Service should return 404 if endpoint doesn't accept"); + deepStrictEqual(response.status, HttpStatusCode.NOT_FOUND, "Service should return 404 if endpoint doesn't accept"); } { const testService = service({ logger: () => { }, endpoint: { - accept: () => { throw { status: BAD_REQUEST } }, - handle: () => ({ status: OK }) + accept: () => { throw { status: HttpStatusCode.BAD_REQUEST } }, + handle: () => ({ status: HttpStatusCode.OK }) }, }); const payload = await testService.accept({} as any); const response = await testService.handle(payload as any); - deepStrictEqual(response.status, BAD_REQUEST, "Service should return correctly when an error is thrown in accept"); + deepStrictEqual(response.status, HttpStatusCode.BAD_REQUEST, "Service should return correctly when an error is thrown in accept"); } { @@ -39,19 +39,19 @@ export const serviceTests = async () => { logger: () => { }, endpoint: { accept: () => true, - handle: () => { throw { status: UNAUTHORIZED } } + handle: () => { throw { status: HttpStatusCode.UNAUTHORIZED } } }, }); const payload = await testService.accept({} as any); const response = await testService.handle(payload as any); - deepStrictEqual(response.status, UNAUTHORIZED, "Service should return correctly when an error is thrown in handle"); + deepStrictEqual(response.status, HttpStatusCode.UNAUTHORIZED, "Service should return correctly when an error is thrown in handle"); } { const request: Request = {} as any; - const response: Response = { status: OK }; + const response: Response = { status: HttpStatusCode.OK }; let loggedRequest: Request | undefined; let loggedResponse: Response | undefined;