Unverified Commit 04840f5a authored by Hong Minhee's avatar Hong Minhee
Browse files

`Context.clone()` method

Implements clone() method for all context types (Context, RequestContext,
InboxContext) to create new contexts with modified data while preserving
other properties. Updates documentation and tests accordingly.
parent 87f95fcc
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -14,6 +14,8 @@ To be released. Note that 1.6.0 was skipped due to a mistake in the versioning.
 -  Added `Context.federation` property to access the `Federation`
    object from the context.  [[#235]]

 -  Added `Context.clone()` method.  [[#237]]

 -  Introduced `FederationBuilder` for creating a federation instance with
    a builder pattern.

@@ -43,6 +45,7 @@ To be released. Note that 1.6.0 was skipped due to a mistake in the versioning.
[#208]: https://github.com/fedify-dev/fedify/issues/208
[#227]: https://github.com/fedify-dev/fedify/issues/227
[#235]: https://github.com/fedify-dev/fedify/pull/235
[#237]: https://github.com/fedify-dev/fedify/pull/237


Version 1.5.3
+18 −0
Original line number Diff line number Diff line
@@ -482,3 +482,21 @@ if (isActor(actor)) {
  }
}
~~~~


Replacing the context data
--------------------------

*This API is available since Fedify 1.6.0.*

You can replace the context data by calling the `Context.clone()` method.
This is useful when you want to create a new context based on the existing one
but with different data.  The following shows an example of replacing the
context data:

~~~~ typescript twoslash
import { type Context } from "@fedify/fedify";
const ctx = null as unknown as Context<{ foo: string; bar: number }>;
// ---cut-before---
const newCtx = ctx.clone({ ...ctx.data, foo: "new value" });
~~~~
+30 −0
Original line number Diff line number Diff line
@@ -84,6 +84,16 @@ export interface Context<TContextData> {
   */
  readonly federation: Federation<TContextData>;

  /**
   * Creates a new context with the same properties as this one,
   * but with the given data.
   * @param data The new data to associate with the context.
   * @returns A new context with the same properties as this one,
   *          but with the given data.
   * @since 1.6.0
   */
  clone(data: TContextData): Context<TContextData>;

  /**
   * Builds the URI of the NodeInfo document.
   * @returns The NodeInfo URI.
@@ -429,6 +439,16 @@ export interface RequestContext<TContextData> extends Context<TContextData> {
   */
  readonly url: URL;

  /**
   * Creates a new context with the same properties as this one,
   * but with the given data.
   * @param data The new data to associate with the context.
   * @returns A new context with the same properties as this one,
   *          but with the given data.
   * @since 1.6.0
   */
  clone(data: TContextData): RequestContext<TContextData>;

  /**
   * Gets an {@link Actor} object for the given identifier.
   * @param identifier The actor's identifier.
@@ -539,6 +559,16 @@ export interface InboxContext<TContextData> extends Context<TContextData> {
   */
  recipient: string | null;

  /**
   * Creates a new context with the same properties as this one,
   * but with the given data.
   * @param data The new data to associate with the context.
   * @returns A new context with the same properties as this one,
   *          but with the given data.
   * @since 1.6.0
   */
  clone(data: TContextData): InboxContext<TContextData>;

  /**
   * Forwards a received activity to the recipients' inboxes.  The forwarded
   * activity will be signed in HTTP Signatures by the forwarder, but its
+25 −9
Original line number Diff line number Diff line
@@ -1180,7 +1180,7 @@ test("handleInbox()", async () => {
    recipient: null,
    context: unsignedContext,
    inboxContextFactory(_activity) {
      return createInboxContext(unsignedContext);
      return createInboxContext({ ...unsignedContext, clone: undefined });
    },
    ...inboxOptions,
    actorDispatcher: undefined,
@@ -1193,7 +1193,11 @@ test("handleInbox()", async () => {
    recipient: "nobody",
    context: unsignedContext,
    inboxContextFactory(_activity) {
      return createInboxContext({ ...unsignedContext, recipient: "nobody" });
      return createInboxContext({
        ...unsignedContext,
        clone: undefined,
        recipient: "nobody",
      });
    },
    ...inboxOptions,
  });
@@ -1205,7 +1209,7 @@ test("handleInbox()", async () => {
    recipient: null,
    context: unsignedContext,
    inboxContextFactory(_activity) {
      return createInboxContext(unsignedContext);
      return createInboxContext({ ...unsignedContext, clone: undefined });
    },
    ...inboxOptions,
  });
@@ -1216,7 +1220,11 @@ test("handleInbox()", async () => {
    recipient: "someone",
    context: unsignedContext,
    inboxContextFactory(_activity) {
      return createInboxContext({ ...unsignedContext, recipient: "someone" });
      return createInboxContext({
        ...unsignedContext,
        clone: undefined,
        recipient: "someone",
      });
    },
    ...inboxOptions,
  });
@@ -1240,7 +1248,7 @@ test("handleInbox()", async () => {
    recipient: null,
    context: signedContext,
    inboxContextFactory(_activity) {
      return createInboxContext(unsignedContext);
      return createInboxContext({ ...unsignedContext, clone: undefined });
    },
    ...inboxOptions,
  });
@@ -1251,7 +1259,11 @@ test("handleInbox()", async () => {
    recipient: "someone",
    context: signedContext,
    inboxContextFactory(_activity) {
      return createInboxContext({ ...unsignedContext, recipient: "someone" });
      return createInboxContext({
        ...unsignedContext,
        clone: undefined,
        recipient: "someone",
      });
    },
    ...inboxOptions,
  });
@@ -1262,7 +1274,7 @@ test("handleInbox()", async () => {
    recipient: null,
    context: unsignedContext,
    inboxContextFactory(_activity) {
      return createInboxContext(unsignedContext);
      return createInboxContext({ ...unsignedContext, clone: undefined });
    },
    ...inboxOptions,
    skipSignatureVerification: true,
@@ -1274,7 +1286,11 @@ test("handleInbox()", async () => {
    recipient: "someone",
    context: unsignedContext,
    inboxContextFactory(_activity) {
      return createInboxContext({ ...unsignedContext, recipient: "someone" });
      return createInboxContext({
        ...unsignedContext,
        clone: undefined,
        recipient: "someone",
      });
    },
    ...inboxOptions,
    skipSignatureVerification: true,
@@ -1311,7 +1327,7 @@ test("handleInbox()", async () => {
    recipient: null,
    context: signedContext,
    inboxContextFactory(_activity) {
      return createInboxContext(signedInvalidContext);
      return createInboxContext({ ...signedInvalidContext, clone: undefined });
    },
    ...inboxOptions,
  });
+34 −0
Original line number Diff line number Diff line
@@ -726,6 +726,22 @@ test("Federation.createContext()", async (t) => {
    );
  });

  await t.step("Context.clone()", () => {
    const federation = createFederation<number>({
      kv,
    });
    const ctx = federation.createContext(new URL("https://example.com/"), 123);
    const clone = ctx.clone(456);
    assertStrictEquals(clone.canonicalOrigin, ctx.canonicalOrigin);
    assertStrictEquals(clone.origin, ctx.origin);
    assertEquals(clone.data, 456);
    assertEquals(clone.host, ctx.host);
    assertEquals(clone.hostname, ctx.hostname);
    assertStrictEquals(clone.documentLoader, ctx.documentLoader);
    assertStrictEquals(clone.contextLoader, ctx.contextLoader);
    assertStrictEquals(clone.federation, ctx.federation);
  });

  mf.mock("GET@/.well-known/nodeinfo", (req) => {
    assertEquals(new URL(req.url).host, "example.com");
    assertEquals(req.headers.get("User-Agent"), "CustomUserAgent/1.2.3");
@@ -875,6 +891,24 @@ test("Federation.createContext()", async (t) => {
    );
  });

  await t.step("RequestContext.clone()", () => {
    const federation = createFederation<number>({
      kv,
    });
    const req = new Request("https://example.com/");
    const ctx = federation.createContext(req, 123);
    const clone = ctx.clone(456);
    assertStrictEquals(clone.request, ctx.request);
    assertEquals(clone.url, ctx.url);
    assertEquals(clone.data, 456);
    assertEquals(clone.origin, ctx.origin);
    assertEquals(clone.host, ctx.host);
    assertEquals(clone.hostname, ctx.hostname);
    assertStrictEquals(clone.documentLoader, ctx.documentLoader);
    assertStrictEquals(clone.contextLoader, ctx.contextLoader);
    assertStrictEquals(clone.federation, ctx.federation);
  });

  mf.uninstall();
});

Loading