Skip to content

Add relationshipLink to ElideNavigator for JSON:API relationship endpoints#286

Open
Brutus5000 wants to merge 3 commits into
developfrom
feat/elide-relationship-link
Open

Add relationshipLink to ElideNavigator for JSON:API relationship endpoints#286
Brutus5000 wants to merge 3 commits into
developfrom
feat/elide-relationship-link

Conversation

@Brutus5000

@Brutus5000 Brutus5000 commented Jun 27, 2026

Copy link
Copy Markdown
Member

What

Adds relationshipLink(name) to ElideNavigatorOnId, which builds the JSON:API relationship-linkage endpoint:

/data/{type}/{id}/relationships/{name}

Why

navigateRelationship(Class, name) already exists, but it addresses the related resource (/data/{type}/{id}/{name}). JSON:API also defines a separate relationship endpoint (.../relationships/{name}) used to read or modify the linkage itself — e.g. PATCH to replace, POST to add, DELETE to remove members of a relationship.

Without this, callers that need to operate on a relationship link have to hand-build the path string ("/data/" + type + "/" + id + "/relationships/" + name), which is exactly what surfaced in a downlords-faf-client review (clearing player.currentAvatar).

Usage

ElideNavigator.of(Player.class).id("5").relationshipLink("currentAvatar").build();
// -> /data/player/5/relationships/currentAvatar

It also composes after navigateRelationship:

ElideNavigator.of(MapPool.class).id("5")
    .navigateRelationship(MapVersion.class, "mapVersion").id("1234")
    .relationshipLink("map").build();
// -> /data/mapPool/5/mapVersion/1234/relationships/map

Tests

Added testRelationshipLink and testRelationshipLinkAfterNavigateRelationship to ElideNavigatorTest (18 tests green).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added ability to generate direct JSON:API relationship-link endpoints from existing navigation paths.
    • Introduced a dedicated “terminal” relationship-link navigator that targets linkage operations for a specified relationship name and related resource type.
  • Bug Fixes
    • Enforced correct call order by rejecting relationship-link generation when include-based navigation has already been configured.
  • Tests
    • Added test coverage for correct URL building for both root and nested relationship-link navigation, plus invalid-order behavior.

…oints

navigateRelationship addresses the related resource (/data/{type}/{id}/{name}),
but JSON:API also defines a relationship-linkage endpoint
(/data/{type}/{id}/relationships/{name}) for reading/modifying the linkage itself
(PATCH/POST/DELETE of members). Add relationshipLink(name) on ElideNavigatorOnId
to build that path so callers don't hand-build it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a terminal relationship-link navigator for JSON:API, with a new interface, API method, route construction, include-order validation, and tests.

Changes

Relationship Link Navigation

Layer / File(s) Summary
Interface, implementation, and tests
api/src/main/java/com/faforever/commons/api/elide/ElideNavigatorOnRelationshipLink.java, api/src/main/java/com/faforever/commons/api/elide/ElideNavigatorOnId.java, api/src/main/java/com/faforever/commons/api/elide/ElideNavigator.java, api/src/test/java/com/faforever/commons/api/elide/ElideNavigatorTest.java
Adds ElideNavigatorOnRelationshipLink, declares relationshipLink(...) on ElideNavigatorOnId, implements /relationships/{name} URL building with an includes check in ElideNavigator, and adds tests for root, nested, and invalid-order cases.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~5 minutes

Poem

🐇 I hop to links both neat and tidy,
Through relationships, small and speedy.
When includes peek, I say “not so!”
Then build the path and off I go.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding relationshipLink support to ElideNavigator for JSON:API relationship endpoints.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/elide-relationship-link

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 PMD (7.25.0)
api/src/main/java/com/faforever/commons/api/elide/ElideNavigator.java

[ERROR] Cannot load ruleset .coderabbit-pmd-ruleset.xml: Cannot resolve rule/ruleset reference '.coderabbit-pmd-ruleset.xml'. Make sure the resource is a valid file or URL and is on the CLASSPATH. Use --debug (or a fine log level) to see the current classpath.
[WARN] Progressbar rendering conflicts with reporting to STDOUT. No progressbar will be shown. Try running with argument -r to output the report to a file instead.

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

[ERROR] Cannot load ruleset .coderabbit-pmd-ruleset.xml: Cannot resolve rule/ruleset reference '.coderabbit-pmd-ruleset.xml'. Make sure the resource is a valid file or URL and is on the CLASSPATH. Use --debug (or a fine log level) to see the current classpath.
[WARN] Progressbar rendering conflicts with reporting to STDOUT. No progressbar will be shown. Try running with argument -r to output the report to a file instead.

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

