Skip to content

Commit

Permalink
Only solution I can think of is to have all fiters adhere to the same…
Browse files Browse the repository at this point in the history
… HttpHandler signature
  • Loading branch information
BBB committed Nov 30, 2023
1 parent 7000876 commit 5f1d8c6
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 55 deletions.
39 changes: 12 additions & 27 deletions packages/http-client/src/FilterResult.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,26 @@
import { ImmutableRequest } from "~/src/ImmutableRequest";
import { Task } from "@ollierelph/result4t";

export type FilterApply<ReturnIn, ReturnOut> = (
next: HttpHandler<ReturnIn>,
) => HttpHandler<ReturnOut>;

export type HttpHandler<Return> = (request: ImmutableRequest) => Return;

export class Filter<
Fn extends FilterApply<any, any>,
ReturnIn = Fn extends FilterApply<infer R, any> ? R : never,
ReturnOut = Fn extends FilterApply<any, infer R> ? R : never,
> {
#task: Task<[input: HttpHandler<ReturnIn>], HttpHandler<ReturnOut>>;
protected constructor(fn: FilterApply<ReturnIn, ReturnOut>) {
export class Filter<HttpHandlerSignature extends HttpHandler<unknown>> {
#task: Task<[input: HttpHandlerSignature], HttpHandlerSignature>;
protected constructor(
fn: (next: HttpHandlerSignature) => HttpHandlerSignature,
) {
this.#task = Task.of(fn);
}
static from<Fn extends FilterApply<any, any>>(fn: Fn) {
return new Filter<Fn>(fn);
static from<HttpHandlerSignature extends HttpHandler<unknown>>(
fn: (next: HttpHandlerSignature) => HttpHandlerSignature,
) {
return new Filter<HttpHandlerSignature>(fn);
}

then<
Other extends Filter<FilterApply<any, ReturnIn>>,
ReturnIn2 = Other extends Filter<
FilterApply<any, ReturnIn>,
infer R,
ReturnIn
>
? R
: never,
>(otherFilter: Other) {
return new Filter((next: HttpHandler<ReturnIn2>) =>
then<Other extends Filter<HttpHandlerSignature>>(otherFilter: Other) {
return new Filter((next: HttpHandlerSignature) =>
this.#task.call(otherFilter.apply(next)),
);
}

apply(filter: HttpHandler<ReturnIn>): HttpHandler<ReturnOut> {
apply(filter: HttpHandlerSignature): HttpHandlerSignature {
return this.#task.call(filter);
}
}
59 changes: 31 additions & 28 deletions packages/http-client/test/FilterResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,9 @@ import { StatusCode } from "~/src/StatusCode";
import { ImmutableResponse } from "~/src/ImmutableResponse";
import { Result } from "@ollierelph/result4t";
import { expect, it } from "vitest";
import { Filter, FilterApply, HttpHandler } from "~/src/FilterResult";
import { Filter, HttpHandler } from "~/src/FilterResult";
import { ImmutableURL } from "~/src/ImmutableURL";

const setHostnameForEnvironment = (env: string) =>
Filter.from((next) => async (request: ImmutableRequest) => {
const finalUrl = request.url.copy({ hostname: `${env}.example.com` });
return next(request.copy({ url: finalUrl }));
});

const addAuth = () =>
Filter.from((next) => (request: ImmutableRequest) => {
const finalHeaders = request.headers.append("Authorization", "Basic 123");
return next(request.copy({ headers: finalHeaders }));
});

const alwaysStatusAndReflectRequest =
(
status: StatusCode,
Expand All @@ -32,26 +20,41 @@ const alwaysStatusAndReflectRequest =
);

it("can build a filter chain", async () => {
const chain: Filter<
FilterApply<
Promise<Result<ImmutableResponse, Error>>,
Promise<Result<StatusCode, Error>>
>
> = setHostnameForEnvironment("stg")
.then(addAuth())
/**
* We have to specify all 4 generics for each Filter for the composition to work
* which kind of ruins the point, as we want it to be inferred
*/
type ResultHttpHandler = (
request: ImmutableRequest,
) => Promise<Result<ImmutableResponse, Error>>;

const client = Filter.from(
(next: ResultHttpHandler) => async (request: ImmutableRequest) => {
const finalUrl = request.url.copy({ hostname: `${"stg"}.example.com` });
return next(request.copy({ url: finalUrl }));
},
)
.then(
Filter.from(
(
next: HttpHandler<Promise<Result<ImmutableResponse, Error>>>,
): HttpHandler<Promise<Result<StatusCode, Error>>> =>
(request: ImmutableRequest) =>
next(request).then((res) => res.map((it) => it.status)),
(next: ResultHttpHandler) => async (request: ImmutableRequest) => {
const finalHeaders = request.headers.append(
"Authorization",
"Basic 123",
);
return next(request.copy({ headers: finalHeaders }));
},
),
);
const client = chain.apply(alwaysStatusAndReflectRequest(StatusCode.OK));
)
.then(
Filter.from(
(next: ResultHttpHandler) => (request: ImmutableRequest) =>
next(request),
),
)
.apply(alwaysStatusAndReflectRequest(StatusCode.OK));
const response = await client(
ImmutableRequest.get(ImmutableURL.fromPathname("/")),
);
expect(response.isSuccess()).toEqual(true);
expect(response.get()).toHaveProperty("value", StatusCode.OK.value);
expect(response.get()).toHaveProperty("status.value", StatusCode.OK.value);
});

0 comments on commit 5f1d8c6

Please sign in to comment.