|
|
@@ -6,17 +6,15 @@ using Invercargill.DataStructures;
|
|
|
* PathRouting Example
|
|
|
*
|
|
|
* Demonstrates path component parsing and routing in Astralis.
|
|
|
- * Uses Invercargill Enumerable for path processing.
|
|
|
+ * Uses the Router's named segment feature for dynamic routing.
|
|
|
*/
|
|
|
-void main() {
|
|
|
- var router = new Router();
|
|
|
- var server = new Server(8082, router);
|
|
|
-
|
|
|
- // Root path
|
|
|
- router.map_func("/", (context) => {
|
|
|
+
|
|
|
+// Root handler - shows available endpoints
|
|
|
+class RootHandler : Object, RouteHandler {
|
|
|
+ public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
|
|
|
return new BufferedHttpResult.from_string(
|
|
|
"""Welcome to the Path Routing Example!
|
|
|
-
|
|
|
+
|
|
|
Available endpoints:
|
|
|
GET / - This message
|
|
|
GET /hello - Simple greeting
|
|
|
@@ -25,35 +23,32 @@ Available endpoints:
|
|
|
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 /api/v1/items/{id} - Get item by ID
|
|
|
GET /files/{category}/{name} - Get file by category and name
|
|
|
+ GET /pathinfo - Path information demo
|
|
|
"""
|
|
|
);
|
|
|
- });
|
|
|
-
|
|
|
- // Simple path
|
|
|
- router.map_func("/hello", (context) => {
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Simple hello handler
|
|
|
+class HelloHandler : Object, RouteHandler {
|
|
|
+ public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
|
|
|
return new BufferedHttpResult.from_string("Hello, World!");
|
|
|
- });
|
|
|
-
|
|
|
- // Path with one component (simulated dynamic routing)
|
|
|
- router.map_func("/hello/", (context) => {
|
|
|
- var components = context.request.path_components.to_vector();
|
|
|
-
|
|
|
- if (components.count() < 2) {
|
|
|
- return new BufferedHttpResult.from_string(
|
|
|
- "Please provide a name: /hello/{name}",
|
|
|
- StatusCode.BAD_REQUEST
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- var name = components[1];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 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");
|
|
|
return new BufferedHttpResult.from_string(@"Hello, $name!");
|
|
|
- });
|
|
|
-
|
|
|
- // Users list endpoint
|
|
|
- router.map_func("/users", (context) => {
|
|
|
- // Simulated user data using Invercargill data structures
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Users list handler
|
|
|
+class UsersHandler : Object, RouteHandler {
|
|
|
+ public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) 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"));
|
|
|
@@ -81,23 +76,13 @@ Available endpoints:
|
|
|
StatusCode.OK,
|
|
|
headers
|
|
|
);
|
|
|
- });
|
|
|
-
|
|
|
- // User by ID endpoint (simulated)
|
|
|
- router.map_func("/users/", (context) => {
|
|
|
- var components = context.request.path_components.to_vector();
|
|
|
-
|
|
|
- if (components.count() < 2) {
|
|
|
- var headers = new Catalogue<string, string>();
|
|
|
- headers.add("Content-Type", "application/json");
|
|
|
- return new BufferedHttpResult.from_string(
|
|
|
- @"{ \"error\": \"User ID required\" }",
|
|
|
- StatusCode.BAD_REQUEST,
|
|
|
- headers
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- var id_str = components[1];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 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");
|
|
|
var id = int.parse(id_str);
|
|
|
|
|
|
// Simulated user lookup
|
|
|
@@ -126,89 +111,48 @@ Available endpoints:
|
|
|
StatusCode.OK,
|
|
|
headers
|
|
|
);
|
|
|
- });
|
|
|
-
|
|
|
- // User posts endpoint (nested path)
|
|
|
- router.map_func("/users/", (context) => {
|
|
|
- var components = context.request.path_components.to_vector();
|
|
|
-
|
|
|
- // Check for /users/{id}/posts pattern
|
|
|
- if (components.count() >= 3 && components[2] == "posts") {
|
|
|
- var id_str = components[1];
|
|
|
- var id = int.parse(id_str);
|
|
|
-
|
|
|
- // Simulated posts for user
|
|
|
- var posts = new Series<Post>();
|
|
|
- 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<string>();
|
|
|
- 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<string>("", (acc, s) => acc + s);
|
|
|
-
|
|
|
- var headers = new Catalogue<string, string>();
|
|
|
- headers.add("Content-Type", "application/json");
|
|
|
- return new BufferedHttpResult.from_string(
|
|
|
- json_string,
|
|
|
- StatusCode.OK,
|
|
|
- headers
|
|
|
- );
|
|
|
- }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 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");
|
|
|
+ var id = int.parse(id_str);
|
|
|
|
|
|
- // Fall back to user detail
|
|
|
- if (components.count() < 2) {
|
|
|
- var headers = new Catalogue<string, string>();
|
|
|
- headers.add("Content-Type", "application/json");
|
|
|
- return new BufferedHttpResult.from_string(
|
|
|
- @"{ \"error\": \"User ID required\" }",
|
|
|
- StatusCode.BAD_REQUEST,
|
|
|
- headers
|
|
|
- );
|
|
|
- }
|
|
|
+ // Simulated posts for user
|
|
|
+ var posts = new Series<Post>();
|
|
|
+ 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 id_str = components[1];
|
|
|
- var id = int.parse(id_str);
|
|
|
+ var json_parts = new Series<string>();
|
|
|
+ json_parts.add(@"{ \"user_id\": $id, \"posts\": [");
|
|
|
|
|
|
- var users = new Series<User>();
|
|
|
- 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"));
|
|
|
+ bool first = true;
|
|
|
+ posts.to_immutable_buffer().iterate((post) => {
|
|
|
+ if (!first) json_parts.add(", ");
|
|
|
+ json_parts.add(post.to_json());
|
|
|
+ first = false;
|
|
|
+ });
|
|
|
|
|
|
- var user = users.to_immutable_buffer()
|
|
|
- .first_or_default(u => u.id == id);
|
|
|
+ json_parts.add("] }");
|
|
|
|
|
|
- if (user == null) {
|
|
|
- var headers = new Catalogue<string, string>();
|
|
|
- headers.add("Content-Type", "application/json");
|
|
|
- return new BufferedHttpResult.from_string(
|
|
|
- @"{ \"error\": \"User not found\" }",
|
|
|
- StatusCode.NOT_FOUND,
|
|
|
- headers
|
|
|
- );
|
|
|
- }
|
|
|
+ var json_string = json_parts.to_immutable_buffer()
|
|
|
+ .aggregate<string>("", (acc, s) => acc + s);
|
|
|
|
|
|
var headers = new Catalogue<string, string>();
|
|
|
headers.add("Content-Type", "application/json");
|
|
|
return new BufferedHttpResult.from_string(
|
|
|
- user.to_json(),
|
|
|
+ json_string,
|
|
|
StatusCode.OK,
|
|
|
headers
|
|
|
);
|
|
|
- });
|
|
|
-
|
|
|
- // API versioned endpoint
|
|
|
- router.map_func("/api/v1/status", (context) => {
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// API status handler
|
|
|
+class ApiStatusHandler : Object, RouteHandler {
|
|
|
+ public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
|
|
|
var status = new Dictionary<string, string>();
|
|
|
status.set("status", "operational");
|
|
|
status.set("version", "1.0.0");
|
|
|
@@ -236,23 +180,13 @@ Available endpoints:
|
|
|
StatusCode.OK,
|
|
|
headers
|
|
|
);
|
|
|
- });
|
|
|
-
|
|
|
- // API items endpoint
|
|
|
- router.map_func("/api/v1/items/", (context) => {
|
|
|
- var components = context.request.path_components.to_vector();
|
|
|
-
|
|
|
- if (components.count() < 4) {
|
|
|
- var headers = new Catalogue<string, string>();
|
|
|
- headers.add("Content-Type", "application/json");
|
|
|
- return new BufferedHttpResult.from_string(
|
|
|
- @"{ \"error\": \"Item ID required\" }",
|
|
|
- StatusCode.BAD_REQUEST,
|
|
|
- headers
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- var id_str = components[3];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 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");
|
|
|
var id = int.parse(id_str);
|
|
|
|
|
|
var items = new Dictionary<int, Item>();
|
|
|
@@ -278,41 +212,31 @@ Available endpoints:
|
|
|
StatusCode.NOT_FOUND,
|
|
|
headers
|
|
|
);
|
|
|
- });
|
|
|
-
|
|
|
- // Files endpoint with category and name
|
|
|
- router.map_func("/files/", (context) => {
|
|
|
- var components = context.request.path_components.to_vector();
|
|
|
-
|
|
|
- if (components.count() < 3) {
|
|
|
- var headers = new Catalogue<string, string>();
|
|
|
- headers.add("Content-Type", "application/json");
|
|
|
- return new BufferedHttpResult.from_string(
|
|
|
- @"{ \"error\": \"Category and filename required\" }",
|
|
|
- StatusCode.BAD_REQUEST,
|
|
|
- headers
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- var category = components[1];
|
|
|
- var name = components[2];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 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");
|
|
|
|
|
|
// Simulated file lookup
|
|
|
- var files = new Dictionary<string, Dictionary<string, File>>();
|
|
|
- var docs = new Dictionary<string, File>();
|
|
|
- docs.set("readme.txt", new File("readme.txt", "docs", "This is the readme file"));
|
|
|
- docs.set("guide.pdf", new File("guide.pdf", "docs", "User guide"));
|
|
|
+ var files = new Dictionary<string, Dictionary<string, ExampleFile>>();
|
|
|
+ var docs = new Dictionary<string, ExampleFile>();
|
|
|
+ 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<string, File>();
|
|
|
- images.set("logo.png", new File("logo.png", "images", "Company logo"));
|
|
|
- images.set("banner.jpg", new File("banner.jpg", "images", "Website banner"));
|
|
|
+ var images = new Dictionary<string, ExampleFile>();
|
|
|
+ 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<string, File>? category_files = null;
|
|
|
+ Dictionary<string, ExampleFile>? category_files = null;
|
|
|
if (files.try_get(category, out category_files)) {
|
|
|
- File? file = null;
|
|
|
+ ExampleFile? file = null;
|
|
|
if (category_files.try_get(name, out file)) {
|
|
|
var headers = new Catalogue<string, string>();
|
|
|
headers.add("Content-Type", "application/json");
|
|
|
@@ -331,29 +255,55 @@ Available endpoints:
|
|
|
StatusCode.NOT_FOUND,
|
|
|
headers
|
|
|
);
|
|
|
- });
|
|
|
-
|
|
|
- // Path information endpoint - shows all path components
|
|
|
- router.map_func("/pathinfo", (context) => {
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Path info handler
|
|
|
+class PathInfoHandler : Object, RouteHandler {
|
|
|
+ public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
|
|
|
var parts = new Series<string>();
|
|
|
parts.add("Path Information:\n");
|
|
|
- parts.add(@" Raw path: $(context.request.raw_path)\n");
|
|
|
- parts.add(@" Path components: $(context.request.path_components.count())\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");
|
|
|
|
|
|
- context.request.path_components
|
|
|
+ http_context.request.path_components
|
|
|
.with_positions()
|
|
|
.iterate((pair) => {
|
|
|
parts.add(@" [$((int)pair.position)]: $(pair.item)\n");
|
|
|
});
|
|
|
|
|
|
- parts.add(@"\n Query string: $(context.request.query_string)\n");
|
|
|
+ parts.add(@"\n Query string: $(http_context.request.query_string)\n");
|
|
|
+
|
|
|
+ // Show named segments from route context
|
|
|
+ parts.add("\n Named segments:\n");
|
|
|
+ route_context.named_segments.to_immutable_buffer()
|
|
|
+ .iterate((kv) => {
|
|
|
+ parts.add(@" $(kv.key): $(kv.value)\n");
|
|
|
+ });
|
|
|
|
|
|
var result = parts.to_immutable_buffer()
|
|
|
.aggregate<string>("", (acc, s) => acc + s);
|
|
|
|
|
|
return new BufferedHttpResult.from_string(result);
|
|
|
- });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void main() {
|
|
|
+ var router = new Router();
|
|
|
+ var server = new Server(8082, router);
|
|
|
+
|
|
|
+ // 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());
|
|
|
|
|
|
print("Path Routing Example Server running on port 8082\n");
|
|
|
print("Try these endpoints:\n");
|
|
|
@@ -424,12 +374,12 @@ class Item {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-class File {
|
|
|
+class ExampleFile {
|
|
|
public string name { get; private set; }
|
|
|
public string category { get; private set; }
|
|
|
public string description { get; private set; }
|
|
|
|
|
|
- public File(string name, string category, string description) {
|
|
|
+ public ExampleFile(string name, string category, string description) {
|
|
|
this.name = name;
|
|
|
this.category = category;
|
|
|
this.description = description;
|