mubit-sdk 0.6.0

Umbrella Rust SDK for Mubit core/control planes
Documentation
#![allow(dead_code)]

use mubit_sdk::{Client, ClientConfig, TransportMode};
use serde_json::{json, Value};
use std::env;
use std::error::Error;
use std::time::{SystemTime, UNIX_EPOCH};

#[derive(Debug, Clone, Copy)]
pub struct ScenarioConfig {
    pub expect_direct_search_enabled: bool,
}

impl ScenarioConfig {
    pub fn from_env() -> Self {
        Self {
            expect_direct_search_enabled: env_bool("MUBIT_EXPECT_DIRECT_SEARCH_ENABLED", false),
        }
    }
}

pub fn boxed_error(message: impl Into<String>) -> Box<dyn Error> {
    Box::new(std::io::Error::other(message.into()))
}

pub fn require(condition: bool, message: impl Into<String>) -> Result<(), Box<dyn Error>> {
    if condition {
        Ok(())
    } else {
        Err(boxed_error(message))
    }
}

pub fn env_bool(name: &str, fallback: bool) -> bool {
    let fallback_raw = if fallback { "1" } else { "0" };
    let raw = env::var(name)
        .unwrap_or_else(|_| fallback_raw.to_string())
        .trim()
        .to_ascii_lowercase();
    matches!(raw.as_str(), "1" | "true" | "yes" | "on")
}

pub fn is_permission_denied_like(message: &str) -> bool {
    let lowered = message.to_ascii_lowercase();
    lowered.contains("permission denied")
        || lowered.contains("permissiondenied")
        || lowered.contains("403")
        || lowered.contains("disabled")
}

pub fn new_run_id(prefix: &str) -> String {
    let ms = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_millis();
    let pid = std::process::id();
    format!("{prefix}_{ms}_{pid}")
}

pub async fn create_client() -> Result<Client, Box<dyn Error>> {
    let endpoint =
        env::var("MUBIT_ENDPOINT").unwrap_or_else(|_| "http://127.0.0.1:3000".to_string());
    let grpc_endpoint =
        env::var("MUBIT_GRPC_ENDPOINT").unwrap_or_else(|_| "127.0.0.1:50051".to_string());
    let timeout_ms = env::var("MUBIT_TIMEOUT_MS")
        .ok()
        .and_then(|raw| raw.parse::<u64>().ok())
        .unwrap_or(30_000);

    let api_key =
        env::var("MUBIT_API_KEY").map_err(|_| boxed_error("MUBIT_API_KEY must be set"))?;

    let mut cfg = ClientConfig::new(endpoint).transport("http");
    cfg.grpc_endpoint = Some(grpc_endpoint);
    cfg.timeout_ms = timeout_ms;
    cfg.api_key = Some(api_key.clone());

    let client = Client::new(cfg)?;
    client.set_transport(TransportMode::Http);
    client.set_api_key(Some(api_key));

    let _ = client.auth.health().await?;

    Ok(client)
}

pub async fn cleanup_run(client: &Client, run_id: &str) -> bool {
    let control_ok = client
        .delete_run(json!({ "run_id": run_id }))
        .await
        .is_ok();
    let core_ok = match client.core.delete_run(json!({ "run_id": run_id })).await {
        Ok(_) => true,
        Err(err) => is_permission_denied_like(&err.to_string()),
    };
    control_ok && core_ok
}

pub fn print_summary(
    name: &str,
    passed: bool,
    detail: &str,
    metrics: &Value,
    duration_sec: f64,
    cleanup_ok: bool,
) {
    println!(
        "[{}] {} {:.2}s  {}",
        if passed { "PASS" } else { "FAIL" },
        name,
        duration_sec,
        detail
    );
    if let Some(map) = metrics.as_object() {
        if !map.is_empty() {
            println!("metrics: {}", metrics);
        }
    }
    println!("cleanup: {}", if cleanup_ok { "PASS" } else { "FAIL" });
}