Skip to main content

chant/config/
validation.rs

1//! Validation logic for configuration and derived fields.
2
3use anyhow::Result;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::fmt;
7
8use super::defaults::{FailureConfig, WatchConfig};
9
10impl WatchConfig {
11    /// Validate watch configuration
12    pub fn validate(&self) -> Result<()> {
13        if self.poll_interval_ms == 0 {
14            anyhow::bail!("watch.poll_interval_ms must be greater than 0");
15        }
16
17        self.failure.validate()
18    }
19}
20
21impl FailureConfig {
22    /// Validate failure configuration
23    pub fn validate(&self) -> Result<()> {
24        if self.backoff_multiplier < 1.0 {
25            anyhow::bail!(
26                "watch.failure.backoff_multiplier must be >= 1.0, got {}",
27                self.backoff_multiplier
28            );
29        }
30
31        Ok(())
32    }
33}
34
35/// Rejection action mode for approval workflow
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
37#[serde(rename_all = "lowercase")]
38pub enum RejectionAction {
39    /// Leave rejected, user handles it manually
40    #[default]
41    Manual,
42    /// Prompt to create fix spec, original becomes blocked with depends_on
43    Dependency,
44    /// Convert to driver with numbered member specs
45    Group,
46}
47
48impl fmt::Display for RejectionAction {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        match self {
51            RejectionAction::Manual => write!(f, "manual"),
52            RejectionAction::Dependency => write!(f, "dependency"),
53            RejectionAction::Group => write!(f, "group"),
54        }
55    }
56}
57
58/// Approval workflow configuration
59#[derive(Debug, Clone, Deserialize, Default)]
60pub struct ApprovalConfig {
61    /// Action to take when a spec is rejected
62    #[serde(default)]
63    pub rejection_action: RejectionAction,
64    /// Require approval for specs worked by agents (auto-detected via Co-Authored-By)
65    #[serde(default)]
66    pub require_approval_for_agent_work: bool,
67}
68
69/// Output validation configuration
70#[derive(Debug, Clone, Deserialize, Default)]
71pub struct OutputValidationConfig {
72    /// If true, fail spec when output doesn't match schema; if false, warn only
73    #[serde(default)]
74    pub strict_output_validation: bool,
75}
76
77/// Enterprise configuration for derived frontmatter and validation
78#[derive(Debug, Clone, Serialize, Deserialize, Default)]
79pub struct EnterpriseConfig {
80    /// Field derivation rules (which fields to derive, from what source, using what pattern)
81    #[serde(default)]
82    pub derived: HashMap<String, DerivedFieldConfig>,
83    /// List of required field names to validate
84    #[serde(default)]
85    pub required: Vec<String>,
86}
87
88/// Configuration for a single derived field
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct DerivedFieldConfig {
91    /// Source of the derived value
92    pub from: DerivationSource,
93    /// Pattern for extracting/formatting the value
94    pub pattern: String,
95    /// Optional validation rule for the derived value
96    #[serde(default)]
97    pub validate: Option<ValidationRule>,
98}
99
100/// Source of a derived field value
101#[derive(Debug, Clone, Serialize, Deserialize)]
102#[serde(rename_all = "snake_case")]
103pub enum DerivationSource {
104    /// Derive from git branch name
105    Branch,
106    /// Derive from file path
107    Path,
108    /// Derive from environment variable
109    Env,
110    /// Derive from git user information
111    GitUser,
112}
113
114/// Validation rule for derived fields
115#[derive(Debug, Clone, Serialize, Deserialize)]
116#[serde(tag = "type", rename_all = "snake_case")]
117pub enum ValidationRule {
118    /// Enum validation: value must be one of the specified values
119    Enum {
120        /// List of allowed values
121        values: Vec<String>,
122    },
123}