Skip to main content

swarm_engine_eval/
environment.rs

1//! Environment Registry - 評価環境ファクトリ
2//!
3//! シナリオの `environment.env_type` から具体的な評価環境を生成するファクトリ。
4//!
5//! ## 設計
6//!
7//! すべての環境は `core::Environment` トレイトを実装する。
8//!
9//! ```text
10//! EvalScenario.environment
11//!     │
12//!     ├── env_type: "maze"
13//!     └── params: { map: "...", worker_count: 1 }
14//!             │
15//!             ▼
16//!     EnvironmentRegistry.create()
17//!             │
18//!             ▼
19//!     EnvironmentBox (Box<dyn Environment>)
20//! ```
21
22use swarm_engine_core::environment::{DefaultEnvironment, EnvironmentBox};
23
24use crate::environments::{MazeEnvironment, NoneEnvironment};
25
26// ============================================================================
27// EnvironmentType
28// ============================================================================
29
30/// 環境タイプ識別子
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum EnvironmentType {
33    /// 環境なし(何もしない)
34    None,
35    /// 迷路探索環境
36    Maze,
37    /// コードベース環境(デフォルト: Bash/Read/Write/Grep/Glob)
38    Codebase,
39}
40
41impl EnvironmentType {
42    /// 文字列から解析
43    pub fn parse(s: &str) -> Option<Self> {
44        match s.to_lowercase().as_str() {
45            "none" | "" => Some(Self::None),
46            "maze" => Some(Self::Maze),
47            "codebase" | "default" => Some(Self::Codebase),
48            _ => None,
49        }
50    }
51
52    /// 文字列表現
53    pub fn as_str(&self) -> &'static str {
54        match self {
55            Self::None => "none",
56            Self::Maze => "maze",
57            Self::Codebase => "codebase",
58        }
59    }
60}
61
62// ============================================================================
63// EnvironmentRegistry
64// ============================================================================
65
66/// 環境レジストリ
67///
68/// シナリオの `environment` セクションから EvalEnvironment インスタンスを生成。
69pub struct EnvironmentRegistry;
70
71impl EnvironmentRegistry {
72    /// 環境を作成
73    ///
74    /// # Arguments
75    ///
76    /// * `env_type` - 環境タイプ文字列 ("none", "maze", "codebase")
77    /// * `params` - 環境固有のパラメータ
78    ///
79    /// # Returns
80    ///
81    /// `EnvironmentBox` (Box<dyn Environment>)
82    pub fn create(
83        env_type: &str,
84        params: &serde_json::Value,
85    ) -> Result<EnvironmentBox, EnvironmentError> {
86        let env_type = EnvironmentType::parse(env_type)
87            .ok_or_else(|| EnvironmentError::UnknownType(env_type.to_string()))?;
88
89        match env_type {
90            EnvironmentType::None => Ok(Box::new(NoneEnvironment)),
91
92            EnvironmentType::Maze => {
93                let map_str = params.get("map").and_then(|v| v.as_str()).ok_or_else(|| {
94                    EnvironmentError::Config("Maze environment requires 'map' parameter".into())
95                })?;
96
97                let worker_count = params
98                    .get("worker_count")
99                    .and_then(|v| v.as_u64())
100                    .unwrap_or(1) as usize;
101
102                Ok(Box::new(MazeEnvironment::from_str(map_str, worker_count)))
103            }
104
105            EnvironmentType::Codebase => Ok(Box::new(DefaultEnvironment::new())),
106        }
107    }
108
109    /// サポートされている環境タイプ一覧
110    pub fn supported_types() -> Vec<EnvironmentType> {
111        vec![
112            EnvironmentType::None,
113            EnvironmentType::Maze,
114            EnvironmentType::Codebase,
115        ]
116    }
117}
118
119// ============================================================================
120// EnvironmentError
121// ============================================================================
122
123/// 環境エラー
124#[derive(Debug, Clone)]
125pub enum EnvironmentError {
126    /// 不明な環境タイプ
127    UnknownType(String),
128    /// 設定エラー
129    Config(String),
130}
131
132impl std::fmt::Display for EnvironmentError {
133    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134        match self {
135            Self::UnknownType(t) => write!(f, "Unknown environment type: {}", t),
136            Self::Config(e) => write!(f, "Environment config error: {}", e),
137        }
138    }
139}
140
141impl std::error::Error for EnvironmentError {}
142
143// ============================================================================
144// Tests
145// ============================================================================
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn test_environment_type_from_str() {
153        assert_eq!(EnvironmentType::parse("none"), Some(EnvironmentType::None));
154        assert_eq!(EnvironmentType::parse("maze"), Some(EnvironmentType::Maze));
155        assert_eq!(
156            EnvironmentType::parse("codebase"),
157            Some(EnvironmentType::Codebase)
158        );
159        assert_eq!(EnvironmentType::parse("unknown"), None);
160    }
161
162    #[test]
163    fn test_create_none_environment() {
164        let env = EnvironmentRegistry::create("none", &serde_json::Value::Null);
165        assert!(env.is_ok());
166        assert_eq!(env.unwrap().name(), "NoneEnvironment");
167    }
168
169    #[test]
170    fn test_create_codebase_environment() {
171        let env = EnvironmentRegistry::create("codebase", &serde_json::Value::Null);
172        assert!(env.is_ok());
173        assert_eq!(env.unwrap().name(), "DefaultEnvironment");
174    }
175
176    #[test]
177    fn test_create_maze_environment() {
178        let params = serde_json::json!({
179            "map": "#####\n#S.G#\n#####",
180            "worker_count": 1
181        });
182        let env = EnvironmentRegistry::create("maze", &params);
183        assert!(env.is_ok());
184        assert_eq!(env.unwrap().name(), "MazeEnvironment");
185    }
186
187    #[test]
188    fn test_create_maze_without_map() {
189        let env = EnvironmentRegistry::create("maze", &serde_json::Value::Null);
190        assert!(env.is_err());
191    }
192}