#![allow(clippy::needless_question_mark)]
use armature_core::*;
use armature_proc_macro::{controller, guard, middleware, module, routes};
fn require_admin_role(ctx: &GuardContext) -> Result<bool, Error> {
match ctx.get_header("authorization").map(String::as_str) {
Some("Bearer admin-token") => Ok(true),
_ => Err(Error::Forbidden("Admin role required".to_string())),
}
}
struct LoggerMiddleware;
#[async_trait::async_trait]
impl Middleware for LoggerMiddleware {
async fn handle(
&self,
request: HttpRequest,
next: middleware::Next,
) -> Result<HttpResponse, Error> {
info!("→ {} {}", request.method, request.path);
let response = next(request).await?;
info!("← Status: {}", response.status);
Ok(response)
}
}
#[controller("/api/public")]
#[middleware(LoggerMiddleware)]
#[derive(Default)]
struct PublicController;
#[routes]
impl PublicController {
#[get("/health")]
async fn health() -> Result<HttpResponse, Error> {
Ok(HttpResponse::ok().with_json(&serde_json::json!({
"status": "healthy",
"group": "public"
}))?)
}
}
#[controller("/api/v1")]
#[middleware(LoggerMiddleware)]
#[derive(Default)]
struct V1Controller;
#[routes]
impl V1Controller {
#[guard(AuthenticationGuard)]
#[get("/users")]
async fn users(_req: HttpRequest) -> Result<HttpResponse, Error> {
Ok(HttpResponse::ok().with_json(&serde_json::json!({
"users": ["alice", "bob", "charlie"],
"version": "v1"
}))?)
}
#[guard(AuthenticationGuard)]
#[get("/posts")]
async fn posts(_req: HttpRequest) -> Result<HttpResponse, Error> {
Ok(HttpResponse::ok().with_json(&serde_json::json!({
"posts": [
{"id": 1, "title": "First Post"},
{"id": 2, "title": "Second Post"}
],
"version": "v1"
}))?)
}
}
#[controller("/api/v1/admin")]
#[middleware(LoggerMiddleware)]
#[derive(Default)]
struct AdminController;
#[routes]
impl AdminController {
#[guard(AuthenticationGuard, CustomGuard::new(require_admin_role))]
#[get("/users")]
async fn users(_req: HttpRequest) -> Result<HttpResponse, Error> {
Ok(HttpResponse::ok().with_json(&serde_json::json!({
"users": [
{"id": 1, "username": "alice", "role": "admin"},
{"id": 2, "username": "bob", "role": "user"},
{"id": 3, "username": "charlie", "role": "user"}
],
"group": "admin",
"version": "v1"
}))?)
}
#[guard(AuthenticationGuard, CustomGuard::new(require_admin_role))]
#[get("/stats")]
async fn stats(_req: HttpRequest) -> Result<HttpResponse, Error> {
Ok(HttpResponse::ok().with_json(&serde_json::json!({
"total_users": 3,
"total_posts": 2,
"total_requests": 42,
"group": "admin"
}))?)
}
}
#[module(controllers: [PublicController, V1Controller, AdminController])]
#[derive(Default)]
struct AppModule;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let _guard = LogConfig::default().init();
info!("Route Groups Example");
info!("====================");
let api = RouteGroup::new().prefix("/api");
info!("Created base /api group");
let public = RouteGroup::new().prefix("/public").with_parent(&api);
info!("Created /api/public group (no auth)");
let v1 = RouteGroup::new().prefix("/v1").with_parent(&api);
info!("Created /api/v1 group");
let admin = RouteGroup::new().prefix("/admin").with_parent(&v1);
info!("Created /api/v1/admin group");
info!("\nRoute Prefix Examples:");
info!("----------------------");
let public_health = public.apply_prefix("/health");
info!("Public health: {}", public_health);
let v1_users = v1.apply_prefix("/users");
info!("V1 users: {}", v1_users);
let admin_users = admin.apply_prefix("/users");
info!("Admin users: {}", admin_users);
info!("\nStarting server on http://localhost:3000");
info!("Try these endpoints:");
info!(" GET /api/public/health (no auth)");
info!(" GET /api/v1/users (requires Authorization: Bearer <token>)");
info!(" GET /api/v1/posts (requires Authorization: Bearer <token>)");
info!(" GET /api/v1/admin/users (requires Authorization: Bearer admin-token)");
info!(" GET /api/v1/admin/stats (requires Authorization: Bearer admin-token)");
let app = Application::create::<AppModule>().await;
info!("\n✓ Server started successfully");
info!("Press Ctrl+C to stop");
app.listen(3000).await?;
Ok(())
}