1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct FieldDependency {
21 pub field_name: String,
23 pub dependency_type: DependencyType,
25}
26
27impl FieldDependency {
28 pub fn new(field_name: impl Into<String>, dependency_type: DependencyType) -> Self {
42 Self {
43 field_name: field_name.into(),
44 dependency_type,
45 }
46 }
47
48 pub fn requires(
58 field_name: impl Into<String>,
59 required_fields: Vec<impl Into<String>>,
60 ) -> Self {
61 Self::new(
62 field_name,
63 DependencyType::Requires {
64 fields: required_fields.into_iter().map(|f| f.into()).collect(),
65 },
66 )
67 }
68
69 pub fn one_of(field_name: impl Into<String>, fields: Vec<impl Into<String>>) -> Self {
79 Self::new(
80 field_name,
81 DependencyType::OneOf {
82 fields: fields.into_iter().map(|f| f.into()).collect(),
83 },
84 )
85 }
86
87 pub fn all_of(field_name: impl Into<String>, fields: Vec<impl Into<String>>) -> Self {
97 Self::new(
98 field_name,
99 DependencyType::AllOf {
100 fields: fields.into_iter().map(|f| f.into()).collect(),
101 },
102 )
103 }
104
105 pub fn conditional(
120 field_name: impl Into<String>,
121 condition_value: impl Into<String>,
122 required_fields: Vec<impl Into<String>>,
123 ) -> Self {
124 Self::new(
125 field_name,
126 DependencyType::Conditional {
127 value: condition_value.into(),
128 requires: required_fields.into_iter().map(|f| f.into()).collect(),
129 },
130 )
131 }
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
136#[serde(tag = "type", rename_all = "snake_case")]
137pub enum DependencyType {
138 Requires {
140 fields: Vec<String>,
142 },
143 OneOf {
145 fields: Vec<String>,
147 },
148 AllOf {
150 fields: Vec<String>,
152 },
153 Conditional {
155 value: String,
157 requires: Vec<String>,
159 },
160}
161
162#[derive(Debug, Clone, Default)]
173pub struct DependencyManager {
174 dependencies: Vec<FieldDependency>,
175}
176
177impl DependencyManager {
178 pub fn new() -> Self {
188 Self {
189 dependencies: Vec::new(),
190 }
191 }
192
193 pub fn add_dependency(&mut self, dependency: FieldDependency) {
204 self.dependencies.push(dependency);
205 }
206
207 pub fn get_dependencies(&self) -> &[FieldDependency] {
219 &self.dependencies
220 }
221
222 pub fn get_field_dependencies(&self, field_name: &str) -> Vec<&FieldDependency> {
237 self.dependencies
238 .iter()
239 .filter(|dep| dep.field_name == field_name)
240 .collect()
241 }
242
243 pub fn to_openapi_dependencies(&self) -> HashMap<String, serde_json::Value> {
257 let mut result = HashMap::new();
258
259 for dep in &self.dependencies {
260 match &dep.dependency_type {
261 DependencyType::Requires { fields } => {
262 result.insert(dep.field_name.clone(), serde_json::json!(fields));
263 }
264 DependencyType::OneOf { fields } => {
265 result.insert(
266 dep.field_name.clone(),
267 serde_json::json!({
268 "oneOf": fields.iter().map(|f| {
269 serde_json::json!({
270 "required": [f]
271 })
272 }).collect::<Vec<_>>()
273 }),
274 );
275 }
276 DependencyType::AllOf { fields } => {
277 result.insert(
278 dep.field_name.clone(),
279 serde_json::json!({
280 "allOf": fields.iter().map(|f| {
281 serde_json::json!({
282 "required": [f]
283 })
284 }).collect::<Vec<_>>()
285 }),
286 );
287 }
288 DependencyType::Conditional { value, requires } => {
289 result.insert(
290 dep.field_name.clone(),
291 serde_json::json!({
292 "if": {
293 "properties": {
294 dep.field_name.clone(): {
295 "const": value
296 }
297 }
298 },
299 "then": {
300 "required": requires
301 }
302 }),
303 );
304 }
305 }
306 }
307
308 result
309 }
310
311 pub fn validate_dependencies(
330 &self,
331 present_fields: &std::collections::HashSet<String>,
332 ) -> Vec<String> {
333 let mut errors = Vec::new();
334
335 for dep in &self.dependencies {
336 if !present_fields.contains(&dep.field_name) {
337 continue;
338 }
339
340 match &dep.dependency_type {
341 DependencyType::Requires { fields } => {
342 for required_field in fields {
343 if !present_fields.contains(required_field) {
344 errors.push(format!(
345 "Field '{}' requires field '{}' to be present",
346 dep.field_name, required_field
347 ));
348 }
349 }
350 }
351 DependencyType::OneOf { fields } => {
352 let count = fields
353 .iter()
354 .filter(|f| present_fields.contains(*f))
355 .count();
356 if count != 1 {
357 errors.push(format!(
358 "Field '{}' requires exactly one of: {:?}",
359 dep.field_name, fields
360 ));
361 }
362 }
363 DependencyType::AllOf { fields } => {
364 let missing: Vec<_> = fields
365 .iter()
366 .filter(|f| !present_fields.contains(*f))
367 .collect();
368 if !missing.is_empty() {
369 errors.push(format!(
370 "Field '{}' requires all of: {:?}, missing: {:?}",
371 dep.field_name, fields, missing
372 ));
373 }
374 }
375 DependencyType::Conditional { requires, .. } => {
376 for required_field in requires {
379 if !present_fields.contains(required_field) {
380 errors.push(format!(
381 "Field '{}' conditionally requires field '{}'",
382 dep.field_name, required_field
383 ));
384 }
385 }
386 }
387 }
388 }
389
390 errors
391 }
392}
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397 use rstest::rstest;
398 use std::collections::HashSet;
399
400 #[rstest]
401 fn test_create_requires_dependency() {
402 let dep = FieldDependency::requires("country", vec!["address"]);
403 assert_eq!(dep.field_name, "country");
404 match dep.dependency_type {
405 DependencyType::Requires { fields } => {
406 assert_eq!(fields.len(), 1);
407 assert_eq!(fields[0], "address");
408 }
409 _ => panic!("Wrong dependency type"),
410 }
411 }
412
413 #[rstest]
414 fn test_create_one_of_dependency() {
415 let dep = FieldDependency::one_of("payment", vec!["credit_card", "paypal"]);
416 match dep.dependency_type {
417 DependencyType::OneOf { fields } => {
418 assert_eq!(fields.len(), 2);
419 assert!(fields.contains(&"credit_card".to_string()));
420 assert!(fields.contains(&"paypal".to_string()));
421 }
422 _ => panic!("Wrong dependency type"),
423 }
424 }
425
426 #[rstest]
427 fn test_create_all_of_dependency() {
428 let dep = FieldDependency::all_of("shipping", vec!["address", "city", "zip"]);
429 match dep.dependency_type {
430 DependencyType::AllOf { fields } => {
431 assert_eq!(fields.len(), 3);
432 }
433 _ => panic!("Wrong dependency type"),
434 }
435 }
436
437 #[rstest]
438 fn test_create_conditional_dependency() {
439 let dep = FieldDependency::conditional("shipping_method", "express", vec!["express_fee"]);
440 match dep.dependency_type {
441 DependencyType::Conditional { value, requires } => {
442 assert_eq!(value, "express");
443 assert_eq!(requires.len(), 1);
444 assert_eq!(requires[0], "express_fee");
445 }
446 _ => panic!("Wrong dependency type"),
447 }
448 }
449
450 #[rstest]
451 fn test_dependency_manager_add_and_get() {
452 let mut manager = DependencyManager::new();
453 manager.add_dependency(FieldDependency::requires("country", vec!["address"]));
454 manager.add_dependency(FieldDependency::requires("zip_code", vec!["city"]));
455
456 assert_eq!(manager.get_dependencies().len(), 2);
457 }
458
459 #[rstest]
460 fn test_get_field_dependencies() {
461 let mut manager = DependencyManager::new();
462 manager.add_dependency(FieldDependency::requires("country", vec!["address"]));
463 manager.add_dependency(FieldDependency::requires("city", vec!["address"]));
464 manager.add_dependency(FieldDependency::requires("country", vec!["phone"]));
465
466 let country_deps = manager.get_field_dependencies("country");
467 assert_eq!(country_deps.len(), 2);
468
469 let city_deps = manager.get_field_dependencies("city");
470 assert_eq!(city_deps.len(), 1);
471 }
472
473 #[rstest]
474 fn test_to_openapi_dependencies_requires() {
475 let mut manager = DependencyManager::new();
476 manager.add_dependency(FieldDependency::requires("country", vec!["address"]));
477
478 let openapi = manager.to_openapi_dependencies();
479 assert!(openapi.contains_key("country"));
480
481 let country_deps = &openapi["country"];
482 assert!(country_deps.is_array());
483 assert_eq!(country_deps.as_array().unwrap().len(), 1);
484 }
485
486 #[rstest]
487 fn test_validate_dependencies_success() {
488 let mut manager = DependencyManager::new();
489 manager.add_dependency(FieldDependency::requires("country", vec!["address"]));
490
491 let mut present_fields = HashSet::new();
492 present_fields.insert("country".to_string());
493 present_fields.insert("address".to_string());
494
495 let errors = manager.validate_dependencies(&present_fields);
496 assert!(errors.is_empty());
497 }
498
499 #[rstest]
500 fn test_validate_dependencies_missing_required() {
501 let mut manager = DependencyManager::new();
502 manager.add_dependency(FieldDependency::requires("country", vec!["address"]));
503
504 let mut present_fields = HashSet::new();
505 present_fields.insert("country".to_string());
506 let errors = manager.validate_dependencies(&present_fields);
509 assert_eq!(errors.len(), 1);
510 assert!(errors[0].contains("requires"));
511 }
512
513 #[rstest]
514 fn test_validate_one_of_success() {
515 let mut manager = DependencyManager::new();
516 manager.add_dependency(FieldDependency::one_of(
517 "payment",
518 vec!["credit_card", "paypal"],
519 ));
520
521 let mut present_fields = HashSet::new();
522 present_fields.insert("payment".to_string());
523 present_fields.insert("credit_card".to_string());
524
525 let errors = manager.validate_dependencies(&present_fields);
526 assert!(errors.is_empty());
527 }
528
529 #[rstest]
530 fn test_validate_one_of_multiple_present() {
531 let mut manager = DependencyManager::new();
532 manager.add_dependency(FieldDependency::one_of(
533 "payment",
534 vec!["credit_card", "paypal"],
535 ));
536
537 let mut present_fields = HashSet::new();
538 present_fields.insert("payment".to_string());
539 present_fields.insert("credit_card".to_string());
540 present_fields.insert("paypal".to_string());
541
542 let errors = manager.validate_dependencies(&present_fields);
543 assert_eq!(errors.len(), 1);
544 assert!(errors[0].contains("exactly one"));
545 }
546
547 #[rstest]
548 fn test_validate_all_of_success() {
549 let mut manager = DependencyManager::new();
550 manager.add_dependency(FieldDependency::all_of(
551 "shipping",
552 vec!["address", "city", "zip"],
553 ));
554
555 let mut present_fields = HashSet::new();
556 present_fields.insert("shipping".to_string());
557 present_fields.insert("address".to_string());
558 present_fields.insert("city".to_string());
559 present_fields.insert("zip".to_string());
560
561 let errors = manager.validate_dependencies(&present_fields);
562 assert!(errors.is_empty());
563 }
564
565 #[rstest]
566 fn test_validate_all_of_missing_some() {
567 let mut manager = DependencyManager::new();
568 manager.add_dependency(FieldDependency::all_of(
569 "shipping",
570 vec!["address", "city", "zip"],
571 ));
572
573 let mut present_fields = HashSet::new();
574 present_fields.insert("shipping".to_string());
575 present_fields.insert("address".to_string());
576 let errors = manager.validate_dependencies(&present_fields);
579 assert_eq!(errors.len(), 1);
580 assert!(errors[0].contains("requires all of"));
581 }
582
583 #[rstest]
584 fn test_dependency_type_serialization() {
585 let dep = FieldDependency::requires("country", vec!["address"]);
586 let json = serde_json::to_string(&dep).unwrap();
587 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
588
589 assert_eq!(
590 parsed["field_name"], "country",
591 "Metadata field name mismatch. Expected 'country', got: {:?}",
592 parsed["field_name"]
593 );
594 assert_eq!(
595 parsed["dependency_type"]["type"], "requires",
596 "Dependency type mismatch. Expected 'requires', got: {:?}",
597 parsed["dependency_type"]["type"]
598 );
599 assert_eq!(
600 parsed["dependency_type"]["fields"][0], "address",
601 "Required field mismatch. Expected 'address', got: {:?}",
602 parsed["dependency_type"]["fields"][0]
603 );
604 }
605
606 #[rstest]
607 fn test_conditional_dependency_serialization() {
608 let dep = FieldDependency::conditional("shipping_method", "express", vec!["express_fee"]);
609 let json = serde_json::to_string(&dep).unwrap();
610 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
611
612 assert_eq!(
613 parsed["field_name"], "shipping_method",
614 "Metadata field name mismatch. Expected 'shipping_method', got: {:?}",
615 parsed["field_name"]
616 );
617 assert_eq!(
618 parsed["dependency_type"]["type"], "conditional",
619 "Dependency type mismatch. Expected 'conditional', got: {:?}",
620 parsed["dependency_type"]["type"]
621 );
622 assert_eq!(
623 parsed["dependency_type"]["value"], "express",
624 "Conditional value mismatch. Expected 'express', got: {:?}",
625 parsed["dependency_type"]["value"]
626 );
627 assert_eq!(
628 parsed["dependency_type"]["requires"][0], "express_fee",
629 "Required field mismatch. Expected 'express_fee', got: {:?}",
630 parsed["dependency_type"]["requires"][0]
631 );
632 }
633}