Skip to content

fix: Preserve inner filter chain when composing RxRouter filters - #4173

Merged
xerial merged 2 commits into
mainfrom
fix/20260417_095814-rxrouter-filter-chain-drop
Apr 17, 2026
Merged

fix: Preserve inner filter chain when composing RxRouter filters#4173
xerial merged 2 commits into
mainfrom
fix/20260417_095814-rxrouter-filter-chain-drop

Conversation

@xerial

@xerial xerial commented Apr 17, 2026

Copy link
Copy Markdown
Member

Summary

  • RxRouter.FilterNode.andThen(next: FilterNode) overwrote next.parent, silently dropping intermediate filters when composing already-built filter chains.
  • Fix: attach this on top of next's existing parent chain so the composition is associative.

Problem

Given:

RxRouter
  .filter[A]
  .andThen(
    RxRouter
      .filter[B]
      .andThen[C]
      .andThen(routes)
  )

The expected chain is A -> B -> C, but the previous implementation produced A -> C, silently losing B. The bug also surfaced inside StemNode.wrapWithFilter and RxRoute.wrapWithFilter, both of which delegate to FilterNode.andThen(FilterNode) via andThenOpt, so nested StemNodes with their own filter chains were affected too.

Fix

When composing this.andThen(next), walk to the top of next.parent and attach this there instead of overwriting next.parent.

Test plan

  • RxRouterTest: existing tests pass
  • New test Preserve the outer filter when wrapping a pre-filtered router (simple 2-filter case)
  • New test Preserve the full inner filter chain when wrapped by an outer filter fails before the fix, passes after; also asserts equivalence with the flat filter[A].andThen[B].andThen[C].andThen(routes) form
  • ./sbt httpJVM/test (Scala 3): 195 tests pass
  • ./sbt '++ 2.13' 'httpJVM/testOnly *RxRouterTest *RxRouterConverterTest' passes
  • ./sbt netty/test passes (119)
  • ./sbt httpCodeGen/test passes (43)

RxRouter.FilterNode.andThen(next: FilterNode) previously overwrote
next.parent, silently dropping intermediate filters when the inner
argument was already a filter chain. For example:

  RxRouter.filter[A].andThen(
    RxRouter.filter[B].andThen[C].andThen(routes)
  )

produced chain A -> C, losing B.

Attach `this` at the top of next's existing parent chain instead, so
composition is associative and equivalent to the flat form
filter[A].andThen[B].andThen[C].andThen(routes).
@github-actions github-actions Bot added the bug label Apr 17, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request fixes a bug in RxRouter where composing filter chains would result in intermediate filters being dropped. The andThen implementation in FilterNode was updated to recursively traverse and attach to the top of the existing parent chain. Additionally, new test cases were added to verify that filter order and completeness are maintained during complex router compositions. I have no feedback to provide.

RxRoute case-class equality depends on MethodSurface equality, which is
not reliable across Surface.methodsOf[T] calls on Scala Native (the CI
failure came from comparing two separately-built routes). Flatten the
FilterNode chain into a List[Surface] instead — that's what the test
actually wants to assert.
@xerial
xerial merged commit 798ff61 into main Apr 17, 2026
24 checks passed
@xerial
xerial deleted the fix/20260417_095814-rxrouter-filter-chain-drop branch April 17, 2026 17:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant