Skip to main content

leptos_forms_rs/validation/
conditional.rs

1//! Conditional validation module - Conditional validation logic
2//!
3//! This module provides conditional validation capabilities including
4//! ConditionalValidator, FieldCondition, and ConditionalRule for implementing
5//! field dependencies and conditional validation rules.
6
7use super::rules::FieldValidator;
8use crate::core::types::FieldValue;
9use crate::core::Form;
10
11/// Conditional validation engine for field dependencies
12#[derive(Default)]
13pub struct ConditionalValidator {
14    rules: Vec<ConditionalRule>,
15}
16
17impl ConditionalValidator {
18    /// Create a new conditional validator
19    pub fn new() -> Self {
20        Self::default()
21    }
22
23    /// Add a conditional rule
24    pub fn add_rule(&mut self, rule: ConditionalRule) {
25        self.rules.push(rule);
26    }
27
28    /// Validate conditional fields
29    pub fn validate_conditional_fields<T: Form>(
30        &self,
31        form: &T,
32        field_name: &str,
33        field_value: &FieldValue,
34    ) -> Result<(), String> {
35        // Find rules that apply to this field
36        for rule in &self.rules {
37            if rule.target_field == field_name {
38                if let Some(condition) = &rule.condition {
39                    // Check if the condition is met
40                    let condition_met = self.evaluate_condition(form, condition)?;
41
42                    if condition_met {
43                        // Apply the validation rule
44                        for validator in &rule.validators {
45                            validator(field_value)?;
46                        }
47                    }
48                }
49            }
50        }
51        Ok(())
52    }
53
54    /// Evaluate a field condition
55    #[allow(clippy::only_used_in_recursion)]
56    fn evaluate_condition<T: Form>(
57        &self,
58        form: &T,
59        condition: &FieldCondition,
60    ) -> Result<bool, String> {
61        match condition {
62            FieldCondition::Equals(field, value) => {
63                let field_value = form.get_field_value(field);
64                Ok(field_value == *value)
65            }
66            FieldCondition::NotEquals(field, value) => {
67                let field_value = form.get_field_value(field);
68                Ok(field_value != *value)
69            }
70            FieldCondition::Contains(field, value) => {
71                if let FieldValue::String(field_str) = form.get_field_value(field) {
72                    Ok(field_str.contains(value))
73                } else {
74                    Ok(false)
75                }
76            }
77            FieldCondition::IsEmpty(field) => {
78                let field_value = form.get_field_value(field);
79                Ok(field_value.is_empty())
80            }
81            FieldCondition::IsNotEmpty(field) => {
82                let field_value = form.get_field_value(field);
83                Ok(!field_value.is_empty())
84            }
85            FieldCondition::And(conditions) => {
86                for condition in conditions {
87                    if !self.evaluate_condition(form, condition)? {
88                        return Ok(false);
89                    }
90                }
91                Ok(true)
92            }
93            FieldCondition::Or(conditions) => {
94                for condition in conditions {
95                    if self.evaluate_condition(form, condition)? {
96                        return Ok(true);
97                    }
98                }
99                Ok(false)
100            }
101        }
102    }
103
104    /// Get all rules for a specific field
105    pub fn get_rules_for_field(&self, field_name: &str) -> Vec<&ConditionalRule> {
106        self.rules
107            .iter()
108            .filter(|rule| rule.target_field == field_name)
109            .collect()
110    }
111
112    /// Check if a field has conditional rules
113    pub fn has_rules_for_field(&self, field_name: &str) -> bool {
114        self.rules
115            .iter()
116            .any(|rule| rule.target_field == field_name)
117    }
118
119    /// Get all conditional rules
120    pub fn get_all_rules(&self) -> &[ConditionalRule] {
121        &self.rules
122    }
123
124    /// Clear all conditional rules
125    pub fn clear_rules(&mut self) {
126        self.rules.clear();
127    }
128
129    /// Remove rules for a specific field
130    pub fn remove_rules_for_field(&mut self, field_name: &str) {
131        self.rules.retain(|rule| rule.target_field != field_name);
132    }
133}
134
135/// A conditional validation rule
136pub struct ConditionalRule {
137    pub target_field: String,
138    pub condition: Option<FieldCondition>,
139    pub validators: Vec<FieldValidator>,
140    pub error_message: Option<String>,
141}
142
143impl std::fmt::Debug for ConditionalRule {
144    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145        f.debug_struct("ConditionalRule")
146            .field("target_field", &self.target_field)
147            .field("condition", &self.condition)
148            .field(
149                "validators",
150                &format!("{} validators", self.validators.len()),
151            )
152            .field("error_message", &self.error_message)
153            .finish()
154    }
155}
156
157impl ConditionalRule {
158    /// Create a new conditional rule
159    pub fn new(target_field: String) -> Self {
160        Self {
161            target_field,
162            condition: None,
163            validators: Vec::new(),
164            error_message: None,
165        }
166    }
167
168    /// Set the condition for this rule
169    pub fn when(mut self, condition: FieldCondition) -> Self {
170        self.condition = Some(condition);
171        self
172    }
173
174    /// Add a validator to this rule
175    pub fn validate_with(mut self, validator: FieldValidator) -> Self {
176        self.validators.push(validator);
177        self
178    }
179
180    /// Set the error message for this rule
181    pub fn with_error_message(mut self, message: String) -> Self {
182        self.error_message = Some(message);
183        self
184    }
185
186    /// Get the target field name
187    pub fn target_field(&self) -> &str {
188        &self.target_field
189    }
190
191    /// Get the condition for this rule
192    pub fn condition(&self) -> Option<&FieldCondition> {
193        self.condition.as_ref()
194    }
195
196    /// Get the validators for this rule
197    pub fn validators(&self) -> &[FieldValidator] {
198        &self.validators
199    }
200
201    /// Get the error message for this rule
202    pub fn error_message(&self) -> Option<&str> {
203        self.error_message.as_ref().map(|s| s.as_str())
204    }
205
206    /// Check if this rule has a condition
207    pub fn has_condition(&self) -> bool {
208        self.condition.is_some()
209    }
210
211    /// Check if this rule has validators
212    pub fn has_validators(&self) -> bool {
213        !self.validators.is_empty()
214    }
215}
216
217/// Field conditions for conditional validation
218#[derive(Debug, Clone)]
219pub enum FieldCondition {
220    Equals(String, FieldValue),
221    NotEquals(String, FieldValue),
222    Contains(String, String),
223    IsEmpty(String),
224    IsNotEmpty(String),
225    And(Vec<FieldCondition>),
226    Or(Vec<FieldCondition>),
227}
228
229impl FieldCondition {
230    /// Create an equals condition
231    pub fn equals(field: &str, value: FieldValue) -> Self {
232        Self::Equals(field.to_string(), value)
233    }
234
235    /// Create a not equals condition
236    pub fn not_equals(field: &str, value: FieldValue) -> Self {
237        Self::NotEquals(field.to_string(), value)
238    }
239
240    /// Create a contains condition
241    pub fn contains(field: &str, value: &str) -> Self {
242        Self::Contains(field.to_string(), value.to_string())
243    }
244
245    /// Create an is empty condition
246    pub fn is_empty(field: &str) -> Self {
247        Self::IsEmpty(field.to_string())
248    }
249
250    /// Create an is not empty condition
251    pub fn is_not_empty(field: &str) -> Self {
252        Self::IsNotEmpty(field.to_string())
253    }
254
255    /// Create an AND condition
256    pub fn and(conditions: Vec<FieldCondition>) -> Self {
257        Self::And(conditions)
258    }
259
260    /// Create an OR condition
261    pub fn or(conditions: Vec<FieldCondition>) -> Self {
262        Self::Or(conditions)
263    }
264
265    /// Get the field name for this condition
266    pub fn field_name(&self) -> Option<&str> {
267        match self {
268            FieldCondition::Equals(field, _) => Some(field),
269            FieldCondition::NotEquals(field, _) => Some(field),
270            FieldCondition::Contains(field, _) => Some(field),
271            FieldCondition::IsEmpty(field) => Some(field),
272            FieldCondition::IsNotEmpty(field) => Some(field),
273            FieldCondition::And(_) => None,
274            FieldCondition::Or(_) => None,
275        }
276    }
277
278    /// Check if this condition is a logical operator
279    pub fn is_logical_operator(&self) -> bool {
280        matches!(self, FieldCondition::And(_) | FieldCondition::Or(_))
281    }
282
283    /// Get the logical operator type if this is a logical operator
284    pub fn logical_operator_type(&self) -> Option<LogicalOperator> {
285        match self {
286            FieldCondition::And(_) => Some(LogicalOperator::And),
287            FieldCondition::Or(_) => Some(LogicalOperator::Or),
288            _ => None,
289        }
290    }
291}
292
293/// Logical operators for field conditions
294#[derive(Debug, Clone, Copy, PartialEq, Eq)]
295pub enum LogicalOperator {
296    And,
297    Or,
298}
299
300impl LogicalOperator {
301    /// Get the string representation of this operator
302    pub fn as_str(&self) -> &'static str {
303        match self {
304            LogicalOperator::And => "AND",
305            LogicalOperator::Or => "OR",
306        }
307    }
308}
309
310impl std::fmt::Display for LogicalOperator {
311    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312        write!(f, "{}", self.as_str())
313    }
314}