agentcarousel 0.2.3

Evaluate agents and skills with YAML fixtures, run cases (mock or live), and keep run rows in SQLite for reports and evidence export.
Documentation
use agentcarousel_core::FixtureFile;
use serde::de::DeserializeOwned;
use serde_json::Value;
use std::fs;
use std::path::{Component, Path, PathBuf};
use thiserror::Error;

#[derive(Debug, Clone, Copy)]
pub enum FixtureSource {
    Yaml,
    Toml,
}

#[derive(Debug, Error)]
pub enum FixtureLoadError {
    #[error("path traversal detected: {0}")]
    PathTraversal(PathBuf),
    #[error("unsupported fixture extension: {0}")]
    UnsupportedExtension(PathBuf),
    #[error("failed to read fixture: {0}")]
    ReadError(PathBuf),
    #[error("failed to parse fixture: {0}")]
    ParseError(String),
}

pub fn load_fixture(path: &Path) -> Result<FixtureFile, FixtureLoadError> {
    load_typed(path)
}

pub fn load_fixture_value(path: &Path) -> Result<Value, FixtureLoadError> {
    load_typed(path)
}

fn load_typed<T: DeserializeOwned>(path: &Path) -> Result<T, FixtureLoadError> {
    guard_path(path)?;
    let contents =
        fs::read_to_string(path).map_err(|_| FixtureLoadError::ReadError(path.to_path_buf()))?;

    match detect_source(path)? {
        FixtureSource::Yaml => serde_yaml::from_str(&contents)
            .map_err(|err| FixtureLoadError::ParseError(err.to_string())),
        FixtureSource::Toml => {
            toml::from_str(&contents).map_err(|err| FixtureLoadError::ParseError(err.to_string()))
        }
    }
}

fn detect_source(path: &Path) -> Result<FixtureSource, FixtureLoadError> {
    let extension = path
        .extension()
        .and_then(|ext| ext.to_str())
        .unwrap_or_default();
    match extension {
        "yaml" | "yml" => Ok(FixtureSource::Yaml),
        "toml" => Ok(FixtureSource::Toml),
        _ => Err(FixtureLoadError::UnsupportedExtension(path.to_path_buf())),
    }
}

fn guard_path(path: &Path) -> Result<(), FixtureLoadError> {
    if path
        .components()
        .any(|component| matches!(component, Component::ParentDir))
    {
        return Err(FixtureLoadError::PathTraversal(path.to_path_buf()));
    }
    Ok(())
}