swarm-engine-eval 0.1.6

Evaluation framework for SwarmEngine
Documentation
//! Environment Registry - 評価環境ファクトリ
//!
//! シナリオの `environment.env_type` から具体的な評価環境を生成するファクトリ。
//!
//! ## 設計
//!
//! すべての環境は `core::Environment` トレイトを実装する。
//!
//! ```text
//! EvalScenario.environment
//!//!     ├── env_type: "maze"
//!     └── params: { map: "...", worker_count: 1 }
//!//!//!     EnvironmentRegistry.create()
//!//!//!     EnvironmentBox (Box<dyn Environment>)
//! ```

use swarm_engine_core::environment::{DefaultEnvironment, EnvironmentBox};

use crate::environments::{MazeEnvironment, NoneEnvironment};

// ============================================================================
// EnvironmentType
// ============================================================================

/// 環境タイプ識別子
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EnvironmentType {
    /// 環境なし(何もしない)
    None,
    /// 迷路探索環境
    Maze,
    /// コードベース環境(デフォルト: Bash/Read/Write/Grep/Glob)
    Codebase,
}

impl EnvironmentType {
    /// 文字列から解析
    pub fn parse(s: &str) -> Option<Self> {
        match s.to_lowercase().as_str() {
            "none" | "" => Some(Self::None),
            "maze" => Some(Self::Maze),
            "codebase" | "default" => Some(Self::Codebase),
            _ => None,
        }
    }

    /// 文字列表現
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::None => "none",
            Self::Maze => "maze",
            Self::Codebase => "codebase",
        }
    }
}

// ============================================================================
// EnvironmentRegistry
// ============================================================================

/// 環境レジストリ
///
/// シナリオの `environment` セクションから EvalEnvironment インスタンスを生成。
pub struct EnvironmentRegistry;

impl EnvironmentRegistry {
    /// 環境を作成
    ///
    /// # Arguments
    ///
    /// * `env_type` - 環境タイプ文字列 ("none", "maze", "codebase")
    /// * `params` - 環境固有のパラメータ
    ///
    /// # Returns
    ///
    /// `EnvironmentBox` (Box<dyn Environment>)
    pub fn create(
        env_type: &str,
        params: &serde_json::Value,
    ) -> Result<EnvironmentBox, EnvironmentError> {
        let env_type = EnvironmentType::parse(env_type)
            .ok_or_else(|| EnvironmentError::UnknownType(env_type.to_string()))?;

        match env_type {
            EnvironmentType::None => Ok(Box::new(NoneEnvironment)),

            EnvironmentType::Maze => {
                let map_str = params.get("map").and_then(|v| v.as_str()).ok_or_else(|| {
                    EnvironmentError::Config("Maze environment requires 'map' parameter".into())
                })?;

                let worker_count = params
                    .get("worker_count")
                    .and_then(|v| v.as_u64())
                    .unwrap_or(1) as usize;

                Ok(Box::new(MazeEnvironment::from_str(map_str, worker_count)))
            }

            EnvironmentType::Codebase => Ok(Box::new(DefaultEnvironment::new())),
        }
    }

    /// サポートされている環境タイプ一覧
    pub fn supported_types() -> Vec<EnvironmentType> {
        vec![
            EnvironmentType::None,
            EnvironmentType::Maze,
            EnvironmentType::Codebase,
        ]
    }
}

// ============================================================================
// EnvironmentError
// ============================================================================

/// 環境エラー
#[derive(Debug, Clone)]
pub enum EnvironmentError {
    /// 不明な環境タイプ
    UnknownType(String),
    /// 設定エラー
    Config(String),
}

impl std::fmt::Display for EnvironmentError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::UnknownType(t) => write!(f, "Unknown environment type: {}", t),
            Self::Config(e) => write!(f, "Environment config error: {}", e),
        }
    }
}

impl std::error::Error for EnvironmentError {}

// ============================================================================
// Tests
// ============================================================================

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_environment_type_from_str() {
        assert_eq!(EnvironmentType::parse("none"), Some(EnvironmentType::None));
        assert_eq!(EnvironmentType::parse("maze"), Some(EnvironmentType::Maze));
        assert_eq!(
            EnvironmentType::parse("codebase"),
            Some(EnvironmentType::Codebase)
        );
        assert_eq!(EnvironmentType::parse("unknown"), None);
    }

    #[test]
    fn test_create_none_environment() {
        let env = EnvironmentRegistry::create("none", &serde_json::Value::Null);
        assert!(env.is_ok());
        assert_eq!(env.unwrap().name(), "NoneEnvironment");
    }

    #[test]
    fn test_create_codebase_environment() {
        let env = EnvironmentRegistry::create("codebase", &serde_json::Value::Null);
        assert!(env.is_ok());
        assert_eq!(env.unwrap().name(), "DefaultEnvironment");
    }

    #[test]
    fn test_create_maze_environment() {
        let params = serde_json::json!({
            "map": "#####\n#S.G#\n#####",
            "worker_count": 1
        });
        let env = EnvironmentRegistry::create("maze", &params);
        assert!(env.is_ok());
        assert_eq!(env.unwrap().name(), "MazeEnvironment");
    }

    #[test]
    fn test_create_maze_without_map() {
        let env = EnvironmentRegistry::create("maze", &serde_json::Value::Null);
        assert!(env.is_err());
    }
}