1use serde::{Deserialize, Serialize};
8use serde_json::Value;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Schema {
13 pub fields: Vec<Field>,
15 pub lrecl_fixed: Option<u32>,
17 pub tail_odo: Option<TailODO>,
19 pub fingerprint: String,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct TailODO {
26 pub counter_path: String,
28 pub min_count: u32,
30 pub max_count: u32,
32 pub array_path: String,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct Field {
39 pub path: String,
41 pub name: String,
43 pub level: u8,
45 pub kind: FieldKind,
47 pub offset: u32,
49 pub len: u32,
51 pub redefines_of: Option<String>,
53 pub occurs: Option<Occurs>,
55 pub sync_padding: Option<u16>,
57 pub synchronized: bool,
59 pub blank_when_zero: bool,
61 pub resolved_renames: Option<ResolvedRenames>,
63 pub children: Vec<Field>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
84pub enum FieldKind {
85 Alphanum {
87 len: u32,
89 },
90 ZonedDecimal {
92 digits: u16,
94 scale: i16,
96 signed: bool,
98 sign_separate: Option<SignSeparateInfo>,
100 },
101 BinaryInt {
103 bits: u16,
105 signed: bool,
107 },
108 PackedDecimal {
110 digits: u16,
112 scale: i16,
114 signed: bool,
116 },
117 Group,
119 Condition {
121 values: Vec<String>,
123 },
124 Renames {
126 from_field: String,
128 thru_field: String,
130 },
131 EditedNumeric {
134 pic_string: String,
136 width: u16,
138 scale: u16,
140 signed: bool,
142 },
143 FloatSingle,
145 FloatDouble,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct ResolvedRenames {
152 pub offset: u32,
154 pub length: u32,
156 pub members: Vec<String>,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct SignSeparateInfo {
163 pub placement: SignPlacement,
165}
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
179#[serde(rename_all = "snake_case")]
180pub enum SignPlacement {
181 Leading,
183 Trailing,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
205pub enum Occurs {
206 Fixed {
208 count: u32,
210 },
211 ODO {
213 min: u32,
215 max: u32,
217 counter_path: String,
219 },
220}
221
222impl Schema {
223 #[must_use]
225 pub fn new() -> Self {
226 Self {
227 fields: Vec::new(),
228 lrecl_fixed: None,
229 tail_odo: None,
230 fingerprint: String::new(),
231 }
232 }
233
234 #[must_use]
236 pub fn from_fields(fields: Vec<Field>) -> Self {
237 let mut schema = Self {
238 fields,
239 lrecl_fixed: None,
240 tail_odo: None,
241 fingerprint: String::new(),
242 };
243 schema.calculate_fingerprint();
244 schema
245 }
246
247 pub fn calculate_fingerprint(&mut self) {
249 use sha2::{Digest, Sha256};
250
251 let canonical_json = self.create_canonical_json();
253
254 let mut hasher = Sha256::new();
256 hasher.update(canonical_json.as_bytes());
257
258 let result = hasher.finalize();
259 self.fingerprint = format!("{result:x}");
260 }
261
262 pub fn create_canonical_json(&self) -> String {
264 use serde_json::{Map, Value};
265
266 let mut schema_obj = Map::new();
267
268 let fields_json: Vec<Value> = self
270 .fields
271 .iter()
272 .map(Self::field_to_canonical_json)
273 .collect();
274 schema_obj.insert("fields".to_string(), Value::Array(fields_json));
275
276 if let Some(lrecl) = self.lrecl_fixed {
278 schema_obj.insert("lrecl_fixed".to_string(), Value::Number(lrecl.into()));
279 }
280
281 if let Some(ref tail_odo) = self.tail_odo {
282 let mut tail_odo_obj = Map::new();
283 tail_odo_obj.insert(
284 "counter_path".to_string(),
285 Value::String(tail_odo.counter_path.clone()),
286 );
287 tail_odo_obj.insert(
288 "min_count".to_string(),
289 Value::Number(tail_odo.min_count.into()),
290 );
291 tail_odo_obj.insert(
292 "max_count".to_string(),
293 Value::Number(tail_odo.max_count.into()),
294 );
295 tail_odo_obj.insert(
296 "array_path".to_string(),
297 Value::String(tail_odo.array_path.clone()),
298 );
299 schema_obj.insert("tail_odo".to_string(), Value::Object(tail_odo_obj));
300 }
301
302 serde_json::to_string(&Value::Object(schema_obj)).unwrap_or_default()
304 }
305
306 fn field_to_canonical_json(field: &Field) -> Value {
308 use serde_json::{Map, Value};
309
310 let mut field_obj = Map::new();
311
312 field_obj.insert("path".to_string(), Value::String(field.path.clone()));
314 field_obj.insert("name".to_string(), Value::String(field.name.clone()));
315 field_obj.insert("level".to_string(), Value::Number(field.level.into()));
316
317 let kind_str = match &field.kind {
319 FieldKind::Alphanum { len } => format!("Alphanum({len})"),
320 FieldKind::ZonedDecimal {
321 digits,
322 scale,
323 signed,
324 sign_separate,
325 } => {
326 format!("ZonedDecimal({digits},{scale},{signed},{sign_separate:?})")
327 }
328 FieldKind::BinaryInt { bits, signed } => {
329 format!("BinaryInt({bits},{signed})")
330 }
331 FieldKind::PackedDecimal {
332 digits,
333 scale,
334 signed,
335 } => {
336 format!("PackedDecimal({digits},{scale},{signed})")
337 }
338 FieldKind::Group => "Group".to_string(),
339 FieldKind::Condition { values } => format!("Condition({values:?})"),
340 FieldKind::Renames {
341 from_field,
342 thru_field,
343 } => format!("Renames({from_field},{thru_field})"),
344 FieldKind::EditedNumeric {
345 pic_string,
346 width,
347 scale,
348 signed,
349 } => {
350 format!("EditedNumeric({pic_string},{width},scale={scale},signed={signed})")
351 }
352 FieldKind::FloatSingle => "FloatSingle".to_string(),
353 FieldKind::FloatDouble => "FloatDouble".to_string(),
354 };
355 field_obj.insert("kind".to_string(), Value::String(kind_str));
356
357 if let Some(ref redefines) = field.redefines_of {
359 field_obj.insert("redefines_of".to_string(), Value::String(redefines.clone()));
360 }
361
362 if let Some(ref occurs) = field.occurs {
363 let occurs_str = match occurs {
364 Occurs::Fixed { count } => format!("Fixed({count})"),
365 Occurs::ODO {
366 min,
367 max,
368 counter_path,
369 } => {
370 format!("ODO({min},{max},{counter_path})")
371 }
372 };
373 field_obj.insert("occurs".to_string(), Value::String(occurs_str));
374 }
375
376 if field.synchronized {
377 field_obj.insert("synchronized".to_string(), Value::Bool(true));
378 }
379
380 if field.blank_when_zero {
381 field_obj.insert("blank_when_zero".to_string(), Value::Bool(true));
382 }
383
384 if !field.children.is_empty() {
386 let children_json: Vec<Value> = field
387 .children
388 .iter()
389 .map(Self::field_to_canonical_json)
390 .collect();
391 field_obj.insert("children".to_string(), Value::Array(children_json));
392 }
393
394 Value::Object(field_obj)
395 }
396
397 #[must_use]
416 pub fn find_field(&self, path: &str) -> Option<&Field> {
417 Self::find_field_recursive(&self.fields, path)
418 }
419
420 fn find_field_recursive<'a>(fields: &'a [Field], path: &str) -> Option<&'a Field> {
421 for field in fields {
422 if field.path == path {
423 return Some(field);
424 }
425 if let Some(found) = Self::find_field_recursive(&field.children, path) {
426 return Some(found);
427 }
428 }
429 None
430 }
431
432 #[must_use]
458 pub fn find_field_or_alias(&self, name_or_path: &str) -> Option<&Field> {
459 if let Some(field) = self.find_field(name_or_path) {
461 return Some(field);
462 }
463
464 let query_name = name_or_path.rsplit('.').next().unwrap_or(name_or_path);
467 Self::find_alias_field_recursive(&self.fields, query_name)
468 }
469
470 fn find_alias_field_recursive<'a>(fields: &'a [Field], alias_name: &str) -> Option<&'a Field> {
472 for field in fields {
473 if field.level == 66 && field.name.eq_ignore_ascii_case(alias_name) {
475 return Some(field);
476 }
477 if let Some(found) = Self::find_alias_field_recursive(&field.children, alias_name) {
479 return Some(found);
480 }
481 }
482 None
483 }
484
485 #[must_use]
508 pub fn resolve_alias_to_target(&self, name_or_path: &str) -> Option<&Field> {
509 if let Some(alias_field) = self.find_field_or_alias(name_or_path) {
511 if alias_field.level == 66
513 && let Some(ref resolved) = alias_field.resolved_renames
514 && let Some(first_member_path) = resolved.members.first()
515 {
516 return self.find_field(first_member_path);
517 }
518 return Some(alias_field);
520 }
521 None
522 }
523
524 #[must_use]
542 pub fn find_redefining_fields<'a>(&'a self, target_path: &str) -> Vec<&'a Field> {
543 fn collect<'a>(fields: &'a [Field], target_path: &str, acc: &mut Vec<&'a Field>) {
544 for f in fields {
545 if let Some(ref redef) = f.redefines_of
546 && redef == target_path
547 {
548 acc.push(f);
549 }
550 collect(&f.children, target_path, acc);
551 }
552 }
553
554 let mut result = Vec::new();
555 collect(&self.fields, target_path, &mut result);
556 result
557 }
558
559 #[must_use]
578 pub fn all_fields(&self) -> Vec<&Field> {
579 let mut result = Vec::new();
580 Self::collect_fields_recursive(&self.fields, &mut result);
581 result
582 }
583
584 fn collect_fields_recursive<'a>(fields: &'a [Field], result: &mut Vec<&'a Field>) {
585 for field in fields {
586 result.push(field);
587 Self::collect_fields_recursive(&field.children, result);
588 }
589 }
590}
591
592impl Field {
593 #[must_use]
595 pub fn new(level: u8, name: String) -> Self {
596 Self {
597 path: name.clone(),
598 name,
599 level,
600 kind: FieldKind::Group, offset: 0,
602 len: 0,
603 redefines_of: None,
604 occurs: None,
605 sync_padding: None,
606 synchronized: false,
607 blank_when_zero: false,
608 resolved_renames: None,
609 children: Vec::new(),
610 }
611 }
612
613 #[must_use]
615 pub fn with_kind(level: u8, name: String, kind: FieldKind) -> Self {
616 Self {
617 path: name.clone(),
618 name,
619 level,
620 kind,
621 offset: 0,
622 len: 0,
623 redefines_of: None,
624 occurs: None,
625 sync_padding: None,
626 synchronized: false,
627 blank_when_zero: false,
628 resolved_renames: None,
629 children: Vec::new(),
630 }
631 }
632
633 #[must_use]
635 pub fn is_group(&self) -> bool {
636 matches!(self.kind, FieldKind::Group)
637 }
638
639 #[must_use]
641 pub fn is_scalar(&self) -> bool {
642 !self.is_group()
643 }
644
645 #[must_use]
647 pub fn effective_length(&self) -> u32 {
648 match &self.occurs {
649 Some(Occurs::Fixed { count }) => self.len * count,
650 Some(Occurs::ODO { max, .. }) => self.len * max,
651 None => self.len,
652 }
653 }
654
655 #[must_use]
657 pub fn sign_separate(&self) -> Option<&SignSeparateInfo> {
658 if let FieldKind::ZonedDecimal { sign_separate, .. } = &self.kind {
659 sign_separate.as_ref()
660 } else {
661 None
662 }
663 }
664
665 #[must_use]
667 pub fn is_packed(&self) -> bool {
668 matches!(self.kind, FieldKind::PackedDecimal { .. })
669 }
670
671 #[must_use]
673 pub fn is_binary(&self) -> bool {
674 matches!(self.kind, FieldKind::BinaryInt { .. })
675 }
676
677 #[must_use]
679 pub fn is_filler(&self) -> bool {
680 self.name.eq_ignore_ascii_case("FILLER")
681 }
682}
683
684impl Default for Schema {
685 fn default() -> Self {
686 Self::new()
687 }
688}
689
690#[cfg(test)]
691mod tests {
692 use super::*;
693
694 #[test]
695 fn test_schema_default() {
696 let schema = Schema::default();
697 assert!(schema.fields.is_empty());
698 assert!(schema.lrecl_fixed.is_none());
699 assert!(schema.tail_odo.is_none());
700 assert!(schema.fingerprint.is_empty());
702 }
703
704 #[test]
705 fn test_schema_new() {
706 let schema = Schema::new();
707 assert!(schema.fields.is_empty());
708 assert!(schema.lrecl_fixed.is_none());
709 assert!(schema.tail_odo.is_none());
710 }
711
712 #[test]
713 fn test_tail_odo_serialization() {
714 let tail_odo = TailODO {
715 counter_path: "ROOT.COUNT".to_string(),
716 min_count: 1,
717 max_count: 100,
718 array_path: "ROOT.ARRAY".to_string(),
719 };
720
721 let serialized = serde_json::to_string(&tail_odo).unwrap();
722 let deserialized: TailODO = serde_json::from_str(&serialized).unwrap();
723
724 assert_eq!(deserialized.counter_path, "ROOT.COUNT");
725 assert_eq!(deserialized.min_count, 1);
726 assert_eq!(deserialized.max_count, 100);
727 assert_eq!(deserialized.array_path, "ROOT.ARRAY");
728 }
729
730 #[test]
731 fn test_field_new() {
732 let field = Field::new(5, "TEST-FIELD".to_string());
733 assert_eq!(field.level, 5);
734 assert_eq!(field.name, "TEST-FIELD");
735 assert_eq!(field.path, "TEST-FIELD");
736 assert!(matches!(field.kind, FieldKind::Group));
737 assert_eq!(field.offset, 0);
738 assert_eq!(field.len, 0);
739 }
740
741 #[test]
742 fn test_field_with_kind() {
743 let kind = FieldKind::Alphanum { len: 10 };
744 let field = Field::with_kind(5, "TEST-FIELD".to_string(), kind.clone());
745 assert_eq!(field.level, 5);
746 assert_eq!(field.name, "TEST-FIELD");
747 assert!(matches!(field.kind, FieldKind::Alphanum { len: 10 }));
748 }
749
750 #[test]
751 fn test_field_is_group() {
752 let group_field = Field::new(1, "GROUP".to_string());
753 assert!(group_field.is_group());
754 assert!(!group_field.is_scalar());
755
756 let scalar_field =
757 Field::with_kind(5, "SCALAR".to_string(), FieldKind::Alphanum { len: 10 });
758 assert!(!scalar_field.is_group());
759 assert!(scalar_field.is_scalar());
760 }
761
762 #[test]
763 fn test_field_effective_length_no_occurs() {
764 let field = Field {
765 path: "TEST".to_string(),
766 name: "TEST".to_string(),
767 level: 5,
768 kind: FieldKind::Alphanum { len: 10 },
769 offset: 0,
770 len: 10,
771 redefines_of: None,
772 occurs: None,
773 sync_padding: None,
774 synchronized: false,
775 blank_when_zero: false,
776 resolved_renames: None,
777 children: Vec::new(),
778 };
779 assert_eq!(field.effective_length(), 10);
780 }
781
782 #[test]
783 fn test_field_effective_length_fixed_occurs() {
784 let field = Field {
785 path: "TEST".to_string(),
786 name: "TEST".to_string(),
787 level: 5,
788 kind: FieldKind::Alphanum { len: 10 },
789 offset: 0,
790 len: 10,
791 redefines_of: None,
792 occurs: Some(Occurs::Fixed { count: 5 }),
793 sync_padding: None,
794 synchronized: false,
795 blank_when_zero: false,
796 resolved_renames: None,
797 children: Vec::new(),
798 };
799 assert_eq!(field.effective_length(), 50);
800 }
801
802 #[test]
803 fn test_field_effective_length_odo_occurs() {
804 let field = Field {
805 path: "TEST".to_string(),
806 name: "TEST".to_string(),
807 level: 5,
808 kind: FieldKind::Alphanum { len: 10 },
809 offset: 0,
810 len: 10,
811 redefines_of: None,
812 occurs: Some(Occurs::ODO {
813 min: 1,
814 max: 100,
815 counter_path: "ROOT.COUNT".to_string(),
816 }),
817 sync_padding: None,
818 synchronized: false,
819 blank_when_zero: false,
820 resolved_renames: None,
821 children: Vec::new(),
822 };
823 assert_eq!(field.effective_length(), 1000);
824 }
825
826 #[test]
827 fn test_field_kind_serialization() {
828 let kinds = vec![
829 FieldKind::Alphanum { len: 10 },
830 FieldKind::ZonedDecimal {
831 digits: 5,
832 scale: 2,
833 signed: true,
834 sign_separate: None,
835 },
836 FieldKind::BinaryInt {
837 bits: 32,
838 signed: true,
839 },
840 FieldKind::PackedDecimal {
841 digits: 7,
842 scale: 2,
843 signed: true,
844 },
845 FieldKind::Group,
846 FieldKind::Condition {
847 values: vec!["A".to_string(), "B".to_string()],
848 },
849 FieldKind::Renames {
850 from_field: "FIELD1".to_string(),
851 thru_field: "FIELD2".to_string(),
852 },
853 FieldKind::EditedNumeric {
854 pic_string: "ZZ9.99".to_string(),
855 width: 6,
856 scale: 2,
857 signed: false,
858 },
859 ];
860
861 for kind in kinds {
862 let serialized = serde_json::to_string(&kind).unwrap();
863 let deserialized: FieldKind = serde_json::from_str(&serialized).unwrap();
864 let re_serialized = serde_json::to_string(&deserialized).unwrap();
866 assert_eq!(serialized, re_serialized);
867 }
868 }
869
870 #[test]
871 fn test_sign_placement_serialization() {
872 let placements = vec![SignPlacement::Leading, SignPlacement::Trailing];
873
874 for placement in placements {
875 let serialized = serde_json::to_string(&placement).unwrap();
876 let deserialized: SignPlacement = serde_json::from_str(&serialized).unwrap();
877 assert_eq!(serialized, serde_json::to_string(&deserialized).unwrap());
878 }
879 }
880
881 #[test]
882 fn test_sign_separate_info_serialization() {
883 let info = SignSeparateInfo {
884 placement: SignPlacement::Leading,
885 };
886
887 let serialized = serde_json::to_string(&info).unwrap();
888 let deserialized: SignSeparateInfo = serde_json::from_str(&serialized).unwrap();
889
890 assert!(matches!(deserialized.placement, SignPlacement::Leading));
891 }
892
893 #[test]
894 fn test_resolved_renames_serialization() {
895 let renames = ResolvedRenames {
896 offset: 10,
897 length: 50,
898 members: vec!["FIELD1".to_string(), "FIELD2".to_string()],
899 };
900
901 let serialized = serde_json::to_string(&renames).unwrap();
902 let deserialized: ResolvedRenames = serde_json::from_str(&serialized).unwrap();
903
904 assert_eq!(deserialized.offset, 10);
905 assert_eq!(deserialized.length, 50);
906 assert_eq!(deserialized.members.len(), 2);
907 assert_eq!(deserialized.members[0], "FIELD1");
908 assert_eq!(deserialized.members[1], "FIELD2");
909 }
910
911 #[test]
912 fn test_schema_serialization() {
913 let schema = Schema {
914 fields: vec![Field::new(1, "ROOT".to_string())],
915 lrecl_fixed: Some(100),
916 tail_odo: Some(TailODO {
917 counter_path: "ROOT.COUNT".to_string(),
918 min_count: 1,
919 max_count: 100,
920 array_path: "ROOT.ARRAY".to_string(),
921 }),
922 fingerprint: "test-fingerprint".to_string(),
923 };
924
925 let serialized = serde_json::to_string(&schema).unwrap();
926 let deserialized: Schema = serde_json::from_str(&serialized).unwrap();
927
928 assert_eq!(deserialized.fields.len(), 1);
929 assert_eq!(deserialized.lrecl_fixed, Some(100));
930 assert!(deserialized.tail_odo.is_some());
931 assert_eq!(deserialized.fingerprint, "test-fingerprint");
932 }
933
934 #[test]
935 fn test_schema_find_field() {
936 let mut field = Field::new(5, "CHILD".to_string());
937 field.path = "PARENT.CHILD".to_string();
938
939 let schema = Schema {
940 fields: vec![Field {
941 path: "PARENT".to_string(),
942 name: "PARENT".to_string(),
943 level: 1,
944 kind: FieldKind::Group,
945 offset: 0,
946 len: 10,
947 redefines_of: None,
948 occurs: None,
949 sync_padding: None,
950 synchronized: false,
951 blank_when_zero: false,
952 resolved_renames: None,
953 children: vec![field],
954 }],
955 lrecl_fixed: None,
956 tail_odo: None,
957 fingerprint: "test".to_string(),
958 };
959
960 let found = schema.find_field("PARENT.CHILD");
961 assert!(found.is_some());
962 assert_eq!(found.unwrap().name, "CHILD");
963
964 let not_found = schema.find_field("NONEXISTENT");
965 assert!(not_found.is_none());
966 }
967
968 #[test]
969 fn test_schema_find_redefining_fields() {
970 let _base_field = Field::new(5, "BASE".to_string());
971 let redef_field1 =
972 Field::with_kind(5, "REDEF1".to_string(), FieldKind::Alphanum { len: 5 });
973 let redef_field2 = Field::with_kind(
974 5,
975 "REDEF2".to_string(),
976 FieldKind::ZonedDecimal {
977 digits: 5,
978 scale: 0,
979 signed: false,
980 sign_separate: None,
981 },
982 );
983
984 let mut base_field_with_redef = Field::new(5, "BASE".to_string());
985 base_field_with_redef.path = "ROOT.BASE".to_string();
986 base_field_with_redef.redefines_of = None;
987
988 let mut redef_field1_with_path = redef_field1.clone();
989 redef_field1_with_path.path = "ROOT.REDEF1".to_string();
990 redef_field1_with_path.redefines_of = Some("ROOT.BASE".to_string());
991
992 let mut redef_field2_with_path = redef_field2.clone();
993 redef_field2_with_path.path = "ROOT.REDEF2".to_string();
994 redef_field2_with_path.redefines_of = Some("ROOT.BASE".to_string());
995
996 let schema = Schema {
997 fields: vec![
998 base_field_with_redef,
999 redef_field1_with_path,
1000 redef_field2_with_path,
1001 ],
1002 lrecl_fixed: None,
1003 tail_odo: None,
1004 fingerprint: "test".to_string(),
1005 };
1006
1007 let redefining = schema.find_redefining_fields("ROOT.BASE");
1008 assert_eq!(redefining.len(), 2);
1009 assert!(redefining.iter().any(|f| f.name == "REDEF1"));
1010 assert!(redefining.iter().any(|f| f.name == "REDEF2"));
1011 }
1012
1013 #[test]
1014 fn test_schema_all_fields() {
1015 let child1 = Field::with_kind(5, "CHILD1".to_string(), FieldKind::Alphanum { len: 5 });
1016 let child2 = Field::with_kind(5, "CHILD2".to_string(), FieldKind::Alphanum { len: 5 });
1017
1018 let mut parent = Field::new(1, "PARENT".to_string());
1019 parent.path = "ROOT.PARENT".to_string();
1020 parent.children = vec![child1, child2];
1021
1022 let top_level = Field::with_kind(1, "TOP".to_string(), FieldKind::Alphanum { len: 10 });
1023
1024 let schema = Schema {
1025 fields: vec![parent, top_level],
1026 lrecl_fixed: None,
1027 tail_odo: None,
1028 fingerprint: "test".to_string(),
1029 };
1030
1031 let all_fields = schema.all_fields();
1032 assert_eq!(all_fields.len(), 4);
1034 assert!(all_fields.iter().any(|f| f.name == "PARENT"));
1035 assert!(all_fields.iter().any(|f| f.name == "CHILD1"));
1036 assert!(all_fields.iter().any(|f| f.name == "CHILD2"));
1037 assert!(all_fields.iter().any(|f| f.name == "TOP"));
1038 }
1039}