diff options
Diffstat (limited to 'u/server/filter')
-rw-r--r-- | u/server/filter/json.ts | 51 | ||||
-rw-r--r-- | u/server/filter/method.ts | 41 | ||||
-rw-r--r-- | u/server/filter/mod.ts | 33 |
3 files changed, 125 insertions, 0 deletions
diff --git a/u/server/filter/json.ts b/u/server/filter/json.ts new file mode 100644 index 0000000..4a2961e --- /dev/null +++ b/u/server/filter/json.ts @@ -0,0 +1,51 @@ +import { + Either, + type IEither, + type ITraceable, + LogLevel, + Metric, + PenguenoError, + type PenguenoRequest, + type RequestFilter, + type ServerTrace, + TraceUtil, +} from "@emprespresso/pengueno"; + +export interface JsonTransformer<R, ParsedJson = unknown> { + (json: ITraceable<ParsedJson, ServerTrace>): IEither<PenguenoError, R>; +} + +const ParseJsonMetric = Metric.fromName("JsonParse"); +export const jsonModel = <MessageT>( + jsonTransformer: JsonTransformer<MessageT>, +): RequestFilter<MessageT> => +(r: ITraceable<PenguenoRequest, ServerTrace>) => + r.bimap(TraceUtil.withMetricTrace(ParseJsonMetric)) + .map((j) => + Either.fromFailableAsync<Error, MessageT>(j.get().json()) + .then((either) => + either.mapLeft((errReason) => { + j.trace.addTrace(LogLevel.WARN).trace(`${errReason}`); + return new PenguenoError( + "seems to be invalid JSON (>//<) can you fix?", + 400, + ); + }) + ) + ) + .peek( + TraceUtil.promiseify((traceableEither) => + traceableEither.get().mapBoth( + () => traceableEither.trace.trace(ParseJsonMetric.failure), + () => traceableEither.trace.trace(ParseJsonMetric.success), + ) + ), + ) + .map( + TraceUtil.promiseify((traceableEitherJson) => + traceableEitherJson.get() + .mapRight(traceableEitherJson.move) + .flatMap(jsonTransformer) + ), + ) + .get(); diff --git a/u/server/filter/method.ts b/u/server/filter/method.ts new file mode 100644 index 0000000..6b0419d --- /dev/null +++ b/u/server/filter/method.ts @@ -0,0 +1,41 @@ +import { + Either, + type ITraceable, + LogLevel, + PenguenoError, + type PenguenoRequest, + type RequestFilter, + type ServerTrace, + TraceUtil, +} from "@emprespresso/pengueno"; + +type HttpMethod = + | "POST" + | "GET" + | "HEAD" + | "PUT" + | "DELETE" + | "CONNECT" + | "OPTIONS" + | "TRACE" + | "PATCH"; + +export const requireMethod = ( + methods: Array<HttpMethod>, +): RequestFilter<HttpMethod> => +(req: ITraceable<PenguenoRequest, ServerTrace>) => + req.bimap(TraceUtil.withFunctionTrace(requireMethod)) + .move(Promise.resolve(req.get())) + .map(TraceUtil.promiseify((t) => { + const { method: _method } = t.get(); + const method = <HttpMethod> _method; + if (!methods.includes(method)) { + const msg = "that's not how you pet me (âīšâ)~"; + t.trace.addTrace(LogLevel.WARN).trace(msg); + return Either.left<PenguenoError, HttpMethod>( + new PenguenoError(msg, 405), + ); + } + return Either.right<PenguenoError, HttpMethod>(method); + })) + .get(); diff --git a/u/server/filter/mod.ts b/u/server/filter/mod.ts new file mode 100644 index 0000000..bbf37df --- /dev/null +++ b/u/server/filter/mod.ts @@ -0,0 +1,33 @@ +import { + type IEither, + type ITraceable, + LogLevel, + type PenguenoRequest, + type ServerTrace, +} from "@emprespresso/pengueno"; + +export enum ErrorSource { + USER = LogLevel.WARN, + SYSTEM = LogLevel.ERROR, +} + +export class PenguenoError extends Error { + public readonly source: ErrorSource; + constructor(message: string, public readonly status: number) { + super(message); + this.source = Math.floor(status / 100) === 4 + ? ErrorSource.USER + : ErrorSource.SYSTEM; + } +} + +export interface RequestFilter< + T, + Err extends PenguenoError = PenguenoError, + RIn = ITraceable<PenguenoRequest, ServerTrace>, +> { + (req: RIn): Promise<IEither<Err, T>>; +} + +export * from "./method.ts"; +export * from "./json.ts"; |