Loading fedify/federation/builder.test.ts +74 −1 Original line number Diff line number Diff line import { assertEquals, assertExists } from "@std/assert"; import { assertEquals, assertExists, assertThrows } from "@std/assert"; import { parseSemVer } from "../nodeinfo/semver.ts"; import type { Protocol } from "../nodeinfo/types.ts"; import { test } from "../testing/mod.ts"; Loading Loading @@ -215,4 +215,77 @@ test("FederationBuilder", async (t) => { assertEquals(personRoute?.values.id, "abc"); }, ); await t.step( "should handle symbol names uniquely in custom collection dispatchers", async () => { const builder = createFederationBuilder<string>(); const kv = new MemoryKvStore(); // Create two unnamed symbols const unnamedSymbol1 = Symbol(); const unnamedSymbol2 = Symbol(); const namedSymbol1 = Symbol.for(""); const namedSymbol2 = Symbol.for(""); const strId = String(unnamedSymbol1); const dispatcher = (_ctx: unknown, _params: unknown) => ({ items: [], }); // Test that different unnamed symbols are treated as different builder.setCollectionDispatcher( unnamedSymbol1, Note, "/unnamed-symbol1/{id}", dispatcher, ); // Test that using the same symbol twice throws an error assertThrows( () => { builder.setCollectionDispatcher( unnamedSymbol1, Note, "/unnamed-symbol1-duplicate/{id}", dispatcher, ); }, Error, "Collection dispatcher for Symbol() already set.", ); // Test that using a different symbol works builder.setCollectionDispatcher( unnamedSymbol2, Note, "/unnamed-symbol2/{id}", dispatcher, ); // Test that using same named symbol twice with a different name throws an error builder.setCollectionDispatcher( namedSymbol1, Note, "/named-symbol/{id}", dispatcher, ); assertThrows( () => { builder.setCollectionDispatcher( namedSymbol2, Note, "/named-symbol/{id}", dispatcher, ); }, ); // Test that using string ID stringified from an unnamed symbol works builder.setCollectionDispatcher( strId, Note, "/string-id/{id}", dispatcher, ); }, ); }); fedify/federation/builder.ts +27 −3 Original line number Diff line number Diff line Loading @@ -114,6 +114,11 @@ export class FederationBuilderImpl<TContextData> > >; /** * Symbol registry for unique identification of unnamed symbols. */ #symbolRegistry = new Map<symbol, string>(); constructor() { this.router = new Router(); this.objectCallbacks = {}; Loading Loading @@ -1264,17 +1269,18 @@ export class FederationBuilderImpl<TContextData> RequestContext<TContextData>, TContextData > { const routeName = `${collectionType}:${String(name)}`; const strName = String(name); const routeName = `${collectionType}:${this.#uniqueCollectionId(name)}`; if (this.router.has(routeName)) { throw new RouterError( `Collection dispatcher for ${String(name)} already set.`, `Collection dispatcher for ${strName} already set.`, ); } // Check if identifier is already used in collectionCallbacks if (this.collectionCallbacks[name] != null) { throw new RouterError( `Collection dispatcher for ${String(name)} already set.`, `Collection dispatcher for ${strName} already set.`, ); } Loading Loading @@ -1342,6 +1348,24 @@ export class FederationBuilderImpl<TContextData> }; return setters; } /** * Converts a name (string or symbol) to a unique string identifier. * For symbols, generates and caches a UUID if not already present. * For strings, returns the string as-is. * @param name The name to convert to a unique identifier * @returns A unique string identifier */ #uniqueCollectionId(name: string | symbol): string { if (typeof name === "string") return name; // Check if symbol already has a unique ID if (!this.#symbolRegistry.has(name)) { // Generate a new UUID for this symbol this.#symbolRegistry.set(name, crypto.randomUUID()); } return this.#symbolRegistry.get(name)!; } } /** Loading Loading
fedify/federation/builder.test.ts +74 −1 Original line number Diff line number Diff line import { assertEquals, assertExists } from "@std/assert"; import { assertEquals, assertExists, assertThrows } from "@std/assert"; import { parseSemVer } from "../nodeinfo/semver.ts"; import type { Protocol } from "../nodeinfo/types.ts"; import { test } from "../testing/mod.ts"; Loading Loading @@ -215,4 +215,77 @@ test("FederationBuilder", async (t) => { assertEquals(personRoute?.values.id, "abc"); }, ); await t.step( "should handle symbol names uniquely in custom collection dispatchers", async () => { const builder = createFederationBuilder<string>(); const kv = new MemoryKvStore(); // Create two unnamed symbols const unnamedSymbol1 = Symbol(); const unnamedSymbol2 = Symbol(); const namedSymbol1 = Symbol.for(""); const namedSymbol2 = Symbol.for(""); const strId = String(unnamedSymbol1); const dispatcher = (_ctx: unknown, _params: unknown) => ({ items: [], }); // Test that different unnamed symbols are treated as different builder.setCollectionDispatcher( unnamedSymbol1, Note, "/unnamed-symbol1/{id}", dispatcher, ); // Test that using the same symbol twice throws an error assertThrows( () => { builder.setCollectionDispatcher( unnamedSymbol1, Note, "/unnamed-symbol1-duplicate/{id}", dispatcher, ); }, Error, "Collection dispatcher for Symbol() already set.", ); // Test that using a different symbol works builder.setCollectionDispatcher( unnamedSymbol2, Note, "/unnamed-symbol2/{id}", dispatcher, ); // Test that using same named symbol twice with a different name throws an error builder.setCollectionDispatcher( namedSymbol1, Note, "/named-symbol/{id}", dispatcher, ); assertThrows( () => { builder.setCollectionDispatcher( namedSymbol2, Note, "/named-symbol/{id}", dispatcher, ); }, ); // Test that using string ID stringified from an unnamed symbol works builder.setCollectionDispatcher( strId, Note, "/string-id/{id}", dispatcher, ); }, ); });
fedify/federation/builder.ts +27 −3 Original line number Diff line number Diff line Loading @@ -114,6 +114,11 @@ export class FederationBuilderImpl<TContextData> > >; /** * Symbol registry for unique identification of unnamed symbols. */ #symbolRegistry = new Map<symbol, string>(); constructor() { this.router = new Router(); this.objectCallbacks = {}; Loading Loading @@ -1264,17 +1269,18 @@ export class FederationBuilderImpl<TContextData> RequestContext<TContextData>, TContextData > { const routeName = `${collectionType}:${String(name)}`; const strName = String(name); const routeName = `${collectionType}:${this.#uniqueCollectionId(name)}`; if (this.router.has(routeName)) { throw new RouterError( `Collection dispatcher for ${String(name)} already set.`, `Collection dispatcher for ${strName} already set.`, ); } // Check if identifier is already used in collectionCallbacks if (this.collectionCallbacks[name] != null) { throw new RouterError( `Collection dispatcher for ${String(name)} already set.`, `Collection dispatcher for ${strName} already set.`, ); } Loading Loading @@ -1342,6 +1348,24 @@ export class FederationBuilderImpl<TContextData> }; return setters; } /** * Converts a name (string or symbol) to a unique string identifier. * For symbols, generates and caches a UUID if not already present. * For strings, returns the string as-is. * @param name The name to convert to a unique identifier * @returns A unique string identifier */ #uniqueCollectionId(name: string | symbol): string { if (typeof name === "string") return name; // Check if symbol already has a unique ID if (!this.#symbolRegistry.has(name)) { // Generate a new UUID for this symbol this.#symbolRegistry.set(name, crypto.randomUUID()); } return this.#symbolRegistry.get(name)!; } } /** Loading