routa-server 0.14.3

Routa.js HTTP Server — axum adapter on top of routa-core
Documentation
use axum::{
    extract::{Query, State},
    http::StatusCode,
    Json,
};
use routa_core::harness::detect_repo_signals;
use routa_core::harness_automation::detect_repo_automations;
use serde_json::Value;

use crate::api::repo_context::{
    json_error, resolve_repo_root, RepoContextQuery, ResolveRepoRootOptions,
};
use crate::error::ServerError;
use crate::state::AppState;

pub async fn get_harness_repo_signals(
    State(state): State<AppState>,
    Query(query): Query<RepoContextQuery>,
) -> Result<Json<Value>, ServerError> {
    let repo_root = resolve_repo_root(
        &state,
        query.workspace_id.as_deref(),
        query.codebase_id.as_deref(),
        query.repo_path.as_deref(),
        "Missing harness repo context. Provide workspaceId, codebaseId, or repoPath.",
        ResolveRepoRootOptions::default(),
    )
    .await?;

    let report = detect_repo_signals(&repo_root).map_err(ServerError::Internal)?;
    Ok(Json(serde_json::to_value(report).map_err(|error| {
        ServerError::Internal(format!("Failed to serialize report: {error}"))
    })?))
}

pub async fn get_harness_automations(
    State(state): State<AppState>,
    Query(query): Query<RepoContextQuery>,
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
    let repo_root = resolve_repo_root(
        &state,
        query.workspace_id.as_deref(),
        query.codebase_id.as_deref(),
        query.repo_path.as_deref(),
        "缺少 harness 上下文,请提供 workspaceId / codebaseId / repoPath 之一",
        ResolveRepoRootOptions::default(),
    )
    .await
    .map_err(map_context_error(
        "Harness automations 上下文无效",
        "读取 Harness automation definitions 失败",
    ))?;

    let schedules = if let Some(workspace_id) = query.workspace_id.as_deref() {
        state
            .schedule_store
            .list_by_workspace(workspace_id)
            .await
            .map_err(map_internal_error("读取 Harness automation runtime 失败"))?
    } else {
        Vec::new()
    };

    let report = detect_repo_automations(&repo_root, &schedules).map_err(|error| {
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(json_error(
                "读取 Harness automation definitions 失败",
                error,
            )),
        )
    })?;

    Ok(Json(serde_json::to_value(report).map_err(|error| {
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(json_error(
                "读取 Harness automation definitions 失败",
                error.to_string(),
            )),
        )
    })?))
}

fn map_context_error(
    public_error: &'static str,
    internal_error: &'static str,
) -> impl Fn(ServerError) -> (StatusCode, Json<Value>) + Clone {
    move |error| match error {
        ServerError::BadRequest(details) => (
            StatusCode::BAD_REQUEST,
            Json(json_error(public_error, details)),
        ),
        other => (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(json_error(internal_error, other.to_string())),
        ),
    }
}

fn map_internal_error(
    public_error: &'static str,
) -> impl Fn(ServerError) -> (StatusCode, Json<Value>) + Clone {
    move |error| {
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(json_error(public_error, error.to_string())),
        )
    }
}