implement basic JSON parsing

This commit is contained in:
Kaan Barmore-Genç 2022-05-21 22:36:48 -04:00
parent 1033a29aa0
commit de396de6b9
12 changed files with 207 additions and 35 deletions

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"editor.formatOnSave": true
}

36
.vscode/tasks.json vendored
View file

@ -1,19 +1,19 @@
{ {
"version": "2.0.0", "version": "2.0.0",
"tasks": [ "tasks": [
{ {
"type": "npm", "type": "npm",
"script": "dev", "script": "dev",
"problemMatcher": [], "problemMatcher": [],
"label": "npm: dev", "label": "npm: dev",
"detail": "Watch code files to auto-rebuild the library and example." "detail": "Watch code files to auto-rebuild the library and example."
}, },
{ {
"type": "npm", "type": "npm",
"script": "build", "script": "build",
"problemMatcher": [], "problemMatcher": [],
"label": "npm: build", "label": "npm: build",
"detail": "Get an optimized build of the library." "detail": "Get an optimized build of the library."
} }
] ]
} }

View file

@ -1,3 +1,17 @@
import { foo } from "../src/utils"; import { endpoint } from "../src";
import { startServer } from "../src/core/server";
console.log(foo()); type Data = {
name: string;
};
startServer(
endpoint<void, Data>(async (req) => {
return {
status: 200,
body: {
name: "foo",
},
};
}),
);

View file

@ -13,5 +13,8 @@
"devDependencies": { "devDependencies": {
"@types/node": "^17.0.35", "@types/node": "^17.0.35",
"esbuild": "^0.14.39" "esbuild": "^0.14.39"
},
"dependencies": {
"tslog": "^3.3.3"
} }
} }

1
src/compose.ts Normal file
View file

@ -0,0 +1 @@
export type Server<Request, Response> = (request: Request) => Promise<Response>;

View file

@ -0,0 +1,52 @@
import { Logger } from "tslog";
import type { Server } from "../compose";
import type { CoreRequest } from "./server";
export type Request = Omit<CoreRequest, "response">;
export type BufferedResponse = {
status: number;
body: string;
};
export function bufferedResponse(
app: Server<Request, BufferedResponse>,
): Server<CoreRequest, void> {
return async function bufferedResp(request) {
const out = await app(request);
request.response.statusCode = out.status;
request.response.write(out.body);
};
}
export type BufferedRequest = Request & {
body: string;
};
export function bufferedRequest<Response>(
app: Server<BufferedRequest, Response>,
): Server<Request, Response> {
return function bufferedReq(request) {
const logger = new Logger();
let body = "";
const response = new Promise<Response>((resolve) => {
request.request.on("data", (chunk) => {
body += chunk.toString();
});
request.request.on("end", () => {
app({
...request,
body,
})
.then((out) => {
resolve(out);
})
.catch((err) => {
logger.error("App did not return a response", err);
resolve(undefined);
});
});
});
return response;
};
}

48
src/core/server.ts Normal file
View file

@ -0,0 +1,48 @@
import { createServer } from "http";
import type { IncomingMessage, ServerResponse } from "http";
import { Logger } from "tslog";
import { Server } from "../compose";
/** A basic request. */
export type CoreRequest = {
/** The full URL for the request. */
url: string;
/** The HTTP method for the request. */
method: string;
/** The headers associated with the request. */
headers: { [key: string]: string | string[] };
/** The request body. */
request: IncomingMessage;
/** The response. */
response: ServerResponse;
};
export function startServer(app: Server<CoreRequest, void>) {
const logger = new Logger();
const server = createServer((req, res) => {
logger.info(req.method, req.url);
app({
url: req.url,
method: req.method,
headers: req.headers,
request: req,
response: res,
})
.then(() => {
logger.debug("request processed successfully");
})
.catch((error) => {
res.statusCode = 500;
logger.error("server throw an unexpected error", error);
res.write("Error!\n");
res.write(JSON.stringify(error));
})
.finally(() => {
res.end();
});
});
server.on("clientError", (err, socket) => {
socket.end("HTTP/1.1 400 Bad CoreRequest\r\n\r\n");
});
server.listen(8000);
}

View file

@ -1,9 +1,10 @@
import http from "http"; import type { Server } from "./compose";
import { bufferedRequest, bufferedResponse } from "./core/bufferedServer";
import type { CoreRequest } from "./core/server";
import { jsonRequest, JSONRequest, jsonResponse, JSONResponse } from "./json";
const server = http.createServer((req, res) => { export function endpoint<ReqData, RespData>(
res.end(); app: Server<JSONRequest<ReqData>, JSONResponse<RespData>>,
}); ): Server<CoreRequest, void> {
server.on('clientError', (err, socket) => { return bufferedResponse(bufferedRequest(jsonResponse(jsonRequest(app))));
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n'); }
});
server.listen(8000);

32
src/json.ts Normal file
View file

@ -0,0 +1,32 @@
import { Server } from "./compose";
import type { BufferedRequest, BufferedResponse } from "./core/bufferedServer";
export type JSONRequest<Data> = Omit<BufferedRequest, "body"> & { body: Data };
export type JSONResponse<Data> = Omit<BufferedResponse, "body"> & {
body: Data;
};
export function jsonResponse<Request, Data>(
app: Server<Request, JSONResponse<Data>>,
): Server<Request, BufferedResponse> {
return async function jsonResp(request) {
const out = await app(request);
return {
...out,
body: JSON.stringify(out.body),
};
};
}
export function jsonRequest<Response, Data>(
app: Server<JSONRequest<Data | undefined>, Response>,
): Server<BufferedRequest, Response> {
return async function jsonReq(request) {
let body: any | undefined;
if (request.body.length > 0) {
body = JSON.parse(request.body);
}
return app({ ...request, body });
};
}

0
src/routing/router.ts Normal file
View file

View file

@ -1,7 +0,0 @@
export function foo() {
return "bar";
}
export function baz() {
return "baz";
}

View file

@ -7,6 +7,11 @@
resolved "https://registry.npmjs.org/@types/node/-/node-17.0.35.tgz" resolved "https://registry.npmjs.org/@types/node/-/node-17.0.35.tgz"
integrity sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg== integrity sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg==
buffer-from@^1.0.0:
version "1.1.2"
resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
esbuild-android-64@0.14.39: esbuild-android-64@0.14.39:
version "0.14.39" version "0.14.39"
resolved "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.39.tgz#09f12a372eed9743fd77ff6d889ac14f7b340c21" resolved "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.39.tgz#09f12a372eed9743fd77ff6d889ac14f7b340c21"
@ -132,3 +137,23 @@ esbuild@^0.14.39:
esbuild-windows-32 "0.14.39" esbuild-windows-32 "0.14.39"
esbuild-windows-64 "0.14.39" esbuild-windows-64 "0.14.39"
esbuild-windows-arm64 "0.14.39" esbuild-windows-arm64 "0.14.39"
source-map-support@^0.5.21:
version "0.5.21"
resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map@^0.6.0:
version "0.6.1"
resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
tslog@^3.3.3:
version "3.3.3"
resolved "https://registry.npmjs.org/tslog/-/tslog-3.3.3.tgz#751a469e0d36841bd7e03676c27e53e7ffe9bc3d"
integrity sha512-lGrkndwpAohZ9ntQpT+xtUw5k9YFV1DjsksiWQlBSf82TTqsSAWBARPRD9juI730r8o3Awpkjp2aXy9k+6vr+g==
dependencies:
source-map-support "^0.5.21"