1use std::fmt;
2use std::ops::{Deref, DerefMut};
3use std::str::FromStr;
4
5use enumset::EnumSet;
6use relay_protocol::{
7 Annotated, Array, Empty, Error, FromValue, IntoValue, Meta, Object, SkipSerialization, Value,
8};
9use serde::{Deserialize, Serialize};
10use uuid::Uuid;
11
12use crate::processor::{ProcessValue, ProcessingResult, ProcessingState, Processor, ValueType};
13use crate::protocol::Addr;
14
15#[derive(Debug, FromValue, IntoValue, Empty, Clone, PartialEq, Deserialize, Serialize)]
20pub struct NativeImagePath(pub String);
21
22impl NativeImagePath {
23 pub fn as_str(&self) -> &str {
24 self.0.as_str()
25 }
26}
27
28impl<T: Into<String>> From<T> for NativeImagePath {
29 fn from(value: T) -> NativeImagePath {
30 NativeImagePath(value.into())
31 }
32}
33
34impl ProcessValue for NativeImagePath {
35 #[inline]
36 fn value_type(&self) -> EnumSet<ValueType> {
37 EnumSet::only(ValueType::String)
44 }
45
46 #[inline]
47 fn process_value<P>(
48 &mut self,
49 meta: &mut Meta,
50 processor: &mut P,
51 state: &ProcessingState<'_>,
52 ) -> ProcessingResult
53 where
54 P: Processor,
55 {
56 processor.process_native_image_path(self, meta, state)
57 }
58
59 fn process_child_values<P>(
60 &mut self,
61 _processor: &mut P,
62 _state: &ProcessingState<'_>,
63 ) -> ProcessingResult
64 where
65 P: Processor,
66 {
67 Ok(())
68 }
69}
70
71#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
76pub struct SystemSdkInfo {
77 pub sdk_name: Annotated<String>,
79
80 pub version_major: Annotated<u64>,
82
83 pub version_minor: Annotated<u64>,
85
86 pub version_patchlevel: Annotated<u64>,
88
89 #[metastructure(additional_properties)]
91 pub other: Object<Value>,
92}
93
94#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
98pub struct AppleDebugImage {
99 #[metastructure(required = true)]
101 pub name: Annotated<String>,
102
103 pub arch: Annotated<String>,
105
106 pub cpu_type: Annotated<u64>,
108
109 pub cpu_subtype: Annotated<u64>,
111
112 #[metastructure(required = true)]
114 pub image_addr: Annotated<Addr>,
115
116 #[metastructure(required = true)]
118 pub image_size: Annotated<u64>,
119
120 pub image_vmaddr: Annotated<Addr>,
122
123 #[metastructure(required = true)]
125 pub uuid: Annotated<Uuid>,
126
127 #[metastructure(additional_properties)]
129 pub other: Object<Value>,
130}
131
132macro_rules! impl_traits {
133 ($type:ident, $inner:path, $expectation:literal) => {
134 impl Empty for $type {
135 #[inline]
136 fn is_empty(&self) -> bool {
137 self.is_nil()
138 }
139 }
140
141 impl FromValue for $type {
142 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
143 match value {
144 Annotated(Some(Value::String(value)), mut meta) => match value.parse() {
145 Ok(value) => Annotated(Some(value), meta),
146 Err(err) => {
147 meta.add_error(Error::invalid(err));
148 meta.set_original_value(Some(value));
149 Annotated(None, meta)
150 }
151 },
152 Annotated(Some(value), mut meta) => {
153 meta.add_error(Error::expected($expectation));
154 meta.set_original_value(Some(value));
155 Annotated(None, meta)
156 }
157 Annotated(None, meta) => Annotated(None, meta),
158 }
159 }
160 }
161
162 impl IntoValue for $type {
163 fn into_value(self) -> Value {
164 Value::String(self.to_string())
165 }
166
167 fn serialize_payload<S>(
168 &self,
169 s: S,
170 _behavior: SkipSerialization,
171 ) -> Result<S::Ok, S::Error>
172 where
173 S: serde::Serializer,
174 {
175 serde::Serialize::serialize(self, s)
176 }
177 }
178
179 impl ProcessValue for $type {}
180
181 impl fmt::Display for $type {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 self.0.fmt(f)
184 }
185 }
186
187 impl FromStr for $type {
188 type Err = <$inner as FromStr>::Err;
189
190 fn from_str(s: &str) -> Result<Self, Self::Err> {
191 FromStr::from_str(s).map($type)
192 }
193 }
194
195 impl Deref for $type {
196 type Target = $inner;
197
198 fn deref(&self) -> &Self::Target {
199 &self.0
200 }
201 }
202
203 impl DerefMut for $type {
204 fn deref_mut(&mut self) -> &mut Self::Target {
205 &mut self.0
206 }
207 }
208 };
209}
210
211#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
212pub struct DebugId(pub debugid::DebugId);
213
214#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
215pub struct CodeId(pub debugid::CodeId);
216
217impl_traits!(CodeId, debugid::CodeId, "a code identifier");
218impl_traits!(DebugId, debugid::DebugId, "a debug identifier");
219
220impl<T> From<T> for DebugId
221where
222 debugid::DebugId: From<T>,
223{
224 fn from(t: T) -> Self {
225 DebugId(t.into())
226 }
227}
228
229impl<T> From<T> for CodeId
230where
231 debugid::CodeId: From<T>,
232{
233 fn from(t: T) -> Self {
234 CodeId(t.into())
235 }
236}
237
238#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
288pub struct NativeDebugImage {
289 pub code_id: Annotated<CodeId>,
308
309 #[metastructure(required = true, legacy_alias = "name")]
315 #[metastructure(pii = "maybe")]
316 pub code_file: Annotated<NativeImagePath>,
317
318 #[metastructure(required = true, legacy_alias = "id")]
340 pub debug_id: Annotated<DebugId>,
341
342 #[metastructure(pii = "maybe")]
350 pub debug_file: Annotated<NativeImagePath>,
351
352 pub debug_checksum: Annotated<String>,
359
360 pub arch: Annotated<String>,
364
365 pub image_addr: Annotated<Addr>,
369
370 pub image_size: Annotated<u64>,
374
375 pub image_vmaddr: Annotated<Addr>,
387
388 #[metastructure(additional_properties)]
390 pub other: Object<Value>,
391}
392
393#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
409pub struct SourceMapDebugImage {
410 #[metastructure(required = true)]
414 pub code_file: Annotated<String>,
415
416 #[metastructure(required = true)]
418 pub debug_id: Annotated<DebugId>,
419
420 #[metastructure(pii = "maybe")]
422 pub debug_file: Annotated<String>,
423
424 #[metastructure(additional_properties)]
426 pub other: Object<Value>,
427}
428
429#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
440pub struct JvmDebugImage {
441 #[metastructure(required = true)]
443 pub debug_id: Annotated<DebugId>,
444
445 #[metastructure(additional_properties)]
447 pub other: Object<Value>,
448}
449
450#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
454pub struct ProguardDebugImage {
455 #[metastructure(required = true)]
457 pub uuid: Annotated<Uuid>,
458
459 #[metastructure(additional_properties)]
461 pub other: Object<Value>,
462}
463
464#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
466#[metastructure(process_func = "process_debug_image")]
467pub enum DebugImage {
468 Apple(Box<AppleDebugImage>),
470 Symbolic(Box<NativeDebugImage>),
472 MachO(Box<NativeDebugImage>),
474 Elf(Box<NativeDebugImage>),
476 Pe(Box<NativeDebugImage>),
478 #[metastructure(tag = "pe_dotnet")]
480 PeDotnet(Box<NativeDebugImage>),
481 Proguard(Box<ProguardDebugImage>),
483 Wasm(Box<NativeDebugImage>),
485 SourceMap(Box<SourceMapDebugImage>),
487 Jvm(Box<JvmDebugImage>),
489 #[metastructure(fallback_variant)]
491 Other(Object<Value>),
492}
493
494#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
515#[metastructure(process_func = "process_debug_meta")]
516pub struct DebugMeta {
517 #[metastructure(field = "sdk_info")]
519 #[metastructure(skip_serialization = "empty")]
520 pub system_sdk: Annotated<SystemSdkInfo>,
521
522 #[metastructure(skip_serialization = "empty")]
524 pub images: Annotated<Array<DebugImage>>,
525
526 #[metastructure(additional_properties)]
528 pub other: Object<Value>,
529}
530
531#[cfg(test)]
532mod tests {
533 use relay_protocol::Map;
534 use similar_asserts::assert_eq;
535
536 use super::*;
537
538 #[test]
539 fn test_debug_image_proguard_roundtrip() {
540 let json = r#"{
541 "uuid": "395835f4-03e0-4436-80d3-136f0749a893",
542 "other": "value",
543 "type": "proguard"
544}"#;
545 let image = Annotated::new(DebugImage::Proguard(Box::new(ProguardDebugImage {
546 uuid: Annotated::new("395835f4-03e0-4436-80d3-136f0749a893".parse().unwrap()),
547 other: {
548 let mut map = Object::new();
549 map.insert(
550 "other".to_string(),
551 Annotated::new(Value::String("value".to_string())),
552 );
553 map
554 },
555 })));
556
557 assert_eq!(image, Annotated::from_json(json).unwrap());
558 assert_eq!(json, image.to_json_pretty().unwrap());
559 }
560
561 #[test]
562 fn test_debug_image_jvm_based_roundtrip() {
563 let json = r#"{
564 "debug_id": "395835f4-03e0-4436-80d3-136f0749a893",
565 "other": "value",
566 "type": "jvm"
567}"#;
568 let image = Annotated::new(DebugImage::Jvm(Box::new(JvmDebugImage {
569 debug_id: Annotated::new("395835f4-03e0-4436-80d3-136f0749a893".parse().unwrap()),
570 other: {
571 let mut map = Map::new();
572 map.insert(
573 "other".to_string(),
574 Annotated::new(Value::String("value".to_string())),
575 );
576 map
577 },
578 })));
579
580 assert_eq!(image, Annotated::from_json(json).unwrap());
581 assert_eq!(json, image.to_json_pretty().unwrap());
582 }
583
584 #[test]
585 fn test_debug_image_apple_roundtrip() {
586 let json = r#"{
587 "name": "CoreFoundation",
588 "arch": "arm64",
589 "cpu_type": 1233,
590 "cpu_subtype": 3,
591 "image_addr": "0x0",
592 "image_size": 4096,
593 "image_vmaddr": "0x8000",
594 "uuid": "494f3aea-88fa-4296-9644-fa8ef5d139b6",
595 "other": "value",
596 "type": "apple"
597}"#;
598
599 let image = Annotated::new(DebugImage::Apple(Box::new(AppleDebugImage {
600 name: Annotated::new("CoreFoundation".to_string()),
601 arch: Annotated::new("arm64".to_string()),
602 cpu_type: Annotated::new(1233),
603 cpu_subtype: Annotated::new(3),
604 image_addr: Annotated::new(Addr(0)),
605 image_size: Annotated::new(4096),
606 image_vmaddr: Annotated::new(Addr(32768)),
607 uuid: Annotated::new("494f3aea-88fa-4296-9644-fa8ef5d139b6".parse().unwrap()),
608 other: {
609 let mut map = Object::new();
610 map.insert(
611 "other".to_string(),
612 Annotated::new(Value::String("value".to_string())),
613 );
614 map
615 },
616 })));
617
618 assert_eq!(image, Annotated::from_json(json).unwrap());
619 assert_eq!(json, image.to_json_pretty().unwrap());
620 }
621
622 #[test]
623 fn test_debug_image_apple_default_values() {
624 let json = r#"{
625 "name": "CoreFoundation",
626 "image_addr": "0x0",
627 "image_size": 4096,
628 "uuid": "494f3aea-88fa-4296-9644-fa8ef5d139b6",
629 "type": "apple"
630}"#;
631
632 let image = Annotated::new(DebugImage::Apple(Box::new(AppleDebugImage {
633 name: Annotated::new("CoreFoundation".to_string()),
634 image_addr: Annotated::new(Addr(0)),
635 image_size: Annotated::new(4096),
636 uuid: Annotated::new("494f3aea-88fa-4296-9644-fa8ef5d139b6".parse().unwrap()),
637 ..Default::default()
638 })));
639
640 assert_eq!(image, Annotated::from_json(json).unwrap());
641 assert_eq!(json, image.to_json_pretty().unwrap());
642 }
643
644 #[test]
645 fn test_debug_image_symbolic_roundtrip() {
646 let json = r#"{
647 "code_id": "59b0d8f3183000",
648 "code_file": "C:\\Windows\\System32\\ntdll.dll",
649 "debug_id": "971f98e5-ce60-41ff-b2d7-235bbeb34578-1",
650 "debug_file": "wntdll.pdb",
651 "arch": "arm64",
652 "image_addr": "0x0",
653 "image_size": 4096,
654 "image_vmaddr": "0x8000",
655 "other": "value",
656 "type": "symbolic"
657}"#;
658
659 let image = Annotated::new(DebugImage::Symbolic(Box::new(NativeDebugImage {
660 code_id: Annotated::new("59b0d8f3183000".parse().unwrap()),
661 code_file: Annotated::new("C:\\Windows\\System32\\ntdll.dll".into()),
662 debug_id: Annotated::new("971f98e5-ce60-41ff-b2d7-235bbeb34578-1".parse().unwrap()),
663 debug_file: Annotated::new("wntdll.pdb".into()),
664 debug_checksum: Annotated::empty(),
665 arch: Annotated::new("arm64".to_string()),
666 image_addr: Annotated::new(Addr(0)),
667 image_size: Annotated::new(4096),
668 image_vmaddr: Annotated::new(Addr(32768)),
669 other: {
670 let mut map = Object::new();
671 map.insert(
672 "other".to_string(),
673 Annotated::new(Value::String("value".to_string())),
674 );
675 map
676 },
677 })));
678
679 assert_eq!(image, Annotated::from_json(json).unwrap());
680 assert_eq!(json, image.to_json_pretty().unwrap());
681 }
682
683 #[test]
684 fn test_debug_image_symbolic_legacy() {
685 let json = r#"{
686 "name": "CoreFoundation",
687 "arch": "arm64",
688 "image_addr": "0x0",
689 "image_size": 4096,
690 "image_vmaddr": "0x8000",
691 "id": "494f3aea-88fa-4296-9644-fa8ef5d139b6-1234",
692 "other": "value",
693 "type": "symbolic"
694}"#;
695
696 let image = Annotated::new(DebugImage::Symbolic(Box::new(NativeDebugImage {
697 code_id: Annotated::empty(),
698 code_file: Annotated::new("CoreFoundation".into()),
699 debug_id: Annotated::new("494f3aea-88fa-4296-9644-fa8ef5d139b6-1234".parse().unwrap()),
700 debug_file: Annotated::empty(),
701 debug_checksum: Annotated::empty(),
702 arch: Annotated::new("arm64".to_string()),
703 image_addr: Annotated::new(Addr(0)),
704 image_size: Annotated::new(4096),
705 image_vmaddr: Annotated::new(Addr(32768)),
706 other: {
707 let mut map = Object::new();
708 map.insert(
709 "other".to_string(),
710 Annotated::new(Value::String("value".to_string())),
711 );
712 map
713 },
714 })));
715
716 assert_eq!(image, Annotated::from_json(json).unwrap());
717 }
718
719 #[test]
720 fn test_debug_image_symbolic_default_values() {
721 let json = r#"{
722 "code_file": "CoreFoundation",
723 "debug_id": "494f3aea-88fa-4296-9644-fa8ef5d139b6-1234",
724 "image_addr": "0x0",
725 "image_size": 4096,
726 "type": "symbolic"
727}"#;
728
729 let image = Annotated::new(DebugImage::Symbolic(Box::new(NativeDebugImage {
730 code_file: Annotated::new("CoreFoundation".into()),
731 debug_id: Annotated::new(
732 "494f3aea-88fa-4296-9644-fa8ef5d139b6-1234"
733 .parse::<DebugId>()
734 .unwrap(),
735 ),
736 image_addr: Annotated::new(Addr(0)),
737 image_size: Annotated::new(4096),
738 ..Default::default()
739 })));
740
741 assert_eq!(image, Annotated::from_json(json).unwrap());
742 assert_eq!(json, image.to_json_pretty().unwrap());
743 }
744
745 #[test]
746 fn test_debug_image_elf_roundtrip() {
747 let json = r#"{
748 "code_id": "f1c3bcc0279865fe3058404b2831d9e64135386c",
749 "code_file": "crash",
750 "debug_id": "c0bcc3f1-9827-fe65-3058-404b2831d9e6",
751 "arch": "arm64",
752 "image_addr": "0x0",
753 "image_size": 4096,
754 "image_vmaddr": "0x8000",
755 "other": "value",
756 "type": "elf"
757}"#;
758
759 let image = Annotated::new(DebugImage::Elf(Box::new(NativeDebugImage {
760 code_id: Annotated::new("f1c3bcc0279865fe3058404b2831d9e64135386c".parse().unwrap()),
761 code_file: Annotated::new("crash".into()),
762 debug_id: Annotated::new("c0bcc3f1-9827-fe65-3058-404b2831d9e6".parse().unwrap()),
763 debug_file: Annotated::empty(),
764 debug_checksum: Annotated::empty(),
765 arch: Annotated::new("arm64".to_string()),
766 image_addr: Annotated::new(Addr(0)),
767 image_size: Annotated::new(4096),
768 image_vmaddr: Annotated::new(Addr(32768)),
769 other: {
770 let mut map = Object::new();
771 map.insert(
772 "other".to_string(),
773 Annotated::new(Value::String("value".to_string())),
774 );
775 map
776 },
777 })));
778
779 assert_eq!(image, Annotated::from_json(json).unwrap());
780 assert_eq!(json, image.to_json_pretty().unwrap());
781 }
782
783 #[test]
784 fn test_debug_image_pe_dotnet_roundtrip() {
785 let json = r#"{
786 "debug_id": "4e2ca887-825e-46f3-968f-25b41ae1b5f3-cc3f6d9e",
787 "debug_file": "TimeZoneConverter.pdb",
788 "debug_checksum": "SHA256:87a82c4e5e82f386968f25b41ae1b5f3cc3f6d9e79cfb4464f8240400fc47dcd79",
789 "type": "pe_dotnet"
790}"#;
791
792 let image = Annotated::new(DebugImage::PeDotnet(Box::new(NativeDebugImage {
793 debug_id: Annotated::new(
794 "4e2ca887-825e-46f3-968f-25b41ae1b5f3-cc3f6d9e"
795 .parse()
796 .unwrap(),
797 ),
798 debug_file: Annotated::new("TimeZoneConverter.pdb".into()),
799 debug_checksum: Annotated::new(
800 "SHA256:87a82c4e5e82f386968f25b41ae1b5f3cc3f6d9e79cfb4464f8240400fc47dcd79".into(),
801 ),
802 ..Default::default()
803 })));
804
805 assert_eq!(image, Annotated::from_json(json).unwrap());
806 assert_eq!(json, image.to_json_pretty().unwrap());
807 }
808
809 #[test]
810 fn test_debug_image_macho_roundtrip() {
811 let json = r#"{
812 "code_id": "67E9247C-814E-392B-A027-DBDE6748FCBF",
813 "code_file": "crash",
814 "debug_id": "67e9247c-814e-392b-a027-dbde6748fcbf",
815 "arch": "arm64",
816 "image_addr": "0x0",
817 "image_size": 4096,
818 "image_vmaddr": "0x8000",
819 "other": "value",
820 "type": "macho"
821}"#;
822
823 let image = Annotated::new(DebugImage::MachO(Box::new(NativeDebugImage {
824 code_id: Annotated::new("67E9247C-814E-392B-A027-DBDE6748FCBF".parse().unwrap()),
825 code_file: Annotated::new("crash".into()),
826 debug_id: Annotated::new("67e9247c-814e-392b-a027-dbde6748fcbf".parse().unwrap()),
827 debug_file: Annotated::empty(),
828 debug_checksum: Annotated::empty(),
829 arch: Annotated::new("arm64".to_string()),
830 image_addr: Annotated::new(Addr(0)),
831 image_size: Annotated::new(4096),
832 image_vmaddr: Annotated::new(Addr(32768)),
833 other: {
834 let mut map = Object::new();
835 map.insert(
836 "other".to_string(),
837 Annotated::new(Value::String("value".to_string())),
838 );
839 map
840 },
841 })));
842
843 assert_eq!(image, Annotated::from_json(json).unwrap());
844 }
845
846 #[test]
847 fn test_debug_image_pe_roundtrip() {
848 let json = r#"{
849 "code_id": "59b0d8f3183000",
850 "code_file": "C:\\Windows\\System32\\ntdll.dll",
851 "debug_id": "971f98e5-ce60-41ff-b2d7-235bbeb34578-1",
852 "debug_file": "wntdll.pdb",
853 "arch": "arm64",
854 "image_addr": "0x0",
855 "image_size": 4096,
856 "image_vmaddr": "0x8000",
857 "other": "value",
858 "type": "pe"
859}"#;
860
861 let image = Annotated::new(DebugImage::Pe(Box::new(NativeDebugImage {
862 code_id: Annotated::new("59b0d8f3183000".parse().unwrap()),
863 code_file: Annotated::new("C:\\Windows\\System32\\ntdll.dll".into()),
864 debug_id: Annotated::new("971f98e5-ce60-41ff-b2d7-235bbeb34578-1".parse().unwrap()),
865 debug_file: Annotated::new("wntdll.pdb".into()),
866 debug_checksum: Annotated::empty(),
867 arch: Annotated::new("arm64".to_string()),
868 image_addr: Annotated::new(Addr(0)),
869 image_size: Annotated::new(4096),
870 image_vmaddr: Annotated::new(Addr(32768)),
871 other: {
872 let mut map = Object::new();
873 map.insert(
874 "other".to_string(),
875 Annotated::new(Value::String("value".to_string())),
876 );
877 map
878 },
879 })));
880
881 assert_eq!(image, Annotated::from_json(json).unwrap());
882 assert_eq!(json, image.to_json_pretty().unwrap());
883 }
884
885 #[test]
886 fn test_source_map_image_roundtrip() {
887 let json = r#"{
888 "code_file": "https://mycdn.invalid/foo.js.min",
889 "debug_id": "971f98e5-ce60-41ff-b2d7-235bbeb34578",
890 "debug_file": "https://mycdn.invalid/foo.js.map",
891 "other": "value",
892 "type": "sourcemap"
893}"#;
894
895 let image = Annotated::new(DebugImage::SourceMap(Box::new(SourceMapDebugImage {
896 code_file: Annotated::new("https://mycdn.invalid/foo.js.min".into()),
897 debug_file: Annotated::new("https://mycdn.invalid/foo.js.map".into()),
898 debug_id: Annotated::new("971f98e5-ce60-41ff-b2d7-235bbeb34578".parse().unwrap()),
899 other: {
900 let mut map = Object::new();
901 map.insert(
902 "other".to_string(),
903 Annotated::new(Value::String("value".to_string())),
904 );
905 map
906 },
907 })));
908
909 assert_eq!(image, Annotated::from_json(json).unwrap());
910 assert_eq!(json, image.to_json_pretty().unwrap());
911 }
912
913 #[test]
914 fn test_debug_image_other_roundtrip() {
915 let json = r#"{"other":"value","type":"mytype"}"#;
916 let image = Annotated::new(DebugImage::Other({
917 let mut map = Map::new();
918 map.insert(
919 "type".to_string(),
920 Annotated::new(Value::String("mytype".to_string())),
921 );
922 map.insert(
923 "other".to_string(),
924 Annotated::new(Value::String("value".to_string())),
925 );
926 map
927 }));
928
929 assert_eq!(image, Annotated::from_json(json).unwrap());
930 assert_eq!(json, image.to_json().unwrap());
931 }
932
933 #[test]
934 fn test_debug_image_untagged_roundtrip() {
935 let json = r#"{"other":"value"}"#;
936 let image = Annotated::new(DebugImage::Other({
937 let mut map = Map::new();
938 map.insert(
939 "other".to_string(),
940 Annotated::new(Value::String("value".to_string())),
941 );
942 map
943 }));
944
945 assert_eq!(image, Annotated::from_json(json).unwrap());
946 assert_eq!(json, image.to_json().unwrap());
947 }
948
949 #[test]
950 fn test_debug_meta_roundtrip() {
951 let json = r#"{
953 "sdk_info": {
954 "sdk_name": "iOS",
955 "version_major": 10,
956 "version_minor": 3,
957 "version_patchlevel": 0,
958 "other": "value"
959 },
960 "other": "value"
961}"#;
962 let meta = Annotated::new(DebugMeta {
963 system_sdk: Annotated::new(SystemSdkInfo {
964 sdk_name: Annotated::new("iOS".to_string()),
965 version_major: Annotated::new(10),
966 version_minor: Annotated::new(3),
967 version_patchlevel: Annotated::new(0),
968 other: {
969 let mut map = Map::new();
970 map.insert(
971 "other".to_string(),
972 Annotated::new(Value::String("value".to_string())),
973 );
974 map
975 },
976 }),
977 other: {
978 let mut map = Map::new();
979 map.insert(
980 "other".to_string(),
981 Annotated::new(Value::String("value".to_string())),
982 );
983 map
984 },
985 ..Default::default()
986 });
987
988 assert_eq!(meta, Annotated::from_json(json).unwrap());
989 assert_eq!(json, meta.to_json_pretty().unwrap());
990 }
991
992 #[test]
993 fn test_debug_meta_default_values() {
994 let json = "{}";
995 let meta = Annotated::new(DebugMeta::default());
996
997 assert_eq!(meta, Annotated::from_json(json).unwrap());
998 assert_eq!(json, meta.to_json_pretty().unwrap());
999 }
1000}