diff options
Diffstat (limited to 'makima/src/daemon/chain/parser.rs')
| -rw-r--r-- | makima/src/daemon/chain/parser.rs | 414 |
1 files changed, 0 insertions, 414 deletions
diff --git a/makima/src/daemon/chain/parser.rs b/makima/src/daemon/chain/parser.rs deleted file mode 100644 index b32d0f2..0000000 --- a/makima/src/daemon/chain/parser.rs +++ /dev/null @@ -1,414 +0,0 @@ -//! Chain YAML parser. -//! -//! Parses chain definition files in YAML format into structured data -//! that can be used to create chains and contracts. - -use serde::{Deserialize, Serialize}; -use std::path::Path; -use thiserror::Error; - -/// Error type for chain parsing operations. -#[derive(Error, Debug)] -pub enum ParseError { - #[error("Failed to read chain file: {0}")] - IoError(#[from] std::io::Error), - - #[error("Failed to parse YAML: {0}")] - YamlError(#[from] serde_yaml::Error), - - #[error("Validation error: {0}")] - ValidationError(String), -} - -/// Repository definition in a chain. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RepositoryDefinition { - /// Name of the repository - pub name: String, - /// Repository URL (for remote repos) - pub repository_url: Option<String>, - /// Local path (for local repos) - pub local_path: Option<String>, - /// Source type: remote, local, or managed - #[serde(default = "default_source_type")] - pub source_type: String, - /// Whether this is the primary repository - #[serde(default)] - pub is_primary: bool, -} - -fn default_source_type() -> String { - "remote".to_string() -} - -/// Chain definition parsed from YAML. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ChainDefinition { - /// Name of the chain - pub name: String, - /// Optional description - pub description: Option<String>, - /// Repositories for this chain - #[serde(default)] - pub repositories: Vec<RepositoryDefinition>, - /// Contracts in this chain - pub contracts: Vec<ContractDefinition>, - /// Loop configuration - #[serde(rename = "loop")] - pub loop_config: Option<LoopConfig>, -} - -/// Contract definition within a chain. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ContractDefinition { - /// Name of the contract - pub name: String, - /// Optional description - pub description: Option<String>, - /// Contract type (defaults to "simple") - #[serde(rename = "type", default = "default_contract_type")] - pub contract_type: String, - /// Phases for this contract - pub phases: Option<Vec<String>>, - /// Names of contracts this depends on (DAG edges) - pub depends_on: Option<Vec<String>>, - /// Tasks to create in this contract - pub tasks: Option<Vec<TaskDefinition>>, - /// Deliverables for this contract - pub deliverables: Option<Vec<DeliverableDefinition>>, -} - -fn default_contract_type() -> String { - "simple".to_string() -} - -/// Task definition within a contract. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TaskDefinition { - /// Name of the task - pub name: String, - /// Plan/instructions for the task - pub plan: String, -} - -/// Deliverable definition within a contract. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DeliverableDefinition { - /// Unique identifier for the deliverable - pub id: String, - /// Name of the deliverable - pub name: String, - /// Priority level (defaults to "required") - #[serde(default = "default_priority")] - pub priority: String, -} - -fn default_priority() -> String { - "required".to_string() -} - -/// Loop configuration for chain iteration. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LoopConfig { - /// Whether loop is enabled - #[serde(default)] - pub enabled: bool, - /// Maximum number of iterations - #[serde(default = "default_max_iterations")] - pub max_iterations: i32, - /// Progress check prompt/criteria - pub progress_check: Option<String>, -} - -fn default_max_iterations() -> i32 { - 10 -} - -impl ChainDefinition { - /// Validate the chain definition. - pub fn validate(&self) -> Result<(), ParseError> { - // Check for empty name - if self.name.trim().is_empty() { - return Err(ParseError::ValidationError( - "Chain name cannot be empty".to_string(), - )); - } - - // Check for at least one contract - if self.contracts.is_empty() { - return Err(ParseError::ValidationError( - "Chain must have at least one contract".to_string(), - )); - } - - // Collect all contract names for dependency validation - let contract_names: std::collections::HashSet<_> = - self.contracts.iter().map(|c| c.name.as_str()).collect(); - - // Check for duplicate contract names - if contract_names.len() != self.contracts.len() { - return Err(ParseError::ValidationError( - "Duplicate contract names found".to_string(), - )); - } - - // Validate each contract - for contract in &self.contracts { - contract.validate(&contract_names)?; - } - - Ok(()) - } -} - -impl ContractDefinition { - /// Validate the contract definition. - pub fn validate( - &self, - valid_contract_names: &std::collections::HashSet<&str>, - ) -> Result<(), ParseError> { - // Check for empty name - if self.name.trim().is_empty() { - return Err(ParseError::ValidationError( - "Contract name cannot be empty".to_string(), - )); - } - - // Validate dependencies exist - if let Some(deps) = &self.depends_on { - for dep in deps { - if !valid_contract_names.contains(dep.as_str()) { - return Err(ParseError::ValidationError(format!( - "Contract '{}' depends on unknown contract '{}'", - self.name, dep - ))); - } - // Self-dependency check - if dep == &self.name { - return Err(ParseError::ValidationError(format!( - "Contract '{}' cannot depend on itself", - self.name - ))); - } - } - } - - // Validate tasks - if let Some(tasks) = &self.tasks { - for task in tasks { - if task.name.trim().is_empty() { - return Err(ParseError::ValidationError(format!( - "Task name cannot be empty in contract '{}'", - self.name - ))); - } - if task.plan.trim().is_empty() { - return Err(ParseError::ValidationError(format!( - "Task '{}' in contract '{}' has empty plan", - task.name, self.name - ))); - } - } - } - - Ok(()) - } -} - -/// Parse a chain definition from a YAML file. -pub fn parse_chain_file<P: AsRef<Path>>(path: P) -> Result<ChainDefinition, ParseError> { - let content = std::fs::read_to_string(path)?; - parse_chain_yaml(&content) -} - -/// Parse a chain definition from a YAML string. -pub fn parse_chain_yaml(yaml: &str) -> Result<ChainDefinition, ParseError> { - let definition: ChainDefinition = serde_yaml::from_str(yaml)?; - definition.validate()?; - Ok(definition) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_simple_chain() { - let yaml = r#" -name: Test Chain -description: A test chain -contracts: - - name: Research - type: simple - tasks: - - name: Analyze - plan: "Analyze the codebase" - - name: Implement - type: simple - depends_on: [Research] - tasks: - - name: Build - plan: "Build the feature" -"#; - let chain = parse_chain_yaml(yaml).unwrap(); - assert_eq!(chain.name, "Test Chain"); - assert_eq!(chain.contracts.len(), 2); - assert_eq!(chain.contracts[0].name, "Research"); - assert_eq!(chain.contracts[1].name, "Implement"); - assert_eq!( - chain.contracts[1].depends_on, - Some(vec!["Research".to_string()]) - ); - } - - #[test] - fn test_parse_chain_with_loop() { - let yaml = r#" -name: Iterative Chain -contracts: - - name: Phase1 - tasks: - - name: Task1 - plan: "Do something" -loop: - enabled: true - max_iterations: 5 - progress_check: "Check if goals are met" -"#; - let chain = parse_chain_yaml(yaml).unwrap(); - assert!(chain.loop_config.is_some()); - let loop_config = chain.loop_config.unwrap(); - assert!(loop_config.enabled); - assert_eq!(loop_config.max_iterations, 5); - } - - #[test] - fn test_parse_chain_with_deliverables() { - let yaml = r#" -name: Feature Chain -contracts: - - name: Research - tasks: - - name: Survey - plan: "Survey existing code" - deliverables: - - id: analysis - name: Codebase Analysis - priority: required -"#; - let chain = parse_chain_yaml(yaml).unwrap(); - let deliverables = chain.contracts[0].deliverables.as_ref().unwrap(); - assert_eq!(deliverables.len(), 1); - assert_eq!(deliverables[0].id, "analysis"); - } - - #[test] - fn test_validation_empty_name() { - let yaml = r#" -name: "" -contracts: - - name: Phase1 - tasks: - - name: Task1 - plan: "Do something" -"#; - let result = parse_chain_yaml(yaml); - assert!(result.is_err()); - assert!(result - .unwrap_err() - .to_string() - .contains("name cannot be empty")); - } - - #[test] - fn test_validation_no_contracts() { - let yaml = r#" -name: Empty Chain -contracts: [] -"#; - let result = parse_chain_yaml(yaml); - assert!(result.is_err()); - assert!(result - .unwrap_err() - .to_string() - .contains("at least one contract")); - } - - #[test] - fn test_validation_unknown_dependency() { - let yaml = r#" -name: Bad Chain -contracts: - - name: Phase1 - depends_on: [NonExistent] - tasks: - - name: Task1 - plan: "Do something" -"#; - let result = parse_chain_yaml(yaml); - assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("unknown contract")); - } - - #[test] - fn test_validation_self_dependency() { - let yaml = r#" -name: Self Ref Chain -contracts: - - name: Phase1 - depends_on: [Phase1] - tasks: - - name: Task1 - plan: "Do something" -"#; - let result = parse_chain_yaml(yaml); - assert!(result.is_err()); - assert!(result - .unwrap_err() - .to_string() - .contains("cannot depend on itself")); - } - - #[test] - fn test_validation_duplicate_names() { - let yaml = r#" -name: Dup Chain -contracts: - - name: Phase1 - tasks: - - name: Task1 - plan: "Do something" - - name: Phase1 - tasks: - - name: Task2 - plan: "Do another thing" -"#; - let result = parse_chain_yaml(yaml); - assert!(result.is_err()); - assert!(result - .unwrap_err() - .to_string() - .contains("Duplicate contract names")); - } - - #[test] - fn test_repo_alias() { - let yaml = r#" -name: Repo Chain -repositories: - - name: main - repository_url: https://github.com/user/project -contracts: - - name: Phase1 - tasks: - - name: Task1 - plan: "Work on repo" -"#; - let chain = parse_chain_yaml(yaml).unwrap(); - assert_eq!(chain.repositories.len(), 1); - assert_eq!( - chain.repositories[0].repository_url, - Some("https://github.com/user/project".to_string()) - ); - } -} |
