islands-actions 0.1.0

Server-side typed action plumbing for islands.rs: ActionContext, ActionError, ActionRegistry, and the /_action/:name router.
Documentation
use std::sync::Arc;

use axum::body::Bytes;
use axum::extract::{Path, State};
use axum::http::HeaderMap;
use axum::response::IntoResponse;
use axum::routing::post;
use axum::Router;

use crate::context::ActionContext;
use crate::error::ActionError;
use crate::registry::ActionRegistry;

/// Build an Axum router that exposes all registered actions at `/_action/:name`.
pub fn action_router(registry: ActionRegistry) -> Router {
    let registry = Arc::new(registry);
    Router::new()
        .route("/_action/:name", post(dispatch))
        .with_state(registry)
}

async fn dispatch(
    State(registry): State<Arc<ActionRegistry>>,
    Path(name): Path<String>,
    headers: HeaderMap,
    body: Bytes,
) -> impl IntoResponse {
    let handler = match registry.get(&name) {
        Some(h) => h,
        None => return ActionError::NotFound.into_response(),
    };
    let context = ActionContext::new(headers);
    match handler.invoke(context, body).await {
        Ok(bytes) => axum::response::Response::builder()
            .header("content-type", "application/json")
            .body(axum::body::Body::from(bytes))
            .unwrap(),
        Err(e) => e.into_response(),
    }
}