Skip to content

Conversation

@nielsenko
Copy link
Collaborator

@nielsenko nielsenko commented May 15, 2025

Description

This change introduces support for wildcard (*) and tail (**) path segments in the PathTrie for more flexible route matching, along with related improvements to NormalizedPath and Router.

Key Changes:

  • PathTrie Enhancements:

    • Introduces internal support for new dynamic segment types: _Wildcard<T> and _Tail<T>, alongside the existing _Parameter<T>.
    • LookupResult now includes matched (the part of the path that matched up to and including dynamic segments) and remaining (the part of the path consumed by a ** tail match).
    • The _build method now handles the creation of nodes for * and ** segments. It includes validation to ensure:
      • * and ** are standalone segments (e.g., *foo or /**bar are invalid).
      • ** (tail segment) must be the last segment in a path definition.
      • Conflicting dynamic segment types cannot be defined at the same level in the trie.
    • The lookup method is updated to:
      • Recognize and match * and ** segments.
      • Correctly populate parameters, matched path, and remaining path in the LookupResult.
      • Handle tail matches that consume zero remaining segments (e.g., route /archive/** matching lookup path /archive).
  • NormalizedPath Improvements:

    • Added @immutable annotation.
    • Introduced NormalizedPath.empty static instance for an empty path.
    • Optimized subPath method to return NormalizedPath.empty for zero-length subpaths or this if the subpath covers the entire original path, improving efficiency by leveraging immutability.
  • Router Update:

    • The lookup method in Router has been updated to correctly construct LookupResult with the new matched and remaining path fields from the PathTrie's lookup result.
  • Testing:

    • Adds new test files (path_trie_tail_test.dart, path_trie_wildcard_test.dart)

Related Issues

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): 10554.986013986014 us.
Spanner Add Static x10000(RunTime): 14277.356643356643 us.
Router Add Static x10000(RunTime): 7479.325373134328 us.
---
Static Lookup
Routingkit Lookup Static x10000(RunTime): 2231.669 us.
Spanner Lookup Static x10000(RunTime): 1741.7113943028485 us.
Router Lookup Static x10000(RunTime): 1523.3925 us.
---
Dynamic Add
Routingkit Add Dynamic x10000(RunTime): 14911.438461538462 us.
Spanner Add Dynamic x10000(RunTime): 59344.606060606064 us.
Router Add Dynamic x10000(RunTime): 10461.368644067798 us.
---
Dynamic Lookup
Routingkit Lookup Dynamic x10000(RunTime): 19026.396226415094 us.
Spanner Lookup Dynamic x10000(RunTime): 5818.2692307692305 us.
Router Lookup Dynamic x10000(RunTime): 3777.58 us.
Done

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.

Summary by CodeRabbit

  • New Features

    • Enhanced routing to support wildcard (*) and tail wildcard (**) path segments for more flexible route matching.
    • Lookup results now provide detailed information about matched and remaining path segments.
    • Optimized path handling with immutable paths and reuse of empty path instances.
  • Bug Fixes

    • Improved error handling and validation for invalid or conflicting route definitions.
  • Tests

    • Added comprehensive tests for wildcard and tail wildcard matching, including precedence, parameter extraction, and error scenarios.
  • Chores

    • Updated the meta package dependency for improved compatibility and stability.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 15, 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

This change introduces support for wildcard (*) and tail (**) segments in router paths, enhances the LookupResult to include matched and remaining path segments, and adds robust error handling for invalid path patterns. It also updates the NormalizedPath class for immutability and optimizes subpath extraction. Comprehensive tests validate wildcard and tail matching behaviors.

Changes

File(s) Change Summary
lib/src/router/normalized_path.dart Added @immutable to NormalizedPath, introduced static empty instance, and optimized subPath to reuse instances for empty/full subpaths.
lib/src/router/path_trie.dart Refactored to support wildcard (*) and tail (**) segments via a sealed class hierarchy; enhanced error handling; updated LookupResult to include matched and remaining paths; improved dynamic segment handling and precedence.
lib/src/router/router.dart Updated lookup to return enriched LookupResult with matched and remaining paths for both static and dynamic routes.
pubspec.yaml Moved and updated meta dependency from dev to main dependencies with a newer version.
test/router/path_trie_crud_test.dart Added tests for CRUD operations involving wildcard and tail segments, ensuring correct update and removal behaviors.
test/router/path_trie_tail_test.dart New test suite covering tail wildcard (**) matching, precedence, parameter extraction, and validation rules.
test/router/path_trie_wildcard_test.dart New test suite for wildcard (*) segment matching, precedence, parameter extraction, and error handling.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Router
    participant PathTrie
    participant LookupResult

    Client->>Router: lookup(method, path)
    Router->>PathTrie: lookup(normalizedPath)
    PathTrie->>PathTrie: Traverse segments (literal, parameter, *, **)
    alt Tail segment (**)
        PathTrie->>LookupResult: Return matched, parameters, remaining
    else Wildcard or Parameter
        PathTrie->>LookupResult: Return matched, parameters, remaining
    end
    Router->>Client: Return LookupResult (value, params, matched, remaining)
Loading

Assessment against linked issues

Objective Addressed Explanation
Support wildcards in router paths and ensure lookup results indicate the exact path matched (#72)
Support tail (**) segments in router paths and include remaining unmatched path in lookup (#73)

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 15, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 81.32%. Comparing base (dc97d4b) to head (9245538).
Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #70      +/-   ##
==========================================
+ Coverage   80.75%   81.32%   +0.56%     
==========================================
  Files          79       79              
  Lines        2370     2426      +56     
  Branches     1341     1361      +20     
==========================================
+ Hits         1914     1973      +59     
+ Misses        456      453       -3     

☔ 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 nielsenko changed the base branch from main to router-middleware May 15, 2025 16:01
@nielsenko nielsenko force-pushed the path-trie-wildcard-and-tail branch from 3da4a8f to 278b651 Compare May 15, 2025 16:18
@nielsenko nielsenko self-assigned this May 15, 2025
@nielsenko nielsenko mentioned this pull request May 16, 2025
8 tasks
@nielsenko nielsenko force-pushed the router-middleware branch 3 times, most recently from 6bc75ec to ad9760f Compare May 16, 2025 11:34
Base automatically changed from router-middleware to main May 16, 2025 11:38
@nielsenko nielsenko force-pushed the path-trie-wildcard-and-tail branch from 6d2b796 to 924254f Compare May 16, 2025 11:43
@nielsenko nielsenko force-pushed the path-trie-wildcard-and-tail branch from 924254f to d7fe6cc Compare May 16, 2025 11:45
@nielsenko
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 16, 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: 0

🧹 Nitpick comments (3)
lib/src/router/path_trie.dart (3)

16-20: Doc-comment refers to obsolete “/::” tail syntax

The comment still mentions /:: and /::name, which were replaced by ** tail segments in this PR. This can confuse future readers.

-  /// This can only happen with path that ends with a tail-match /:: or /::name,
+  /// This can only happen with a path that ends with a tail segment `/**`,

223-229: Interpolating the generic type literal is not helpful

"$U" will interpolate to the (erased) type name which is the same for every conflicting segment case and does not add actionable information. Re-word the message to explicitly reference the requested segment kind instead.

-            'New: "$U"');
+            'New: $U'); // or hard-code the literal `"wildcard" / "tail" / "parameter"`

265-272: Double-throw makes the code harder to read

raiseInvalidSegment already throws, so wrapping it into an additional throw is redundant and slightly obscures intent.

-          throw normalizedPath.raiseInvalidSegment(
+          normalizedPath.raiseInvalidSegment(
             i,
             'Conflicting parameter names at the same level: '
             'Existing: ":${parameter.name}", '
             'New: ":$paramName"',
           );
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between dc97d4b and 65f9e5f.

📒 Files selected for processing (7)
  • lib/src/router/normalized_path.dart (4 hunks)
  • lib/src/router/path_trie.dart (9 hunks)
  • lib/src/router/router.dart (2 hunks)
  • pubspec.yaml (1 hunks)
  • test/router/path_trie_crud_test.dart (2 hunks)
  • test/router/path_trie_tail_test.dart (1 hunks)
  • test/router/path_trie_wildcard_test.dart (1 hunks)
🔇 Additional comments (17)
pubspec.yaml (1)

14-14: Appropriate dependency addition.

Moving the meta package from dev_dependencies to dependencies is necessary to support the @immutable annotation used in the NormalizedPath class. The version constraint ^1.16.0 is appropriate.

test/router/path_trie_crud_test.dart (8)

179-196: Great test coverage for wildcard path updates.

This test thoroughly verifies that updating an existing wildcard path works correctly, with proper assertions checking the value, parameters, matched path, and remaining segments.


198-205: Good error case testing for wildcard paths.

This test appropriately verifies that attempting to update a non-existent wildcard path throws an ArgumentError and doesn't inadvertently create the path.


207-223: Comprehensive tail path update test.

This test thoroughly validates updating tail path values and verifies the matched/remaining path segments are correctly populated. The assertions for the remaining path segments are particularly valuable.


225-232: Good error handling test for tail paths.

This test ensures that attempts to update non-existent tail paths fail appropriately with ArgumentError.


359-375: Well-structured wildcard path removal test.

This test properly verifies that wildcard paths can be removed while sibling paths remain unaffected, validating both the removed value and subsequent lookup behavior.


377-389: Good negative test for wildcard path removal.

This test confirms that attempting to remove a non-existent wildcard path returns null and doesn't affect other paths.


391-411: Thorough tail path removal test.

This test effectively verifies tail path removal behavior while checking that more specific child paths remain accessible, which is important for maintaining route hierarchy.


413-423: Appropriate negative test for tail path removal.

This test ensures that attempting to remove a non-existent tail path safely returns null without side effects on other paths.

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

99-106: Enhanced lookup result for static routes.

Good enhancement to the lookup method to return a more detailed LookupResult for static cache hits. Using the full normalized path as the matched path and NormalizedPath.empty for the remaining path makes sense for static routes.


120-125: Updated trie lookup result handling.

The lookup result now properly includes the matched and remaining path information from the trie's lookup, providing consistent behavior between static and dynamic route resolutions.

lib/src/router/normalized_path.dart (4)

1-2: Appropriate package import.

Adding the import for package:meta/meta.dart supports the @immutable annotation used below.


14-14: Good use of immutability annotation.

The @immutable annotation explicitly indicates that this class should not be mutated after construction, which helps prevent bugs and makes the code's intent clearer.


26-28: Efficient empty path singleton.

Creating a static constant for an empty path is a good optimization that avoids unnecessary object creation when empty paths are needed.


69-76: Optimized subpath extraction.

The improvements to the subPath method are excellent:

  1. Handling optional end parameter
  2. Returning the empty path singleton for zero-length subpaths
  3. Returning the original instance for full-path subpaths

These optimizations reduce object creation and improve performance for router operations.

test/router/path_trie_wildcard_test.dart (1)

13-24: Great set of positive-path assertions

The test verifies value, parameters, matched & remaining paths – exactly the fields most likely to regress. Nice work!

test/router/path_trie_tail_test.dart (1)

79-99: Solid precedence coverage for / vs /**

These cases often trip implementations up; covering them here will prevent subtle routing bugs. 👍

@nielsenko
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 16, 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: 0

🧹 Nitpick comments (1)
lib/src/router/path_trie.dart (1)

56-60: Update class documentation to reflect new capabilities.

The class documentation for PathTrie only mentions "literal segments and parameterized segments" but now also supports wildcard and tail segments.

/// A Trie (prefix tree) data structure optimized for matching URL paths.
///
-/// Supports literal segments and parameterized segments (e.g., `:id`). Allows
+/// Supports literal segments, parameterized segments (e.g., `:id`), wildcard segments (`*`),
+/// and tail segments (`**`). Allows
/// associating a value of type [T] with each complete 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 65f9e5f and 08161e9.

📒 Files selected for processing (1)
  • lib/src/router/path_trie.dart (9 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Run Unit Tests (macos-latest, beta, upgrade)
  • GitHub Check: Run Unit Tests (macos-latest, 3.3.0, upgrade)
  • GitHub Check: Run Unit Tests (macos-latest, 3.3.0, downgrade)
🔇 Additional comments (16)
lib/src/router/path_trie.dart (16)

3-4: Good addition of a clear type alias.

The Parameters type alias enhances code readability and makes the contract more explicit.


11-20: Well-documented new fields for enhancing route lookup capabilities.

The addition of matched and remaining fields with clear documentation improves the API by providing clients with information about what part of the path was matched and what remains unmatched after a tail segment.


23-23: Constructor properly updated to support new fields.

The LookupResult constructor has been correctly updated to include the new matched and remaining fields.


32-32: Refactored to support multiple dynamic segment types.

Generalizing from _Parameter<T>? to _DynamicSegment<T>? enables the trie to handle various types of dynamic segments.


40-54: Well-designed sealed class hierarchy for dynamic segments.

Using a sealed class hierarchy for dynamic segments is an excellent choice, providing:

  1. Type safety through compile-time exhaustiveness checking
  2. Clear representation of different segment types
  3. Polymorphic behavior while maintaining strong typing

The implementation is clean and minimal.


174-182: Smart helper function for type-safe node resolution.

The nextIf helper function elegantly handles type checking and node retrieval in a concise, reusable way. The @pragma('vm:prefer-inline') annotation is a good performance optimization.


186-200: Clear prioritization of segment matching types.

The segment matching logic maintains a clear priority order (literal segments first, then dynamic segments) while properly handling the new wildcard and tail segment types.


213-224: Effective validation helper for segment type conflicts.

The isA helper function provides a clean way to validate segment type consistency while generating precise error messages.


226-240: Robust validation for tail segments.

The implementation properly enforces that:

  1. Tail segments must be exactly ** (no variations)
  2. Tail segments must be the last segment in the path
  3. No conflicting segment types are allowed at the same level

These constraints help prevent ambiguous routing configurations.


241-248: Proper handling of wildcard segments.

The wildcard segment handling ensures wildcards are exactly * (no variations) and prevents conflicting segment types at the same level.


249-270: Refactored parameter segment handling.

The parameter segment handling has been updated to work with the new class hierarchy while maintaining the existing validation for empty parameter names and conflicting parameter names.


303-305: Updated conflict check in attach.

The conflict check in attach has been properly updated to use the generalized dynamicSegment.


315-315: Updated field assignment in attach.

The field assignment in attach has been correctly updated to use the generalized dynamicSegment.


332-347: Enhanced lookup with pattern matching and early termination.

The lookup method now:

  1. Uses Dart's pattern matching for cleaner parameter handling
  2. Supports early termination on tail matches
  3. Properly tracks the segment index for constructing matched and remaining paths

This enables the new wildcard and tail matching capabilities.


350-367: Smart handling of tail segments at path end.

The additional logic for handling tail segments at the end of a path is important for cases like /archive/** matching /archive (where the remaining path would be empty).


371-376: Excellent extension method for precise error messages.

The raiseInvalidSegment extension method on NormalizedPath provides more informative error messages by including the specific segment that caused the error.

@nielsenko nielsenko force-pushed the path-trie-wildcard-and-tail branch from 08161e9 to 87420ac Compare May 16, 2025 13:04
@nielsenko nielsenko marked this pull request as ready for review May 16, 2025 13:05
@nielsenko nielsenko requested review from SandPod and tp May 16, 2025 13:05
@nielsenko
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 16, 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

@tp tp left a comment

Choose a reason for hiding this comment

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

👏 Nice work!

Just a few questions for my understanding really 😀

Co-authored-by: Timm Preetz <52437+tp@users.noreply.github.com>
@nielsenko nielsenko force-pushed the path-trie-wildcard-and-tail branch from da468b6 to e253ee5 Compare May 16, 2025 17:39
nielsenko added 2 commits May 19, 2025 09:53
List.unmodifiable is implemented in the runtime.
I doubt it actually wraps the list object passed.
I would expect a bigger cost on the benchmarks if it did.
@nielsenko
Copy link
Collaborator Author

Resolved all comments to merge

@nielsenko nielsenko merged commit e014e89 into main May 19, 2025
24 checks passed
@nielsenko nielsenko deleted the path-trie-wildcard-and-tail branch May 19, 2025 09:32
@coderabbitai coderabbitai bot mentioned this pull request May 19, 2025
9 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.

Route over tail matches Route over wildcards

3 participants