[ERROR] Cannot load ruleset .coderabbit-pmd-ruleset.xml: Cannot resolve rule/ruleset reference '.coderabbit-pmd-ruleset.xml'. Make sure the resource is a valid file or URL and is on the CLASSPATH. Use --debug (or a fine log level) to see the current classpath.
[WARN] Progressbar rendering conflicts with reporting to STDOUT. No progressbar will be shown. Try running with argument -r to output the report to a file instead.

  • 1 others

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/src/main/java/com/faforever/commons/api/elide/ElideNavigator.java`:
- Around line 118-123: The relationshipLink(`@NotNull` String name) method
currently allows a null name to be concatenated into the route, so add an
explicit null check before build() is used and fail fast like
navigateRelationship(...). Keep the existing includes guard and log.trace, but
ensure relationshipLink rejects null before constructing the "/relationships/"
path so the `@NotNull` contract is enforced consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d9243ccf-aff3-477c-b3b9-a649e5bf7129

📥 Commits

Reviewing files that changed from the base of the PR and between ed187f7 and 79b210e.

📒 Files selected for processing (4)
  • api/src/main/java/com/faforever/commons/api/elide/ElideNavigator.java
  • api/src/main/java/com/faforever/commons/api/elide/ElideNavigatorOnId.java
  • api/src/main/java/com/faforever/commons/api/elide/ElideNavigatorOnRelationshipLink.java
  • api/src/test/java/com/faforever/commons/api/elide/ElideNavigatorTest.java

Comment on lines +118 to +123
public ElideNavigatorOnRelationshipLink relationshipLink(@NotNull String name) {
if (!includes.isEmpty()) {
throw new IllegalStateException("Cannot navigate to a relationship link with includes on parent");
}
log.trace("relationship link added: {}", name);
String route = build() + "/relationships/" + name;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject null relationship names before path concatenation.

relationshipLink(null) currently builds .../relationships/null because String concatenation coerces null. That weakens the @NotNull contract and diverges from navigateRelationship(...), which fails fast. Add an explicit null check before building the route.

Proposed fix
   `@Override`
   public ElideNavigatorOnRelationshipLink relationshipLink(`@NotNull` String name) {
     if (!includes.isEmpty()) {
       throw new IllegalStateException("Cannot navigate to a relationship link with includes on parent");
     }
+    java.util.Objects.requireNonNull(name, "name");
     log.trace("relationship link added: {}", name);
     String route = build() + "/relationships/" + name;
     return () -> route;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public ElideNavigatorOnRelationshipLink relationshipLink(@NotNull String name) {
if (!includes.isEmpty()) {
throw new IllegalStateException("Cannot navigate to a relationship link with includes on parent");
}
log.trace("relationship link added: {}", name);
String route = build() + "/relationships/" + name;
public ElideNavigatorOnRelationshipLink relationshipLink(`@NotNull` String name) {
if (!includes.isEmpty()) {
throw new IllegalStateException("Cannot navigate to a relationship link with includes on parent");
}
java.util.Objects.requireNonNull(name, "name");
log.trace("relationship link added: {}", name);
String route = build() + "/relationships/" + name;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/src/main/java/com/faforever/commons/api/elide/ElideNavigator.java` around
lines 118 - 123, The relationshipLink(`@NotNull` String name) method currently
allows a null name to be concatenated into the route, so add an explicit null
check before build() is used and fail fast like navigateRelationship(...). Keep
the existing includes guard and log.trace, but ensure relationshipLink rejects
null before constructing the "/relationships/" path so the `@NotNull` contract is
enforced consistently.

Mirror navigateRelationship by taking the related entity class:
relationshipLink(Class<R>, name) now returns ElideNavigatorOnRelationshipLink<R>,
which exposes getDtoClass() so callers can derive the related type (e.g. to build
the linkage body) instead of passing it separately.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
api/src/main/java/com/faforever/commons/api/elide/ElideNavigatorOnId.java (1)

15-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Update the terminal-operation Javadoc to mention getDtoClass().

