Skip to content

Commit 2c1d58a

Browse files
committed
Split identifier path validation into loose and strict asserts
`validateSingleIdentifierVariablePath` was applied to every setter that takes a single-identifier path, which tightened upstream's inline `variables.size!==1 || !variables.has("identifier")` check into a stricter one that also rejected the named operators (`{+identifier}`, `{?identifier}`, etc.) and any explode/prefix modifier. That restriction only matched the runtime behaviour the outbox setters historically required; the other eight setters preserved the looser upstream rule and the TS signatures already allowed `Rfc6570Expression<"identifier">`. Split the helper: - `assertIdentifierPath` keeps the upstream rule (one variable named `identifier`, any operator). - `assertStrictIdentifierPath` layers the operator/explode/prefix guard on top by calling `assertIdentifierPath` first. Both helpers carry an `asserts path is Path` predicate so callers get the `Path` narrowing automatically. Apply `assertStrictIdentifierPath` to setOutboxDispatcher and setOutboxListeners (the two setters upstream already routed through the strict centralized validator) and `assertIdentifierPath` everywhere else. All existing "wrong variables in path" tests pass. Addresses the type-vs-runtime mismatch flagged by CodeRabbit in #758 (2026-05-11). Assisted-by: Claude Opus 4.7 (1M context)
1 parent 5e96c8c commit 2c1d58a

1 file changed

Lines changed: 22 additions & 14 deletions

File tree

packages/fedify/src/federation/builder.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,23 @@ import type {
7171

7272
export const ACTOR_ALIAS_PREFIX = "actorAlias:";
7373

74-
function validateSingleIdentifierVariablePath(
74+
function assertIdentifierPath(
7575
path: string,
7676
errorMessage: string,
77-
): void {
77+
): asserts path is Path {
7878
assertPath(path);
79-
const pattern = Router.compile(path);
80-
if (pattern.variables.size !== 1 || !pattern.variables.has("identifier")) {
79+
const variables = Router.variables(path);
80+
if (variables.size !== 1 || !variables.has("identifier")) {
8181
throw new RouterError(errorMessage);
8282
}
83+
}
84+
85+
function assertStrictIdentifierPath(
86+
path: string,
87+
errorMessage: string,
88+
): asserts path is Path {
89+
assertIdentifierPath(path, errorMessage);
90+
const pattern = Router.compile(path);
8391
const expressions = pattern.template.tokens
8492
.filter(isExpression)
8593
.filter((token) => token.vars.some(({ name }) => name === "identifier"));
@@ -258,7 +266,7 @@ export class FederationBuilderImpl<TContextData>
258266
if (this.router.has("actor")) {
259267
throw new RouterError("Actor dispatcher already set.");
260268
}
261-
validateSingleIdentifierVariablePath(
269+
assertIdentifierPath(
262270
path,
263271
"Path for actor dispatcher must have one variable: {identifier}",
264272
);
@@ -716,7 +724,7 @@ export class FederationBuilderImpl<TContextData>
716724
);
717725
}
718726
} else {
719-
validateSingleIdentifierVariablePath(
727+
assertIdentifierPath(
720728
path,
721729
"Path for inbox dispatcher must have one variable: {identifier}",
722730
);
@@ -790,7 +798,7 @@ export class FederationBuilderImpl<TContextData>
790798
);
791799
}
792800
} else {
793-
validateSingleIdentifierVariablePath(
801+
assertStrictIdentifierPath(
794802
path,
795803
"Path for outbox dispatcher must have one variable: {identifier}",
796804
);
@@ -854,7 +862,7 @@ export class FederationBuilderImpl<TContextData>
854862
);
855863
}
856864
} else {
857-
validateSingleIdentifierVariablePath(
865+
assertStrictIdentifierPath(
858866
outboxPath,
859867
"Path for outbox must have one variable: {identifier}",
860868
);
@@ -905,7 +913,7 @@ export class FederationBuilderImpl<TContextData>
905913
if (this.router.has("following")) {
906914
throw new RouterError("Following collection dispatcher already set.");
907915
}
908-
validateSingleIdentifierVariablePath(
916+
assertIdentifierPath(
909917
path,
910918
"Path for following collection dispatcher must have one variable: " +
911919
"{identifier}",
@@ -967,7 +975,7 @@ export class FederationBuilderImpl<TContextData>
967975
if (this.router.has("followers")) {
968976
throw new RouterError("Followers collection dispatcher already set.");
969977
}
970-
validateSingleIdentifierVariablePath(
978+
assertIdentifierPath(
971979
path,
972980
"Path for followers collection dispatcher must have one variable: " +
973981
"{identifier}",
@@ -1025,7 +1033,7 @@ export class FederationBuilderImpl<TContextData>
10251033
if (this.router.has("liked")) {
10261034
throw new RouterError("Liked collection dispatcher already set.");
10271035
}
1028-
validateSingleIdentifierVariablePath(
1036+
assertIdentifierPath(
10291037
path,
10301038
"Path for liked collection dispatcher must have one variable: " +
10311039
"{identifier}",
@@ -1091,7 +1099,7 @@ export class FederationBuilderImpl<TContextData>
10911099
if (this.router.has("featured")) {
10921100
throw new RouterError("Featured collection dispatcher already set.");
10931101
}
1094-
validateSingleIdentifierVariablePath(
1102+
assertIdentifierPath(
10951103
path,
10961104
"Path for featured collection dispatcher must have one variable: " +
10971105
"{identifier}",
@@ -1157,7 +1165,7 @@ export class FederationBuilderImpl<TContextData>
11571165
if (this.router.has("featuredTags")) {
11581166
throw new RouterError("Featured tags collection dispatcher already set.");
11591167
}
1160-
validateSingleIdentifierVariablePath(
1168+
assertIdentifierPath(
11611169
path,
11621170
"Path for featured tags collection dispatcher must have one " +
11631171
"variable: {identifier}",
@@ -1221,7 +1229,7 @@ export class FederationBuilderImpl<TContextData>
12211229
);
12221230
}
12231231
} else {
1224-
validateSingleIdentifierVariablePath(
1232+
assertIdentifierPath(
12251233
inboxPath,
12261234
"Path for inbox must have one variable: {identifier}",
12271235
);

0 commit comments

Comments
 (0)