platform/doc/route_package_specification.md
2024-11-25 20:33:45 -07:00

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

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

  1. Implement core routing
  2. Add route groups
  3. Add route parameters
  4. Add middleware support
  5. Write tests
  6. Add benchmarks

Development Guidelines

1. Getting Started

Before implementing routing features:

  1. Review Getting Started Guide
  2. Check Laravel Compatibility Roadmap
  3. Follow Testing Guide
  4. Use Foundation Integration Guide
  5. Review Pipeline Package Specification
  6. Review Container Package Specification

2. Implementation Process

For each routing feature:

  1. Write tests following Testing Guide
  2. Implement following Laravel patterns
  3. Document following Getting Started Guide
  4. Integrate following Foundation Integration Guide

3. Quality Requirements

All implementations must:

  1. Pass all tests (see Testing Guide)
  2. Meet Laravel compatibility requirements
  3. Follow integration patterns (see Foundation Integration Guide)
  4. Support middleware (see Pipeline Package Specification)
  5. Support dependency injection (see Container Package Specification)

4. Integration Considerations

When implementing routing features:

  1. Follow patterns in Foundation Integration Guide
  2. Ensure Laravel compatibility per Laravel Compatibility Roadmap
  3. Use testing approaches from Testing Guide
  4. Follow development setup in Getting Started Guide

5. Performance Guidelines

Routing system must:

  1. Match routes efficiently
  2. Handle complex patterns
  3. Support caching
  4. Scale with route count
  5. Meet performance targets in Laravel Compatibility Roadmap

6. Testing Requirements

Route tests must:

  1. Cover all route types
  2. Test pattern matching
  3. Verify middleware
  4. Check parameter binding
  5. Follow patterns in Testing Guide

7. Documentation Requirements

Route documentation must:

  1. Explain routing patterns
  2. Show group examples
  3. Cover parameter binding
  4. Include performance tips
  5. Follow standards in Getting Started Guide