Explorar el Código

refactor(router)!: migrate examples to new Endpoint/Pipeline API

- Convert all examples from RouteHandler interface to Endpoint interface
- Update handlers to use new route and methods properties
- Replace Router with EndpointRouter and Pipeline pattern
- Change route parameter extraction from get_segment() to named_components dictionary
- Add wildcard route support ("*") in EndpointRouter
- Change Endpoint.methods type from Enumerable<Method> to Method[]
- Fix route matching logic in EndpointRouter
- Enable all example executables in meson.build

BREAKING CHANGE: Endpoint interface now requires Method[] instead of Enumerable<Method>. Route handlers must implement the new Endpoint interface with route and methods properties.
Billy Barrow hace 4 semanas
padre
commit
c36f08cdff

+ 40 - 24
examples/DataStructuresDemo.vala

@@ -11,8 +11,10 @@ using Invercargill.DataStructures;
  */
 
 // Root handler
-class RootHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class RootEndpoint : Object, Endpoint {
+    public string route { get { return "/"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         return new HttpStringResult("""Data Structures Demo
 
 This example demonstrates various Invercargill data structures:
@@ -29,8 +31,10 @@ Endpoints:
 }
 
 // Series operations
-class SeriesHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class SeriesEndpoint : Object, Endpoint {
+    public string route { get { return "/series"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var series_array = new string[3];
         series_array[0] = "First";
         series_array[1] = "Second";
@@ -54,8 +58,10 @@ class SeriesHandler : Object, RouteHandler {
 }
 
 // Vector operations
-class VectorHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class VectorEndpoint : Object, Endpoint {
+    public string route { get { return "/vector"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var numbers = new int[5];
         numbers[0] = 1;
         numbers[1] = 2;
@@ -80,8 +86,10 @@ class VectorHandler : Object, RouteHandler {
 }
 
 // RingBuffer operations
-class RingBufferHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class RingBufferEndpoint : Object, Endpoint {
+    public string route { get { return "/ring-buffer"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var buffer_array = new string[5];
         buffer_array[0] = "Item 1";
         buffer_array[1] = "Item 2";
@@ -103,8 +111,10 @@ class RingBufferHandler : Object, RouteHandler {
 }
 
 // ImmutableBuffer operations
-class ImmutableBufferHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class ImmutableBufferEndpoint : Object, Endpoint {
+    public string route { get { return "/immutable-buffer"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var items_array = new string[4];
         items_array[0] = "Apple";
         items_array[1] = "Banana";
@@ -132,8 +142,10 @@ class ImmutableBufferHandler : Object, RouteHandler {
 }
 
 // Wrap utility functions
-class WrapOperationsHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class WrapOperationsEndpoint : Object, Endpoint {
+    public string route { get { return "/wrap-operations"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var numbers = new int[5];
         numbers[0] = 1;
         numbers[1] = 2;
@@ -162,8 +174,10 @@ class WrapOperationsHandler : Object, RouteHandler {
 }
 
 // Combined operations
-class CombinedOperationsHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class CombinedOperationsEndpoint : Object, Endpoint {
+    public string route { get { return "/combined-operations"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var numbers1 = new int[3];
         numbers1[0] = 1;
         numbers1[1] = 2;
@@ -205,17 +219,19 @@ class CombinedOperationsHandler : Object, RouteHandler {
 }
 
 void main() {
-    var router = new Router();
-    var server = new Server(8086, router);
+    var router = new EndpointRouter()
+        .add_endpoint(new RootEndpoint())
+        .add_endpoint(new SeriesEndpoint())
+        .add_endpoint(new VectorEndpoint())
+        .add_endpoint(new RingBufferEndpoint())
+        .add_endpoint(new ImmutableBufferEndpoint())
+        .add_endpoint(new WrapOperationsEndpoint())
+        .add_endpoint(new CombinedOperationsEndpoint());
     
-    // Register routes
-    router.get("/", new RootHandler());
-    router.get("/series", new SeriesHandler());
-    router.get("/vector", new VectorHandler());
-    router.get("/ring-buffer", new RingBufferHandler());
-    router.get("/immutable-buffer", new ImmutableBufferHandler());
-    router.get("/wrap-operations", new WrapOperationsHandler());
-    router.get("/combined-operations", new CombinedOperationsHandler());
+    var pipeline = new Pipeline()
+        .add_component(router);
+
+    var server = new Server(8086, pipeline);
     
     print("Data Structures Demo Server running on port 8086\n");
     print("Try these endpoints:\n");

+ 45 - 27
examples/EnumerableOperations.vala

@@ -10,8 +10,10 @@ using Invercargill.DataStructures;
  */
 
 // Root handler
-class RootHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class RootEndpoint : Object, Endpoint {
+    public string route { get { return "/"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         return new HttpStringResult("""Enumerable Operations Demo
 
 This example demonstrates various LINQ-like operations available in Invercargill Enumerable:
@@ -29,8 +31,10 @@ Endpoints:
 }
 
 // Filtering examples
-class FilterHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class FilterEndpoint : Object, Endpoint {
+    public string route { get { return "/filter"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var numbers = Wrap.array<int>({1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
         
         var parts = new Series<string>();
@@ -74,8 +78,10 @@ class FilterHandler : Object, RouteHandler {
 }
 
 // Mapping/Projection examples
-class MapHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class MapEndpoint : Object, Endpoint {
+    public string route { get { return "/map"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var numbers = Wrap.array<int>({1, 2, 3, 4, 5});
         
         var parts = new Series<string>();
@@ -119,8 +125,10 @@ class MapHandler : Object, RouteHandler {
 }
 
 // Grouping examples
-class GroupByHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class GroupByEndpoint : Object, Endpoint {
+    public string route { get { return "/group-by"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var words = Wrap.array<string>({
             "apple", "banana", "cherry", "apricot", 
             "blueberry", "blackberry", "coconut"
@@ -150,8 +158,10 @@ class GroupByHandler : Object, RouteHandler {
 }
 
 // Aggregation examples
-class AggregateHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class AggregateEndpoint : Object, Endpoint {
+    public string route { get { return "/aggregate"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var numbers = Wrap.array<int>({1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
         
         var parts = new Series<string>();
@@ -196,8 +206,10 @@ class AggregateHandler : Object, RouteHandler {
 }
 
 // Sorting examples
-class SortHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class SortEndpoint : Object, Endpoint {
+    public string route { get { return "/sort"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var numbers = Wrap.array<int>({5, 2, 8, 1, 9, 3, 7, 4, 6, 10});
         
         var parts = new Series<string>();
@@ -226,8 +238,10 @@ class SortHandler : Object, RouteHandler {
 }
 
 // Set operations
-class SetOperationsHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class SetOperationsEndpoint : Object, Endpoint {
+    public string route { get { return "/set-operations"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var set1 = Wrap.array<int>({1, 2, 3, 4, 5});
         var set2 = Wrap.array<int>({4, 5, 6, 7, 8});
         
@@ -264,8 +278,10 @@ class SetOperationsHandler : Object, RouteHandler {
 }
 
 // Advanced operations
-class AdvancedHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class AdvancedEndpoint : Object, Endpoint {
+    public string route { get { return "/advanced"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var numbers = Wrap.array<int>({1, 2, 3, 4, 5});
         
         var parts = new Series<string>();
@@ -302,18 +318,20 @@ class AdvancedHandler : Object, RouteHandler {
 }
 
 void main() {
-    var router = new Router();
-    var server = new Server(8087, router);
+    var router = new EndpointRouter()
+        .add_endpoint(new RootEndpoint())
+        .add_endpoint(new FilterEndpoint())
+        .add_endpoint(new MapEndpoint())
+        .add_endpoint(new GroupByEndpoint())
+        .add_endpoint(new AggregateEndpoint())
+        .add_endpoint(new SortEndpoint())
+        .add_endpoint(new SetOperationsEndpoint())
+        .add_endpoint(new AdvancedEndpoint());
     
-    // Register routes
-    router.get("/", new RootHandler());
-    router.get("/filter", new FilterHandler());
-    router.get("/map", new MapHandler());
-    router.get("/group-by", new GroupByHandler());
-    router.get("/aggregate", new AggregateHandler());
-    router.get("/sort", new SortHandler());
-    router.get("/set-operations", new SetOperationsHandler());
-    router.get("/advanced", new AdvancedHandler());
+    var pipeline = new Pipeline()
+        .add_component(router);
+
+    var server = new Server(8087, pipeline);
     
     print("Enumerable Operations Demo Server running on port 8087\n");
     print("Try these endpoints:\n");

+ 72 - 44
examples/ErrorHandling.vala

@@ -10,8 +10,10 @@ using Invercargill.DataStructures;
  */
 
 // Root handler
-class RootHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class RootEndpoint : Object, Endpoint {
+    public string route { get { return "/"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         return new HttpStringResult("""Error Handling Demo
 
 This example demonstrates various error handling patterns in Astralis:
@@ -34,40 +36,50 @@ Endpoints:
 }
 
 // 200 OK response
-class OkHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class OkEndpoint : Object, Endpoint {
+    public string route { get { return "/ok"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         return new HttpStringResult(@"{ \"status\": \"success\", \"message\": \"Request completed successfully\" }")
             .set_header("Content-Type", "application/json");
     }
 }
 
 // 404 Not Found
-class NotFoundHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class NotFoundEndpoint : Object, Endpoint {
+    public string route { get { return "/not-found"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         return new HttpStringResult(@"{ \"error\": \"Not Found\", \"message\": \"The requested resource was not found\" }")
             .set_header("Content-Type", "application/json");
     }
 }
 
 // 400 Bad Request
-class BadRequestHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class BadRequestEndpoint : Object, Endpoint {
+    public string route { get { return "/bad-request"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         return new HttpStringResult(@"{ \"error\": \"Bad Request\", \"message\": \"The request could not be understood\" }")
             .set_header("Content-Type", "application/json");
     }
 }
 
 // 500 Internal Server Error
-class InternalErrorHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class InternalErrorEndpoint : Object, Endpoint {
+    public string route { get { return "/internal-error"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         return new HttpStringResult(@"{ \"error\": \"Internal Server Error\", \"message\": \"An unexpected error occurred\" }")
             .set_header("Content-Type", "application/json");
     }
 }
 
 // Input validation example
-class ValidationHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class ValidationEndpoint : Object, Endpoint {
+    public string route { get { return "/validation"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var email = http_context.request.get_query("email");
         var age_str = http_context.request.get_query("age");
         
@@ -115,9 +127,11 @@ class ValidationHandler : Object, RouteHandler {
 }
 
 // Resource lookup with error handling
-class ResourceHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        var id_str = route_context.get_segment("id");
+class ResourceEndpoint : Object, Endpoint {
+    public string route { get { return "/resource/{id}"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route_info) throws Error {
+        var id_str = route_info.named_components["id"];
         int id;
         
         if (!int.try_parse(id_str, out id)) {
@@ -148,9 +162,11 @@ class ResourceHandler : Object, RouteHandler {
 }
 
 // API endpoint simulation
-class ApiEndpointHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        var endpoint = route_context.get_segment("endpoint");
+class ApiEndpoint : Object, Endpoint {
+    public string route { get { return "/api/{endpoint}"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route_info) throws Error {
+        var endpoint = route_info.named_components["endpoint"];
         
         // Simulate different API responses
         switch (endpoint) {
@@ -172,8 +188,10 @@ class ApiEndpointHandler : Object, RouteHandler {
 }
 
 // Custom error response
-class CustomErrorHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class CustomErrorEndpoint : Object, Endpoint {
+    public string route { get { return "/custom-error"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var error_code = http_context.request.get_query_or_default("code", "CUSTOM_ERROR");
         var message = http_context.request.get_query_or_default("message", "A custom error occurred");
         
@@ -192,16 +210,20 @@ class CustomErrorHandler : Object, RouteHandler {
 }
 
 // Simulated timeout (actually returns 408)
-class TimeoutHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class TimeoutEndpoint : Object, Endpoint {
+    public string route { get { return "/timeout"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         return new HttpStringResult(@"{ \"error\": \"Request Timeout\", \"message\": \"The request took too long to complete\" }")
             .set_header("Content-Type", "application/json");
     }
 }
 
 // 401 Unauthorized
-class UnauthorizedHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class UnauthorizedEndpoint : Object, Endpoint {
+    public string route { get { return "/unauthorized"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var auth_header = http_context.request.get_header("Authorization");
         
         if (auth_header == null) {
@@ -223,8 +245,10 @@ class UnauthorizedHandler : Object, RouteHandler {
 }
 
 // 403 Forbidden
-class ForbiddenHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class ForbiddenEndpoint : Object, Endpoint {
+    public string route { get { return "/forbidden"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var user_role = http_context.request.get_cookie("role") ?? "guest";
         
         if (user_role != "admin") {
@@ -238,8 +262,10 @@ class ForbiddenHandler : Object, RouteHandler {
 }
 
 // 405 Method Not Allowed
-class MethodNotAllowedHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class MethodNotAllowedEndpoint : Object, Endpoint {
+    public string route { get { return "/method-not-allowed"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         return new HttpStringResult(@"{ \"error\": \"Method Not Allowed\", \"message\": \"GET method is not allowed for this endpoint\" }")
             .set_header("Content-Type", "application/json")
             .set_header("Allow", "POST, PUT, DELETE");
@@ -247,23 +273,25 @@ class MethodNotAllowedHandler : Object, RouteHandler {
 }
 
 void main() {
-    var router = new Router();
-    var server = new Server(8088, router);
+    var router = new EndpointRouter()
+        .add_endpoint(new RootEndpoint())
+        .add_endpoint(new OkEndpoint())
+        .add_endpoint(new NotFoundEndpoint())
+        .add_endpoint(new BadRequestEndpoint())
+        .add_endpoint(new InternalErrorEndpoint())
+        .add_endpoint(new ValidationEndpoint())
+        .add_endpoint(new ResourceEndpoint())
+        .add_endpoint(new ApiEndpoint())
+        .add_endpoint(new CustomErrorEndpoint())
+        .add_endpoint(new TimeoutEndpoint())
+        .add_endpoint(new UnauthorizedEndpoint())
+        .add_endpoint(new ForbiddenEndpoint())
+        .add_endpoint(new MethodNotAllowedEndpoint());
     
-    // Register routes
-    router.get("/", new RootHandler());
-    router.get("/ok", new OkHandler());
-    router.get("/not-found", new NotFoundHandler());
-    router.get("/bad-request", new BadRequestHandler());
-    router.get("/internal-error", new InternalErrorHandler());
-    router.get("/validation", new ValidationHandler());
-    router.get("/resource/{id}", new ResourceHandler());
-    router.get("/api/{endpoint}", new ApiEndpointHandler());
-    router.get("/custom-error", new CustomErrorHandler());
-    router.get("/timeout", new TimeoutHandler());
-    router.get("/unauthorized", new UnauthorizedHandler());
-    router.get("/forbidden", new ForbiddenHandler());
-    router.get("/method-not-allowed", new MethodNotAllowedHandler());
+    var pipeline = new Pipeline()
+        .add_component(router);
+
+    var server = new Server(8088, pipeline);
     
     print("Error Handling Demo Server running on port 8088\n");
     print("Try these endpoints:\n");

+ 37 - 39
examples/FormData.vala

@@ -5,13 +5,15 @@ using Invercargill.DataStructures;
 /**
  * FormData Example
  * 
- * Demonstrates handling POST form data in Astralis using async RouteHandler.
+ * Demonstrates handling POST form data in Astralis using async Endpoint.
  * Shows both application/x-www-form-urlencoded and multipart/form-data handling.
  */
 
 // HTML form page handler
-class FormPageHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext context, RouteContext route_context) throws Error {
+class FormPageEndpoint : Object, Endpoint {
+    public string route { get { return "/"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext context, RouteInformation route) throws Error {
         return new HttpStringResult("""<!DOCTYPE html>
 <html>
 <head>
@@ -123,12 +125,10 @@ class FormPageHandler : Object, RouteHandler {
 }
 
 // Simple form submission handler
-class SimpleFormHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext context, RouteContext route_context) throws Error {
-        if (!context.request.is_post()) {
-            return new HttpStringResult("Please use POST method");
-        }
-        
+class SimpleFormEndpoint : Object, Endpoint {
+    public string route { get { return "/submit-simple"; } }
+    public Method[] methods { owned get { return { Method.POST }; } }
+    public async HttpResult handle_request(HttpContext context, RouteInformation route) throws Error {
         // Parse form data asynchronously from the request body
         FormData form_data = yield FormDataParser.parse(
             context.request.request_body,
@@ -160,12 +160,10 @@ class SimpleFormHandler : Object, RouteHandler {
 }
 
 // Registration form submission handler
-class RegisterFormHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext context, RouteContext route_context) throws Error {
-        if (!context.request.is_post()) {
-            return new HttpStringResult("Please use POST method");
-        }
-        
+class RegisterFormEndpoint : Object, Endpoint {
+    public string route { get { return "/submit-register"; } }
+    public Method[] methods { owned get { return { Method.POST }; } }
+    public async HttpResult handle_request(HttpContext context, RouteInformation route) throws Error {
         FormData form_data = yield FormDataParser.parse(
             context.request.request_body,
             context.request.content_type
@@ -214,12 +212,10 @@ class RegisterFormHandler : Object, RouteHandler {
 }
 
 // Search form submission handler
-class SearchFormHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext context, RouteContext route_context) throws Error {
-        if (!context.request.is_post()) {
-            return new HttpStringResult("Please use POST method");
-        }
-        
+class SearchFormEndpoint : Object, Endpoint {
+    public string route { get { return "/submit-search"; } }
+    public Method[] methods { owned get { return { Method.POST }; } }
+    public async HttpResult handle_request(HttpContext context, RouteInformation route) throws Error {
         FormData form_data = yield FormDataParser.parse(
             context.request.request_body,
             context.request.content_type
@@ -274,12 +270,10 @@ class SearchFormHandler : Object, RouteHandler {
 }
 
 // File upload handler (multipart/form-data)
-class FileUploadHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext context, RouteContext route_context) throws Error {
-        if (!context.request.is_post()) {
-            return new HttpStringResult("Please use POST method");
-        }
-        
+class FileUploadEndpoint : Object, Endpoint {
+    public string route { get { return "/submit-file"; } }
+    public Method[] methods { owned get { return { Method.POST }; } }
+    public async HttpResult handle_request(HttpContext context, RouteInformation route) throws Error {
         FormData form_data = yield FormDataParser.parse(
             context.request.request_body,
             context.request.content_type
@@ -311,13 +305,15 @@ class FileUploadHandler : Object, RouteHandler {
 }
 
 // Form debug tool handler
-class FormDebugHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext context, RouteContext route_context) throws Error {
+class FormDebugEndpoint : Object, Endpoint {
+    public string route { get { return "/form-debug"; } }
+    public Method[] methods { owned get { return { Method.GET, Method.POST }; } }
+    public async HttpResult handle_request(HttpContext context, RouteInformation route) throws Error {
         FormData? form_data = null;
         string? body_text = null;
         
         // Try to parse form data if this is a POST with form content
-        if (context.request.is_post() && 
+        if (context.request.method == Method.POST && 
             (context.request.is_form_urlencoded() || context.request.is_multipart())) {
             try {
                 form_data = yield FormDataParser.parse(
@@ -484,16 +480,18 @@ class Product {
 }
 
 void main() {
-    var router = new Router();
-    var server = new Server(8084, router);
+    var router = new EndpointRouter()
+        .add_endpoint(new FormPageEndpoint())
+        .add_endpoint(new SimpleFormEndpoint())
+        .add_endpoint(new RegisterFormEndpoint())
+        .add_endpoint(new SearchFormEndpoint())
+        .add_endpoint(new FileUploadEndpoint())
+        .add_endpoint(new FormDebugEndpoint());
     
-    // Register handlers
-    router.get("/", new FormPageHandler());
-    router.post("/submit-simple", new SimpleFormHandler());
-    router.post("/submit-register", new RegisterFormHandler());
-    router.post("/submit-search", new SearchFormHandler());
-    router.post("/submit-file", new FileUploadHandler());
-    router.map("/form-debug", new FormDebugHandler());  // Handles both GET and POST
+    var pipeline = new Pipeline()
+        .add_component(router);
+
+    var server = new Server(8084, pipeline);
     
     print("Form Data Example Server running on port 8084\n");
     print("Open http://localhost:8084/ in your browser to try the forms\n");

+ 85 - 51
examples/HeadersAndCookies.vala

@@ -10,8 +10,10 @@ using Invercargill.DataStructures;
  */
 
 // Display all request headers
-class HeadersHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class HeadersEndpoint : Object, Endpoint {
+    public string route { get { return "/headers"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var parts = new Series<string>();
         parts.add("Request Headers:\n");
         parts.add("================\n\n");
@@ -31,8 +33,10 @@ class HeadersHandler : Object, RouteHandler {
 }
 
 // Check for user-agent header
-class UserAgentHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class UserAgentEndpoint : Object, Endpoint {
+    public string route { get { return "/user-agent"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var user_agent = http_context.request.user_agent ?? "Unknown";
         
         return new HttpStringResult(@"Your User-Agent is: $user_agent");
@@ -40,8 +44,10 @@ class UserAgentHandler : Object, RouteHandler {
 }
 
 // Check content type and content length
-class ContentInfoHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class ContentInfoEndpoint : Object, Endpoint {
+    public string route { get { return "/content-info"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var parts = new Series<string>();
         parts.add("Content Information:\n");
         parts.add("===================\n\n");
@@ -57,8 +63,10 @@ class ContentInfoHandler : Object, RouteHandler {
 }
 
 // Check if header exists
-class CheckHeaderHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class CheckHeaderEndpoint : Object, Endpoint {
+    public string route { get { return "/check-header"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var header_name = http_context.request.get_query_or_default("name", "Accept");
         var exists = http_context.request.has_header(header_name);
         var value = http_context.request.get_header(header_name);
@@ -68,8 +76,10 @@ class CheckHeaderHandler : Object, RouteHandler {
 }
 
 // Set custom response headers
-class CustomHeadersHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class CustomHeadersEndpoint : Object, Endpoint {
+    public string route { get { return "/custom-headers"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         return new HttpStringResult("Response with custom headers!")
             .set_header("X-Custom-Header", "Custom-Value")
             .set_header("X-Request-Id", generate_request_id())
@@ -79,8 +89,10 @@ class CustomHeadersHandler : Object, RouteHandler {
 }
 
 // Display all cookies
-class CookiesHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class CookiesEndpoint : Object, Endpoint {
+    public string route { get { return "/cookies"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var parts = new Series<string>();
         parts.add("Request Cookies:\n");
         parts.add("================\n\n");
@@ -105,8 +117,10 @@ class CookiesHandler : Object, RouteHandler {
 }
 
 // Get specific cookie
-class GetCookieHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class GetCookieEndpoint : Object, Endpoint {
+    public string route { get { return "/get-cookie"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var name = http_context.request.get_query_or_default("name", "session");
         var value = http_context.request.get_cookie(name);
         
@@ -119,8 +133,10 @@ class GetCookieHandler : Object, RouteHandler {
 }
 
 // Set a cookie (via Set-Cookie header)
-class SetCookieHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class SetCookieEndpoint : Object, Endpoint {
+    public string route { get { return "/set-cookie"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var name = http_context.request.get_query_or_default("name", "test");
         var value = http_context.request.get_query_or_default("value", "123");
         var max_age = http_context.request.get_query_or_default("max_age", "3600");
@@ -131,8 +147,10 @@ class SetCookieHandler : Object, RouteHandler {
 }
 
 // Set multiple cookies
-class SetCookiesHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class SetCookiesEndpoint : Object, Endpoint {
+    public string route { get { return "/set-cookies"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         // Note: Setting multiple cookies with the same header name requires
         // special handling - this example shows the approach
         return new HttpStringResult("Multiple cookies set!")
@@ -143,8 +161,10 @@ class SetCookiesHandler : Object, RouteHandler {
 }
 
 // Delete a cookie
-class DeleteCookieHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class DeleteCookieEndpoint : Object, Endpoint {
+    public string route { get { return "/delete-cookie"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var name = http_context.request.get_query_or_default("name", "test");
         
         return new HttpStringResult(@"Cookie '$name' deleted")
@@ -153,8 +173,10 @@ class DeleteCookieHandler : Object, RouteHandler {
 }
 
 // Check if cookie exists
-class HasCookieHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class HasCookieEndpoint : Object, Endpoint {
+    public string route { get { return "/has-cookie"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var name = http_context.request.get_query_or_default("name", "session");
         var exists = http_context.request.has_cookie(name);
         
@@ -163,8 +185,10 @@ class HasCookieHandler : Object, RouteHandler {
 }
 
 // Cookie-based session simulation
-class SessionHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class SessionEndpoint : Object, Endpoint {
+    public string route { get { return "/session"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var session_id = http_context.request.get_cookie("session_id");
         
         if (session_id == null) {
@@ -187,8 +211,10 @@ Your session is active.");
 }
 
 // CORS headers example
-class CorsHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class CorsEndpoint : Object, Endpoint {
+    public string route { get { return "/cors"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var origin = http_context.request.get_header("Origin") ?? "*";
         
         return new HttpStringResult(@"CORS enabled for origin: $origin")
@@ -200,8 +226,10 @@ class CorsHandler : Object, RouteHandler {
 }
 
 // Cache control headers
-class CacheHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class CacheEndpoint : Object, Endpoint {
+    public string route { get { return "/cache"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var cache_type = http_context.request.get_query_or_default("type", "public");
         
         return new HttpStringResult(@"This response is cached ($cache_type cache, 1 hour)")
@@ -212,8 +240,10 @@ class CacheHandler : Object, RouteHandler {
 }
 
 // Content negotiation
-class NegotiateHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class NegotiateEndpoint : Object, Endpoint {
+    public string route { get { return "/negotiate"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var accept = http_context.request.get_header("Accept") ?? "*/*";
         
         if (accept.contains("application/json")) {
@@ -230,8 +260,10 @@ class NegotiateHandler : Object, RouteHandler {
 }
 
 // Security headers
-class SecureHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class SecureEndpoint : Object, Endpoint {
+    public string route { get { return "/secure"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         return new HttpStringResult("Response with security headers!")
             .set_header("X-Content-Type-Options", "nosniff")
             .set_header("X-Frame-Options", "DENY")
@@ -243,26 +275,28 @@ class SecureHandler : Object, RouteHandler {
 }
 
 void main() {
-    var router = new Router();
-    var server = new Server(8083, router);
+    var router = new EndpointRouter()
+        .add_endpoint(new HeadersEndpoint())
+        .add_endpoint(new UserAgentEndpoint())
+        .add_endpoint(new ContentInfoEndpoint())
+        .add_endpoint(new CheckHeaderEndpoint())
+        .add_endpoint(new CustomHeadersEndpoint())
+        .add_endpoint(new CookiesEndpoint())
+        .add_endpoint(new GetCookieEndpoint())
+        .add_endpoint(new SetCookieEndpoint())
+        .add_endpoint(new SetCookiesEndpoint())
+        .add_endpoint(new DeleteCookieEndpoint())
+        .add_endpoint(new HasCookieEndpoint())
+        .add_endpoint(new SessionEndpoint())
+        .add_endpoint(new CorsEndpoint())
+        .add_endpoint(new CacheEndpoint())
+        .add_endpoint(new NegotiateEndpoint())
+        .add_endpoint(new SecureEndpoint());
     
-    // Register routes
-    router.get("/headers", new HeadersHandler());
-    router.get("/user-agent", new UserAgentHandler());
-    router.get("/content-info", new ContentInfoHandler());
-    router.get("/check-header", new CheckHeaderHandler());
-    router.get("/custom-headers", new CustomHeadersHandler());
-    router.get("/cookies", new CookiesHandler());
-    router.get("/get-cookie", new GetCookieHandler());
-    router.get("/set-cookie", new SetCookieHandler());
-    router.get("/set-cookies", new SetCookiesHandler());
-    router.get("/delete-cookie", new DeleteCookieHandler());
-    router.get("/has-cookie", new HasCookieHandler());
-    router.get("/session", new SessionHandler());
-    router.get("/cors", new CorsHandler());
-    router.get("/cache", new CacheHandler());
-    router.get("/negotiate", new NegotiateHandler());
-    router.get("/secure", new SecureHandler());
+    var pipeline = new Pipeline()
+        .add_component(router);
+
+    var server = new Server(8083, pipeline);
     
     print("Headers and Cookies Example Server running on port 8083\n");
     print("Try these endpoints:\n");

+ 63 - 39
examples/JsonApi.vala

@@ -11,8 +11,10 @@ using Invercargill.DataStructures;
  */
 
 // API root handler
-class ApiRootHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class ApiRootEndpoint : Object, Endpoint {
+    public string route { get { return "/api"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var json = @"{
   \"name\": \"Astralis JSON API\",
   \"version\": \"1.0.0\",
@@ -31,8 +33,10 @@ class ApiRootHandler : Object, RouteHandler {
 }
 
 // List all users
-class UsersListHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class UsersListEndpoint : Object, Endpoint {
+    public string route { get { return "/api/users"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var users = get_users();
         
         var json_parts = new Series<string>();
@@ -59,9 +63,11 @@ class UsersListHandler : Object, RouteHandler {
 }
 
 // Get user by ID
-class UserByIdHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        var id_str = route_context.get_segment("id");
+class UserByIdEndpoint : Object, Endpoint {
+    public string route { get { return "/api/users/{id}"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route_info) throws Error {
+        var id_str = route_info.named_components["id"];
         var id = int.parse(id_str);
         var user = find_user(id);
         
@@ -76,8 +82,10 @@ class UserByIdHandler : Object, RouteHandler {
 }
 
 // List all posts
-class PostsListHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class PostsListEndpoint : Object, Endpoint {
+    public string route { get { return "/api/posts"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var posts = get_posts();
         
         // Filter by user_id if provided
@@ -113,9 +121,11 @@ class PostsListHandler : Object, RouteHandler {
 }
 
 // Get post by ID
-class PostByIdHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        var id_str = route_context.get_segment("id");
+class PostByIdEndpoint : Object, Endpoint {
+    public string route { get { return "/api/posts/{id}"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route_info) throws Error {
+        var id_str = route_info.named_components["id"];
         var id = int.parse(id_str);
         var post = find_post(id);
         
@@ -157,8 +167,10 @@ class PostByIdHandler : Object, RouteHandler {
 }
 
 // List all comments
-class CommentsListHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class CommentsListEndpoint : Object, Endpoint {
+    public string route { get { return "/api/comments"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var comments = get_comments();
         
         // Filter by post_id if provided
@@ -194,9 +206,11 @@ class CommentsListHandler : Object, RouteHandler {
 }
 
 // Get comment by ID
-class CommentByIdHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        var id_str = route_context.get_segment("id");
+class CommentByIdEndpoint : Object, Endpoint {
+    public string route { get { return "/api/comments/{id}"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route_info) throws Error {
+        var id_str = route_info.named_components["id"];
         var id = int.parse(id_str);
         var comment = find_comment(id);
         
@@ -211,8 +225,10 @@ class CommentByIdHandler : Object, RouteHandler {
 }
 
 // API statistics
-class StatsHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class StatsEndpoint : Object, Endpoint {
+    public string route { get { return "/api/stats"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var users = get_users();
         var posts = get_posts();
         var comments = get_comments();
@@ -238,8 +254,10 @@ class StatsHandler : Object, RouteHandler {
 }
 
 // Search endpoint
-class SearchHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class SearchEndpoint : Object, Endpoint {
+    public string route { get { return "/api/search"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var query = http_context.request.get_query("q");
         
         if (query == null || query == "") {
@@ -286,8 +304,10 @@ class SearchHandler : Object, RouteHandler {
 }
 
 // Pagination example
-class PaginatedPostsHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class PaginatedPostsEndpoint : Object, Endpoint {
+    public string route { get { return "/api/posts/paginated"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var page = int.parse(http_context.request.get_query_or_default("page", "1"));
         var per_page = int.parse(http_context.request.get_query_or_default("per_page", "10"));
         
@@ -323,8 +343,10 @@ class PaginatedPostsHandler : Object, RouteHandler {
 }
 
 // Error handling endpoint
-class ApiErrorHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class ApiErrorEndpoint : Object, Endpoint {
+    public string route { get { return "/api/error"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var error_type = http_context.request.get_query_or_default("type", "generic");
         
         switch (error_type) {
@@ -341,21 +363,23 @@ class ApiErrorHandler : Object, RouteHandler {
 }
 
 void main() {
-    var router = new Router();
-    var server = new Server(8085, router);
+    var router = new EndpointRouter()
+        .add_endpoint(new ApiRootEndpoint())
+        .add_endpoint(new UsersListEndpoint())
+        .add_endpoint(new UserByIdEndpoint())
+        .add_endpoint(new PostsListEndpoint())
+        .add_endpoint(new PostByIdEndpoint())
+        .add_endpoint(new CommentsListEndpoint())
+        .add_endpoint(new CommentByIdEndpoint())
+        .add_endpoint(new StatsEndpoint())
+        .add_endpoint(new SearchEndpoint())
+        .add_endpoint(new PaginatedPostsEndpoint())
+        .add_endpoint(new ApiErrorEndpoint());
     
-    // Register routes
-    router.get("/api", new ApiRootHandler());
-    router.get("/api/users", new UsersListHandler());
-    router.get("/api/users/{id}", new UserByIdHandler());
-    router.get("/api/posts", new PostsListHandler());
-    router.get("/api/posts/{id}", new PostByIdHandler());
-    router.get("/api/comments", new CommentsListHandler());
-    router.get("/api/comments/{id}", new CommentByIdHandler());
-    router.get("/api/stats", new StatsHandler());
-    router.get("/api/search", new SearchHandler());
-    router.get("/api/posts/paginated", new PaginatedPostsHandler());
-    router.get("/api/error", new ApiErrorHandler());
+    var pipeline = new Pipeline()
+        .add_component(router);
+
+    var server = new Server(8085, pipeline);
     
     print("JSON API Example Server running on port 8085\n");
     print("Try these endpoints:\n");

+ 64 - 42
examples/PathRouting.vala

@@ -6,12 +6,14 @@ using Invercargill.DataStructures;
  * PathRouting Example
  * 
  * Demonstrates path component parsing and routing in Astralis.
- * Uses the Router's named segment feature for dynamic routing.
+ * Uses the EndpointRouter's named segment feature for dynamic routing.
  */
 
 // Root handler - shows available endpoints
-class RootHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class RootEndpoint : Object, Endpoint {
+    public string route { get { return "/"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         return new HttpStringResult("""Welcome to the Path Routing Example!
 
 Available endpoints:
@@ -30,23 +32,29 @@ Available endpoints:
 }
 
 // Simple hello handler
-class HelloHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class HelloEndpoint : Object, Endpoint {
+    public string route { get { return "/hello"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         return new HttpStringResult("Hello, World!");
     }
 }
 
 // Hello with name handler - uses named segment
-class HelloNameHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        var name = route_context.get_segment("name");
+class HelloNameEndpoint : Object, Endpoint {
+    public string route { get { return "/hello/{name}"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route_info) throws Error {
+        var name = route_info.named_components["name"];
         return new HttpStringResult(@"Hello, $name!");
     }
 }
 
 // Users list handler
-class UsersHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class UsersEndpoint : Object, Endpoint {
+    public string route { get { return "/users"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var users = new Series<User>();
         users.add(new User(1, "Alice", "alice@example.com"));
         users.add(new User(2, "Bob", "bob@example.com"));
@@ -73,9 +81,11 @@ class UsersHandler : Object, RouteHandler {
 }
 
 // User by ID handler - uses named segment
-class UserByIdHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        var id_str = route_context.get_segment("id");
+class UserByIdEndpoint : Object, Endpoint {
+    public string route { get { return "/users/{id}"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route_info) throws Error {
+        var id_str = route_info.named_components["id"];
         var id = int.parse(id_str);
         
         // Simulated user lookup
@@ -98,9 +108,11 @@ class UserByIdHandler : Object, RouteHandler {
 }
 
 // User posts handler - uses named segment
-class UserPostsHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        var id_str = route_context.get_segment("id");
+class UserPostsEndpoint : Object, Endpoint {
+    public string route { get { return "/users/{id}/posts"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route_info) throws Error {
+        var id_str = route_info.named_components["id"];
         var id = int.parse(id_str);
         
         // Simulated posts for user
@@ -129,8 +141,10 @@ class UserPostsHandler : Object, RouteHandler {
 }
 
 // API status handler
-class ApiStatusHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class ApiStatusEndpoint : Object, Endpoint {
+    public string route { get { return "/api/v1/status"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var status = new Dictionary<string, string>();
         status.set("status", "operational");
         status.set("version", "1.0.0");
@@ -157,9 +171,11 @@ class ApiStatusHandler : Object, RouteHandler {
 }
 
 // API item handler - uses named segment
-class ApiItemHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        var id_str = route_context.get_segment("id");
+class ApiItemEndpoint : Object, Endpoint {
+    public string route { get { return "/api/v1/items/{id}"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route_info) throws Error {
+        var id_str = route_info.named_components["id"];
         var id = int.parse(id_str);
         
         var items = new Dictionary<int, Item>();
@@ -179,10 +195,12 @@ class ApiItemHandler : Object, RouteHandler {
 }
 
 // Files handler - uses two named segments
-class FilesHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        var category = route_context.get_segment("category");
-        var name = route_context.get_segment("name");
+class FilesEndpoint : Object, Endpoint {
+    public string route { get { return "/files/{category}/{name}"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route_info) throws Error {
+        var category = route_info.named_components["category"];
+        var name = route_info.named_components["name"];
         
         // Simulated file lookup
         var files = new Dictionary<string, Dictionary<string, ExampleFile>>();
@@ -212,8 +230,10 @@ class FilesHandler : Object, RouteHandler {
 }
 
 // Path info handler
-class PathInfoHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class PathInfoEndpoint : Object, Endpoint {
+    public string route { get { return "/pathinfo"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route_info) throws Error {
         var parts = new Series<string>();
         parts.add("Path Information:\n");
         parts.add(@"  Raw path: $(http_context.request.raw_path)\n");
@@ -228,9 +248,9 @@ class PathInfoHandler : Object, RouteHandler {
         
         parts.add(@"\n  Query string: $(http_context.request.query_string)\n");
         
-        // Show named segments from route context
+        // Show named segments from route information
         parts.add("\n  Named segments:\n");
-        route_context.named_segments.to_immutable_buffer()
+        route_info.named_components.to_immutable_buffer()
             .iterate((kv) => {
                 parts.add(@"    $(kv.key): $(kv.value)\n");
             });
@@ -243,20 +263,22 @@ class PathInfoHandler : Object, RouteHandler {
 }
 
 void main() {
-    var router = new Router();
-    var server = new Server(8082, router);
+    var router = new EndpointRouter()
+        .add_endpoint(new RootEndpoint())
+        .add_endpoint(new HelloEndpoint())
+        .add_endpoint(new HelloNameEndpoint())
+        .add_endpoint(new UsersEndpoint())
+        .add_endpoint(new UserByIdEndpoint())
+        .add_endpoint(new UserPostsEndpoint())
+        .add_endpoint(new ApiStatusEndpoint())
+        .add_endpoint(new ApiItemEndpoint())
+        .add_endpoint(new FilesEndpoint())
+        .add_endpoint(new PathInfoEndpoint());
     
-    // Register routes with named segments
-    router.get("/", new RootHandler());
-    router.get("/hello", new HelloHandler());
-    router.get("/hello/{name}", new HelloNameHandler());
-    router.get("/users", new UsersHandler());
-    router.get("/users/{id}", new UserByIdHandler());
-    router.get("/users/{id}/posts", new UserPostsHandler());
-    router.get("/api/v1/status", new ApiStatusHandler());
-    router.get("/api/v1/items/{id}", new ApiItemHandler());
-    router.get("/files/{category}/{name}", new FilesHandler());
-    router.get("/pathinfo", new PathInfoHandler());
+    var pipeline = new Pipeline()
+        .add_component(router);
+
+    var server = new Server(8082, pipeline);
     
     print("Path Routing Example Server running on port 8082\n");
     print("Try these endpoints:\n");

+ 30 - 18
examples/QueryParameters.vala

@@ -11,8 +11,10 @@ using Invercargill.DataStructures;
  */
 
 // Greet handler - simple query parameter access
-class GreetHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class GreetEndpoint : Object, Endpoint {
+    public string route { get { return "/greet"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var name = http_context.request.get_query_or_default("name", "World");
         var greeting = http_context.request.get_query_or_default("greeting", "Hello");
         
@@ -21,8 +23,10 @@ class GreetHandler : Object, RouteHandler {
 }
 
 // Search handler - multiple query parameters with validation
-class SearchHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class SearchEndpoint : Object, Endpoint {
+    public string route { get { return "/search"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var query = http_context.request.get_query("q");
         var limit = int.parse(http_context.request.get_query_or_default("limit", "10"));
         var offset = int.parse(http_context.request.get_query_or_default("offset", "0"));
@@ -58,8 +62,10 @@ class SearchHandler : Object, RouteHandler {
 }
 
 // Debug handler - boolean flag query parameters
-class DebugHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class DebugEndpoint : Object, Endpoint {
+    public string route { get { return "/debug"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var verbose = http_context.request.has_query("verbose");
         var level = http_context.request.get_query_or_default("level", "info");
         
@@ -85,8 +91,10 @@ class DebugHandler : Object, RouteHandler {
 }
 
 // Filter handler - query parameter with multiple values (comma-separated)
-class FilterHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class FilterEndpoint : Object, Endpoint {
+    public string route { get { return "/filter"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var tags_param = http_context.request.get_query("tags");
         
         if (tags_param == null) {
@@ -121,8 +129,10 @@ class FilterHandler : Object, RouteHandler {
 }
 
 // Range handler - numeric query parameters with range validation
-class RangeHandler : Object, RouteHandler {
-    public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
+class RangeEndpoint : Object, Endpoint {
+    public string route { get { return "/range"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         var min = int.parse(http_context.request.get_query_or_default("min", "0"));
         var max = int.parse(http_context.request.get_query_or_default("max", "100"));
         var step = int.parse(http_context.request.get_query_or_default("step", "1"));
@@ -157,15 +167,17 @@ class RangeHandler : Object, RouteHandler {
 }
 
 void main() {
-    var router = new Router();
-    var server = new Server(8081, router);
+    var router = new EndpointRouter()
+        .add_endpoint(new GreetEndpoint())
+        .add_endpoint(new SearchEndpoint())
+        .add_endpoint(new DebugEndpoint())
+        .add_endpoint(new FilterEndpoint())
+        .add_endpoint(new RangeEndpoint());
     
-    // Register routes
-    router.get("/greet", new GreetHandler());
-    router.get("/search", new SearchHandler());
-    router.get("/debug", new DebugHandler());
-    router.get("/filter", new FilterHandler());
-    router.get("/range", new RangeHandler());
+    var pipeline = new Pipeline()
+        .add_component(router);
+
+    var server = new Server(8081, pipeline);
     
     print("Query Parameters Example Server running on port 8081\n");
     print("Try these endpoints:\n");

+ 26 - 15
examples/RemoteAddress.vala

@@ -2,22 +2,18 @@ using Astralis;
 using Invercargill;
 using Invercargill.DataStructures;
 
-public class RemoteAddressExample : Object {
-    public static int main(string[] args) {
-        // Create a simple handler that logs the client's remote address
-        var handler = new SimpleHttpHandler();
-        
-        // Start the server on port 8080
-        var server = new Server(8080, handler);
-        server.run();
-        
-        return 0;
-    }
-}
+/**
+ * RemoteAddress Example
+ * 
+ * Demonstrates accessing client remote address information.
+ */
 
-// Simple handler that responds with client information
-public class SimpleHttpHandler : Object, RequestHandler {
-    public async HttpResult handle_request(HttpContext context) throws Error {
+// Endpoint that responds with client information
+public class RemoteAddressEndpoint : Object, Endpoint {
+    public string route { get { return "*"; } }
+    public Method[] methods { owned get { return { Method.GET }; } }
+    
+    public async HttpResult handle_request(HttpContext context, RouteInformation route) throws Error {
         var request = context.request;
         
         // Access the remote address
@@ -46,3 +42,18 @@ public class SimpleHttpHandler : Object, RequestHandler {
         return now.format("%Y-%m-%d %H:%M:%S");
     }
 }
+
+void main() {
+    var router = new EndpointRouter()
+        .add_endpoint(new RemoteAddressEndpoint());
+    
+    var pipeline = new Pipeline()
+        .add_component(router);
+
+    var server = new Server(8080, pipeline);
+    
+    print("Remote Address Example Server running on port 8080\n");
+    print("Try: http://localhost:8080/\n");
+    
+    server.run();
+}

+ 2 - 2
examples/SimpleApi.vala

@@ -5,7 +5,7 @@ using Invercargill.DataStructures;
 // Simple handler for /hello endpoint
 class HelloEndpoint : Object, Endpoint {
     public string route { get { return "/hello"; } }
-    public Enumerable<Method> methods { owned get { return Iterate.these(Method.GET);} }
+    public Method[] methods { owned get { return { Method.GET };} }
     public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         print("Handling /hello\n");
         return new HttpStringResult("Hello from Astralis!");
@@ -15,7 +15,7 @@ class HelloEndpoint : Object, Endpoint {
 // Simple handler for /json endpoint
 class JsonEndpoint : Object, Endpoint {
     public string route { get { return "/json"; } }
-    public Enumerable<Method> methods { owned get { return Iterate.these(Method.GET);} }
+    public Method[] methods { owned get { return { Method.GET };} }
     public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
         print("Handling /json\n");
         return new HttpStringResult("{ \"message\": \"Hello JSON\" }")

+ 55 - 55
examples/meson.build

@@ -1,69 +1,69 @@
-# # Query Parameters Example
-# executable('query-parameters',
-#     'QueryParameters.vala',
-#     dependencies: [astralis_dep, invercargill_dep],
-#     install: false
-# )
+# Query Parameters Example
+executable('query-parameters',
+    'QueryParameters.vala',
+    dependencies: [astralis_dep, invercargill_dep],
+    install: false
+)
 
-# # Path Routing Example
-# executable('path-routing',
-#     'PathRouting.vala',
-#     dependencies: [astralis_dep, invercargill_dep],
-#     install: false
-# )
+# Path Routing Example
+executable('path-routing',
+    'PathRouting.vala',
+    dependencies: [astralis_dep, invercargill_dep],
+    install: false
+)
 
-# # Headers and Cookies Example
-# executable('headers-and-cookies',
-#     'HeadersAndCookies.vala',
-#     dependencies: [astralis_dep, invercargill_dep],
-#     install: false
-# )
+# Headers and Cookies Example
+executable('headers-and-cookies',
+    'HeadersAndCookies.vala',
+    dependencies: [astralis_dep, invercargill_dep],
+    install: false
+)
 
-# # Form Data Example
-# executable('form-data',
-#     'FormData.vala',
-#     dependencies: [astralis_dep, invercargill_dep],
-#     install: false
-# )
+# Form Data Example
+executable('form-data',
+    'FormData.vala',
+    dependencies: [astralis_dep, invercargill_dep],
+    install: false
+)
 
-# # JSON API Example
-# executable('json-api',
-#     'JsonApi.vala',
-#     dependencies: [astralis_dep, invercargill_dep],
-#     install: false
-# )
+# JSON API Example
+executable('json-api',
+    'JsonApi.vala',
+    dependencies: [astralis_dep, invercargill_dep],
+    install: false
+)
 
-# # Data Structures Demo Example
-# executable('data-structures-demo',
-#     'DataStructuresDemo.vala',
-#     dependencies: [astralis_dep, invercargill_dep],
-#     install: false
-# )
+# Data Structures Demo Example
+executable('data-structures-demo',
+    'DataStructuresDemo.vala',
+    dependencies: [astralis_dep, invercargill_dep],
+    install: false
+)
 
-# # Enumerable Operations Example
-# executable('enumerable-operations',
-#     'EnumerableOperations.vala',
-#     dependencies: [astralis_dep, invercargill_dep],
-#     install: false
-# )
+# Enumerable Operations Example
+executable('enumerable-operations',
+    'EnumerableOperations.vala',
+    dependencies: [astralis_dep, invercargill_dep],
+    install: false
+)
 
-# # Error Handling Example
-# executable('error-handling',
-#     'ErrorHandling.vala',
-#     dependencies: [astralis_dep, invercargill_dep],
-#     install: false
-# )
+# Error Handling Example
+executable('error-handling',
+    'ErrorHandling.vala',
+    dependencies: [astralis_dep, invercargill_dep],
+    install: false
+)
 
-# # Original Simple API Example
+# Original Simple API Example
 executable('simple-api',
     'SimpleApi.vala',
     dependencies: [astralis_dep, invercargill_dep],
     install: false
 )
 
-# # Remote Address Example
-# executable('remote-address',
-#     'RemoteAddress.vala',
-#     dependencies: [astralis_dep, invercargill_dep, json_glib_dep],
-#     install: false
-# )
+# Remote Address Example
+executable('remote-address',
+    'RemoteAddress.vala',
+    dependencies: [astralis_dep, invercargill_dep, json_glib_dep],
+    install: false
+)

+ 19 - 9
src/Handlers/EndpointRouter.vala

@@ -9,17 +9,17 @@ namespace Astralis {
 
         public async HttpResult process_request(HttpContext http_context, PipelineContext pipeline_context) throws Error {
             var path_components = http_context.request.path_components.to_vector();
-            var endpoint = endpoint_sources
+            var endpoint_attempt = endpoint_sources
                 .attempt_select<Endpoint>(s => s.get_endpoint())
+                .where(e => Wrap.array<Method>(e.methods).any(m => m == http_context.request.method))
                 .where(e => matches_endpoint(path_components, e))
-                .where(e => e.methods.any(m => m == http_context.request.method))
-                .first_or_default()
-                .unwrap();
+                .first_or_default();
 
-            if(endpoint == null) {
+            if(endpoint_attempt == null) {
                 throw new EndpointError.ROUTE_NOT_FOUND(@"No route found for /$(path_components.to_string(null, "/"))");
             }
 
+            var endpoint = endpoint_attempt.unwrap();
             var info = new RouteInformation() {
                 path_components = path_components,
                 named_components = extract_named_components(path_components, endpoint)
@@ -38,13 +38,19 @@ namespace Astralis {
         }
 
         private bool matches_endpoint(ReadOnlyAddressable<string> path_components, Endpoint endpoint) {
+            // "*" (with no preceding slash) is match all.
+            if(endpoint.route == "*") {
+                return true;
+            }
+            print(@"Checking route $(endpoint.route)\n");
             var endpoint_components = Wrap.array<string>(endpoint.route.split("/")).where(c => c.length != 0);
-            return path_components
-                .pair_up<string>(endpoint_components)
+            return endpoint_components
+                .pair_up<string>(path_components)
                 .all(p => {
                     if(p.value1_is_set != p.value2_is_set) {
                         return false;
                     }
+                    print(@"$(p.value1) - $(p.value2)\n");
                     if(p.value1.has_prefix("{") && p.value1.has_suffix("}")) {
                         return true;
                     }
@@ -74,7 +80,11 @@ namespace Astralis {
         }
 
         private Dictionary<string, string> extract_named_components(ReadOnlyAddressable<string> path_components, Endpoint endpoint) throws IndexError {
-            var endpoint_components = endpoint.route.split("/");
+            print(@"Extracting from $(endpoint.route)\n");
+            var endpoint_components = Wrap.array<string>(endpoint.route.split("/"))
+                .where(c => c.length != 0)
+                .to_immutable_buffer();
+                
             var dictionary = new Dictionary<string, string>();
             for(int i = 0; i < endpoint_components.length; i++) {
                 if(!endpoint_components[i].has_prefix("{") || !endpoint_components[i].has_suffix("}")) {
@@ -97,7 +107,7 @@ namespace Astralis {
     public interface Endpoint : Object {
 
         public abstract string route { get; }
-        public abstract Enumerable<Method> methods { owned get; }
+        public abstract Method[] methods { owned get; }
         public abstract async HttpResult handle_request(HttpContext http_context, RouteInformation route_context) throws Error;
 
     }