using Astralis; using Invercargill; using Invercargill.DataStructures; /** * PathRouting Example * * Demonstrates path component parsing and routing in Astralis. * Uses the EndpointRouter's named segment feature for dynamic routing. */ // Root handler - shows available endpoints class RootEndpoint : Object, Endpoint { public async HttpResult handle_request(HttpContext http_context, RouteContext route) throws Error { return new HttpStringResult("""Welcome to the Path Routing Example! Available endpoints: GET / - This message GET /hello - Simple greeting GET /hello/{name} - Greeting with name GET /users - List all users GET /users/{id} - Get user by ID GET /users/{id}/posts - Get posts for a user GET /api/v1/status - API status GET /api/v1/items/{id} - Get item by ID GET /files/{category}/{name} - Get file by category and name GET /pathinfo - Path information demo """); } } // Simple hello handler class HelloEndpoint : Object, Endpoint { public async HttpResult handle_request(HttpContext http_context, RouteContext route) throws Error { return new HttpStringResult("Hello, World!"); } } // Hello with name handler - uses named segment class HelloNameEndpoint : Object, Endpoint { public async HttpResult handle_request(HttpContext http_context, RouteContext route_info) throws Error { string? name = null; route_info.mapped_parameters.try_get("name", out name); return new HttpStringResult(@"Hello, $(name ?? "Unknown")!"); } } // Users list handler class UsersEndpoint : Object, Endpoint { public async HttpResult handle_request(HttpContext http_context, RouteContext route) throws Error { var users = new Series(); users.add(new User(1, "Alice", "alice@example.com")); users.add(new User(2, "Bob", "bob@example.com")); users.add(new User(3, "Charlie", "charlie@example.com")); var json_parts = new Series(); json_parts.add(@"{ \"users\": ["); bool first = true; users.to_immutable_buffer().iterate((user) => { if (!first) json_parts.add(", "); json_parts.add(user.to_json()); first = false; }); json_parts.add("] }"); var json_string = json_parts.to_immutable_buffer() .aggregate("", (acc, s) => acc + s); return new HttpStringResult(json_string) .set_header("Content-Type", "application/json"); } } // User by ID handler - uses named segment class UserByIdEndpoint : Object, Endpoint { public async HttpResult handle_request(HttpContext http_context, RouteContext route_info) throws Error { string? id_str = null; route_info.mapped_parameters.try_get("id", out id_str); var id = int.parse(id_str ?? "0"); // Simulated user lookup var users = new Series(); users.add(new User(1, "Alice", "alice@example.com")); users.add(new User(2, "Bob", "bob@example.com")); users.add(new User(3, "Charlie", "charlie@example.com")); var user = users.to_immutable_buffer() .first_or_default(u => u.id == id); if (user == null) { return new HttpStringResult(@"{ \"error\": \"User not found\" }") .set_header("Content-Type", "application/json"); } return new HttpStringResult(user.to_json()) .set_header("Content-Type", "application/json"); } } // User posts handler - uses named segment class UserPostsEndpoint : Object, Endpoint { public async HttpResult handle_request(HttpContext http_context, RouteContext route_info) throws Error { string? id_str = null; route_info.mapped_parameters.try_get("id", out id_str); var id = int.parse(id_str ?? "0"); // Simulated posts for user var posts = new Series(); posts.add(new Post(101, id, "First Post", "This is my first post")); posts.add(new Post(102, id, "Second Post", "This is my second post")); var json_parts = new Series(); json_parts.add(@"{ \"user_id\": $id, \"posts\": ["); bool first = true; posts.to_immutable_buffer().iterate((post) => { if (!first) json_parts.add(", "); json_parts.add(post.to_json()); first = false; }); json_parts.add("] }"); var json_string = json_parts.to_immutable_buffer() .aggregate("", (acc, s) => acc + s); return new HttpStringResult(json_string) .set_header("Content-Type", "application/json"); } } // API status handler class ApiStatusEndpoint : Object, Endpoint { public async HttpResult handle_request(HttpContext http_context, RouteContext route) throws Error { var status = new Dictionary(); status.set("status", "operational"); status.set("version", "1.0.0"); status.set("timestamp", new DateTime.now_local().format_iso8601()); var json_parts = new Series(); json_parts.add("{ "); bool first = true; status.to_immutable_buffer().iterate((kv) => { if (!first) json_parts.add(", "); json_parts.add(@"\"$(kv.key)\": \"$(kv.value)\""); first = false; }); json_parts.add(" }"); var json_string = json_parts.to_immutable_buffer() .aggregate("", (acc, s) => acc + s); return new HttpStringResult(json_string) .set_header("Content-Type", "application/json"); } } // API item handler - uses named segment class ApiItemEndpoint : Object, Endpoint { public async HttpResult handle_request(HttpContext http_context, RouteContext route_info) throws Error { string? id_str = null; route_info.mapped_parameters.try_get("id", out id_str); var id = int.parse(id_str ?? "0"); var items = new Dictionary(); items.set(1, new Item(1, "Widget", "A useful widget", 9.99)); items.set(2, new Item(2, "Gadget", "A fancy gadget", 19.99)); items.set(3, new Item(3, "Doohickey", "A mysterious doohickey", 29.99)); Item? item = null; if (items.try_get(id, out item)) { return new HttpStringResult(item.to_json()) .set_header("Content-Type", "application/json"); } return new HttpStringResult(@"{ \"error\": \"Item not found\" }") .set_header("Content-Type", "application/json"); } } // Files handler - uses two named segments class FilesEndpoint : Object, Endpoint { public async HttpResult handle_request(HttpContext http_context, RouteContext route_info) throws Error { string? category = null; string? name = null; route_info.mapped_parameters.try_get("category", out category); route_info.mapped_parameters.try_get("name", out name); // Simulated file lookup var files = new Dictionary>(); var docs = new Dictionary(); docs.set("readme.txt", new ExampleFile("readme.txt", "docs", "This is the readme file")); docs.set("guide.pdf", new ExampleFile("guide.pdf", "docs", "User guide")); var images = new Dictionary(); images.set("logo.png", new ExampleFile("logo.png", "images", "Company logo")); images.set("banner.jpg", new ExampleFile("banner.jpg", "images", "Website banner")); files.set("docs", docs); files.set("images", images); Dictionary? category_files = null; if (files.try_get(category ?? "", out category_files)) { ExampleFile? file = null; if (category_files.try_get(name ?? "", out file)) { return new HttpStringResult(file.to_json()) .set_header("Content-Type", "application/json"); } } return new HttpStringResult(@"{ \"error\": \"File not found in category '$category'\" }") .set_header("Content-Type", "application/json"); } } // Path info handler class PathInfoEndpoint : Object, Endpoint { public async HttpResult handle_request(HttpContext http_context, RouteContext route_info) throws Error { var parts = new Series(); parts.add("Path Information:\n"); parts.add(@" Raw path: $(http_context.request.raw_path)\n"); parts.add(@" Path components: $(http_context.request.path_components.count())\n"); parts.add("\n Components:\n"); http_context.request.path_components .with_positions() .iterate((pair) => { parts.add(@" [$((int)pair.position)]: $(pair.item)\n"); }); parts.add(@"\n Query string: $(http_context.request.query_string)\n"); // Show named segments from route information parts.add("\n Named segments:\n"); route_info.mapped_parameters.to_immutable_buffer() .iterate((kv) => { parts.add(@" $(kv.key): $(kv.value)\n"); }); var result = parts.to_immutable_buffer() .aggregate("", (acc, s) => acc + s); return new HttpStringResult(result); } } void main() { var application = new WebApplication(8082); application.container.register_scoped(() => new RootEndpoint()) .with_metadata(new EndpointRoute("/")); application.container.register_scoped(() => new HelloEndpoint()) .with_metadata(new EndpointRoute("/hello")); application.container.register_scoped(() => new HelloNameEndpoint()) .with_metadata(new EndpointRoute("/hello/{name}")); application.container.register_scoped(() => new UsersEndpoint()) .with_metadata(new EndpointRoute("/users")); application.container.register_scoped(() => new UserByIdEndpoint()) .with_metadata(new EndpointRoute("/users/{id}")); application.container.register_scoped(() => new UserPostsEndpoint()) .with_metadata(new EndpointRoute("/users/{id}/posts")); application.container.register_scoped(() => new ApiStatusEndpoint()) .with_metadata(new EndpointRoute("/api/v1/status")); application.container.register_scoped(() => new ApiItemEndpoint()) .with_metadata(new EndpointRoute("/api/v1/items/{id}")); application.container.register_scoped(() => new FilesEndpoint()) .with_metadata(new EndpointRoute("/files/{category}/{name}")); application.container.register_scoped(() => new PathInfoEndpoint()) .with_metadata(new EndpointRoute("/pathinfo")); print("Path Routing Example Server running on port 8082\n"); print("Try these endpoints:\n"); print(" - http://localhost:8082/\n"); print(" - http://localhost:8082/hello\n"); print(" - http://localhost:8082/hello/Alice\n"); print(" - http://localhost:8082/users\n"); print(" - http://localhost:8082/users/1\n"); print(" - http://localhost:8082/users/1/posts\n"); print(" - http://localhost:8082/api/v1/status\n"); print(" - http://localhost:8082/api/v1/items/2\n"); print(" - http://localhost:8082/files/docs/readme.txt\n"); print(" - http://localhost:8082/pathinfo?test=1\n"); application.run(); } // Helper classes for the example class User { public int id { get; private set; } public string name { get; private set; } public string email { get; private set; } public User(int id, string name, string email) { this.id = id; this.name = name; this.email = email; } public string to_json() { return @"{ \"id\": $id, \"name\": \"$name\", \"email\": \"$email\" }"; } } class Post { public int id { get; private set; } public int user_id { get; private set; } public string title { get; private set; } public string content { get; private set; } public Post(int id, int user_id, string title, string content) { this.id = id; this.user_id = user_id; this.title = title; this.content = content; } public string to_json() { return @"{ \"id\": $id, \"user_id\": $user_id, \"title\": \"$title\", \"content\": \"$content\" }"; } } class Item { public int id { get; private set; } public string name { get; private set; } public string description { get; private set; } public double price { get; private set; } public Item(int id, string name, string description, double price) { this.id = id; this.name = name; this.description = description; this.price = price; } public string to_json() { return @"{ \"id\": $id, \"name\": \"$name\", \"description\": \"$description\", \"price\": $price }"; } } class ExampleFile { public string name { get; private set; } public string category { get; private set; } public string description { get; private set; } public ExampleFile(string name, string category, string description) { this.name = name; this.category = category; this.description = description; } public string to_json() { return @"{ \"name\": \"$name\", \"category\": \"$category\", \"description\": \"$description\" }"; } }