1use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8use crate::domain::value_objects::Schema;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct SchemaRegistrationDto {
15 pub id: String,
17 pub schema: SchemaDefinitionDto,
19 pub metadata: Option<SchemaMetadataDto>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct SchemaMetadataDto {
26 pub version: String,
28 pub description: Option<String>,
30 pub author: Option<String>,
32 pub created_at: Option<i64>,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
40#[serde(tag = "type", rename_all = "lowercase")]
41pub enum SchemaDefinitionDto {
42 String {
43 #[serde(skip_serializing_if = "Option::is_none")]
44 min_length: Option<usize>,
45 #[serde(skip_serializing_if = "Option::is_none")]
46 max_length: Option<usize>,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pattern: Option<String>,
49 #[serde(skip_serializing_if = "Option::is_none")]
50 enum_values: Option<Vec<String>>,
51 },
52 Integer {
53 #[serde(skip_serializing_if = "Option::is_none")]
54 minimum: Option<i64>,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 maximum: Option<i64>,
57 },
58 Number {
59 #[serde(skip_serializing_if = "Option::is_none")]
60 minimum: Option<f64>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 maximum: Option<f64>,
63 },
64 Boolean,
65 Null,
66 Array {
67 #[serde(skip_serializing_if = "Option::is_none")]
68 items: Option<Box<SchemaDefinitionDto>>,
69 #[serde(skip_serializing_if = "Option::is_none")]
70 min_items: Option<usize>,
71 #[serde(skip_serializing_if = "Option::is_none")]
72 max_items: Option<usize>,
73 #[serde(default)]
74 unique_items: bool,
75 },
76 Object {
77 properties: HashMap<String, SchemaDefinitionDto>,
78 #[serde(default)]
79 required: Vec<String>,
80 #[serde(default = "default_true")]
81 additional_properties: bool,
82 },
83 OneOf {
84 schemas: Vec<SchemaDefinitionDto>,
85 },
86 AllOf {
87 schemas: Vec<SchemaDefinitionDto>,
88 },
89 Any,
90}
91
92fn default_true() -> bool {
93 true
94}
95
96impl From<SchemaDefinitionDto> for Schema {
97 fn from(dto: SchemaDefinitionDto) -> Self {
98 match dto {
99 SchemaDefinitionDto::String {
100 min_length,
101 max_length,
102 pattern,
103 enum_values,
104 } => Self::String {
105 min_length,
106 max_length,
107 pattern,
108 allowed_values: enum_values
109 .map(|values| values.into_iter().collect::<smallvec::SmallVec<[_; 8]>>()),
110 },
111 SchemaDefinitionDto::Integer { minimum, maximum } => Self::Integer { minimum, maximum },
112 SchemaDefinitionDto::Number { minimum, maximum } => Self::Number { minimum, maximum },
113 SchemaDefinitionDto::Boolean => Self::Boolean,
114 SchemaDefinitionDto::Null => Self::Null,
115 SchemaDefinitionDto::Array {
116 items,
117 min_items,
118 max_items,
119 unique_items,
120 } => Self::Array {
121 items: items.map(|i| Box::new((*i).into())),
122 min_items,
123 max_items,
124 unique_items,
125 },
126 SchemaDefinitionDto::Object {
127 properties,
128 required,
129 additional_properties,
130 } => Self::Object {
131 properties: properties.into_iter().map(|(k, v)| (k, v.into())).collect(),
132 required,
133 additional_properties,
134 },
135 SchemaDefinitionDto::OneOf { schemas } => Self::OneOf {
136 schemas: schemas
137 .into_iter()
138 .map(|s| Box::new(s.into()))
139 .collect::<smallvec::SmallVec<[_; 4]>>(),
140 },
141 SchemaDefinitionDto::AllOf { schemas } => Self::AllOf {
142 schemas: schemas
143 .into_iter()
144 .map(|s| Box::new(s.into()))
145 .collect::<smallvec::SmallVec<[_; 4]>>(),
146 },
147 SchemaDefinitionDto::Any => Self::Any,
148 }
149 }
150}
151
152impl From<&Schema> for SchemaDefinitionDto {
153 fn from(schema: &Schema) -> Self {
154 match schema {
155 Schema::String {
156 min_length,
157 max_length,
158 pattern,
159 allowed_values,
160 } => Self::String {
161 min_length: *min_length,
162 max_length: *max_length,
163 pattern: pattern.as_ref().map(|p| p.to_string()),
164 enum_values: allowed_values
165 .as_ref()
166 .map(|v| v.iter().map(|s| s.to_string()).collect()),
167 },
168 Schema::Integer { minimum, maximum } => Self::Integer {
169 minimum: *minimum,
170 maximum: *maximum,
171 },
172 Schema::Number { minimum, maximum } => Self::Number {
173 minimum: *minimum,
174 maximum: *maximum,
175 },
176 Schema::Boolean => Self::Boolean,
177 Schema::Null => Self::Null,
178 Schema::Array {
179 items,
180 min_items,
181 max_items,
182 unique_items,
183 } => Self::Array {
184 items: items.as_ref().map(|i| Box::new(i.as_ref().into())),
185 min_items: *min_items,
186 max_items: *max_items,
187 unique_items: *unique_items,
188 },
189 Schema::Object {
190 properties,
191 required,
192 additional_properties,
193 } => Self::Object {
194 properties: properties
195 .iter()
196 .map(|(k, v)| (k.clone(), v.into()))
197 .collect(),
198 required: required.clone(),
199 additional_properties: *additional_properties,
200 },
201 Schema::OneOf { schemas } => Self::OneOf {
202 schemas: schemas.iter().map(|s| s.as_ref().into()).collect(),
203 },
204 Schema::AllOf { schemas } => Self::AllOf {
205 schemas: schemas.iter().map(|s| s.as_ref().into()).collect(),
206 },
207 Schema::Any => Self::Any,
208 }
209 }
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct ValidationRequestDto {
215 pub schema_id: String,
217 pub data: String,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
223pub struct ValidationResultDto {
224 pub valid: bool,
226 #[serde(skip_serializing_if = "Vec::is_empty")]
228 pub errors: Vec<ValidationErrorDto>,
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize)]
233pub struct ValidationErrorDto {
234 pub path: String,
236 pub message: String,
238 pub error_type: String,
240}
241
242impl From<&crate::domain::value_objects::SchemaValidationError> for ValidationErrorDto {
243 fn from(error: &crate::domain::value_objects::SchemaValidationError) -> Self {
244 use crate::domain::value_objects::SchemaValidationError;
245
246 let (error_type, path, message) = match error {
247 SchemaValidationError::TypeMismatch {
248 path,
249 expected,
250 actual,
251 } => (
252 "type_mismatch".to_string(),
253 path.clone(),
254 format!("Expected {expected}, got {actual}"),
255 ),
256 SchemaValidationError::MissingRequired { path, field } => (
257 "missing_required".to_string(),
258 path.clone(),
259 format!("Missing required field: {field}"),
260 ),
261 SchemaValidationError::OutOfRange {
262 path,
263 value,
264 min,
265 max,
266 } => (
267 "out_of_range".to_string(),
268 path.clone(),
269 format!("Value {value} not in range [{min}, {max}]"),
270 ),
271 SchemaValidationError::StringLengthConstraint {
272 path,
273 actual,
274 min,
275 max,
276 } => (
277 "string_length".to_string(),
278 path.clone(),
279 format!("String length {actual} not in range [{min}, {max}]"),
280 ),
281 SchemaValidationError::PatternMismatch {
282 path,
283 value,
284 pattern,
285 } => (
286 "pattern_mismatch".to_string(),
287 path.clone(),
288 format!("Value '{value}' does not match pattern '{pattern}'"),
289 ),
290 SchemaValidationError::ArraySizeConstraint {
291 path,
292 actual,
293 min,
294 max,
295 } => (
296 "array_size".to_string(),
297 path.clone(),
298 format!("Array size {actual} not in range [{min}, {max}]"),
299 ),
300 SchemaValidationError::DuplicateItems { path } => (
301 "duplicate_items".to_string(),
302 path.clone(),
303 "Array contains duplicate items".to_string(),
304 ),
305 SchemaValidationError::InvalidEnumValue { path, value } => (
306 "invalid_enum".to_string(),
307 path.clone(),
308 format!("Value '{value}' not in allowed values"),
309 ),
310 SchemaValidationError::AdditionalPropertyNotAllowed { path, property } => (
311 "additional_property".to_string(),
312 path.clone(),
313 format!("Additional property '{property}' not allowed"),
314 ),
315 SchemaValidationError::NoMatchingOneOf { path } => (
316 "no_matching_one_of".to_string(),
317 path.clone(),
318 "No matching schema in OneOf".to_string(),
319 ),
320 SchemaValidationError::AllOfFailure { path, failures } => (
321 "all_of_failure".to_string(),
322 path.clone(),
323 format!("AllOf validation failed for schemas: {failures}"),
324 ),
325 };
326
327 Self {
328 path,
329 message,
330 error_type,
331 }
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338 use crate::domain::value_objects::{Schema, SchemaValidationError};
339
340 #[test]
345 fn test_schema_dto_string_serialization() {
346 let dto = SchemaDefinitionDto::String {
347 min_length: Some(1),
348 max_length: Some(100),
349 pattern: Some("^[a-z]+$".to_string()),
350 enum_values: Some(vec!["hello".to_string(), "world".to_string()]),
351 };
352
353 let json = serde_json::to_string(&dto).unwrap();
354 let deserialized: SchemaDefinitionDto = serde_json::from_str(&json).unwrap();
355
356 assert!(matches!(
357 deserialized,
358 SchemaDefinitionDto::String {
359 min_length: Some(1),
360 max_length: Some(100),
361 pattern: Some(_),
362 enum_values: Some(_)
363 }
364 ));
365 }
366
367 #[test]
368 fn test_schema_dto_string_minimal_serialization() {
369 let dto = SchemaDefinitionDto::String {
370 min_length: None,
371 max_length: None,
372 pattern: None,
373 enum_values: None,
374 };
375
376 let json = serde_json::to_string(&dto).unwrap();
377 let deserialized: SchemaDefinitionDto = serde_json::from_str(&json).unwrap();
378
379 assert!(matches!(
380 deserialized,
381 SchemaDefinitionDto::String {
382 min_length: None,
383 max_length: None,
384 pattern: None,
385 enum_values: None
386 }
387 ));
388 }
389
390 #[test]
391 fn test_schema_dto_integer_serialization() {
392 let dto = SchemaDefinitionDto::Integer {
393 minimum: Some(-100),
394 maximum: Some(100),
395 };
396
397 let json = serde_json::to_string(&dto).unwrap();
398 let deserialized: SchemaDefinitionDto = serde_json::from_str(&json).unwrap();
399
400 assert!(matches!(
401 deserialized,
402 SchemaDefinitionDto::Integer {
403 minimum: Some(-100),
404 maximum: Some(100)
405 }
406 ));
407 }
408
409 #[test]
410 fn test_schema_dto_number_serialization() {
411 let dto = SchemaDefinitionDto::Number {
412 minimum: Some(0.5),
413 maximum: Some(99.9),
414 };
415
416 let json = serde_json::to_string(&dto).unwrap();
417 let deserialized: SchemaDefinitionDto = serde_json::from_str(&json).unwrap();
418
419 assert!(matches!(deserialized, SchemaDefinitionDto::Number { .. }));
420 }
421
422 #[test]
423 fn test_schema_dto_boolean_serialization() {
424 let dto = SchemaDefinitionDto::Boolean;
425
426 let json = serde_json::to_string(&dto).unwrap();
427 let deserialized: SchemaDefinitionDto = serde_json::from_str(&json).unwrap();
428
429 assert!(matches!(deserialized, SchemaDefinitionDto::Boolean));
430 }
431
432 #[test]
433 fn test_schema_dto_null_serialization() {
434 let dto = SchemaDefinitionDto::Null;
435
436 let json = serde_json::to_string(&dto).unwrap();
437 let deserialized: SchemaDefinitionDto = serde_json::from_str(&json).unwrap();
438
439 assert!(matches!(deserialized, SchemaDefinitionDto::Null));
440 }
441
442 #[test]
443 fn test_schema_dto_array_serialization() {
444 let dto = SchemaDefinitionDto::Array {
445 items: Some(Box::new(SchemaDefinitionDto::String {
446 min_length: None,
447 max_length: None,
448 pattern: None,
449 enum_values: None,
450 })),
451 min_items: Some(1),
452 max_items: Some(10),
453 unique_items: true,
454 };
455
456 let json = serde_json::to_string(&dto).unwrap();
457 let deserialized: SchemaDefinitionDto = serde_json::from_str(&json).unwrap();
458
459 assert!(matches!(
460 deserialized,
461 SchemaDefinitionDto::Array {
462 items: Some(_),
463 min_items: Some(1),
464 max_items: Some(10),
465 unique_items: true
466 }
467 ));
468 }
469
470 #[test]
471 fn test_schema_dto_array_minimal_serialization() {
472 let dto = SchemaDefinitionDto::Array {
473 items: None,
474 min_items: None,
475 max_items: None,
476 unique_items: false,
477 };
478
479 let json = serde_json::to_string(&dto).unwrap();
480 let deserialized: SchemaDefinitionDto = serde_json::from_str(&json).unwrap();
481
482 assert!(matches!(
483 deserialized,
484 SchemaDefinitionDto::Array {
485 items: None,
486 min_items: None,
487 max_items: None,
488 unique_items: false
489 }
490 ));
491 }
492
493 #[test]
494 fn test_schema_dto_object_serialization() {
495 let mut properties = HashMap::new();
496 properties.insert(
497 "name".to_string(),
498 SchemaDefinitionDto::String {
499 min_length: Some(1),
500 max_length: None,
501 pattern: None,
502 enum_values: None,
503 },
504 );
505 properties.insert(
506 "age".to_string(),
507 SchemaDefinitionDto::Integer {
508 minimum: Some(0),
509 maximum: Some(150),
510 },
511 );
512
513 let dto = SchemaDefinitionDto::Object {
514 properties,
515 required: vec!["name".to_string()],
516 additional_properties: false,
517 };
518
519 let json = serde_json::to_string(&dto).unwrap();
520 let deserialized: SchemaDefinitionDto = serde_json::from_str(&json).unwrap();
521
522 assert!(matches!(deserialized, SchemaDefinitionDto::Object {
523 properties,
524 required,
525 additional_properties: false
526 } if properties.len() == 2 && required.len() == 1));
527 }
528
529 #[test]
530 fn test_schema_dto_object_allow_additional_properties() {
531 let properties = HashMap::new();
532 let dto = SchemaDefinitionDto::Object {
533 properties,
534 required: vec![],
535 additional_properties: true,
536 };
537
538 let json = serde_json::to_string(&dto).unwrap();
539 let deserialized: SchemaDefinitionDto = serde_json::from_str(&json).unwrap();
540
541 assert!(matches!(
542 deserialized,
543 SchemaDefinitionDto::Object {
544 additional_properties: true,
545 ..
546 }
547 ));
548 }
549
550 #[test]
551 fn test_schema_dto_oneof_serialization() {
552 let dto = SchemaDefinitionDto::OneOf {
553 schemas: vec![
554 SchemaDefinitionDto::String {
555 min_length: None,
556 max_length: None,
557 pattern: None,
558 enum_values: None,
559 },
560 SchemaDefinitionDto::Integer {
561 minimum: None,
562 maximum: None,
563 },
564 ],
565 };
566
567 let json = serde_json::to_string(&dto).unwrap();
568 let deserialized: SchemaDefinitionDto = serde_json::from_str(&json).unwrap();
569
570 assert!(
571 matches!(deserialized, SchemaDefinitionDto::OneOf { schemas } if schemas.len() == 2)
572 );
573 }
574
575 #[test]
576 fn test_schema_dto_allof_serialization() {
577 let dto = SchemaDefinitionDto::AllOf {
578 schemas: vec![
579 SchemaDefinitionDto::String {
580 min_length: Some(1),
581 max_length: None,
582 pattern: None,
583 enum_values: None,
584 },
585 SchemaDefinitionDto::String {
586 min_length: None,
587 max_length: Some(100),
588 pattern: None,
589 enum_values: None,
590 },
591 ],
592 };
593
594 let json = serde_json::to_string(&dto).unwrap();
595 let deserialized: SchemaDefinitionDto = serde_json::from_str(&json).unwrap();
596
597 assert!(
598 matches!(deserialized, SchemaDefinitionDto::AllOf { schemas } if schemas.len() == 2)
599 );
600 }
601
602 #[test]
603 fn test_schema_dto_any_serialization() {
604 let dto = SchemaDefinitionDto::Any;
605
606 let json = serde_json::to_string(&dto).unwrap();
607 let deserialized: SchemaDefinitionDto = serde_json::from_str(&json).unwrap();
608
609 assert!(matches!(deserialized, SchemaDefinitionDto::Any));
610 }
611
612 #[test]
617 fn test_schema_dto_to_domain_string() {
618 let dto = SchemaDefinitionDto::String {
619 min_length: Some(5),
620 max_length: Some(50),
621 pattern: Some("[a-z]+".to_string()),
622 enum_values: Some(vec!["foo".to_string(), "bar".to_string()]),
623 };
624
625 let schema: Schema = dto.into();
626
627 assert!(matches!(
628 schema,
629 Schema::String {
630 min_length: Some(5),
631 max_length: Some(50),
632 pattern: Some(_),
633 allowed_values: Some(_)
634 }
635 ));
636 }
637
638 #[test]
639 fn test_schema_dto_to_domain_integer() {
640 let dto = SchemaDefinitionDto::Integer {
641 minimum: Some(10),
642 maximum: Some(20),
643 };
644
645 let schema: Schema = dto.into();
646
647 assert!(matches!(
648 schema,
649 Schema::Integer {
650 minimum: Some(10),
651 maximum: Some(20)
652 }
653 ));
654 }
655
656 #[test]
657 fn test_schema_dto_to_domain_number() {
658 let dto = SchemaDefinitionDto::Number {
659 minimum: Some(1.5),
660 maximum: Some(9.9),
661 };
662
663 let schema: Schema = dto.into();
664
665 assert!(matches!(schema, Schema::Number {
666 minimum: Some(min),
667 maximum: Some(max)
668 } if (min - 1.5).abs() < 0.001 && (max - 9.9).abs() < 0.001));
669 }
670
671 #[test]
672 fn test_schema_dto_to_domain_boolean() {
673 let dto = SchemaDefinitionDto::Boolean;
674 let schema: Schema = dto.into();
675
676 assert!(matches!(schema, Schema::Boolean));
677 }
678
679 #[test]
680 fn test_schema_dto_to_domain_null() {
681 let dto = SchemaDefinitionDto::Null;
682 let schema: Schema = dto.into();
683
684 assert!(matches!(schema, Schema::Null));
685 }
686
687 #[test]
688 fn test_schema_dto_to_domain_array_with_items() {
689 let dto = SchemaDefinitionDto::Array {
690 items: Some(Box::new(SchemaDefinitionDto::Integer {
691 minimum: None,
692 maximum: None,
693 })),
694 min_items: Some(0),
695 max_items: Some(100),
696 unique_items: true,
697 };
698
699 let schema: Schema = dto.into();
700
701 assert!(matches!(
702 schema,
703 Schema::Array {
704 items: Some(_),
705 min_items: Some(0),
706 max_items: Some(100),
707 unique_items: true
708 }
709 ));
710 }
711
712 #[test]
713 fn test_schema_dto_to_domain_array_without_items() {
714 let dto = SchemaDefinitionDto::Array {
715 items: None,
716 min_items: None,
717 max_items: None,
718 unique_items: false,
719 };
720
721 let schema: Schema = dto.into();
722
723 assert!(matches!(
724 schema,
725 Schema::Array {
726 items: None,
727 min_items: None,
728 max_items: None,
729 unique_items: false
730 }
731 ));
732 }
733
734 #[test]
735 fn test_schema_dto_to_domain_object() {
736 let mut properties = HashMap::new();
737 properties.insert(
738 "id".to_string(),
739 SchemaDefinitionDto::Integer {
740 minimum: Some(1),
741 maximum: None,
742 },
743 );
744
745 let dto = SchemaDefinitionDto::Object {
746 properties,
747 required: vec!["id".to_string()],
748 additional_properties: false,
749 };
750
751 let schema: Schema = dto.into();
752
753 assert!(matches!(schema, Schema::Object {
754 properties,
755 required,
756 additional_properties: false
757 } if properties.len() == 1 && required.len() == 1));
758 }
759
760 #[test]
761 fn test_schema_dto_to_domain_oneof() {
762 let dto = SchemaDefinitionDto::OneOf {
763 schemas: vec![
764 SchemaDefinitionDto::String {
765 min_length: None,
766 max_length: None,
767 pattern: None,
768 enum_values: None,
769 },
770 SchemaDefinitionDto::Integer {
771 minimum: None,
772 maximum: None,
773 },
774 ],
775 };
776
777 let schema: Schema = dto.into();
778
779 assert!(matches!(schema, Schema::OneOf { schemas } if schemas.len() == 2));
780 }
781
782 #[test]
783 fn test_schema_dto_to_domain_allof() {
784 let dto = SchemaDefinitionDto::AllOf {
785 schemas: vec![
786 SchemaDefinitionDto::String {
787 min_length: Some(1),
788 max_length: None,
789 pattern: None,
790 enum_values: None,
791 },
792 SchemaDefinitionDto::String {
793 min_length: None,
794 max_length: Some(100),
795 pattern: None,
796 enum_values: None,
797 },
798 ],
799 };
800
801 let schema: Schema = dto.into();
802
803 assert!(matches!(schema, Schema::AllOf { schemas } if schemas.len() == 2));
804 }
805
806 #[test]
807 fn test_schema_dto_to_domain_any() {
808 let dto = SchemaDefinitionDto::Any;
809 let schema: Schema = dto.into();
810
811 assert!(matches!(schema, Schema::Any));
812 }
813
814 #[test]
819 fn test_domain_schema_to_dto_string() {
820 let schema = Schema::String {
821 min_length: Some(10),
822 max_length: Some(100),
823 pattern: Some("[0-9]+".into()),
824 allowed_values: Some(smallvec::smallvec!["123".into(), "456".into()]),
825 };
826
827 let dto: SchemaDefinitionDto = (&schema).into();
828
829 assert!(matches!(
830 dto,
831 SchemaDefinitionDto::String {
832 min_length: Some(10),
833 max_length: Some(100),
834 pattern: Some(_),
835 enum_values: Some(_)
836 }
837 ));
838 }
839
840 #[test]
841 fn test_domain_schema_to_dto_integer() {
842 let schema = Schema::Integer {
843 minimum: Some(0),
844 maximum: Some(1000),
845 };
846
847 let dto: SchemaDefinitionDto = (&schema).into();
848
849 assert!(matches!(
850 dto,
851 SchemaDefinitionDto::Integer {
852 minimum: Some(0),
853 maximum: Some(1000)
854 }
855 ));
856 }
857
858 #[test]
859 fn test_domain_schema_to_dto_number() {
860 let schema = Schema::Number {
861 minimum: Some(0.0),
862 maximum: Some(99.99),
863 };
864
865 let dto: SchemaDefinitionDto = (&schema).into();
866
867 assert!(matches!(dto, SchemaDefinitionDto::Number { .. }));
868 }
869
870 #[test]
871 fn test_domain_schema_to_dto_boolean() {
872 let schema = Schema::Boolean;
873 let dto: SchemaDefinitionDto = (&schema).into();
874
875 assert!(matches!(dto, SchemaDefinitionDto::Boolean));
876 }
877
878 #[test]
879 fn test_domain_schema_to_dto_null() {
880 let schema = Schema::Null;
881 let dto: SchemaDefinitionDto = (&schema).into();
882
883 assert!(matches!(dto, SchemaDefinitionDto::Null));
884 }
885
886 #[test]
887 fn test_domain_schema_to_dto_array() {
888 let schema = Schema::Array {
889 items: Some(Box::new(Schema::String {
890 min_length: None,
891 max_length: None,
892 pattern: None,
893 allowed_values: None,
894 })),
895 min_items: Some(1),
896 max_items: Some(50),
897 unique_items: true,
898 };
899
900 let dto: SchemaDefinitionDto = (&schema).into();
901
902 assert!(matches!(
903 dto,
904 SchemaDefinitionDto::Array {
905 items: Some(_),
906 min_items: Some(1),
907 max_items: Some(50),
908 unique_items: true
909 }
910 ));
911 }
912
913 #[test]
914 fn test_domain_schema_to_dto_object() {
915 let mut properties = HashMap::new();
916 properties.insert(
917 "email".to_string(),
918 Schema::String {
919 min_length: Some(5),
920 max_length: Some(200),
921 pattern: None,
922 allowed_values: None,
923 },
924 );
925
926 let schema = Schema::Object {
927 properties,
928 required: vec!["email".to_string()],
929 additional_properties: true,
930 };
931
932 let dto: SchemaDefinitionDto = (&schema).into();
933
934 assert!(matches!(dto, SchemaDefinitionDto::Object {
935 properties,
936 required,
937 additional_properties: true
938 } if properties.len() == 1 && required.len() == 1));
939 }
940
941 #[test]
942 fn test_domain_schema_to_dto_oneof() {
943 let schema = Schema::OneOf {
944 schemas: smallvec::smallvec![
945 Box::new(Schema::String {
946 min_length: None,
947 max_length: None,
948 pattern: None,
949 allowed_values: None,
950 }),
951 Box::new(Schema::Integer {
952 minimum: None,
953 maximum: None,
954 }),
955 ],
956 };
957
958 let dto: SchemaDefinitionDto = (&schema).into();
959
960 assert!(matches!(dto, SchemaDefinitionDto::OneOf { schemas } if schemas.len() == 2));
961 }
962
963 #[test]
964 fn test_domain_schema_to_dto_allof() {
965 let schema = Schema::AllOf {
966 schemas: smallvec::smallvec![
967 Box::new(Schema::String {
968 min_length: Some(1),
969 max_length: None,
970 pattern: None,
971 allowed_values: None,
972 }),
973 Box::new(Schema::String {
974 min_length: None,
975 max_length: Some(50),
976 pattern: None,
977 allowed_values: None,
978 }),
979 ],
980 };
981
982 let dto: SchemaDefinitionDto = (&schema).into();
983
984 assert!(matches!(dto, SchemaDefinitionDto::AllOf { schemas } if schemas.len() == 2));
985 }
986
987 #[test]
988 fn test_domain_schema_to_dto_any() {
989 let schema = Schema::Any;
990 let dto: SchemaDefinitionDto = (&schema).into();
991
992 assert!(matches!(dto, SchemaDefinitionDto::Any));
993 }
994
995 #[test]
1000 fn test_schema_registration_dto_serialization() {
1001 let dto = SchemaRegistrationDto {
1002 id: "user-schema".to_string(),
1003 schema: SchemaDefinitionDto::Object {
1004 properties: HashMap::new(),
1005 required: vec![],
1006 additional_properties: true,
1007 },
1008 metadata: Some(SchemaMetadataDto {
1009 version: "1.0".to_string(),
1010 description: Some("User schema".to_string()),
1011 author: Some("John Doe".to_string()),
1012 created_at: Some(1234567890),
1013 }),
1014 };
1015
1016 let json = serde_json::to_string(&dto).unwrap();
1017 let deserialized: SchemaRegistrationDto = serde_json::from_str(&json).unwrap();
1018
1019 assert_eq!(deserialized.id, "user-schema");
1020 assert!(matches!(
1021 deserialized.schema,
1022 SchemaDefinitionDto::Object { .. }
1023 ));
1024 assert!(deserialized.metadata.is_some());
1025 }
1026
1027 #[test]
1028 fn test_schema_registration_dto_without_metadata() {
1029 let dto = SchemaRegistrationDto {
1030 id: "simple-schema".to_string(),
1031 schema: SchemaDefinitionDto::String {
1032 min_length: None,
1033 max_length: None,
1034 pattern: None,
1035 enum_values: None,
1036 },
1037 metadata: None,
1038 };
1039
1040 let json = serde_json::to_string(&dto).unwrap();
1041 let deserialized: SchemaRegistrationDto = serde_json::from_str(&json).unwrap();
1042
1043 assert_eq!(deserialized.id, "simple-schema");
1044 assert!(deserialized.metadata.is_none());
1045 }
1046
1047 #[test]
1052 fn test_schema_metadata_dto_full() {
1053 let dto = SchemaMetadataDto {
1054 version: "2.5".to_string(),
1055 description: Some("Complete metadata".to_string()),
1056 author: Some("Jane Smith".to_string()),
1057 created_at: Some(9876543210),
1058 };
1059
1060 let json = serde_json::to_string(&dto).unwrap();
1061 let deserialized: SchemaMetadataDto = serde_json::from_str(&json).unwrap();
1062
1063 assert_eq!(deserialized.version, "2.5");
1064 assert_eq!(
1065 deserialized.description,
1066 Some("Complete metadata".to_string())
1067 );
1068 assert_eq!(deserialized.author, Some("Jane Smith".to_string()));
1069 assert_eq!(deserialized.created_at, Some(9876543210));
1070 }
1071
1072 #[test]
1073 fn test_schema_metadata_dto_minimal() {
1074 let dto = SchemaMetadataDto {
1075 version: "1.0".to_string(),
1076 description: None,
1077 author: None,
1078 created_at: None,
1079 };
1080
1081 let json = serde_json::to_string(&dto).unwrap();
1082 let deserialized: SchemaMetadataDto = serde_json::from_str(&json).unwrap();
1083
1084 assert_eq!(deserialized.version, "1.0");
1085 assert!(deserialized.description.is_none());
1086 assert!(deserialized.author.is_none());
1087 assert!(deserialized.created_at.is_none());
1088 }
1089
1090 #[test]
1095 fn test_validation_request_dto_serialization() {
1096 let dto = ValidationRequestDto {
1097 schema_id: "user-schema".to_string(),
1098 data: r#"{"name": "John", "age": 30}"#.to_string(),
1099 };
1100
1101 let json = serde_json::to_string(&dto).unwrap();
1102 let deserialized: ValidationRequestDto = serde_json::from_str(&json).unwrap();
1103
1104 assert_eq!(deserialized.schema_id, "user-schema");
1105 assert_eq!(deserialized.data, r#"{"name": "John", "age": 30}"#);
1106 }
1107
1108 #[test]
1109 fn test_validation_result_dto_valid() {
1110 let dto = ValidationResultDto {
1111 valid: true,
1112 errors: vec![],
1113 };
1114
1115 let json = serde_json::to_string(&dto).unwrap();
1116 let json_with_errors = if json.contains("errors") {
1119 json
1120 } else {
1121 json.replace("}", r#","errors":[]}"#)
1122 };
1123 let deserialized: ValidationResultDto = serde_json::from_str(&json_with_errors).unwrap();
1124
1125 assert!(deserialized.valid);
1126 assert!(deserialized.errors.is_empty());
1127 }
1128
1129 #[test]
1130 fn test_validation_result_dto_with_errors() {
1131 let dto = ValidationResultDto {
1132 valid: false,
1133 errors: vec![
1134 ValidationErrorDto {
1135 path: "$.name".to_string(),
1136 message: "Too short".to_string(),
1137 error_type: "string_length".to_string(),
1138 },
1139 ValidationErrorDto {
1140 path: "$.age".to_string(),
1141 message: "Out of range".to_string(),
1142 error_type: "out_of_range".to_string(),
1143 },
1144 ],
1145 };
1146
1147 let json = serde_json::to_string(&dto).unwrap();
1148 let deserialized: ValidationResultDto = serde_json::from_str(&json).unwrap();
1149
1150 assert!(!deserialized.valid);
1151 assert_eq!(deserialized.errors.len(), 2);
1152 }
1153
1154 #[test]
1159 fn test_validation_error_type_mismatch_conversion() {
1160 let domain_error = SchemaValidationError::TypeMismatch {
1161 path: "$.field".to_string(),
1162 expected: "string".to_string(),
1163 actual: "number".to_string(),
1164 };
1165
1166 let dto: ValidationErrorDto = (&domain_error).into();
1167
1168 assert_eq!(dto.path, "$.field");
1169 assert_eq!(dto.error_type, "type_mismatch");
1170 assert!(dto.message.contains("string"));
1171 assert!(dto.message.contains("number"));
1172 }
1173
1174 #[test]
1175 fn test_validation_error_missing_required_conversion() {
1176 let domain_error = SchemaValidationError::MissingRequired {
1177 path: "$.".to_string(),
1178 field: "email".to_string(),
1179 };
1180
1181 let dto: ValidationErrorDto = (&domain_error).into();
1182
1183 assert_eq!(dto.path, "$.");
1184 assert_eq!(dto.error_type, "missing_required");
1185 assert!(dto.message.contains("email"));
1186 }
1187
1188 #[test]
1189 fn test_validation_error_out_of_range_conversion() {
1190 let domain_error = SchemaValidationError::OutOfRange {
1191 path: "$.age".to_string(),
1192 value: "200".to_string(),
1193 min: "0".to_string(),
1194 max: "150".to_string(),
1195 };
1196
1197 let dto: ValidationErrorDto = (&domain_error).into();
1198
1199 assert_eq!(dto.path, "$.age");
1200 assert_eq!(dto.error_type, "out_of_range");
1201 assert!(dto.message.contains("200"));
1202 }
1203
1204 #[test]
1205 fn test_validation_error_string_length_conversion() {
1206 let domain_error = SchemaValidationError::StringLengthConstraint {
1207 path: "$.name".to_string(),
1208 actual: 150,
1209 min: 1,
1210 max: 100,
1211 };
1212
1213 let dto: ValidationErrorDto = (&domain_error).into();
1214
1215 assert_eq!(dto.path, "$.name");
1216 assert_eq!(dto.error_type, "string_length");
1217 assert!(dto.message.contains("150"));
1218 }
1219
1220 #[test]
1221 fn test_validation_error_pattern_mismatch_conversion() {
1222 let domain_error = SchemaValidationError::PatternMismatch {
1223 path: "$.email".to_string(),
1224 value: "invalid".to_string(),
1225 pattern: "[a-z]+@[a-z]+\\.[a-z]+".to_string(),
1226 };
1227
1228 let dto: ValidationErrorDto = (&domain_error).into();
1229
1230 assert_eq!(dto.path, "$.email");
1231 assert_eq!(dto.error_type, "pattern_mismatch");
1232 assert!(dto.message.contains("invalid"));
1233 }
1234
1235 #[test]
1236 fn test_validation_error_array_size_conversion() {
1237 let domain_error = SchemaValidationError::ArraySizeConstraint {
1238 path: "$.items".to_string(),
1239 actual: 20,
1240 min: 1,
1241 max: 10,
1242 };
1243
1244 let dto: ValidationErrorDto = (&domain_error).into();
1245
1246 assert_eq!(dto.path, "$.items");
1247 assert_eq!(dto.error_type, "array_size");
1248 assert!(dto.message.contains("20"));
1249 }
1250
1251 #[test]
1252 fn test_validation_error_duplicate_items_conversion() {
1253 let domain_error = SchemaValidationError::DuplicateItems {
1254 path: "$.values".to_string(),
1255 };
1256
1257 let dto: ValidationErrorDto = (&domain_error).into();
1258
1259 assert_eq!(dto.path, "$.values");
1260 assert_eq!(dto.error_type, "duplicate_items");
1261 assert!(dto.message.contains("duplicate"));
1262 }
1263
1264 #[test]
1265 fn test_validation_error_invalid_enum_conversion() {
1266 let domain_error = SchemaValidationError::InvalidEnumValue {
1267 path: "$.status".to_string(),
1268 value: "pending".to_string(),
1269 };
1270
1271 let dto: ValidationErrorDto = (&domain_error).into();
1272
1273 assert_eq!(dto.path, "$.status");
1274 assert_eq!(dto.error_type, "invalid_enum");
1275 assert!(dto.message.contains("pending"));
1276 }
1277
1278 #[test]
1279 fn test_validation_error_additional_property_conversion() {
1280 let domain_error = SchemaValidationError::AdditionalPropertyNotAllowed {
1281 path: "$.".to_string(),
1282 property: "extra_field".to_string(),
1283 };
1284
1285 let dto: ValidationErrorDto = (&domain_error).into();
1286
1287 assert_eq!(dto.path, "$.");
1288 assert_eq!(dto.error_type, "additional_property");
1289 assert!(dto.message.contains("extra_field"));
1290 }
1291
1292 #[test]
1293 fn test_validation_error_no_matching_oneof_conversion() {
1294 let domain_error = SchemaValidationError::NoMatchingOneOf {
1295 path: "$.value".to_string(),
1296 };
1297
1298 let dto: ValidationErrorDto = (&domain_error).into();
1299
1300 assert_eq!(dto.path, "$.value");
1301 assert_eq!(dto.error_type, "no_matching_one_of");
1302 }
1303
1304 #[test]
1305 fn test_validation_error_allof_failure_conversion() {
1306 let domain_error = SchemaValidationError::AllOfFailure {
1307 path: "$.item".to_string(),
1308 failures: "schema1, schema2".to_string(),
1309 };
1310
1311 let dto: ValidationErrorDto = (&domain_error).into();
1312
1313 assert_eq!(dto.path, "$.item");
1314 assert_eq!(dto.error_type, "all_of_failure");
1315 assert!(dto.message.contains("schema1"));
1316 }
1317
1318 #[test]
1323 fn test_nested_object_with_array_conversion() {
1324 let mut inner_properties = HashMap::new();
1325 inner_properties.insert(
1326 "id".to_string(),
1327 SchemaDefinitionDto::Integer {
1328 minimum: Some(1),
1329 maximum: None,
1330 },
1331 );
1332
1333 let dto = SchemaDefinitionDto::Object {
1334 properties: {
1335 let mut props = HashMap::new();
1336 props.insert(
1337 "items".to_string(),
1338 SchemaDefinitionDto::Array {
1339 items: Some(Box::new(SchemaDefinitionDto::Object {
1340 properties: inner_properties,
1341 required: vec!["id".to_string()],
1342 additional_properties: false,
1343 })),
1344 min_items: Some(1),
1345 max_items: None,
1346 unique_items: false,
1347 },
1348 );
1349 props
1350 },
1351 required: vec!["items".to_string()],
1352 additional_properties: true,
1353 };
1354
1355 let schema: Schema = dto.into();
1356 assert!(matches!(schema, Schema::Object { .. }));
1357 }
1358
1359 #[test]
1360 fn test_nested_object_roundtrip() {
1361 let mut inner_props = HashMap::new();
1362 inner_props.insert(
1363 "name".to_string(),
1364 Schema::String {
1365 min_length: Some(1),
1366 max_length: Some(100),
1367 pattern: None,
1368 allowed_values: None,
1369 },
1370 );
1371
1372 let original_schema = Schema::Object {
1373 properties: {
1374 let mut props = HashMap::new();
1375 props.insert(
1376 "user".to_string(),
1377 Schema::Object {
1378 properties: inner_props,
1379 required: vec!["name".to_string()],
1380 additional_properties: false,
1381 },
1382 );
1383 props
1384 },
1385 required: vec!["user".to_string()],
1386 additional_properties: true,
1387 };
1388
1389 let dto: SchemaDefinitionDto = (&original_schema).into();
1390 let schema: Schema = dto.into();
1391
1392 assert!(matches!(schema, Schema::Object { .. }));
1393 }
1394
1395 #[test]
1396 fn test_deeply_nested_array() {
1397 let innermost_dto = SchemaDefinitionDto::String {
1398 min_length: None,
1399 max_length: None,
1400 pattern: None,
1401 enum_values: None,
1402 };
1403
1404 let level1 = SchemaDefinitionDto::Array {
1405 items: Some(Box::new(innermost_dto)),
1406 min_items: None,
1407 max_items: None,
1408 unique_items: false,
1409 };
1410
1411 let level2 = SchemaDefinitionDto::Array {
1412 items: Some(Box::new(level1)),
1413 min_items: None,
1414 max_items: None,
1415 unique_items: false,
1416 };
1417
1418 let schema: Schema = level2.into();
1419
1420 assert!(matches!(schema, Schema::Array {
1421 items: Some(boxed),
1422 ..
1423 } if matches!(*boxed, Schema::Array { .. })));
1424 }
1425}