raz_validation/
provider.rs

1//! Option provider trait and related types
2
3use crate::error::{ValidationError, ValidationResult};
4use serde::{Deserialize, Serialize};
5
6/// Trait for providing option definitions and validation for a specific tool or framework
7pub trait OptionProvider: Send + Sync {
8    /// Get the name of this provider (e.g., "cargo", "leptos", "dioxus")
9    fn name(&self) -> &str;
10
11    /// Get all options available for a specific command
12    fn get_options(&self, command: &str) -> Vec<OptionDef>;
13
14    /// Validate a specific option and its value
15    fn validate(&self, command: &str, option: &str, value: Option<&str>) -> ValidationResult<()>;
16
17    /// Get commands supported by this provider
18    fn get_commands(&self) -> Vec<String>;
19
20    /// Check if this provider supports a specific command
21    fn supports_command(&self, command: &str) -> bool {
22        self.get_commands().contains(&command.to_string())
23    }
24}
25
26/// Definition of a command-line option
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct OptionDef {
29    /// Option name (e.g., "--release", "--features")
30    pub name: String,
31
32    /// Type of value this option expects
33    pub value_type: OptionValueType,
34
35    /// Human-readable description
36    pub description: String,
37
38    /// Options that conflict with this one
39    pub conflicts_with: Vec<String>,
40
41    /// Options that are required when this one is used
42    pub requires: Vec<String>,
43
44    /// Deprecation message if this option is deprecated
45    pub deprecated: Option<String>,
46
47    /// Whether this option can appear multiple times
48    pub repeatable: bool,
49}
50
51impl OptionDef {
52    /// Create a new flag option
53    pub fn flag(name: impl Into<String>, description: impl Into<String>) -> Self {
54        Self {
55            name: name.into(),
56            value_type: OptionValueType::Flag,
57            description: description.into(),
58            conflicts_with: Vec::new(),
59            requires: Vec::new(),
60            deprecated: None,
61            repeatable: false,
62        }
63    }
64
65    /// Create a new single value option
66    pub fn single(
67        name: impl Into<String>,
68        description: impl Into<String>,
69        validator: ValueValidator,
70    ) -> Self {
71        Self {
72            name: name.into(),
73            value_type: OptionValueType::Single(validator),
74            description: description.into(),
75            conflicts_with: Vec::new(),
76            requires: Vec::new(),
77            deprecated: None,
78            repeatable: false,
79        }
80    }
81
82    /// Create a new multiple value option
83    pub fn multiple(
84        name: impl Into<String>,
85        description: impl Into<String>,
86        validator: ValueValidator,
87    ) -> Self {
88        Self {
89            name: name.into(),
90            value_type: OptionValueType::Multiple(validator),
91            description: description.into(),
92            conflicts_with: Vec::new(),
93            requires: Vec::new(),
94            deprecated: None,
95            repeatable: false,
96        }
97    }
98
99    /// Add conflicting options
100    pub fn conflicts_with(mut self, options: Vec<String>) -> Self {
101        self.conflicts_with = options;
102        self
103    }
104
105    /// Add required options
106    pub fn requires(mut self, options: Vec<String>) -> Self {
107        self.requires = options;
108        self
109    }
110
111    /// Mark as deprecated
112    pub fn deprecated(mut self, message: impl Into<String>) -> Self {
113        self.deprecated = Some(message.into());
114        self
115    }
116
117    /// Mark as repeatable
118    pub fn repeatable(mut self) -> Self {
119        self.repeatable = true;
120        self
121    }
122}
123
124/// Type of value an option expects
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub enum OptionValueType {
127    /// Boolean flag (e.g., --release)
128    Flag,
129
130    /// Single value (e.g., --bin myapp)
131    Single(ValueValidator),
132
133    /// Multiple values (e.g., --features "a,b,c")
134    Multiple(ValueValidator),
135}
136
137/// Validator for option values
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub enum ValueValidator {
140    /// Any string value
141    Any,
142
143    /// Must be a valid number
144    Number,
145
146    /// Must be one of the specified values
147    Enum(Vec<String>),
148
149    /// Must match the regex pattern
150    Regex(String),
151
152    /// Must be a valid file path
153    FilePath,
154
155    /// Must be a valid directory path
156    DirectoryPath,
157
158    /// Custom validation function name (for extensibility)
159    Custom(String),
160}
161
162impl ValueValidator {
163    /// Validate a value against this validator
164    pub fn validate(&self, value: &str) -> ValidationResult<()> {
165        match self {
166            Self::Any => Ok(()),
167
168            Self::Number => value
169                .parse::<f64>()
170                .map(|_| ())
171                .map_err(|_| ValidationError::invalid_value("", value, "must be a number")),
172
173            Self::Enum(valid_values) => {
174                if valid_values.contains(&value.to_string()) {
175                    Ok(())
176                } else {
177                    Err(ValidationError::invalid_value(
178                        "",
179                        value,
180                        format!("must be one of: {}", valid_values.join(", ")),
181                    ))
182                }
183            }
184
185            Self::Regex(pattern) => {
186                let regex = regex::Regex::new(pattern).map_err(|_| {
187                    ValidationError::invalid_value("", value, "invalid regex pattern")
188                })?;
189
190                if regex.is_match(value) {
191                    Ok(())
192                } else {
193                    Err(ValidationError::invalid_value(
194                        "",
195                        value,
196                        format!("must match pattern: {pattern}"),
197                    ))
198                }
199            }
200
201            Self::FilePath => {
202                // Basic file path validation - could be enhanced
203                if value.is_empty() {
204                    Err(ValidationError::invalid_value(
205                        "",
206                        value,
207                        "file path cannot be empty",
208                    ))
209                } else {
210                    Ok(())
211                }
212            }
213
214            Self::DirectoryPath => {
215                // Basic directory path validation - could be enhanced
216                if value.is_empty() {
217                    Err(ValidationError::invalid_value(
218                        "",
219                        value,
220                        "directory path cannot be empty",
221                    ))
222                } else {
223                    Ok(())
224                }
225            }
226
227            Self::Custom(_name) => {
228                // Custom validators would be implemented by providers
229                Ok(())
230            }
231        }
232    }
233}
234
235/// Built-in providers
236pub mod cargo;
237pub mod leptos;