Skip to content

Commit

Permalink
chore: Refactor Mastodon specific logic with hook API
Browse files Browse the repository at this point in the history
  • Loading branch information
neet committed Oct 16, 2024
1 parent 16084f7 commit ea74b3c
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 62 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { httpGet, HttpMockImpl, httpPost } from "../../__mocks__";
import { MastoHttpError, MastoTimeoutError } from "../errors";
import { ActionDispatcherHookMastodon } from "../hook/hook-action-dispatcher-mastodon";
import { HttpActionDispatcher } from "./dispatcher-http";
import { HttpActionDispatcherHookMastodon } from "./dispatcher-http-hook-mastodon";

describe("DispatcherHttp", () => {
describe("HttpActionDispatcher", () => {
afterEach(() => {
httpGet.mockClear();
httpPost.mockClear();
Expand All @@ -13,7 +13,7 @@ describe("DispatcherHttp", () => {
const http = new HttpMockImpl();
const dispatcher = new HttpActionDispatcher(
http,
new HttpActionDispatcherHookMastodon(http),
new ActionDispatcherHookMastodon(http),
);

httpPost.mockResolvedValueOnce({ id: "1" });
Expand Down Expand Up @@ -43,7 +43,7 @@ describe("DispatcherHttp", () => {
const http = new HttpMockImpl();
const dispatcher = new HttpActionDispatcher(
http,
new HttpActionDispatcherHookMastodon(http, 1),
new ActionDispatcherHookMastodon(http, 1),
);

httpPost.mockResolvedValueOnce({ id: "1" });
Expand All @@ -65,7 +65,7 @@ describe("DispatcherHttp", () => {
const http = new HttpMockImpl();
const dispatcher = new HttpActionDispatcher(
http,
new HttpActionDispatcherHookMastodon(http),
new ActionDispatcherHookMastodon(http),
);

httpPost.mockResolvedValueOnce({ id: "1" });
Expand All @@ -85,7 +85,7 @@ describe("DispatcherHttp", () => {
const http = new HttpMockImpl();
const dispatcher = new HttpActionDispatcher(
http,
new HttpActionDispatcherHookMastodon(http),
new ActionDispatcherHookMastodon(http),
);

httpPost.mockResolvedValueOnce({ id: "1" });
Expand Down
35 changes: 16 additions & 19 deletions src/adapters/action/dispatcher-http.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
import {
type Action,
type ActionDispatcher,
type ActionDispatcherHook,
type AnyAction,
type Http,
} from "../../interfaces";
import { PaginatorHttp } from "./paginator-http";

export type HttpActionType = "fetch" | "create" | "update" | "remove" | "list";
export type HttpAction = Action<HttpActionType>;

export class HttpActionDispatcher implements ActionDispatcher<HttpAction> {
export class HttpActionDispatcher implements ActionDispatcher<AnyAction> {
constructor(
private readonly http: Http,
private readonly hook: ActionDispatcherHook<HttpAction>,
private readonly hook?: ActionDispatcherHook,
) {}

dispatch<T>(action: HttpAction): T | Promise<T> {
if (this.hook != undefined) {
action = this.hook.beforeDispatch(action);
dispatch<T>(action: AnyAction): T | Promise<T> {
if (this.hook) {
action = this.hook.before(action);
}

let result = this.hook.dispatch(action) as T | Promise<T> | false;
if (result !== false) {
return result;
}
let result!: T | Promise<T>;

switch (action.type) {
case "fetch": {
Expand All @@ -48,12 +42,15 @@ export class HttpActionDispatcher implements ActionDispatcher<HttpAction> {
}
}

/* eslint-disable unicorn/prefer-ternary, prettier/prettier */
if (result instanceof Promise) {
return result.then((result) => this.hook?.afterDispatch(action, result)) as Promise<T>;
} else {
return this.hook.afterDispatch(action, result) as T;
if (this.hook) {
/* eslint-disable unicorn/prefer-ternary, prettier/prettier */
if (result instanceof Promise) {
return result.then((result) => this.hook?.after(result, action)) as Promise<T>;
} else {
return this.hook?.after(result, action) as T;
}
}
/* eslint-enable unicorn/prefer-ternary, prettier/prettier */

return result;

Check warning on line 54 in src/adapters/action/dispatcher-http.ts

View check run for this annotation

Codecov / codecov/patch

src/adapters/action/dispatcher-http.ts#L54

Added line #L54 was not covered by tests
}
}
13 changes: 7 additions & 6 deletions src/adapters/clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import {
HttpActionDispatcher,
WebSocketActionDispatcher,
} from "./action";
import { HttpActionDispatcherHookMastodon } from "./action/dispatcher-http-hook-mastodon";
import {
HttpConfigImpl,
type MastoHttpConfigProps,
WebSocketConfigImpl,
type WebSocketConfigProps,
} from "./config";
import { ActionDispatcherHookMastodon, HttpHookMastodon } from "./hook";
import { HttpNativeImpl } from "./http";
import { createLogger } from "./logger";
import { SerializerNativeImpl } from "./serializers";
Expand All @@ -37,9 +37,10 @@ export const createRestAPIClient = (
const serializer = new SerializerNativeImpl();
const config = new HttpConfigImpl(props, serializer);
const logger = createLogger(props.log);
const http = new HttpNativeImpl(serializer, config, logger);
const hook = new HttpActionDispatcherHookMastodon(http);
const actionDispatcher = new HttpActionDispatcher(http, hook);
const httpHook = new HttpHookMastodon();
const http = new HttpNativeImpl(serializer, config, logger, httpHook);
const actionDispatcherHook = new ActionDispatcherHookMastodon(http);
const actionDispatcher = new HttpActionDispatcher(http, actionDispatcherHook);
const actionProxy = createActionProxy(actionDispatcher, {
context: ["api"],
}) as mastodon.rest.Client;
Expand All @@ -53,8 +54,8 @@ export const createOAuthAPIClient = (
const config = new HttpConfigImpl(props, serializer);
const logger = createLogger(props.log);
const http = new HttpNativeImpl(serializer, config, logger);
const hook = new HttpActionDispatcherHookMastodon(http);
const actionDispatcher = new HttpActionDispatcher(http, hook);
const actionDispatcherHook = new ActionDispatcherHookMastodon(http);
const actionDispatcher = new HttpActionDispatcher(http, actionDispatcherHook);
const actionProxy = createActionProxy(actionDispatcher, {
context: ["oauth"],
}) as mastodon.oauth.Client;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
import { type mastodon } from "../../mastodon";
import { isRecord, sleep } from "../../utils";
import { MastoHttpError, MastoTimeoutError } from "../errors";
import { type HttpAction, type HttpActionType } from "./dispatcher-http";

type HttpActionType = "fetch" | "create" | "update" | "remove" | "list";

function isHttpActionType(actionType: string): actionType is HttpActionType {
return ["fetch", "create", "update", "remove", "list"].includes(actionType);
Expand Down Expand Up @@ -84,15 +85,15 @@ async function waitForMediaAttachment(
return media;
}

export class HttpActionDispatcherHookMastodon
implements ActionDispatcherHook<AnyAction>
{
export class ActionDispatcherHookMastodon implements ActionDispatcherHook {
readonly type = "ActionDispatcher";

constructor(
private readonly http: Http,
private readonly mediaTimeout = 1000 * 60,
) {}

beforeDispatch(action: AnyAction): HttpAction {
before(action: AnyAction): AnyAction {
const type = toHttpActionType(action.type);
const path = isHttpActionType(action.type)
? action.path
Expand All @@ -103,18 +104,7 @@ export class HttpActionDispatcherHookMastodon
return { type, path, data: action.data, meta };
}

dispatch(action: AnyAction): false | Promise<unknown> {
if (
action.type === "update" &&
action.path === "/api/v1/accounts/update_credentials"
) {
return this.http.patch(action.path, action.data, action.meta);
}

return false;
}

afterDispatch(action: AnyAction, result: unknown): unknown {
after(result: unknown, action: AnyAction): unknown {
if (action.type === "create" && action.path === "/api/v2/media") {
const media = result as mastodon.v1.MediaAttachment;
if (isRecord(action.data) && action.data?.skipPolling === true) {
Expand Down
17 changes: 17 additions & 0 deletions src/adapters/hook/hook-http-mastodon.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import assert from "node:assert";

import { HttpHookMastodon } from "./hook-http-mastodon";

describe("hookHttpMastodon", () => {
it("returns a new Request with method PATCH if the request is PUT and the URL ends with /api/v1/accounts/update_credentials", async () => {
const request = new Request(
"https://example.com/api/v1/accounts/update_credentials",
{ method: "PUT" },
);
const hook = new HttpHookMastodon();
const result = await hook.before(request);
assert(result instanceof Request);
expect(result).toBeInstanceOf(Request);
expect(result.method).toBe("PATCH");
});
});
19 changes: 19 additions & 0 deletions src/adapters/hook/hook-http-mastodon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { type HttpHook } from "../../interfaces/hook";

export class HttpHookMastodon implements HttpHook {
readonly type = "Http";

async before(request: Request): Promise<Request> {
if (
request.method === "PUT" &&
request.url.toString().endsWith("/api/v1/accounts/update_credentials")
) {
return new Request(request, { method: "PATCH" });
}
return request;
}

async after(response: Response): Promise<Response> {
return response;
}
}
2 changes: 2 additions & 0 deletions src/adapters/hook/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./hook-action-dispatcher-mastodon";
export * from "./hook-http-mastodon";
26 changes: 19 additions & 7 deletions src/adapters/http/http-native-impl.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
type Http,
type HttpConfig,
type HttpHook,
type HttpRequestParams,
type HttpRequestResult,
type Logger,
Expand All @@ -20,20 +21,31 @@ export class HttpNativeImpl extends BaseHttp implements Http {
private readonly serializer: Serializer,
private readonly config: HttpConfig,
private readonly logger?: Logger,
private readonly hook?: HttpHook,
) {
super();
}

async request(params: HttpRequestParams): Promise<HttpRequestResult> {
const request = this.createRequest(params);
let request = this.createRequest(params);

if (this.hook) {
request = await this.hook.before(request);
}

this.logger?.log("info", `↑ ${request.method} ${request.url}`);
this.logger?.log("debug", "\tbody", {
encoding: params.encoding,
body: params.body,
});

try {
this.logger?.log("info", `↑ ${request.method} ${request.url}`);
this.logger?.log("debug", "\tbody", {
encoding: params.encoding,
body: params.body,
});
const response = await fetch(request);
let response = await fetch(request);

if (this.hook) {
response = await this.hook.after(response);
}

if (!response.ok) {
throw response;
}
Expand Down
10 changes: 5 additions & 5 deletions src/interfaces/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export interface ActionDispatcher<T extends AnyAction> {
[Symbol.dispose]?(): void;
}

export interface ActionDispatcherHook<T extends AnyAction, U = unknown> {
beforeDispatch(action: T): T;
dispatch(action: T): U | Promise<U> | false;
afterDispatch(action: T, result: U | Promise<U>): U;
}
// export interface ActionDispatcherHook<T extends AnyAction, U = unknown> {
// beforeDispatch(action: T): T;
// dispatch(action: T): U | Promise<U> | false;
// afterDispatch(action: T, result: U | Promise<U>): U;
// }
35 changes: 35 additions & 0 deletions src/interfaces/hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type AnyAction } from "./action";

export type BeforeFn<Ctx extends unknown[], Result> = (...args: Ctx) => Result;

export type AfterFn<Ctx extends unknown[], Result> = (...args: Ctx) => Result;

export interface Hook<
T,
BCtx extends unknown[],
BRes,
ACtx extends unknown[],
ARes,
> {
type: T;
before: BeforeFn<BCtx, BRes>;
after: AfterFn<ACtx, ARes>;
}

export type AnyHook = Hook<string, [unknown], unknown, [unknown], unknown>;

export type HttpHook = Hook<
"Http",
[Request],
Promise<Request>,
[Response],
Promise<Response>
>;

export type ActionDispatcherHook = Hook<
"ActionDispatcher",
[AnyAction],
AnyAction,
[unknown, AnyAction],
unknown
>;
5 changes: 3 additions & 2 deletions src/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from "./action";
export * from "./config";
export * from "./hook";
export * from "./http";
export * from "./logger";
export * from "./serializer";
export * from "./config";
export * from "./action";
export * from "./ws";

0 comments on commit ea74b3c

Please sign in to comment.