Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: medz/routingkit
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v5.0.1
Choose a base ref
...
head repository: medz/routingkit
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v5.1.0
Choose a head ref
  • 3 commits
  • 6 files changed
  • 1 contributor

Commits on Mar 3, 2025

  1. Refactor Router to use interface and private implementation

    - Introduce Router interface in types.dart
    - Create _RouterImpl as private implementation of Router
    - Update library exports to expose only public API
    - Modify createRouter to return _RouterImpl
    - Add @OverRide annotations to implementation methods
    medz committed Mar 3, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    e08ddd0 View commit details
  2. Make anyMethodToken parameter required in Router constructor

    - Update _RouterImpl constructor to require anyMethodToken
    - Remove default value for anyMethodToken parameter
    - Ensure explicit token configuration for route matching
    medz committed Mar 3, 2025
    Copy the full SHA
    4e03532 View commit details
  3. Copy the full SHA
    0c3e178 View commit details
Showing with 136 additions and 9 deletions.
  1. +8 −0 CHANGELOG.md
  2. +4 −3 lib/routingkit.dart
  3. +37 −5 lib/src/router.dart
  4. +47 −0 lib/src/types.dart
  5. +1 −1 pubspec.yaml
  6. +39 −0 test/router_test.dart
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 5.1.0

### New Features

- Added `caseSensitive` parameter to `createRouter` function to configure case sensitivity for path matching (defaults to `true`)
- Added `caseSensitive` property to the `Router` interface to indicate the router's case sensitivity setting
- Parameter names (like `:ID`) maintain their original case even in case-insensitive mode

## 5.0.1

### Bug Fixes
7 changes: 4 additions & 3 deletions lib/routingkit.dart
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@
/// - Type-safe route handlers
///
/// Key components:
/// - [Router]: The main router class for managing routes
/// - [Router]: The router interface for managing routes
/// - [createRouter]: Creates a new router instance
///
/// Example usage:
@@ -23,5 +23,6 @@
/// ![Pub version](https://img.shields.io/pub/v/routingkit?logo=dart)
library routingkit;

export 'src/router.dart';
export 'src/types.dart' show MatchedRoute;
// Export the public API only
export 'src/router.dart' show createRouter;
export 'src/types.dart' show MatchedRoute, Router;
42 changes: 37 additions & 5 deletions lib/src/router.dart
Original file line number Diff line number Diff line change
@@ -4,19 +4,35 @@ import 'types.dart';
///
/// Generic type [T] represents the data type associated with routes
/// [anyMethodToken] is the token used to represent any HTTP method, defaults to 'routerkit-method://any'
Router<T> createRouter<T>({String anyMethodToken = 'routerkit-method://any'}) =>
Router<T>(anyMethodToken: anyMethodToken);
/// [caseSensitive] determines whether path matching is case sensitive, defaults to true
Router<T> createRouter<T>({
String anyMethodToken = 'routerkit-method://any',
bool caseSensitive = true,
}) =>
_RouterImpl<T>(
anyMethodToken: anyMethodToken,
caseSensitive: caseSensitive,
);

