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