moire-web 1.0.0

Web dashboard server for moire: REST API, MCP tools, and live entity graph viewer
Documentation
use axum::http::{HeaderMap, StatusCode, header};
use axum::response::IntoResponse;
use facet::Facet;
use moire_types::ApiError;

pub fn copy_request_headers(headers: &HeaderMap) -> Vec<(String, String)> {
    headers
        .iter()
        .filter_map(|(name, value)| {
            value
                .to_str()
                .ok()
                .map(|v| (name.as_str().to_string(), v.to_string()))
        })
        .collect()
}

pub fn skip_request_header(name: &str) -> bool {
    let lower = name.to_ascii_lowercase();
    lower == "host" || lower == "content-length" || is_hop_by_hop(&lower)
}

pub fn skip_response_header(name: &str) -> bool {
    let lower = name.to_ascii_lowercase();
    lower == "content-length" || is_hop_by_hop(&lower)
}

pub fn json_ok<T>(value: &T) -> axum::response::Response
where
    T: for<'facet> Facet<'facet>,
{
    match facet_json::to_string(value) {
        Ok(body) => (
            StatusCode::OK,
            [(header::CONTENT_TYPE, "application/json; charset=utf-8")],
            body,
        )
            .into_response(),
        Err(error) => (
            StatusCode::INTERNAL_SERVER_ERROR,
            [(header::CONTENT_TYPE, "text/plain; charset=utf-8")],
            format!("json encode error: {error}"),
        )
            .into_response(),
    }
}

pub fn json_error(status: StatusCode, message: impl Into<String>) -> axum::response::Response {
    json_with_status(
        status,
        &ApiError {
            error: message.into(),
        },
    )
}

pub fn json_with_status<T>(status: StatusCode, value: &T) -> axum::response::Response
where
    T: for<'facet> Facet<'facet>,
{
    match facet_json::to_string(value) {
        Ok(body) => (
            status,
            [(header::CONTENT_TYPE, "application/json; charset=utf-8")],
            body,
        )
            .into_response(),
        Err(error) => (
            StatusCode::INTERNAL_SERVER_ERROR,
            [(header::CONTENT_TYPE, "text/plain; charset=utf-8")],
            format!("json encode error: {error}"),
        )
            .into_response(),
    }
}

fn is_hop_by_hop(lowercase_name: &str) -> bool {
    matches!(
        lowercase_name,
        "connection"
            | "keep-alive"
            | "proxy-authenticate"
            | "proxy-authorization"
            | "te"
            | "trailers"
            | "transfer-encoding"
            | "upgrade"
    )
}