summaryrefslogtreecommitdiff
path: root/makima/src/daemon/chain/parser.rs
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src/daemon/chain/parser.rs')
-rw-r--r--makima/src/daemon/chain/parser.rs414
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())
- );
- }
-}