1use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct CallEdge {
16 pub name: String,
17 pub address: String,
18 pub external: bool,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24pub struct FunctionContext {
25 pub name: String,
26 pub entry_point: String,
27 pub signature: String,
28 pub calling_convention: String,
29 pub is_thunk: bool,
30 pub thunked_to: Option<String>,
32 pub c: String,
33 pub c_truncated: bool,
34 pub callers: Vec<CallEdge>,
35 pub callees: Vec<CallEdge>,
36 #[serde(default)]
37 pub data_refs: Vec<DataRef>,
38 #[serde(default)]
39 pub data_refs_truncated: bool,
40 #[serde(default, skip_serializing_if = "Vec::is_empty")]
41 pub comments: Vec<CommentEntry>,
42 #[serde(default)]
43 pub comments_truncated: bool,
44 #[serde(default, skip_serializing_if = "Option::is_none")]
49 pub prototype: Option<Prototype>,
50 #[serde(default, skip_serializing_if = "Option::is_none")]
54 pub locals: Option<Vec<LocalEntry>>,
55 #[serde(default, skip_serializing_if = "Option::is_none")]
56 pub locals_truncated: Option<bool>,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64pub struct DataRef {
65 pub address: String,
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub name: Option<String>,
68 pub ref_type: String,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
75pub struct CommentEntry {
76 pub address: String,
77 #[serde(rename = "type")]
78 pub kind: String,
79 pub comment: String,
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
87pub struct ParamEntry {
88 pub name: String,
89 #[serde(rename = "type")]
90 pub type_: String,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
98pub struct Prototype {
99 pub return_type: String,
100 pub calling_convention: String,
101 pub params: Vec<ParamEntry>,
102 pub variadic: bool,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
112pub struct LocalEntry {
113 pub name: String,
114 #[serde(rename = "type")]
115 pub type_: String,
116 pub storage: String,
117 pub kind: String,
118}
119
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
123pub struct ProgramEntry {
124 pub program_path: String,
125 pub name: String,
126 #[serde(skip_serializing_if = "Option::is_none")]
127 pub language_id: Option<String>,
128 #[serde(skip_serializing_if = "Option::is_none")]
129 pub size: Option<u64>,
130}
131
132#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
134pub struct ListProjectPrograms {
135 pub programs: Vec<ProgramEntry>,
136 pub total: u64,
137 pub truncated: bool,
138}
139
140#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
144pub struct AttachResult {
145 pub attached: Option<String>,
148 pub default_space: String,
149 pub pointer_size: u8,
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
155pub struct SymbolCandidate {
156 pub name: String,
157 pub address: String,
158}
159
160#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
163pub struct FunctionSummary {
164 pub name: String,
165 pub entry_point: String,
166 pub signature: String,
167 pub calling_convention: String,
168 pub is_thunk: bool,
169 #[serde(skip_serializing_if = "Option::is_none")]
170 pub thunked_to: Option<String>,
171 #[serde(skip_serializing_if = "Option::is_none")]
172 pub size: Option<u64>,
173 pub external: bool,
174}
175
176#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
178pub struct ListFunctions {
179 pub functions: Vec<FunctionSummary>,
180 pub has_more: bool,
181 #[serde(skip_serializing_if = "Option::is_none")]
182 pub total: Option<u64>,
183}
184
185#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
188pub struct Symbol {
189 pub name: String,
190 pub address: String,
191 pub kind: String,
192 #[serde(skip_serializing_if = "Option::is_none")]
193 pub namespace: Option<String>,
194 #[serde(skip_serializing_if = "Option::is_none")]
195 pub library: Option<String>,
196 #[serde(skip_serializing_if = "Option::is_none")]
197 pub size: Option<u64>,
198}
199
200#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
202pub struct ResolveSymbol {
203 pub candidates: Vec<Symbol>,
204}
205
206#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
208pub struct ListSymbols {
209 pub symbols: Vec<Symbol>,
210 pub has_more: bool,
211 #[serde(skip_serializing_if = "Option::is_none")]
212 pub total: Option<u64>,
213}
214
215#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
219pub struct StringItem {
220 pub address: String,
221 pub value: String,
222 pub length: u64,
223 pub encoding: String,
224}
225
226#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
228pub struct ListStrings {
229 pub strings: Vec<StringItem>,
230 pub has_more: bool,
231 #[serde(skip_serializing_if = "Option::is_none")]
232 pub total: Option<u64>,
233}
234
235#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
240pub struct Reference {
241 pub from_address: String,
242 pub to_address: String,
243 pub ref_type: String,
244 #[serde(skip_serializing_if = "Option::is_none")]
245 pub from_function: Option<String>,
246}
247
248#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
250pub struct Xrefs {
251 pub xrefs: Vec<Reference>,
252 pub has_more: bool,
253 #[serde(skip_serializing_if = "Option::is_none")]
254 pub total: Option<u64>,
255}
256
257#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
262pub struct Instruction {
263 pub address: String,
264 pub bytes: String,
265 pub mnemonic: String,
266 pub operands: String,
267 #[serde(default, skip_serializing_if = "Option::is_none")]
268 pub comments: Option<std::collections::BTreeMap<String, String>>,
269}
270
271#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
274pub struct Disassembly {
275 pub instructions: Vec<Instruction>,
276 pub has_more: bool,
277 #[serde(skip_serializing_if = "Option::is_none")]
278 pub total: Option<u64>,
279}
280
281#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
286pub struct ReadBytes {
287 pub address: String,
288 pub length: u64,
289 pub bytes: String,
290 pub truncated: bool,
291}
292
293#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
296pub struct DataTypeMember {
297 pub name: String,
298 #[serde(rename = "type")]
299 pub type_: String,
300 pub offset: u64,
301 pub size: u64,
302}
303
304#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
308pub struct EnumValue {
309 pub name: String,
310 pub value: String,
311}
312
313#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
317pub struct DataTypeInfo {
318 pub name: String,
319 pub kind: String,
320 pub size: u64,
321 #[serde(skip_serializing_if = "Option::is_none")]
322 pub members: Option<Vec<DataTypeMember>>,
323 #[serde(skip_serializing_if = "Option::is_none")]
324 pub values: Option<Vec<EnumValue>>,
325}
326
327#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
330pub struct Segment {
331 pub name: String,
332 pub start: String,
333 pub end: String,
334 pub size: u64,
335 pub readable: bool,
336 pub writable: bool,
337 pub executable: bool,
338 pub initialized: bool,
339}
340
341#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
343pub struct ListSegments {
344 pub segments: Vec<Segment>,
345}
346
347#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
351pub struct DataItem {
352 pub address: String,
353 #[serde(skip_serializing_if = "Option::is_none")]
354 pub name: Option<String>,
355 pub data_type: String,
356 #[serde(skip_serializing_if = "Option::is_none")]
357 pub value: Option<String>,
358 pub size: u64,
359}
360
361#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
364pub struct ListDataItems {
365 pub data: Vec<DataItem>,
366 pub has_more: bool,
367 #[serde(skip_serializing_if = "Option::is_none")]
368 pub total: Option<u64>,
369}
370
371#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
374pub struct ContainingFunction {
375 pub name: String,
376 pub entry_point: String,
377}
378
379#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
382pub struct AddressSymbol {
383 pub name: String,
384 pub kind: String,
385}
386
387#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
391pub struct AddressInfo {
392 pub address: String,
393 pub mapped: bool,
394 #[serde(skip_serializing_if = "Option::is_none")]
395 pub segment: Option<String>,
396 pub is_code: bool,
397 #[serde(skip_serializing_if = "Option::is_none")]
398 pub containing_function: Option<ContainingFunction>,
399 #[serde(skip_serializing_if = "Option::is_none")]
400 pub symbol: Option<AddressSymbol>,
401 #[serde(skip_serializing_if = "Option::is_none")]
402 pub data_type: Option<String>,
403 #[serde(default, skip_serializing_if = "Option::is_none")]
404 pub comments: Option<std::collections::BTreeMap<String, String>>,
405}
406
407#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
410pub struct RenameResult {
411 pub status: String,
412 pub address: String,
413 pub name: String,
414 #[serde(skip_serializing_if = "Option::is_none")]
415 pub previous_name: Option<String>,
416 pub durable: bool,
417}
418
419#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
422pub struct CommentResult {
423 pub status: String,
424 pub address: String,
425 pub comment_type: String,
426 #[serde(skip_serializing_if = "Option::is_none")]
427 pub previous_comment: Option<String>,
428 pub durable: bool,
429}
430
431#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
436pub struct SetDatatypeResult {
437 pub status: String,
438 pub address: String,
439 pub datatype: String,
440 #[serde(skip_serializing_if = "Option::is_none")]
441 pub previous_datatype: Option<String>,
442 pub length: i64,
443 pub durable: bool,
444}
445
446#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
451pub struct SetPrototypeResult {
452 pub status: String,
453 pub address: String,
454 pub name: String,
455 pub signature: String,
456 #[serde(skip_serializing_if = "Option::is_none")]
457 pub previous_signature: Option<String>,
458 pub durable: bool,
459}
460
461#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
468pub struct SetLocalResult {
469 pub status: String,
470 pub function: String,
471 pub storage: String,
472 pub name: String,
473 #[serde(rename = "type")]
474 pub type_: String,
475 #[serde(skip_serializing_if = "Option::is_none")]
476 pub previous_name: Option<String>,
477 #[serde(skip_serializing_if = "Option::is_none")]
478 pub previous_type: Option<String>,
479 pub durable: bool,
480}
481
482#[cfg(test)]
483mod tests {
484 use super::*;
485
486 fn sample_fc() -> FunctionContext {
487 FunctionContext {
488 name: "add_two".into(),
489 entry_point: "0x00401000".into(),
490 signature: "uint __cdecl add_two(uint param_1, uint param_2)".into(),
491 calling_convention: "__cdecl".into(),
492 is_thunk: false,
493 thunked_to: None,
494 c: "uint add_two(...){ ... }".into(),
495 c_truncated: false,
496 callers: vec![CallEdge {
497 name: "main".into(),
498 address: "0x00401040".into(),
499 external: false,
500 }],
501 callees: vec![CallEdge {
502 name: "MessageBoxW".into(),
503 address: "EXTERNAL:0x00000001".into(),
504 external: true,
505 }],
506 data_refs: vec![],
507 data_refs_truncated: false,
508 comments: vec![],
509 comments_truncated: false,
510 prototype: None,
511 locals: None,
512 locals_truncated: None,
513 }
514 }
515
516 #[test]
517 fn function_context_locals_roundtrip_uses_wire_key_type() {
518 let le = LocalEntry {
519 name: "uVar1".into(),
520 type_: "uint".into(),
521 storage: "EAX:4".into(),
522 kind: "register".into(),
523 };
524 let j = serde_json::to_value(&le).unwrap();
525 assert_eq!(j["type"], "uint"); assert_eq!(j["storage"], "EAX:4");
527 let mut fc = sample_fc();
529 assert!(serde_json::to_value(&fc).unwrap().get("locals").is_none());
530 fc.locals = Some(vec![le]);
531 fc.locals_truncated = Some(false);
532 let back: FunctionContext =
533 serde_json::from_str(&serde_json::to_string(&fc).unwrap()).unwrap();
534 assert_eq!(back, fc);
535 }
536
537 #[test]
538 fn function_context_roundtrips_with_spec_shape() {
539 let fc = FunctionContext {
540 name: "add_two".into(),
541 entry_point: "0x00401000".into(),
542 signature: "uint __cdecl add_two(uint param_1, uint param_2)".into(),
543 calling_convention: "__cdecl".into(),
544 is_thunk: false,
545 thunked_to: None,
546 c: "uint add_two(...){ ... }".into(),
547 c_truncated: false,
548 callers: vec![CallEdge {
549 name: "main".into(),
550 address: "0x00401040".into(),
551 external: false,
552 }],
553 callees: vec![CallEdge {
554 name: "MessageBoxW".into(),
555 address: "EXTERNAL:0x00000001".into(),
556 external: true,
557 }],
558 data_refs: vec![],
559 data_refs_truncated: false,
560 comments: vec![],
561 comments_truncated: false,
562 prototype: None,
563 locals: None,
564 locals_truncated: None,
565 };
566 let s = serde_json::to_string(&fc).unwrap();
567 let back: FunctionContext = serde_json::from_str(&s).unwrap();
568 assert_eq!(back, fc);
569 assert_eq!(
571 serde_json::to_value(&fc).unwrap()["thunked_to"],
572 serde_json::Value::Null
573 );
574 assert!(serde_json::to_value(&fc).unwrap().get("comments").is_none());
576 }
577
578 #[test]
579 fn function_context_comments_roundtrip_uses_wire_key_type() {
580 let fc = FunctionContext {
581 name: "add_two".into(),
582 entry_point: "0x00401000".into(),
583 signature: "uint __cdecl add_two(uint param_1, uint param_2)".into(),
584 calling_convention: "__cdecl".into(),
585 is_thunk: false,
586 thunked_to: None,
587 c: "uint add_two(...){ ... }".into(),
588 c_truncated: false,
589 callers: vec![CallEdge {
590 name: "main".into(),
591 address: "0x00401040".into(),
592 external: false,
593 }],
594 callees: vec![CallEdge {
595 name: "MessageBoxW".into(),
596 address: "EXTERNAL:0x00000001".into(),
597 external: true,
598 }],
599 data_refs: vec![],
600 data_refs_truncated: false,
601 comments: vec![
602 CommentEntry {
603 address: "ram:00401000".into(),
604 kind: "plate".into(),
605 comment: "hdr".into(),
606 },
607 CommentEntry {
608 address: "ram:00401004".into(),
609 kind: "eol".into(),
610 comment: "note".into(),
611 },
612 ],
613 comments_truncated: false,
614 prototype: None,
615 locals: None,
616 locals_truncated: None,
617 };
618 let v = serde_json::to_value(&fc).unwrap();
619 assert_eq!(v["comments"][0]["type"], "plate");
621 assert!(v["comments"][0].get("kind").is_none());
622 let back: FunctionContext = serde_json::from_value(v).unwrap();
623 assert_eq!(back, fc);
624 }
625
626 #[test]
627 fn function_context_prototype_roundtrips_and_omits_when_none() {
628 let proto = Prototype {
629 return_type: "uint".into(),
630 calling_convention: "__cdecl".into(),
631 params: vec![
632 ParamEntry {
633 name: "param_1".into(),
634 type_: "uint".into(),
635 },
636 ParamEntry {
637 name: "param_2".into(),
638 type_: "uint".into(),
639 },
640 ],
641 variadic: false,
642 };
643 let v = serde_json::to_value(&proto).unwrap();
644 assert_eq!(v["params"][0]["type"], "uint"); assert!(v["params"][0].get("type_").is_none());
646 let missing = serde_json::json!({
648 "name":"f","entry_point":"ram:1","signature":"s","calling_convention":"__cdecl",
649 "is_thunk":false,"thunked_to":null,"c":"","c_truncated":false,"callers":[],"callees":[]
650 });
651 let fc: FunctionContext = serde_json::from_value(missing).unwrap();
652 assert_eq!(fc.prototype, None);
653 }
654
655 #[test]
656 fn program_entry_omits_absent_optional_metadata() {
657 let e = ProgramEntry {
658 program_path: "/add.exe".into(),
659 name: "add.exe".into(),
660 language_id: None,
661 size: None,
662 };
663 let v = serde_json::to_value(&e).unwrap();
664 assert!(v.get("language_id").is_none());
665 assert!(v.get("size").is_none());
666 }
667
668 #[test]
669 fn attach_result_carries_address_geometry() {
670 let a = AttachResult {
671 attached: Some("/add.exe".into()),
672 default_space: "ram".into(),
673 pointer_size: 4,
674 };
675 let back: AttachResult = serde_json::from_str(&serde_json::to_string(&a).unwrap()).unwrap();
676 assert_eq!(back, a);
677 }
678
679 #[test]
680 fn function_summary_omits_absent_optionals_and_roundtrips() {
681 let s = FunctionSummary {
682 name: "add_two".into(),
683 entry_point: "ram:401000".into(),
684 signature: "uint add_two(uint,uint)".into(),
685 calling_convention: "__cdecl".into(),
686 is_thunk: false,
687 thunked_to: None,
688 size: Some(42),
689 external: false,
690 };
691 let v = serde_json::to_value(&s).unwrap();
692 assert!(v.get("thunked_to").is_none());
693 let lf = ListFunctions {
694 functions: vec![s.clone()],
695 has_more: false,
696 total: Some(1),
697 };
698 assert_eq!(
699 serde_json::from_str::<ListFunctions>(&serde_json::to_string(&lf).unwrap())
700 .unwrap()
701 .functions[0],
702 s
703 );
704 }
705
706 #[test]
707 fn symbol_omits_absent_optionals_and_resolve_symbol_roundtrips() {
708 let s = Symbol {
709 name: "api_entry".into(),
710 address: "ram:401000".into(),
711 kind: "function".into(),
712 namespace: None,
713 library: None,
714 size: None,
715 };
716 let v = serde_json::to_value(&s).unwrap();
717 assert!(v.get("namespace").is_none());
718 assert!(v.get("library").is_none());
719 assert!(v.get("size").is_none());
720 let rs = ResolveSymbol {
721 candidates: vec![s.clone()],
722 };
723 let back: ResolveSymbol =
724 serde_json::from_str(&serde_json::to_string(&rs).unwrap()).unwrap();
725 assert_eq!(back, rs);
726 }
727
728 #[test]
729 fn list_symbols_roundtrips() {
730 let s = Symbol {
731 name: "printf".into(),
732 address: "EXTERNAL:1".into(),
733 kind: "label".into(),
734 namespace: Some("ns".into()),
735 library: Some("msvcrt.dll".into()),
736 size: Some(4),
737 };
738 let ls = ListSymbols {
739 symbols: vec![s.clone()],
740 has_more: true,
741 total: None,
742 };
743 let v = serde_json::to_value(&ls).unwrap();
744 assert!(v.get("total").is_none());
745 let back: ListSymbols = serde_json::from_str(&serde_json::to_string(&ls).unwrap()).unwrap();
746 assert_eq!(back, ls);
747 assert_eq!(back.symbols[0], s);
748 }
749
750 #[test]
751 fn list_strings_roundtrips() {
752 let item = StringItem {
753 address: "ram:402000".into(),
754 value: "https://example.test".into(),
755 length: 21,
756 encoding: "string".into(),
757 };
758 let ls = ListStrings {
759 strings: vec![item.clone()],
760 has_more: false,
761 total: Some(1),
762 };
763 let back: ListStrings = serde_json::from_str(&serde_json::to_string(&ls).unwrap()).unwrap();
764 assert_eq!(back, ls);
765 assert_eq!(back.strings[0], item);
766 }
767
768 #[test]
769 fn xrefs_omits_absent_from_function_and_roundtrips() {
770 let r = Reference {
771 from_address: "ram:401040".into(),
772 to_address: "ram:401000".into(),
773 ref_type: "CALL".into(),
774 from_function: Some("compute".into()),
775 };
776 let x = Xrefs {
777 xrefs: vec![r.clone()],
778 has_more: false,
779 total: None,
780 };
781 let v = serde_json::to_value(&x).unwrap();
782 assert!(v.get("total").is_none());
783 let back: Xrefs = serde_json::from_str(&serde_json::to_string(&x).unwrap()).unwrap();
784 assert_eq!(back, x);
785 assert_eq!(back.xrefs[0], r);
786
787 let r2 = Reference {
788 from_address: "ram:500000".into(),
789 to_address: "ram:401000".into(),
790 ref_type: "DATA".into(),
791 from_function: None,
792 };
793 let v2 = serde_json::to_value(&r2).unwrap();
794 assert!(v2.get("from_function").is_none());
795 }
796
797 #[test]
798 fn disassembly_omits_absent_comments_and_roundtrips_map() {
799 let i = Instruction {
800 address: "ram:401000".into(),
801 bytes: "8b442404".into(),
802 mnemonic: "MOV".into(),
803 operands: "EAX, dword ptr [ESP + 0x4]".into(),
804 comments: None,
805 };
806 let v = serde_json::to_value(&i).unwrap();
807 assert!(v.get("comments").is_none(), "absent comments omitted");
808 let mut m = std::collections::BTreeMap::new();
809 m.insert("eol".to_string(), "note".to_string());
810 let i2 = Instruction {
811 comments: Some(m),
812 ..i.clone()
813 };
814 let v2 = serde_json::to_value(&i2).unwrap();
815 assert_eq!(v2["comments"]["eol"], "note");
816 let d = Disassembly {
817 instructions: vec![i2.clone()],
818 has_more: false,
819 total: Some(1),
820 };
821 let back: Disassembly = serde_json::from_str(&serde_json::to_string(&d).unwrap()).unwrap();
822 assert_eq!(back.instructions[0], i2);
823 }
824
825 #[test]
826 fn read_bytes_roundtrips() {
827 let rb = ReadBytes {
828 address: "ram:401000".into(),
829 length: 4,
830 bytes: "8b442404".into(),
831 truncated: false,
832 };
833 let back: ReadBytes = serde_json::from_str(&serde_json::to_string(&rb).unwrap()).unwrap();
834 assert_eq!(back, rb);
835 }
836
837 #[test]
838 fn data_type_info_struct_roundtrips_and_serializes_type_key() {
839 let m = DataTypeMember {
840 name: "left".into(),
841 type_: "int".into(),
842 offset: 0,
843 size: 4,
844 };
845 let v = serde_json::to_value(&m).unwrap();
846 assert_eq!(v["type"], "int");
848 assert!(v.get("type_").is_none());
849 let dt = DataTypeInfo {
850 name: "Rect".into(),
851 kind: "struct".into(),
852 size: 16,
853 members: Some(vec![m.clone()]),
854 values: None,
855 };
856 let back: DataTypeInfo =
857 serde_json::from_str(&serde_json::to_string(&dt).unwrap()).unwrap();
858 assert_eq!(back, dt);
859 assert_eq!(back.members.unwrap()[0], m);
860 }
861
862 #[test]
863 fn data_type_info_enum_roundtrips_with_string_values() {
864 let ev = EnumValue {
865 name: "RED".into(),
866 value: "0".into(),
867 };
868 let v = serde_json::to_value(&ev).unwrap();
869 assert!(v["value"].is_string());
871 let dt = DataTypeInfo {
872 name: "Color".into(),
873 kind: "enum".into(),
874 size: 4,
875 members: None,
876 values: Some(vec![ev.clone()]),
877 };
878 let v2 = serde_json::to_value(&dt).unwrap();
879 assert!(v2.get("members").is_none());
880 let back: DataTypeInfo =
881 serde_json::from_str(&serde_json::to_string(&dt).unwrap()).unwrap();
882 assert_eq!(back, dt);
883 assert_eq!(back.values.unwrap()[0], ev);
884 }
885
886 #[test]
887 fn list_segments_roundtrips() {
888 let s = Segment {
889 name: ".text".into(),
890 start: "ram:401000".into(),
891 end: "ram:402000".into(),
892 size: 0x1000,
893 readable: true,
894 writable: false,
895 executable: true,
896 initialized: true,
897 };
898 let ls = ListSegments {
899 segments: vec![s.clone()],
900 };
901 let back: ListSegments =
902 serde_json::from_str(&serde_json::to_string(&ls).unwrap()).unwrap();
903 assert_eq!(back, ls);
904 assert_eq!(back.segments[0], s);
905 }
906
907 #[test]
908 fn list_data_items_omits_absent_optionals_and_roundtrips() {
909 let item = DataItem {
910 address: "ram:403000".into(),
911 name: Some("g_rect".into()),
912 data_type: "Rect".into(),
913 value: None,
914 size: 16,
915 };
916 let v = serde_json::to_value(&item).unwrap();
917 assert!(v.get("value").is_none());
918 let ld = ListDataItems {
919 data: vec![item.clone()],
920 has_more: false,
921 total: None,
922 };
923 let v2 = serde_json::to_value(&ld).unwrap();
924 assert!(v2.get("total").is_none());
925 let back: ListDataItems =
926 serde_json::from_str(&serde_json::to_string(&ld).unwrap()).unwrap();
927 assert_eq!(back, ld);
928 assert_eq!(back.data[0], item);
929 }
930
931 #[test]
932 fn address_info_mapped_roundtrips_with_all_optionals_present() {
933 let info = AddressInfo {
934 address: "ram:401000".into(),
935 mapped: true,
936 segment: Some(".text".into()),
937 is_code: true,
938 containing_function: Some(ContainingFunction {
939 name: "add_two".into(),
940 entry_point: "ram:401000".into(),
941 }),
942 symbol: Some(AddressSymbol {
943 name: "add_two".into(),
944 kind: "function".into(),
945 }),
946 data_type: None,
947 comments: None,
948 };
949 let v = serde_json::to_value(&info).unwrap();
950 assert!(v.get("data_type").is_none());
952 assert_eq!(v["containing_function"]["name"], "add_two");
953 assert_eq!(v["symbol"]["kind"], "function");
954 let back: AddressInfo =
955 serde_json::from_str(&serde_json::to_string(&info).unwrap()).unwrap();
956 assert_eq!(back, info);
957 }
958
959 #[test]
960 fn rename_result_roundtrips_and_omits_previous_on_already_applied() {
961 let renamed = RenameResult {
962 status: "renamed".into(),
963 address: "ram:00401000".into(),
964 name: "decrypt".into(),
965 previous_name: Some("FUN_00401000".into()),
966 durable: true,
967 };
968 let s = serde_json::to_string(&renamed).unwrap();
969 assert_eq!(serde_json::from_str::<RenameResult>(&s).unwrap(), renamed);
970 let noop = RenameResult {
971 status: "already_applied".into(),
972 address: "ram:00401000".into(),
973 name: "decrypt".into(),
974 previous_name: None,
975 durable: true,
976 };
977 let v = serde_json::to_value(&noop).unwrap();
978 assert!(
979 v.get("previous_name").is_none(),
980 "previous_name omitted when None"
981 );
982 }
983
984 #[test]
985 fn address_info_unmapped_omits_all_optionals_and_roundtrips() {
986 let info = AddressInfo {
987 address: "0xffffffff0".into(),
988 mapped: false,
989 segment: None,
990 is_code: false,
991 containing_function: None,
992 symbol: None,
993 data_type: None,
994 comments: None,
995 };
996 let v = serde_json::to_value(&info).unwrap();
997 assert!(v.get("segment").is_none());
998 assert!(v.get("containing_function").is_none());
999 assert!(v.get("symbol").is_none());
1000 assert!(v.get("data_type").is_none());
1001 assert_eq!(v["mapped"], false);
1002 let back: AddressInfo =
1003 serde_json::from_str(&serde_json::to_string(&info).unwrap()).unwrap();
1004 assert_eq!(back, info);
1005 }
1006
1007 #[test]
1008 fn comment_result_roundtrips_and_omits_previous_when_none() {
1009 let set = CommentResult {
1010 status: "set".into(),
1011 address: "ram:00401000".into(),
1012 comment_type: "plate".into(),
1013 previous_comment: Some("old".into()),
1014 durable: true,
1015 };
1016 let s = serde_json::to_string(&set).unwrap();
1017 assert_eq!(serde_json::from_str::<CommentResult>(&s).unwrap(), set);
1018 let fresh = CommentResult {
1019 status: "set".into(),
1020 address: "ram:00401000".into(),
1021 comment_type: "eol".into(),
1022 previous_comment: None,
1023 durable: true,
1024 };
1025 let v = serde_json::to_value(&fresh).unwrap();
1026 assert!(v.get("previous_comment").is_none(), "omitted when None");
1027 let missing = serde_json::json!({"status":"set","address":"ram:1","comment_type":"eol","durable":true});
1028 let d: CommentResult = serde_json::from_value(missing).unwrap();
1029 assert_eq!(d.previous_comment, None);
1030 }
1031
1032 #[test]
1033 fn set_datatype_result_roundtrips_and_omits_previous_when_none() {
1034 let set = SetDatatypeResult {
1035 status: "set".into(),
1036 address: "ram:00402000".into(),
1037 datatype: "int".into(),
1038 previous_datatype: Some("undefined4".into()),
1039 length: 4,
1040 durable: true,
1041 };
1042 let s = serde_json::to_string(&set).unwrap();
1043 assert_eq!(serde_json::from_str::<SetDatatypeResult>(&s).unwrap(), set);
1044 let cleared = SetDatatypeResult {
1045 status: "cleared".into(),
1046 address: "ram:00402000".into(),
1047 datatype: "undefined".into(),
1048 previous_datatype: None,
1049 length: 0,
1050 durable: true,
1051 };
1052 let v = serde_json::to_value(&cleared).unwrap();
1053 assert!(v.get("previous_datatype").is_none(), "omitted when None");
1054 let missing = serde_json::json!({"status":"set","address":"ram:2","datatype":"int","length":4,"durable":true});
1055 let d: SetDatatypeResult = serde_json::from_value(missing).unwrap();
1056 assert_eq!(d.previous_datatype, None);
1057 }
1058
1059 #[test]
1060 fn set_prototype_result_roundtrips_and_omits_previous_when_none() {
1061 let set = SetPrototypeResult {
1062 status: "set".into(),
1063 address: "ram:00401000".into(),
1064 name: "compute".into(),
1065 signature: "int __cdecl compute(Rect * param_1)".into(),
1066 previous_signature: Some("undefined4 __cdecl compute(void)".into()),
1067 durable: true,
1068 };
1069 let s = serde_json::to_string(&set).unwrap();
1070 assert_eq!(serde_json::from_str::<SetPrototypeResult>(&s).unwrap(), set);
1071 let noop = SetPrototypeResult {
1072 status: "already_applied".into(),
1073 address: "ram:00401000".into(),
1074 name: "compute".into(),
1075 signature: "int __cdecl compute(Rect * param_1)".into(),
1076 previous_signature: None,
1077 durable: true,
1078 };
1079 let v = serde_json::to_value(&noop).unwrap();
1080 assert!(v.get("previous_signature").is_none(), "omitted when None");
1081 let missing = serde_json::json!({"status":"set","address":"ram:1","name":"f","signature":"s","durable":true});
1082 let d: SetPrototypeResult = serde_json::from_value(missing).unwrap();
1083 assert_eq!(d.previous_signature, None);
1084 }
1085
1086 #[test]
1087 fn set_local_result_omits_absent_previous_fields() {
1088 let r = SetLocalResult {
1089 status: "retyped".into(),
1090 function: "compute".into(),
1091 storage: "stack:-0x18".into(),
1092 name: "packet_len".into(),
1093 type_: "size_t".into(),
1094 previous_name: None,
1095 previous_type: Some("undefined8".into()),
1096 durable: true,
1097 };
1098 let j = serde_json::to_value(&r).unwrap();
1099 assert_eq!(j["type"], "size_t"); assert_eq!(j["previous_type"], "undefined8");
1101 assert!(j.get("previous_name").is_none()); let back: SetLocalResult =
1103 serde_json::from_str(&serde_json::to_string(&r).unwrap()).unwrap();
1104 assert_eq!(back, r);
1105 }
1106}