leptos_forms_rs/validation/
conditional.rs1use super::rules::FieldValidator;
8use crate::core::types::FieldValue;
9use crate::core::Form;
10
11#[derive(Default)]
13pub struct ConditionalValidator {
14 rules: Vec<ConditionalRule>,
15}
16
17impl ConditionalValidator {
18 pub fn new() -> Self {
20 Self::default()
21 }
22
23 pub fn add_rule(&mut self, rule: ConditionalRule) {
25 self.rules.push(rule);
26 }
27
28 pub fn validate_conditional_fields<T: Form>(
30 &self,
31 form: &T,
32 field_name: &str,
33 field_value: &FieldValue,
34 ) -> Result<(), String> {
35 for rule in &self.rules {
37 if rule.target_field == field_name {
38 if let Some(condition) = &rule.condition {
39 let condition_met = self.evaluate_condition(form, condition)?;
41
42 if condition_met {
43 for validator in &rule.validators {
45 validator(field_value)?;
46 }
47 }
48 }
49 }
50 }
51 Ok(())
52 }
53
54 #[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 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 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 pub fn get_all_rules(&self) -> &[ConditionalRule] {
121 &self.rules
122 }
123
124 pub fn clear_rules(&mut self) {
126 self.rules.clear();
127 }
128
129 pub fn remove_rules_for_field(&mut self, field_name: &str) {
131 self.rules.retain(|rule| rule.target_field != field_name);
132 }
133}
134
135pub 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 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 pub fn when(mut self, condition: FieldCondition) -> Self {
170 self.condition = Some(condition);
171 self
172 }
173
174 pub fn validate_with(mut self, validator: FieldValidator) -> Self {
176 self.validators.push(validator);
177 self
178 }
179
180 pub fn with_error_message(mut self, message: String) -> Self {
182 self.error_message = Some(message);
183 self
184 }
185
186 pub fn target_field(&self) -> &str {
188 &self.target_field
189 }
190
191 pub fn condition(&self) -> Option<&FieldCondition> {
193 self.condition.as_ref()
194 }
195
196 pub fn validators(&self) -> &[FieldValidator] {
198 &self.validators
199 }
200
201 pub fn error_message(&self) -> Option<&str> {
203 self.error_message.as_ref().map(|s| s.as_str())
204 }
205
206 pub fn has_condition(&self) -> bool {
208 self.condition.is_some()
209 }
210
211 pub fn has_validators(&self) -> bool {
213 !self.validators.is_empty()
214 }
215}
216
217#[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 pub fn equals(field: &str, value: FieldValue) -> Self {
232 Self::Equals(field.to_string(), value)
233 }
234
235 pub fn not_equals(field: &str, value: FieldValue) -> Self {
237 Self::NotEquals(field.to_string(), value)
238 }
239
240 pub fn contains(field: &str, value: &str) -> Self {
242 Self::Contains(field.to_string(), value.to_string())
243 }
244
245 pub fn is_empty(field: &str) -> Self {
247 Self::IsEmpty(field.to_string())
248 }
249
250 pub fn is_not_empty(field: &str) -> Self {
252 Self::IsNotEmpty(field.to_string())
253 }
254
255 pub fn and(conditions: Vec<FieldCondition>) -> Self {
257 Self::And(conditions)
258 }
259
260 pub fn or(conditions: Vec<FieldCondition>) -> Self {
262 Self::Or(conditions)
263 }
264
265 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 pub fn is_logical_operator(&self) -> bool {
280 matches!(self, FieldCondition::And(_) | FieldCondition::Or(_))
281 }
282
283 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
295pub enum LogicalOperator {
296 And,
297 Or,
298}
299
300impl LogicalOperator {
301 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}