omni_dev/data/
amendments.rs

1//! Amendment data structures and validation
2
3use anyhow::{Context, Result};
4use serde::{Deserialize, Serialize};
5use std::fs;
6use std::path::Path;
7
8/// Amendment file structure
9#[derive(Debug, Serialize, Deserialize)]
10pub struct AmendmentFile {
11    /// List of commit amendments to apply
12    pub amendments: Vec<Amendment>,
13}
14
15/// Individual commit amendment
16#[derive(Debug, Serialize, Deserialize)]
17pub struct Amendment {
18    /// Full 40-character SHA-1 commit hash
19    pub commit: String,
20    /// New commit message
21    pub message: String,
22}
23
24impl AmendmentFile {
25    /// Load amendments from YAML file
26    pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
27        let content = fs::read_to_string(&path).with_context(|| {
28            format!("Failed to read amendment file: {}", path.as_ref().display())
29        })?;
30
31        let amendment_file: AmendmentFile =
32            serde_yaml::from_str(&content).context("Failed to parse YAML amendment file")?;
33
34        amendment_file.validate()?;
35
36        Ok(amendment_file)
37    }
38
39    /// Validate amendment file structure and content
40    pub fn validate(&self) -> Result<()> {
41        if self.amendments.is_empty() {
42            anyhow::bail!("Amendment file must contain at least one amendment");
43        }
44
45        for (i, amendment) in self.amendments.iter().enumerate() {
46            amendment
47                .validate()
48                .with_context(|| format!("Invalid amendment at index {}", i))?;
49        }
50
51        Ok(())
52    }
53
54    /// Save amendments to YAML file
55    pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
56        let yaml_content =
57            serde_yaml::to_string(self).context("Failed to serialize amendments to YAML")?;
58
59        fs::write(&path, yaml_content).with_context(|| {
60            format!(
61                "Failed to write amendment file: {}",
62                path.as_ref().display()
63            )
64        })?;
65
66        Ok(())
67    }
68}
69
70impl Amendment {
71    /// Create a new amendment
72    pub fn new(commit: String, message: String) -> Self {
73        Self { commit, message }
74    }
75
76    /// Validate amendment structure
77    pub fn validate(&self) -> Result<()> {
78        // Validate commit hash format
79        if self.commit.len() != 40 {
80            anyhow::bail!(
81                "Commit hash must be exactly 40 characters long, got: {}",
82                self.commit.len()
83            );
84        }
85
86        if !self.commit.chars().all(|c| c.is_ascii_hexdigit()) {
87            anyhow::bail!("Commit hash must contain only hexadecimal characters");
88        }
89
90        if !self
91            .commit
92            .chars()
93            .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit())
94        {
95            anyhow::bail!("Commit hash must be lowercase");
96        }
97
98        // Validate message content
99        if self.message.trim().is_empty() {
100            anyhow::bail!("Commit message cannot be empty");
101        }
102
103        Ok(())
104    }
105}