13 KiB
13 KiB
Route Package Specification
Overview
The Route package provides a robust routing system that matches Laravel's routing functionality. It supports route registration, middleware, parameter binding, and route groups while integrating with our Pipeline and Container packages.
Related Documentation
- See Laravel Compatibility Roadmap for implementation status
- See Foundation Integration Guide for integration patterns
- See Testing Guide for testing approaches
- See Getting Started Guide for development setup
- See Pipeline Package Specification for middleware pipeline
- See Container Package Specification for dependency injection
Core Features
1. Router
/// Core router implementation
class Router implements RouterContract {
/// Container instance
final Container _container;
/// Route collection
final RouteCollection _routes;
/// Current route
Route? _current;
/// Global middleware
final List<dynamic> _middleware = [];
Router(this._container)
: _routes = RouteCollection();
/// Gets current route
Route? get current => _current;
/// Gets global middleware
List<dynamic> get middleware => List.from(_middleware);
/// Adds global middleware
void pushMiddleware(dynamic middleware) {
_middleware.add(middleware);
}
/// Registers GET route
Route get(String uri, dynamic action) {
return addRoute(['GET', 'HEAD'], uri, action);
}
/// Registers POST route
Route post(String uri, dynamic action) {
return addRoute(['POST'], uri, action);
}
/// Registers PUT route
Route put(String uri, dynamic action) {
return addRoute(['PUT'], uri, action);
}
/// Registers DELETE route
Route delete(String uri, dynamic action) {
return addRoute(['DELETE'], uri, action);
}
/// Registers PATCH route
Route patch(String uri, dynamic action) {
return addRoute(['PATCH'], uri, action);
}
/// Registers OPTIONS route
Route options(String uri, dynamic action) {
return addRoute(['OPTIONS'], uri, action);
}
/// Adds route to collection
Route addRoute(List<String> methods, String uri, dynamic action) {
var route = Route(methods, uri, action);
_routes.add(route);
return route;
}
/// Creates route group
void group(Map<String, dynamic> attributes, Function callback) {
var group = RouteGroup(attributes);
_routes.pushGroup(group);
callback();
_routes.popGroup();
}
/// Matches request to route
Route? match(Request request) {
_current = _routes.match(request);
return _current;
}
/// Dispatches request to route
Future<Response> dispatch(Request request) async {
var route = match(request);
if (route == null) {
throw RouteNotFoundException();
}
return await _runRoute(route, request);
}
/// Runs route through middleware
Future<Response> _runRoute(Route route, Request request) async {
var pipeline = _container.make<MiddlewarePipeline>();
return await pipeline
.send(request)
.through([
..._middleware,
...route.gatherMiddleware()
])
.then((request) => route.run(request));
}
}
2. Route Collection
/// Route collection
class RouteCollection {
/// Routes by method
final Map<String, List<Route>> _routes = {};
/// Route groups
final List<RouteGroup> _groups = [];
/// Adds route to collection
void add(Route route) {
for (var method in route.methods) {
_routes.putIfAbsent(method, () => []).add(route);
}
if (_groups.isNotEmpty) {
route.group = _groups.last;
}
}
/// Pushes route group
void pushGroup(RouteGroup group) {
if (_groups.isNotEmpty) {
group.parent = _groups.last;
}
_groups.add(group);
}
/// Pops route group
void popGroup() {
_groups.removeLast();
}
/// Matches request to route
Route? match(Request request) {
var routes = _routes[request.method] ?? [];
for (var route in routes) {
if (route.matches(request)) {
return route;
}
}
return null;
}
}
3. Route
/// Route definition
class Route {
/// HTTP methods
final List<String> methods;
/// URI pattern
final String uri;
/// Route action
final dynamic action;
/// Route group
RouteGroup? group;
/// Route middleware
final List<dynamic> _middleware = [];
/// Route parameters
final Map<String, dynamic> _parameters = {};
Route(this.methods, this.uri, this.action);
/// Adds middleware
Route middleware(List<dynamic> middleware) {
_middleware.addAll(middleware);
return this;
}
/// Gets route name
String? get name => _parameters['as'];
/// Sets route name
Route name(String name) {
_parameters['as'] = name;
return this;
}
/// Gets route domain
String? get domain => _parameters['domain'];
/// Sets route domain
Route domain(String domain) {
_parameters['domain'] = domain;
return this;
}
/// Gets route prefix
String get prefix {
var prefix = '';
var group = this.group;
while (group != null) {
if (group.prefix != null) {
prefix = '${group.prefix}/$prefix';
}
group = group.parent;
}
return prefix.isEmpty ? '' : prefix;
}
/// Gets full URI
String get fullUri => '${prefix.isEmpty ? "" : "$prefix/"}$uri';
/// Gathers middleware
List<dynamic> gatherMiddleware() {
var middleware = [..._middleware];
var group = this.group;
while (group != null) {
middleware.addAll(group.middleware);
group = group.parent;
}
return middleware;
}
/// Matches request
bool matches(Request request) {
return _matchesMethod(request.method) &&
_matchesUri(request.uri) &&
_matchesDomain(request.host);
}
/// Matches HTTP method
bool _matchesMethod(String method) {
return methods.contains(method);
}
/// Matches URI pattern
bool _matchesUri(Uri uri) {
var pattern = RegExp(_compilePattern());
return pattern.hasMatch(uri.path);
}
/// Matches domain pattern
bool _matchesDomain(String? host) {
if (domain == null) return true;
if (host == null) return false;
var pattern = RegExp(_compileDomainPattern());
return pattern.hasMatch(host);
}
/// Compiles URI pattern
String _compilePattern() {
return fullUri
.replaceAll('/', '\\/')
.replaceAllMapped(
RegExp(r'{([^}]+)}'),
(match) => '(?<${match[1]}>[^/]+)'
);
}
/// Compiles domain pattern
String _compileDomainPattern() {
return domain!
.replaceAll('.', '\\.')
.replaceAllMapped(
RegExp(r'{([^}]+)}'),
(match) => '(?<${match[1]}>[^.]+)'
);
}
/// Runs route action
Future<Response> run(Request request) async {
var action = _resolveAction();
var parameters = _resolveParameters(request);
if (action is Function) {
return await Function.apply(action, parameters);
}
if (action is Controller) {
return await action.callAction(
action.runtimeType.toString(),
parameters
);
}
throw RouteActionNotFoundException();
}
/// Resolves route action
dynamic _resolveAction() {
if (action is String) {
var parts = action.split('@');
var controller = _container.make(parts[0]);
controller.method = parts[1];
return controller;
}
return action;
}
/// Resolves route parameters
List<dynamic> _resolveParameters(Request request) {
var pattern = RegExp(_compilePattern());
var match = pattern.firstMatch(request.uri.path);
if (match == null) return [];
return match.groupNames.map((name) {
return _resolveParameter(name, match.namedGroup(name)!);
}).toList();
}
/// Resolves route parameter
dynamic _resolveParameter(String name, String value) {
if (_parameters.containsKey(name)) {
return _parameters[name](value);
}
return value;
}
}
4. Route Groups
/// Route group
class RouteGroup {
/// Group attributes
final Map<String, dynamic> attributes;
/// Parent group
RouteGroup? parent;
RouteGroup(this.attributes);
/// Gets group prefix
String? get prefix => attributes['prefix'];
/// Gets group middleware
List<dynamic> get middleware => attributes['middleware'] ?? [];
/// Gets group domain
String? get domain => attributes['domain'];
/// Gets group name prefix
String? get namePrefix => attributes['as'];
/// Gets merged attributes
Map<String, dynamic> get mergedAttributes {
var merged = Map.from(attributes);
var parent = this.parent;
while (parent != null) {
for (var entry in parent.attributes.entries) {
if (!merged.containsKey(entry.key)) {
merged[entry.key] = entry.value;
}
}
parent = parent.parent;
}
return merged;
}
}
Integration Examples
1. Basic Routing
// Register routes
router.get('/', HomeController);
router.post('/users', UsersController);
router.get('/users/{id}', (String id) {
return User.find(id);
});
// Match and dispatch
var route = router.match(request);
var response = await router.dispatch(request);
2. Route Groups
router.group({
'prefix': 'api',
'middleware': ['auth'],
'namespace': 'Api'
}, () {
router.get('users', UsersController);
router.get('posts', PostsController);
router.group({
'prefix': 'admin',
'middleware': ['admin']
}, () {
router.get('stats', StatsController);
});
});
3. Route Parameters
// Required parameters
router.get('users/{id}', (String id) {
return User.find(id);
});
// Optional parameters
router.get('posts/{id?}', (String? id) {
return id != null ? Post.find(id) : Post.all();
});
// Regular expression constraints
router.get('users/{id}', UsersController)
.where('id', '[0-9]+');
Testing
void main() {
group('Router', () {
test('matches routes', () {
var router = Router(container);
router.get('/users/{id}', UsersController);
var request = Request('GET', '/users/1');
var route = router.match(request);
expect(route, isNotNull);
expect(route!.action, equals(UsersController));
});
test('handles route groups', () {
var router = Router(container);
router.group({
'prefix': 'api',
'middleware': ['auth']
}, () {
router.get('users', UsersController);
});
var route = router.match(Request('GET', '/api/users'));
expect(route, isNotNull);
expect(route!.gatherMiddleware(), contains('auth'));
});
});
}
Next Steps
- Implement core routing
- Add route groups
- Add route parameters
- Add middleware support
- Write tests
- Add benchmarks
Development Guidelines
1. Getting Started
Before implementing routing features:
- Review Getting Started Guide
- Check Laravel Compatibility Roadmap
- Follow Testing Guide
- Use Foundation Integration Guide
- Review Pipeline Package Specification
- Review Container Package Specification
2. Implementation Process
For each routing feature:
- Write tests following Testing Guide
- Implement following Laravel patterns
- Document following Getting Started Guide
- Integrate following Foundation Integration Guide
3. Quality Requirements
All implementations must:
- Pass all tests (see Testing Guide)
- Meet Laravel compatibility requirements
- Follow integration patterns (see Foundation Integration Guide)
- Support middleware (see Pipeline Package Specification)
- Support dependency injection (see Container Package Specification)
4. Integration Considerations
When implementing routing features:
- Follow patterns in Foundation Integration Guide
- Ensure Laravel compatibility per Laravel Compatibility Roadmap
- Use testing approaches from Testing Guide
- Follow development setup in Getting Started Guide
5. Performance Guidelines
Routing system must:
- Match routes efficiently
- Handle complex patterns
- Support caching
- Scale with route count
- Meet performance targets in Laravel Compatibility Roadmap
6. Testing Requirements
Route tests must:
- Cover all route types
- Test pattern matching
- Verify middleware
- Check parameter binding
- Follow patterns in Testing Guide
7. Documentation Requirements
Route documentation must:
- Explain routing patterns
- Show group examples
- Cover parameter binding
- Include performance tips
- Follow standards in Getting Started Guide