diff options
author | Elizabeth Hunt <me@liz.coffee> | 2025-07-27 17:03:10 -0700 |
---|---|---|
committer | Elizabeth Hunt <me@liz.coffee> | 2025-07-27 18:30:30 -0700 |
commit | 9970036d203ba2d0a46b35ba6fad21d49441cdd4 (patch) | |
tree | a585d13933bf4149dcb07e28526063d071453105 /lib/server/filter | |
download | pengueno-9970036d203ba2d0a46b35ba6fad21d49441cdd4.tar.gz pengueno-9970036d203ba2d0a46b35ba6fad21d49441cdd4.zip |
hai
Diffstat (limited to 'lib/server/filter')
-rw-r--r-- | lib/server/filter/index.ts | 34 | ||||
-rw-r--r-- | lib/server/filter/json.ts | 42 | ||||
-rw-r--r-- | lib/server/filter/method.ts | 30 |
3 files changed, 106 insertions, 0 deletions
diff --git a/lib/server/filter/index.ts b/lib/server/filter/index.ts new file mode 100644 index 0000000..509deb3 --- /dev/null +++ b/lib/server/filter/index.ts @@ -0,0 +1,34 @@ +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( + override readonly 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): IEither<Err, T> | Promise<IEither<Err, T>>; +} + +export * from './method'; +export * from './json'; diff --git a/lib/server/filter/json.ts b/lib/server/filter/json.ts new file mode 100644 index 0000000..bc53d47 --- /dev/null +++ b/lib/server/filter/json.ts @@ -0,0 +1,42 @@ +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').asResult(); +export const jsonModel = + <MessageT>(jsonTransformer: JsonTransformer<MessageT>): RequestFilter<MessageT> => + (r: ITraceable<PenguenoRequest, ServerTrace>) => + r + .flatMap(TraceUtil.withFunctionTrace(jsonModel)) + .flatMap(TraceUtil.withMetricTrace(ParseJsonMetric)) + .map((j) => + Either.fromFailableAsync<Error, MessageT>(<Promise<MessageT>>j.get().req.json()).then((either) => + either.mapLeft((errReason) => { + j.trace.traceScope(LogLevel.WARN).trace(errReason); + return new PenguenoError('seems to be invalid JSON (>//<) can you fix?', 400); + }), + ), + ) + .flatMapAsync(TraceUtil.promiseify(TraceUtil.traceResultingEither(ParseJsonMetric))) + .map( + TraceUtil.promiseify((traceableEitherJson) => + traceableEitherJson + .get() + .mapRight((j) => traceableEitherJson.move(j)) + .flatMap(jsonTransformer), + ), + ) + .get(); diff --git a/lib/server/filter/method.ts b/lib/server/filter/method.ts new file mode 100644 index 0000000..7d6aa76 --- /dev/null +++ b/lib/server/filter/method.ts @@ -0,0 +1,30 @@ +import { + Either, + HttpMethod, + IEither, + type ITraceable, + LogLevel, + PenguenoError, + type PenguenoRequest, + type RequestFilter, + type ServerTrace, + TraceUtil, +} from '@emprespresso/pengueno'; + +export const requireMethod = + (methods: Array<HttpMethod>): RequestFilter<HttpMethod> => + (req: ITraceable<PenguenoRequest, ServerTrace>) => + req + .flatMap(TraceUtil.withFunctionTrace(requireMethod)) + .map((t): IEither<PenguenoError, HttpMethod> => { + const { + req: { method }, + } = t.get(); + if (!methods.includes(method)) { + const msg = "that's not how you pet me (âīšâ)~"; + t.trace.traceScope(LogLevel.WARN).trace(msg); + return Either.left(new PenguenoError(msg, 405)); + } + return Either.right(method); + }) + .get(); |