assay-workflow 0.1.7

Durable workflow engine with REST+SSE API, PostgreSQL/SQLite backends. Embeddable library or standalone server.
Documentation
use std::sync::Arc;

use axum::extract::{Path, State};
use axum::routing::{get, post};
use axum::{Json, Router};
use serde::Deserialize;
use utoipa::ToSchema;

use crate::api::workflows::AppError;
use crate::api::AppState;
use crate::store::WorkflowStore;

pub fn router<S: WorkflowStore + 'static>() -> Router<Arc<AppState<S>>> {
    Router::new()
        .route("/namespaces", post(create_namespace).get(list_namespaces))
        .route(
            "/namespaces/{name}",
            get(get_namespace_stats).delete(delete_namespace),
        )
}

#[derive(Deserialize, ToSchema)]
pub struct CreateNamespaceRequest {
    pub name: String,
}

#[utoipa::path(
    post, path = "/api/v1/namespaces",
    tag = "namespaces",
    request_body = CreateNamespaceRequest,
    responses(
        (status = 201, description = "Namespace created"),
        (status = 500, description = "Internal error"),
    ),
)]
pub async fn create_namespace<S: WorkflowStore>(
    State(state): State<Arc<AppState<S>>>,
    Json(req): Json<CreateNamespaceRequest>,
) -> Result<axum::http::StatusCode, AppError> {
    state.engine.create_namespace(&req.name).await?;
    Ok(axum::http::StatusCode::CREATED)
}

#[utoipa::path(
    get, path = "/api/v1/namespaces",
    tag = "namespaces",
    responses(
        (status = 200, description = "List of namespaces", body = Vec<NamespaceRecord>),
    ),
)]
pub async fn list_namespaces<S: WorkflowStore>(
    State(state): State<Arc<AppState<S>>>,
) -> Result<Json<Vec<serde_json::Value>>, AppError> {
    let namespaces = state.engine.list_namespaces().await?;
    let json: Vec<serde_json::Value> = namespaces
        .into_iter()
        .map(|n| serde_json::to_value(n).unwrap_or_default())
        .collect();
    Ok(Json(json))
}

#[utoipa::path(
    get, path = "/api/v1/namespaces/{name}",
    tag = "namespaces",
    params(("name" = String, Path, description = "Namespace name")),
    responses(
        (status = 200, description = "Namespace statistics", body = NamespaceStats),
    ),
)]
pub async fn get_namespace_stats<S: WorkflowStore>(
    State(state): State<Arc<AppState<S>>>,
    Path(name): Path<String>,
) -> Result<Json<serde_json::Value>, AppError> {
    let stats = state.engine.get_namespace_stats(&name).await?;
    Ok(Json(serde_json::to_value(stats)?))
}

#[utoipa::path(
    delete, path = "/api/v1/namespaces/{name}",
    tag = "namespaces",
    params(("name" = String, Path, description = "Namespace name")),
    responses(
        (status = 200, description = "Namespace deleted"),
        (status = 404, description = "Namespace not found or is 'main'"),
    ),
)]
pub async fn delete_namespace<S: WorkflowStore>(
    State(state): State<Arc<AppState<S>>>,
    Path(name): Path<String>,
) -> Result<axum::http::StatusCode, AppError> {
    if name == "main" {
        return Err(AppError::NotFound(
            "cannot delete the 'main' namespace".to_string(),
        ));
    }
    let deleted = state.engine.delete_namespace(&name).await?;
    if deleted {
        Ok(axum::http::StatusCode::OK)
    } else {
        Err(AppError::NotFound(format!("namespace {name}")))
    }
}

use crate::store::{NamespaceRecord, NamespaceStats};