The comment still says the returned navigator only exposes build(), but this PR makes the related DTO type part of the public contract as well. Please document getDtoClass() here so the API description matches the implementation and tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/src/main/java/com/faforever/commons/api/elide/ElideNavigatorOnId.java`
around lines 15 - 20, The terminal-operation Javadoc on ElideNavigatorOnId
should be updated to reflect the new public contract: it currently says the
returned navigator only allows ElideNavigatorOnRelationshipLink#build(), but it
should also mention getDtoClass(). Update the documentation for
relationshipLink(Class<R>, String) so it explicitly states that the returned
navigator supports both build() and getDtoClass(), keeping the API comment
aligned with the implementation and tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/src/main/java/com/faforever/commons/api/elide/ElideNavigator.java`:
- Around line 118-133: The relationshipLink method in ElideNavigator should fail
fast when entityClass is null instead of returning a terminal navigator with a
null DTO class. Add a null check for the entityClass parameter before creating
the anonymous ElideNavigatorOnRelationshipLink and make it throw immediately, so
getDtoClass() is never backed by a null value.

---

Nitpick comments:
In `@api/src/main/java/com/faforever/commons/api/elide/ElideNavigatorOnId.java`:
- Around line 15-20: The terminal-operation Javadoc on ElideNavigatorOnId should
be updated to reflect the new public contract: it currently says the returned
navigator only allows ElideNavigatorOnRelationshipLink#build(), but it should
also mention getDtoClass(). Update the documentation for
relationshipLink(Class<R>, String) so it explicitly states that the returned
navigator supports both build() and getDtoClass(), keeping the API comment
aligned with the implementation and tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ac6de138-f230-42b3-9355-01e97b52da9b

📥 Commits

Reviewing files that changed from the base of the PR and between 79b210e and 05c82a3.

📒 Files selected for processing (4)
  • api/src/main/java/com/faforever/commons/api/elide/ElideNavigator.java
  • api/src/main/java/com/faforever/commons/api/elide/ElideNavigatorOnId.java
  • api/src/main/java/com/faforever/commons/api/elide/ElideNavigatorOnRelationshipLink.java
  • api/src/test/java/com/faforever/commons/api/elide/ElideNavigatorTest.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • api/src/test/java/com/faforever/commons/api/elide/ElideNavigatorTest.java

Comment on lines +118 to +133
public <R extends ElideEntity> ElideNavigatorOnRelationshipLink<R> relationshipLink(@NotNull Class<R> entityClass,
@NotNull String name) {
if (!includes.isEmpty()) {
throw new IllegalStateException("Cannot navigate to a relationship link with includes on parent");
}
log.trace("relationship link added: {}", name);
String route = build() + "/relationships/" + name;
return new ElideNavigatorOnRelationshipLink<>() {
@Override
public String build() {
return route;
}

@Override
public Class<R> getDtoClass() {
return entityClass;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fail fast on null entityClass.

The new terminal navigator makes getDtoClass() part of the contract. Right now relationshipLink(null, "mapVersion") still succeeds and returns a navigator with a null DTO class, so callers fail later when they derive linkage bodies from it. Validate entityClass before constructing the terminal navigator.

Proposed fix
   public <R extends ElideEntity> ElideNavigatorOnRelationshipLink<R> relationshipLink(`@NotNull` Class<R> entityClass,
                                                                                       `@NotNull` String name) {
+    java.util.Objects.requireNonNull(entityClass, "entityClass");
     if (!includes.isEmpty()) {
       throw new IllegalStateException("Cannot navigate to a relationship link with includes on parent");
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public <R extends ElideEntity> ElideNavigatorOnRelationshipLink<R> relationshipLink(@NotNull Class<R> entityClass,
@NotNull String name) {
if (!includes.isEmpty()) {
throw new IllegalStateException("Cannot navigate to a relationship link with includes on parent");
}
log.trace("relationship link added: {}", name);
String route = build() + "/relationships/" + name;
return new ElideNavigatorOnRelationshipLink<>() {
@Override
public String build() {
return route;
}
@Override
public Class<R> getDtoClass() {
return entityClass;
public <R extends ElideEntity> ElideNavigatorOnRelationshipLink<R> relationshipLink(`@NotNull` Class<R> entityClass,
`@NotNull` String name) {
java.util.Objects.requireNonNull(entityClass, "entityClass");
if (!includes.isEmpty()) {
throw new IllegalStateException("Cannot navigate to a relationship link with includes on parent");
}
log.trace("relationship link added: {}", name);
String route = build() + "/relationships/" + name;
return new ElideNavigatorOnRelationshipLink<>() {
`@Override`
public String build() {
return route;
}
`@Override`
public Class<R> getDtoClass() {
return entityClass;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/src/main/java/com/faforever/commons/api/elide/ElideNavigator.java` around
lines 118 - 133, The relationshipLink method in ElideNavigator should fail fast
when entityClass is null instead of returning a terminal navigator with a null
DTO class. Add a null check for the entityClass parameter before creating the
anonymous ElideNavigatorOnRelationshipLink and make it throw immediately, so
getDtoClass() is never backed by a null value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant