mirror of
https://github.com/versia-pub/api.git
synced 2026-03-13 12:19:15 +01:00
feat(federation): ✨ Add new RequestParserHandler to less verbosely handle body parsing
This commit is contained in:
parent
8860d09eb4
commit
09a9f0bbf5
6 changed files with 541 additions and 316 deletions
95
federation/http/index.test.ts
Normal file
95
federation/http/index.test.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { beforeEach, describe, expect, jest, test } from "bun:test";
|
||||
import { RequestParserHandler } from "../http/index.ts";
|
||||
import { EntityValidator } from "../validator/index.ts";
|
||||
|
||||
// Pulled from social.lysand.org
|
||||
const validUser = {
|
||||
id: "018eb863-753f-76ff-83d6-fd590de7740a",
|
||||
type: "User",
|
||||
uri: "https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a",
|
||||
bio: {
|
||||
"text/html": {
|
||||
content: "<p>Hey</p>\n",
|
||||
},
|
||||
},
|
||||
created_at: "2024-04-07T11:48:29.623Z",
|
||||
dislikes:
|
||||
"https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a/dislikes",
|
||||
featured:
|
||||
"https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a/featured",
|
||||
likes: "https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a/likes",
|
||||
followers:
|
||||
"https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a/followers",
|
||||
following:
|
||||
"https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a/following",
|
||||
inbox: "https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a/inbox",
|
||||
outbox: "https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a/outbox",
|
||||
indexable: false,
|
||||
username: "jessew",
|
||||
display_name: "Jesse Wierzbinski",
|
||||
fields: [
|
||||
{
|
||||
key: { "text/html": { content: "<p>Identity</p>\n" } },
|
||||
value: {
|
||||
"text/html": {
|
||||
content:
|
||||
'<p><a href="https://keyoxide.org/aspe:keyoxide.org:NKLLPWPV7P35NEU7JP4K4ID4CA">https://keyoxide.org/aspe:keyoxide.org:NKLLPWPV7P35NEU7JP4K4ID4CA</a></p>\n',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
public_key: {
|
||||
actor: "https://social.lysand.org/users/018eb863-753f-76ff-83d6-fd590de7740a",
|
||||
public_key: "XXXXXXXX",
|
||||
},
|
||||
extensions: { "org.lysand:custom_emojis": { emojis: [] } },
|
||||
};
|
||||
|
||||
describe("LysandRequestHandler", () => {
|
||||
let validator: EntityValidator;
|
||||
|
||||
beforeEach(() => {
|
||||
validator = new EntityValidator();
|
||||
});
|
||||
|
||||
test("parseBody with valid User", async () => {
|
||||
const handler = new RequestParserHandler(validUser, validator);
|
||||
|
||||
const noteCallback = jest.fn();
|
||||
await handler.parseBody({ user: noteCallback });
|
||||
|
||||
expect(noteCallback).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("Throw on invalid Note", async () => {
|
||||
const handler = new RequestParserHandler(
|
||||
{
|
||||
type: "Note",
|
||||
body: "bad",
|
||||
},
|
||||
validator,
|
||||
);
|
||||
|
||||
const noteCallback = jest.fn();
|
||||
await expect(
|
||||
handler.parseBody({ note: noteCallback }),
|
||||
).rejects.toThrow();
|
||||
expect(noteCallback).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("Throw on incorrect body type property", async () => {
|
||||
const handler = new RequestParserHandler(
|
||||
{
|
||||
type: "DoesntExist",
|
||||
body: "bad",
|
||||
},
|
||||
validator,
|
||||
);
|
||||
|
||||
const noteCallback = jest.fn();
|
||||
await expect(
|
||||
handler.parseBody({ note: noteCallback }),
|
||||
).rejects.toThrow();
|
||||
expect(noteCallback).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
129
federation/http/index.ts
Normal file
129
federation/http/index.ts
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
import type { EntityValidator } from "../validator/index";
|
||||
|
||||
type MaybePromise<T> = T | Promise<T>;
|
||||
|
||||
type ParserCallbacks = {
|
||||
note: (note: typeof EntityValidator.$Note) => MaybePromise<void>;
|
||||
follow: (follow: typeof EntityValidator.$Follow) => MaybePromise<void>;
|
||||
followAccept: (
|
||||
followAccept: typeof EntityValidator.$FollowAccept,
|
||||
) => MaybePromise<void>;
|
||||
followReject: (
|
||||
followReject: typeof EntityValidator.$FollowReject,
|
||||
) => MaybePromise<void>;
|
||||
user: (user: typeof EntityValidator.$User) => MaybePromise<void>;
|
||||
like: (like: typeof EntityValidator.$Like) => MaybePromise<void>;
|
||||
dislike: (dislike: typeof EntityValidator.$Dislike) => MaybePromise<void>;
|
||||
undo: (undo: typeof EntityValidator.$Undo) => MaybePromise<void>;
|
||||
serverMetadata: (
|
||||
serverMetadata: typeof EntityValidator.$ServerMetadata,
|
||||
) => MaybePromise<void>;
|
||||
extension: (
|
||||
extension: typeof EntityValidator.$Extension,
|
||||
) => MaybePromise<void>;
|
||||
};
|
||||
|
||||
export class RequestParserHandler {
|
||||
constructor(
|
||||
private readonly body: Record<
|
||||
string,
|
||||
string | number | object | boolean | null
|
||||
>,
|
||||
private readonly validator: EntityValidator,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Parse the body of the request and call the appropriate callback.
|
||||
* @param callbacks The callbacks to call when a specific entity is found.
|
||||
* @returns A promise that resolves when the body has been parsed, and the callbacks have finished executing.
|
||||
*/
|
||||
public async parseBody(callbacks: Partial<ParserCallbacks>): Promise<void> {
|
||||
if (!this.body.type) throw new Error("Missing type field in body");
|
||||
|
||||
switch (this.body.type) {
|
||||
case "Note": {
|
||||
const note = await this.validator.Note(this.body);
|
||||
|
||||
if (callbacks.note) await callbacks.note(note);
|
||||
|
||||
break;
|
||||
}
|
||||
case "Follow": {
|
||||
const follow = await this.validator.Follow(this.body);
|
||||
|
||||
if (callbacks.follow) await callbacks.follow(follow);
|
||||
|
||||
break;
|
||||
}
|
||||
case "FollowAccept": {
|
||||
const followAccept = await this.validator.FollowAccept(
|
||||
this.body,
|
||||
);
|
||||
|
||||
if (callbacks.followAccept)
|
||||
await callbacks.followAccept(followAccept);
|
||||
|
||||
break;
|
||||
}
|
||||
case "FollowReject": {
|
||||
const followReject = await this.validator.FollowReject(
|
||||
this.body,
|
||||
);
|
||||
|
||||
if (callbacks.followReject)
|
||||
await callbacks.followReject(followReject);
|
||||
|
||||
break;
|
||||
}
|
||||
case "User": {
|
||||
const user = await this.validator.User(this.body);
|
||||
|
||||
if (callbacks.user) await callbacks.user(user);
|
||||
|
||||
break;
|
||||
}
|
||||
case "Like": {
|
||||
const like = await this.validator.Like(this.body);
|
||||
|
||||
if (callbacks.like) await callbacks.like(like);
|
||||
|
||||
break;
|
||||
}
|
||||
case "Dislike": {
|
||||
const dislike = await this.validator.Dislike(this.body);
|
||||
|
||||
if (callbacks.dislike) await callbacks.dislike(dislike);
|
||||
|
||||
break;
|
||||
}
|
||||
case "Undo": {
|
||||
const undo = await this.validator.Undo(this.body);
|
||||
|
||||
if (callbacks.undo) await callbacks.undo(undo);
|
||||
|
||||
break;
|
||||
}
|
||||
case "ServerMetadata": {
|
||||
const serverMetadata = await this.validator.ServerMetadata(
|
||||
this.body,
|
||||
);
|
||||
|
||||
if (callbacks.serverMetadata)
|
||||
await callbacks.serverMetadata(serverMetadata);
|
||||
|
||||
break;
|
||||
}
|
||||
case "Extension": {
|
||||
const extension = await this.validator.Extension(this.body);
|
||||
|
||||
if (callbacks.extension) await callbacks.extension(extension);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(
|
||||
`Invalid type field in body: ${this.body.type}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue