Skip to content

Conversation

@nielsenko
Copy link
Collaborator

@nielsenko nielsenko commented May 13, 2025

Description

Router class now require method(/verb) on add and lookup. Hence, handlers can now defer to the router to ensure they are invoked with correct method.

The extra cost is kept to a constant factor (O(1)) for both speed and space.

Related Issues

Pre-Launch Checklist

Please ensure that your PR meets the following requirements before submitting:

  • This update focuses on a single feature or bug fix. (For multiple fixes, please submit separate PRs.)
  • I have read and followed the Dart Style Guide and formatted the code using dart format.
  • I have referenced at least one issue this PR fixes or is related to.
  • I have updated/added relevant documentation (doc comments with ///), ensuring consistency with existing project documentation.
  • I have added new tests to verify the changes.
  • All existing and new tests pass successfully.
  • I have documented any breaking changes below.

Breaking Changes

  • Includes breaking changes.
  • No breaking changes.

Public interface of Router<T> changed

Benchmark results

2,4 GHz 8-Core Intel Core i9

./benchmark/benchmark.exe
Setting up benchmark data with 10000 routes...
Setup complete.
Starting benchmarks
---
Static Add
Routingkit Add Static x10000(RunTime): 10676.455 us.
Spanner Add Static x10000(RunTime): 14466.405594405594 us.
Router Add Static x10000(RunTime): 6849.0675 us.
---
Static Lookup
Routingkit Lookup Static x10000(RunTime): 2317.468 us.
Spanner Lookup Static x10000(RunTime): 1890.7721139430284 us.
Router Lookup Static x10000(RunTime): 1448.368 us.
---
Dynamic Add
Routingkit Add Dynamic x10000(RunTime): 15025.561538461538 us.
Spanner Add Dynamic x10000(RunTime): 60634.91176470588 us.
Router Add Dynamic x10000(RunTime): 27387.536842105263 us.
---
Dynamic Lookup
Routingkit Lookup Dynamic x10000(RunTime): 18773.025 us.
Spanner Lookup Dynamic x10000(RunTime): 5808.370879120879 us.
Router Lookup Dynamic x10000(RunTime): 3398.6293706293704 us.
Done

Summary by CodeRabbit

  • New Features
    • Introduced HTTP method-based routing, allowing registration and lookup of route handlers specific to HTTP verbs (GET, POST, PUT, DELETE, etc.).
    • Added convenience methods for registering routes per HTTP method and for all methods at once.
    • Added new benchmarks for the spanner router package.
  • Improvements
    • Enhanced benchmark reporting with clearer grouping and naming consistency.
    • Simplified and unified method usage across benchmarks and router APIs.
  • Bug Fixes
    • Improved conflict detection when registering multiple handlers for the same path and HTTP method.
  • Tests
    • Added comprehensive tests for method-specific routing, parameterized paths, and conflict scenarios.
    • Updated existing tests to use method-aware route registration and lookup.
  • Chores
    • Updated dependencies, including adding the spanner package and updating collection to a newer version.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 13, 2025

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

The changes introduce HTTP method-aware routing to the Router class, allowing handlers to be registered for specific HTTP verbs (GET, POST, etc.) and updating all related APIs, benchmarks, and tests accordingly. The PathTrie gains an in-place update method, and new benchmarks and tests are added for method-specific routing and the spanner package.

Changes

File(s) Change Summary
lib/src/router/router.dart Introduced HTTP method-aware routing: added Method enum, changed internal data structures to map paths to handlers per method, updated add and lookup APIs to require a method, and added extension methods for method-specific route registration (get, post, etc.) and any for all methods.
lib/src/router/path_trie.dart Removed isDynamic from LookupResult, updated constructor, and added addOrUpdateInPlace for in-place value updates at a path.
benchmark/benchmark.dart Updated all router benchmarks to use method-aware APIs, added benchmarks for the spanner package, refactored variable naming, and reorganized benchmark reporting by category.
test/router/router_methods_test.dart Added comprehensive test suite for method-specific routing, including registration, lookup, conflict detection, parameterized paths, and any method support.
test/router/router_test.dart Updated all route addition and lookup calls to use method-specific APIs (get and lookup(Method.get, ...)). No test logic changes.
pubspec.yaml Updated collection dependency version and added spanner as a dev dependency.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Router
    participant Handler

    Client->>Router: add(Method.get, "/foo", handlerA)
    Router->>Router: Store handlerA for GET on "/foo"

    Client->>Router: add(Method.post, "/foo", handlerB)
    Router->>Router: Store handlerB for POST on "/foo"

    Client->>Router: lookup(Method.get, "/foo")
    Router->>Router: Find handlerA for GET on "/foo"
    Router-->>Client: handlerA

    Client->>Router: lookup(Method.post, "/foo")
    Router->>Router: Find handlerB for POST on "/foo"
    Router-->>Client: handlerB

    Client->>Router: lookup(Method.put, "/foo")
    Router->>Router: No handler for PUT on "/foo"
    Router-->>Client: null
Loading

Assessment against linked issues

Objective Addressed Explanation
Support for defining routes with HTTP methods (GET, POST, PUT, DELETE, etc.) [#53]

Possibly related PRs


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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@codecov
Copy link

codecov bot commented May 13, 2025

Codecov Report

Attention: Patch coverage is 97.87234% with 1 line in your changes missing coverage. Please review.

Project coverage is 80.54%. Comparing base (fa540dc) to head (cffc3bd).
Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
lib/src/router/router.dart 97.56% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #65      +/-   ##
==========================================
+ Coverage   80.29%   80.54%   +0.25%     
==========================================
  Files          78       78              
  Lines        2304     2339      +35     
  Branches     1300     1321      +21     
==========================================
+ Hits         1850     1884      +34     
- Misses        454      455       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@nielsenko
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 13, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 (2)
benchmark/benchmark.dart (1)

236-238: Typo in class name (DynamicLookupSpanneBenchmark)

The missing “r” makes the spelling inconsistent with the other benchmark classes and with the package name. It is only cosmetic, but keeping names consistent improves discoverability (e.g., for IDE search).

-class DynamicLookupSpanneBenchmark extends RouterBenchmark {
-  DynamicLookupSpanneBenchmark() : super('Spanner Lookup Dynamic x$routeCount');
+class DynamicLookupSpannerBenchmark extends RouterBenchmark {
+  DynamicLookupSpannerBenchmark()
+      : super('Spanner Lookup Dynamic x$routeCount');

Remember to update the instantiation in driver():

-  DynamicLookupSpanneBenchmark().report();
+  DynamicLookupSpannerBenchmark().report();
lib/src/router/router.dart (1)

26-31: Provide more helpful error details when a verb is already registered

ArgumentError('$method already registered') gives no information about where the duplication happened. Including the offending path (or at least clarifying the message) makes debugging far easier.

-      throw ArgumentError('$method already registered');
+      throw ArgumentError(
+          'A route for HTTP method $method is already registered on this path');
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fa540dc and 803daff.

📒 Files selected for processing (6)
  • benchmark/benchmark.dart (10 hunks)
  • lib/src/router/path_trie.dart (3 hunks)
  • lib/src/router/router.dart (3 hunks)
  • pubspec.yaml (2 hunks)
  • test/router/router_methods_test.dart (1 hunks)
  • test/router/router_test.dart (23 hunks)
🧰 Additional context used
🧠 Learnings (1)
test/router/router_test.dart (1)
Learnt from: nielsenko
PR: serverpod/relic#52
File: lib/src/router/router.dart:37-53
Timestamp: 2025-05-05T14:40:00.323Z
Learning: In the Router and PathTrie implementation in Dart, both static and dynamic routes consistently throw ArgumentError when attempting to add duplicate routes, despite comments suggesting dynamic routes would be overwritten with a warning.
🔇 Additional comments (13)
pubspec.yaml (2)

11-11: Updated collection package dependency

The collection package has been updated to version ^1.18.0 from a previous version, which is likely required for compatibility with the new router method-aware functionality.


26-26: Added spanner router package for benchmarking

The addition of the spanner package as a development dependency is appropriate for the benchmark classes that were introduced to compare routing performance.

test/router/router_test.dart (2)

26-27: Router API updated to support HTTP method awareness

The existing test suite has been properly updated to use the new method-aware API, consistently replacing router.add(path, value) with router.get(path, value) and router.lookup(path) with router.lookup(Method.get, path). The test logic remains the same, ensuring backward compatibility while adapting to the new HTTP method-aware routing.

Also applies to: 33-34, 40-41, 47-48, 55-56, 63-64, 82-83, 89-90, 96-97, 114-115, 121-122, 140-141, 147-148, 168-169, 175-176, 196-197, 203-204, 230-231, 244-245, 268-269, 285-286, 292-293, 300-301, 319-320, 326-327, 354-355, 361-362, 368-369, 389-390, 396-397, 418-419, 425-426, 446-447, 453-454, 476-477, 483-484, 500-501, 507-508, 523-524, 530-531, 546-547, 562-563, 583-605, 627-628, 634-636, 639-641, 645-647, 651-652, 668-670


572-578: Consistent HTTP method awareness in tests with various path patterns

The updated test case maintains proper coverage for different path variations (with/without leading/trailing slashes) and dynamic routes, ensuring the method-aware routing works correctly with all path configurations.

lib/src/router/path_trie.dart (3)

12-12: Simplified LookupResult constructor

The isDynamic parameter has been removed from the LookupResult constructor, streamlining the implementation. This simplification is appropriate as the dynamic route indication is no longer necessary or tracked in the returned lookup results.


83-107: Added new method for in-place updates to the PathTrie

The new addOrUpdateInPlace method is a well-designed addition that facilitates updating trie node values based on their current state. This is essential for implementing HTTP method-aware routing where each node might store multiple handlers (one per HTTP method).

The implementation properly reuses the existing _build method to ensure parameter conflict checks are preserved, and the documentation clearly explains the method's purpose and behavior.


275-275: Updated lookup method return statement

The return statement has been correctly updated to match the simplified LookupResult constructor, no longer including the isDynamic flag.

test/router/router_methods_test.dart (5)

1-53: Well-structured method-specific routing tests

This comprehensive test suite for HTTP method-specific routing is well-designed. The use of parameterized testing elegantly verifies that all HTTP methods (GET, POST, PUT, etc.) work correctly with the router, avoiding code duplication while ensuring thorough coverage.


63-96: Thorough testing of the 'any' method registration

The tests for the router.any() method thoroughly verify that a handler registered with this method is correctly returned for all HTTP methods. The separate test that explicitly checks all methods in a loop provides complete coverage of this important functionality.


98-164: Comprehensive conflict detection tests

The conflict detection tests are thorough and verify multiple important scenarios:

  1. Conflicts between same-method registrations (GET + GET)
  2. Conflicts using different registration approaches (router.get() vs router.add())
  3. Conflicts between specific methods and any() registrations
  4. Absence of conflicts between different paths with different methods

These tests ensure that route registration conflicts are properly detected and reported.


166-226: Method handling with parameterized paths

These tests effectively verify that method-specific routing works correctly with parameterized paths, ensuring that:

  1. Parameters are correctly extracted for specific HTTP methods
  2. Different methods can be registered for the same parameterized path
  3. Lookups with non-matching methods return null
  4. The any() method works with parameterized paths

This is a critical aspect of the router functionality and is well-tested.


228-254: Edge case testing for unregistered paths/methods

The tests properly verify the behavior of the router when looking up unregistered paths or methods:

  1. Empty router returns null for any path/method
  2. Lookup of non-existent path returns null
  3. Lookup with wrong method returns null

These tests ensure the router behaves correctly in edge cases and doesn't return incorrect handlers.

lib/src/router/router.dart (1)

167-170: any() may throw unexpectedly when called repeatedly

any() iterates over Method.values and calls add() for each.
If the same path is later registered again (e.g., another module re-exports routes), add() will throw, but the exception originates deep inside any(). Consider either:

  • Documenting that any() must be called only once per (path, value) pair, or
  • Adding a fast-fail check (e.g., containsRoute) before the loop.

This will help users avoid surprising runtime errors.

@nielsenko nielsenko requested a review from SandPod May 13, 2025 07:29
@nielsenko nielsenko self-assigned this May 13, 2025
@nielsenko nielsenko marked this pull request as ready for review May 13, 2025 07:29
Copy link
Contributor

@SandPod SandPod left a comment

Choose a reason for hiding this comment

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

Changes look good!

Relative performance is looking good as well!

@nielsenko nielsenko merged commit 200a5b2 into main May 14, 2025
24 checks passed
@nielsenko nielsenko deleted the router-verbs branch May 14, 2025 09:27
@coderabbitai coderabbitai bot mentioned this pull request May 14, 2025
8 tasks
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.

Support for defining routes with HTTP methods (e.g., GET, POST, PUT, DELETE).

3 participants