import { BaseResponse, Body, HttpStatusCodes, isEither, ITraceable, Metric, Optional, PenguenoRequest, ResponseOpts, ServerTrace, } from '@emprespresso/pengueno'; const getHeaders = (req: PenguenoRequest, extraHeaders: Record) => { const optHeaders = { ...req.getResponseHeaders(), ...extraHeaders, }; optHeaders['Content-Type'] = (optHeaders['Content-Type'] ?? 'text/plain') + '; charset=utf-8'; return optHeaders; }; const ResponseCodeMetrics = [0, 1, 2, 3, 4, 5].map((x) => Metric.fromName(`response.${x}xx`).asResult()); export const getResponseMetrics = (status: number, elapsedMs?: number) => { const index = Math.floor(status / 100); return ResponseCodeMetrics.flatMap((metric, i) => Optional.from(i) .filter((i) => i === index) .map(() => [metric.count.withValue(1.0)]) .flatMap((metricValues) => Optional.from(elapsedMs) .map((ms) => metricValues.concat(metric.time.withValue(ms))) .orSome(() => metricValues), ) .orSome(() => [metric.count.withValue(0.0)]) .get(), ); }; export class PenguenoResponse implements BaseResponse { public readonly statusText: string; public readonly status: number; public readonly headers: Record; constructor( req: ITraceable, private readonly _body: Body, opts: ResponseOpts, ) { this.headers = getHeaders(req.get(), opts?.headers ?? {}); this.status = opts.status; this.statusText = opts.statusText ?? HttpStatusCodes[this.status]!; req.trace.trace(getResponseMetrics(opts.status, req.get().elapsedTimeMs())); } public body() { return this._body; } } type Jsonable = any; export class JsonResponse extends PenguenoResponse { constructor(req: ITraceable, e: Jsonable, _opts: ResponseOpts) { const opts = { ..._opts, headers: { ..._opts.headers, 'Content-Type': 'application/json' } }; if (isEither(e)) { super( req, JSON.stringify( e.fold( (error) => ({ error, ok: undefined }), (ok) => ({ ok }), ), ), opts, ); return; } super(req, JSON.stringify(Math.floor(opts.status / 100) > 4 ? { error: e } : { ok: e }), opts); } }