Skip to content

Commit 79b210e

Browse files
Brutus5000claude
andcommitted
Make relationshipLink a terminal navigator
Previously relationshipLink set a field on the navigator and returned itself, so it could coexist with a navigated relationship and be mutated further, allowing nonsensical paths. It now returns a dedicated terminal type (ElideNavigatorOnRelationshipLink) that only exposes build(), builds the path eagerly, and rejects includes on the parent (mirroring navigateRelationship). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent e3d4a90 commit 79b210e

4 files changed

Lines changed: 29 additions & 6 deletions

File tree

api/src/main/java/com/faforever/commons/api/elide/ElideNavigator.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ public class ElideNavigator<T extends ElideEntity> implements ElideNavigatorSele
2727
private final Optional<ElideNavigator<?>> parentNavigator;
2828
private Optional<String> id = Optional.empty();
2929
private Optional<String> relationship = Optional.empty();
30-
private Optional<String> relationshipLink = Optional.empty();
3130
private Optional<Condition<?>> filterCondition = Optional.empty();
3231
private Optional<Integer> pageSize = Optional.empty();
3332
private Optional<Integer> pageNumber = Optional.empty();
@@ -116,10 +115,13 @@ public <R extends ElideEntity> ElideNavigatorSelector<R> navigateRelationship(@N
116115
}
117116

118117
@Override
119-
public ElideNavigatorOnId<T> relationshipLink(@NotNull String name) {
118+
public ElideNavigatorOnRelationshipLink relationshipLink(@NotNull String name) {
119+
if (!includes.isEmpty()) {
120+
throw new IllegalStateException("Cannot navigate to a relationship link with includes on parent");
121+
}
120122
log.trace("relationship link added: {}", name);
121-
this.relationshipLink = Optional.of(name);
122-
return this;
123+
String route = build() + "/relationships/" + name;
124+
return () -> route;
123125
}
124126

125127
/**
@@ -197,7 +199,6 @@ public String build() {
197199
String route = parentNavigator.map(ElideNavigator::build).orElse("/data/" + dtoPath) +
198200
id.map(i -> "/" + i).orElse("") +
199201
relationship.map(r -> "/" + r).orElse("") +
200-
relationshipLink.map(r -> "/relationships/" + r).orElse("") +
201202
queryArgs;
202203
log.trace("Route built: {}", route);
203204
return route;

api/src/main/java/com/faforever/commons/api/elide/ElideNavigatorOnId.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ public interface ElideNavigatorOnId<T extends ElideEntity> extends ElideEndpoint
1111
* ({@code /data/{type}/{id}/relationships/{name}}), used to read or modify the relationship linkage itself
1212
* (e.g. PATCH/POST/DELETE to add, replace or remove members). This differs from
1313
* {@link #navigateRelationship(Class, String)}, which addresses the related resource(s).
14+
*
15+
* <p>This is a terminal operation: the returned navigator only allows {@link
16+
* ElideNavigatorOnRelationshipLink#build()}. Includes are not allowed on the parent.
1417
*/
15-
ElideNavigatorOnId<T> relationshipLink(String name);
18+
ElideNavigatorOnRelationshipLink relationshipLink(String name);
1619

1720
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.faforever.commons.api.elide;
2+
3+
/**
4+
* Terminal navigator pointing at a JSON:API relationship endpoint
5+
* ({@code /data/{type}/{id}/relationships/{name}}). It only exposes {@link #build()} because a relationship
6+
* link addresses the linkage itself (read or modify via PATCH/POST/DELETE) and cannot be navigated further.
7+
*/
8+
@FunctionalInterface
9+
public interface ElideNavigatorOnRelationshipLink {
10+
String build();
11+
}

api/src/test/java/com/faforever/commons/api/elide/ElideNavigatorTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,14 @@ void testRelationshipLinkAfterNavigateRelationship() {
105105
.build(), is("/data/mapPool/5/mapVersion/1234/relationships/map"));
106106
}
107107

108+
@Test
109+
void testCannotRelationshipLinkAfterIncludes() {
110+
assertThrows(IllegalStateException.class, () -> ElideNavigator.of(MapPool.class)
111+
.id("5")
112+
.addInclude("mapVersion")
113+
.relationshipLink("mapVersion"));
114+
}
115+
108116
@Test
109117
void testGetListPages() {
110118
assertThat(ElideNavigator.of(MapPoolAssignment.class)

0 commit comments

Comments
 (0)