Unverified Commit 8699f21b authored by Hong Minhee's avatar Hong Minhee
Browse files

Add trailingSlashInsensitive option

parent 84c5262a
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -38,6 +38,14 @@ To be released.
        interface.
     -  Added `Federation.startQueue()` method.

 -  Made the router able to be insensitive to trailing slashes in the URL paths.
    [[#81]]

     -  Added `trailingSlashInsensitive` option to `CreateFederationOptions`
        interface.
     -  Added `RouterOptions` interface.
     -  Added an optional parameter to `new Router()` constructor.

 -  Added `ChatMessage` class to Activity Vocabulary API.  [[#85]]

 -  Added `Move` class to Activity Vocabulary API.  [[#65], [#92] by Lee Dogeon]
@@ -86,6 +94,7 @@ To be released.
[#53]: https://github.com/dahlia/fedify/issues/53
[#66]: https://github.com/dahlia/fedify/issues/66
[#70]: https://github.com/dahlia/fedify/issues/70
[#81]: https://github.com/dahlia/fedify/issues/81
[#85]: https://github.com/dahlia/fedify/issues/85
[#92]: https://github.com/dahlia/fedify/pull/92

+11 −1
Original line number Diff line number Diff line
@@ -38,7 +38,7 @@ const federation = createFederation<void>({
Constructor parameters
----------------------

The `Federation` constructor function takes an object with the following
The `createFederation()` function takes an object with the following
properties.  Some of them are required:

### `kv`
@@ -203,6 +203,16 @@ the retry policy by providing a custom function that satisfies the `RetryPolicy`
type.  Or you can adjust the parameters of the built-in 
`createExponentialBackoffRetryPolicy()` function.

### `trailingSlashInsensitive`

*This API is available since Fedify 0.12.0.*

Whether the router should be insensitive to trailing slashes in the URL paths.
For example, if this option is `true`, `/foo` and `/foo/` are treated as the
same path.

Turned off by default.


How the `Federation` object recognizes the domain name
------------------------------------------------------
+11 −1
Original line number Diff line number Diff line
@@ -153,6 +153,14 @@ export interface CreateFederationOptions {
   * @since 0.12.0
   */
  inboxRetryPolicy?: RetryPolicy;

  /**
   * Whether the router should be insensitive to trailing slashes in the URL
   * paths.  For example, if this option is `true`, `/foo` and `/foo/` are
   * treated as the same path.  Turned off by default.
   * @since 0.12.0
   */
  trailingSlashInsensitive?: boolean;
}

/**
@@ -326,7 +334,9 @@ export class Federation<TContextData> {
    this.#queue = options.queue;
    this.#queueStarted = false;
    this.#manuallyStartQueue = options.manuallyStartQueue ?? false;
    this.#router = new Router();
    this.#router = new Router({
      trailingSlashInsensitive: options.trailingSlashInsensitive,
    });
    this.#router.add("/.well-known/webfinger", "webfinger");
    this.#router.add("/.well-known/nodeinfo", "nodeInfoJrd");
    this.#objectCallbacks = {};
+32 −8
Original line number Diff line number Diff line
import { assertEquals, assertThrows } from "@std/assert";
import { test } from "../testing/mod.ts";
import { Router, RouterError } from "./router.ts";
import { Router, RouterError, type RouterOptions } from "./router.ts";

function setUp(): Router {
  const router = new Router();
function setUp(options: RouterOptions = {}): Router {
  const router = new Router(options);
  router.add("/users/{name}", "user");
  router.add("/users/{name}/{postId}", "post");
  router.add(
    "/users/{name}/posts/{postId}" +
      (options.trailingSlashInsensitive ? "/" : ""),
    "post",
  );
  return router;
}

@@ -14,7 +18,7 @@ test("Router.add()", () => {
  assertEquals(router.add("/users", "users"), new Set());
  assertEquals(router.add("/users/{name}", "user"), new Set(["name"]));
  assertEquals(
    router.add("/users/{name}/{postId}", "post"),
    router.add("/users/{name}/posts/{postId}", "post"),
    new Set([
      "name",
      "postId",
@@ -24,15 +28,35 @@ test("Router.add()", () => {
});

test("Router.route()", () => {
  const router = setUp();
  let router = setUp();
  assertEquals(router.route("/users/alice"), {
    name: "user",
    values: { name: "alice" },
  });
  assertEquals(router.route("/users/alice/123"), {
  assertEquals(router.route("/users/bob/"), null);
  assertEquals(router.route("/users/alice/posts/123"), {
    name: "post",
    values: { name: "alice", postId: "123" },
  });
  assertEquals(router.route("/users/bob/posts/456/"), null);

  router = setUp({ trailingSlashInsensitive: true });
  assertEquals(router.route("/users/alice"), {
    name: "user",
    values: { name: "alice" },
  });
  assertEquals(router.route("/users/bob/"), {
    name: "user",
    values: { name: "bob" },
  });
  assertEquals(router.route("/users/alice/posts/123"), {
    name: "post",
    values: { name: "alice", postId: "123" },
  });
  assertEquals(router.route("/users/bob/posts/456/"), {
    name: "post",
    values: { name: "bob", postId: "456" },
  });
});

test("Router.build()", () => {
@@ -40,6 +64,6 @@ test("Router.build()", () => {
  assertEquals(router.build("user", { name: "alice" }), "/users/alice");
  assertEquals(
    router.build("post", { name: "alice", postId: "123" }),
    "/users/alice/123",
    "/users/alice/posts/123",
  );
});
+22 −3
Original line number Diff line number Diff line
@@ -2,6 +2,17 @@
import { Router as InnerRouter } from "uri-template-router";
import { parseTemplate, type Template } from "url-template";

/**
 * Options for the {@link Router}.
 * @since 0.12.0
 */
export interface RouterOptions {
  /**
   * Whether to ignore trailing slashes when matching paths.
   */
  trailingSlashInsensitive?: boolean;
}

/**
 * URL router and constructor based on URI Template
 * ([RFC 6570](https://tools.ietf.org/html/rfc6570)).
@@ -9,13 +20,16 @@ import { parseTemplate, type Template } from "url-template";
export class Router {
  #router: InnerRouter;
  #templates: Record<string, Template>;
  #trailingSlashInsensitive: boolean;

  /**
   * Create a new {@link Router}.
   * @param options Options for the router.
   */
  constructor() {
  constructor(options: RouterOptions = {}) {
    this.#router = new InnerRouter();
    this.#templates = {};
    this.#trailingSlashInsensitive = options.trailingSlashInsensitive ?? false;
  }

  /**
@@ -49,8 +63,13 @@ export class Router {
   *          `null`.
   */
  route(url: string): { name: string; values: Record<string, string> } | null {
    const match = this.#router.resolveURI(url);
    let match = this.#router.resolveURI(url);
    if (match == null) {
      if (!this.#trailingSlashInsensitive) return null;
      url = url.endsWith("/") ? url.replace(/\/+$/, "") : `${url}/`;
      match = this.#router.resolveURI(url);
      if (match == null) return null;
    }
    return {
      name: match.matchValue,
      values: match.params,