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 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: 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 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 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 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(); 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 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 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 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 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 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(); 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 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(); 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 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>(); 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 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(); 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.named_components.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 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()); 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"); 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"); server.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\" }"; } }