/// Router class, provides route management and matching functionality
class Router<T> {
/// Internal implementation of Router interface
class _RouterImpl<T> implements Router<T> {
/// Creates a new router instance
///
/// [anyMethodToken] is the token used to represent any HTTP method
Router({this.anyMethodToken = 'routerkit-method://any'});
/// [caseSensitive] determines whether path matching is case sensitive
_RouterImpl({
this.anyMethodToken = 'routerkit-method://any',
this.caseSensitive = true,
});

/// Token used to represent any HTTP method
@override
final String anyMethodToken;

/// Whether path matching is case sensitive
@override
final bool caseSensitive;

/// Root node
final _root = _RouterNode<T>('');

@@ -28,6 +44,7 @@ class Router<T> {
/// [method] HTTP method like 'GET', 'POST', etc. If null, matches any method
/// [path] Route path pattern
/// [data] Data associated with this route
@override
void add(String? method, String path, T data) {
// Normalize HTTP method to uppercase
final normalizedMethod = method?.toUpperCase();
@@ -155,6 +172,7 @@ class Router<T> {
/// [includeParams] Whether to include matched parameters in the result, defaults to true
///
/// Returns [MatchedRoute<T>] if a match is found, null otherwise
@override
MatchedRoute<T>? find(String? method, String path,
{bool includeParams = true}) {
// Normalize HTTP method to uppercase
@@ -207,6 +225,7 @@ class Router<T> {
/// [includeParams] Whether to include matched parameters in the result, defaults to true
///
/// Returns a list of all matching routes
@override
List<MatchedRoute<T>> findAll(String? method, String path,
{bool includeParams = true}) {
// Normalize HTTP method to uppercase
@@ -276,6 +295,7 @@ class Router<T> {
/// [data] Optional, if provided, only removes the route if the route data matches
///
/// Returns whether a route was removed
@override
bool remove(String? method, String path, [T? data]) {
// Normalize HTTP method to uppercase
final normalizedMethod = method?.toUpperCase();
@@ -587,6 +607,17 @@ class Router<T> {
// Split path into segments and remove empty segments
final segments = cleanPath.split('/').where((s) => s.isNotEmpty).toList();

// Apply case insensitivity if needed, but preserve parameter names
if (!caseSensitive) {
for (int i = 0; i < segments.length; i++) {
final segment = segments[i];
// Only convert non-parameter segments to lowercase
if (!segment.startsWith(':') && !segment.startsWith('*')) {
segments[i] = segment.toLowerCase();
}
}
}

// Process segments for special format parameters like ':filename.:format?'
for (int i = 0; i < segments.length; i++) {
final segment = segments[i];
@@ -630,6 +661,7 @@ class Router<T> {
// Extract just the parameter name for pattern creation
return segment.split(' with ')[0].substring(1);
}
// Return the original parameter name without ':' prefix, preserving case
return segment.substring(1);
}

47 changes: 47 additions & 0 deletions lib/src/types.dart
Original file line number Diff line number Diff line change
@@ -20,3 +20,50 @@ class MatchedRoute<T> {
@override
String toString() => 'MatchedRoute(data: $data, params: $params)';
}

/// Router interface for route management and matching
///
/// Generic type [T] represents the data type associated with routes
abstract class Router<T> {
/// Token used to represent any HTTP method
String get anyMethodToken;

/// Whether path matching is case sensitive
bool get caseSensitive;

/// Adds a new route to the router
///
/// [method] HTTP method like 'GET', 'POST', etc. If null, matches any method
/// [path] Route path pattern
/// [data] Data associated with this route
void add(String? method, String path, T data);

/// Find the first route matching the given path and method
///
/// [method] HTTP method to match. If null, matches any method
/// [path] Path to match
/// [includeParams] Whether to include matched parameters in the result, defaults to true
///
/// Returns [MatchedRoute<T>] if a match is found, null otherwise
MatchedRoute<T>? find(String? method, String path,
{bool includeParams = true});

/// Find all routes matching the given path and method
///
/// [method] HTTP method to match. If null, matches any method
/// [path] Path to match
/// [includeParams] Whether to include matched parameters in the result, defaults to true
///
/// Returns a list of all matching routes
List<MatchedRoute<T>> findAll(String? method, String path,
{bool includeParams = true});

/// Remove a route from the router
///
/// [method] HTTP method to remove. If null, matches any method
/// [path] Route path to remove
/// [data] Optional, if provided, only removes the route if the route data matches
///
/// Returns whether a route was removed
bool remove(String? method, String path, [T? data]);
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ description: >-
Routing Kit
- Lightweight and fast router.
- A composable pure function routing kit.
version: 5.0.1
version: 5.1.0
repository: https://github.com/medz/routingkit

funding:
39 changes: 39 additions & 0 deletions test/router_test.dart
Original file line number Diff line number Diff line change
@@ -156,4 +156,43 @@ void main() {
equals('any-api')); // Any method should match
});
});

group('Case Sensitivity', () {
test('Case sensitive matching (default)', () {
final router = createRouter<String>();

router.add('GET', '/Users', 'users-uppercase');
router.add('GET', '/users', 'users-lowercase');

expect(router.find('GET', '/Users')?.data, equals('users-uppercase'));
expect(router.find('GET', '/users')?.data, equals('users-lowercase'));
expect(router.find('GET', '/USERS'), isNull);
});

test('Case insensitive matching', () {
final router = createRouter<String>(caseSensitive: false);

router.add('GET', '/Users', 'users-route');

// All these should match the same route regardless of case
expect(router.find('GET', '/Users')?.data, equals('users-route'));
expect(router.find('GET', '/users')?.data, equals('users-route'));
expect(router.find('GET', '/USERS')?.data, equals('users-route'));
expect(router.find('GET', '/uSeRs')?.data, equals('users-route'));
});

test('Case insensitive with parameters', () {
final router = createRouter<String>(caseSensitive: false);

router.add('GET', '/Users/:ID', 'user-detail');

final result1 = router.find('GET', '/users/123');
expect(result1?.data, equals('user-detail'));
expect(result1?.params, equals({'ID': '123'}));

final result2 = router.find('GET', '/USERS/456');
expect(result2?.data, equals('user-detail'));
expect(result2?.params, equals({'ID': '456'}));
});
});
}