1use crate::errors::VisualSignError;
2use serde::ser::SerializeMap;
3use serde::{Deserialize, Serialize, Serializer};
4use serde_json::Value;
5pub mod encodings;
6pub mod errors;
7pub mod field_builders;
8pub mod registry;
9pub mod test_utils;
10pub mod vsptrait;
11
12pub trait DeterministicOrdering: Serialize {
16 fn verify_deterministic_ordering(&self) -> Result<(), String> {
19 let json = serde_json::to_value(self).map_err(|e| e.to_string())?;
20 verify_json_deterministic(&json, "")
22 }
23}
24
25#[macro_export]
29macro_rules! impl_deterministic_serialize {
30 ($type:ty) => {
31 impl DeterministicOrdering for $type {}
34 };
35}
36
37pub struct StaticAssertDeterministic<T: DeterministicOrdering>(std::marker::PhantomData<T>);
39
40pub const fn assert_deterministic<T: DeterministicOrdering>() -> StaticAssertDeterministic<T> {
42 StaticAssertDeterministic(std::marker::PhantomData)
43}
44
45fn verify_json_deterministic(value: &serde_json::Value, path: &str) -> Result<(), String> {
47 match value {
48 serde_json::Value::Object(map) => {
49 let keys: Vec<_> = map.keys().cloned().collect();
50 let mut sorted_keys = keys.clone();
51 sorted_keys.sort();
52
53 if keys != sorted_keys {
54 return Err(format!(
55 "Keys at path '{}' are not alphabetically ordered. Got: {:?}, Expected: {:?}",
56 if path.is_empty() { "root" } else { path },
57 keys,
58 sorted_keys
59 ));
60 }
61
62 for (key, nested_value) in map {
64 let new_path = if path.is_empty() {
65 key.clone()
66 } else {
67 format!("{path}.{key}")
68 };
69 verify_json_deterministic(nested_value, &new_path)?;
70 }
71 }
72 serde_json::Value::Array(arr) => {
73 for (i, item) in arr.iter().enumerate() {
74 let new_path = format!("{path}[{i}]");
75 verify_json_deterministic(item, &new_path)?;
76 }
77 }
78 _ => {} }
80 Ok(())
81}
82
83fn is_empty_string(s: &str) -> bool {
85 s.is_empty()
86}
87
88#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
92pub struct SignablePayload {
93 #[serde(rename = "Fields")]
94 pub fields: Vec<SignablePayloadField>,
95 #[serde(rename = "PayloadType", skip_serializing_if = "is_empty_string")]
96 pub payload_type: String,
97 #[serde(rename = "Subtitle", skip_serializing_if = "Option::is_none")]
98 pub subtitle: Option<String>,
99 #[serde(rename = "Title")]
100 pub title: String,
101 #[serde(rename = "Version")]
102 pub version: String,
103}
104
105#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
107pub struct SignablePayloadFieldCommon {
108 #[serde(rename = "FallbackText")]
109 pub fallback_text: String,
110 #[serde(rename = "Label")]
111 pub label: String,
112}
113
114impl DeterministicOrdering for SignablePayloadFieldCommon {}
116
117#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
119#[serde(tag = "Type")]
120pub enum SignablePayloadField {
121 #[serde(rename = "text")]
122 Text {
123 #[serde(flatten)]
124 common: SignablePayloadFieldCommon,
125 #[serde(rename = "Text")]
126 text: SignablePayloadFieldText,
127 },
128
129 #[serde(rename = "text_v2")]
130 TextV2 {
131 #[serde(flatten)]
132 common: SignablePayloadFieldCommon,
133 #[serde(rename = "TextV2")]
134 text_v2: SignablePayloadFieldTextV2,
135 },
136
137 #[serde(rename = "address")]
138 Address {
139 #[serde(flatten)]
140 common: SignablePayloadFieldCommon,
141 #[serde(rename = "Address")]
142 address: SignablePayloadFieldAddress,
143 },
144
145 #[serde(rename = "address_v2")]
146 AddressV2 {
147 #[serde(flatten)]
148 common: SignablePayloadFieldCommon,
149 #[serde(rename = "AddressV2")]
150 address_v2: SignablePayloadFieldAddressV2,
151 },
152
153 #[serde(rename = "number")]
154 Number {
155 #[serde(flatten)]
156 common: SignablePayloadFieldCommon,
157 #[serde(rename = "Number")]
158 number: SignablePayloadFieldNumber,
159 },
160
161 #[serde(rename = "amount")]
162 Amount {
163 #[serde(flatten)]
164 common: SignablePayloadFieldCommon,
165 #[serde(rename = "Amount")]
166 amount: SignablePayloadFieldAmount,
167 },
168
169 #[serde(rename = "amount_v2")]
170 AmountV2 {
171 #[serde(flatten)]
172 common: SignablePayloadFieldCommon,
173 #[serde(rename = "AmountV2")]
174 amount_v2: SignablePayloadFieldAmountV2,
175 },
176
177 #[serde(rename = "divider")]
178 Divider {
179 #[serde(flatten)]
180 common: SignablePayloadFieldCommon,
181 #[serde(rename = "Divider")]
182 divider: SignablePayloadFieldDivider,
183 },
184
185 #[serde(rename = "preview_layout")]
186 PreviewLayout {
187 #[serde(flatten)]
188 common: SignablePayloadFieldCommon,
189 #[serde(rename = "PreviewLayout")]
190 preview_layout: SignablePayloadFieldPreviewLayout,
191 },
192
193 #[serde(rename = "list_layout")]
194 ListLayout {
195 #[serde(flatten)]
196 common: SignablePayloadFieldCommon,
197 #[serde(rename = "ListLayout")]
198 list_layout: SignablePayloadFieldListLayout,
199 },
200
201 #[serde(rename = "unknown")]
202 Unknown {
203 #[serde(flatten)]
204 common: SignablePayloadFieldCommon,
205 #[serde(rename = "Unknown")]
206 unknown: SignablePayloadFieldUnknown,
207 },
208}
209
210trait FieldSerializer {
212 fn serialize_to_map(
213 &self,
214 ) -> Result<std::collections::BTreeMap<String, serde_json::Value>, serde_json::Error>;
215 fn get_expected_fields(&self) -> Vec<&'static str>;
216}
217
218macro_rules! serialize_field_variant {
220 ($fields:expr, $variant_name:literal, $common:expr, $(($field_name:literal, $field_value:expr)),* $(,)?) => {
221 $fields.insert("FallbackText".to_string(), serde_json::to_value(&$common.fallback_text).unwrap());
223 $fields.insert("Label".to_string(), serde_json::to_value(&$common.label).unwrap());
224 $fields.insert("Type".to_string(), serde_json::Value::String($variant_name.to_string()));
225
226 $(
228 $fields.insert($field_name.to_string(), serde_json::to_value($field_value).unwrap());
229 )*
230 };
231}
232
233impl FieldSerializer for SignablePayloadField {
235 fn serialize_to_map(
236 &self,
237 ) -> Result<std::collections::BTreeMap<String, serde_json::Value>, serde_json::Error> {
238 let mut fields = std::collections::HashMap::new();
239
240 match self {
242 SignablePayloadField::Text { common, text } => {
243 serialize_field_variant!(fields, "text", common, ("Text", text));
244 }
245 SignablePayloadField::TextV2 { common, text_v2 } => {
246 serialize_field_variant!(fields, "text_v2", common, ("TextV2", text_v2));
247 }
248 SignablePayloadField::Address { common, address } => {
249 serialize_field_variant!(fields, "address", common, ("Address", address));
250 }
251 SignablePayloadField::AddressV2 { common, address_v2 } => {
252 serialize_field_variant!(fields, "address_v2", common, ("AddressV2", address_v2));
253 }
254 SignablePayloadField::Number { common, number } => {
255 serialize_field_variant!(fields, "number", common, ("Number", number));
256 }
257 SignablePayloadField::Amount { common, amount } => {
258 serialize_field_variant!(fields, "amount", common, ("Amount", amount));
259 }
260 SignablePayloadField::AmountV2 { common, amount_v2 } => {
261 serialize_field_variant!(fields, "amount_v2", common, ("AmountV2", amount_v2));
262 }
263 SignablePayloadField::Divider { common, divider } => {
264 serialize_field_variant!(fields, "divider", common, ("Divider", divider));
265 }
266 SignablePayloadField::PreviewLayout {
267 common,
268 preview_layout,
269 } => {
270 serialize_field_variant!(
271 fields,
272 "preview_layout",
273 common,
274 ("PreviewLayout", preview_layout)
275 );
276 }
277 SignablePayloadField::ListLayout {
278 common,
279 list_layout,
280 } => {
281 serialize_field_variant!(
282 fields,
283 "list_layout",
284 common,
285 ("ListLayout", list_layout)
286 );
287 }
288 SignablePayloadField::Unknown { common, unknown } => {
289 serialize_field_variant!(fields, "unknown", common, ("Unknown", unknown));
290 }
291 }
292
293 Ok(fields.into_iter().collect())
295 }
296
297 fn get_expected_fields(&self) -> Vec<&'static str> {
298 let mut base_fields = vec!["FallbackText", "Label", "Type"];
299
300 match self {
301 SignablePayloadField::Text { .. } => base_fields.push("Text"),
302 SignablePayloadField::TextV2 { .. } => base_fields.push("TextV2"),
303 SignablePayloadField::Address { .. } => base_fields.push("Address"),
304 SignablePayloadField::AddressV2 { .. } => base_fields.push("AddressV2"),
305 SignablePayloadField::Number { .. } => base_fields.push("Number"),
306 SignablePayloadField::Amount { .. } => base_fields.push("Amount"),
307 SignablePayloadField::AmountV2 { .. } => base_fields.push("AmountV2"),
308 SignablePayloadField::Divider { .. } => base_fields.push("Divider"),
309 SignablePayloadField::PreviewLayout { .. } => base_fields.push("PreviewLayout"),
310 SignablePayloadField::ListLayout { .. } => base_fields.push("ListLayout"),
311 SignablePayloadField::Unknown { .. } => base_fields.push("Unknown"),
312 }
313
314 base_fields.sort();
315 base_fields
316 }
317}
318
319impl Serialize for SignablePayloadField {
321 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
322 where
323 S: Serializer,
324 {
325 let sorted_map = self.serialize_to_map().map_err(serde::ser::Error::custom)?;
327
328 let expected_fields = self.get_expected_fields();
330 let actual_fields: Vec<_> = sorted_map.keys().map(|s| s.as_str()).collect();
331
332 for expected in &expected_fields {
334 if !actual_fields.contains(expected) {
335 return Err(serde::ser::Error::custom(format!(
336 "Missing expected field '{}' in serialization of {:?}. Expected fields: {:?}, Actual fields: {:?}",
337 expected,
338 std::mem::discriminant(self),
339 expected_fields,
340 actual_fields
341 )));
342 }
343 }
344
345 for actual in &actual_fields {
347 if !expected_fields.contains(actual) {
348 return Err(serde::ser::Error::custom(format!(
349 "Unexpected field '{}' found in serialization of {:?}. Expected fields: {:?}",
350 actual,
351 std::mem::discriminant(self),
352 expected_fields
353 )));
354 }
355 }
356
357 let mut map_ser = serializer.serialize_map(Some(sorted_map.len()))?;
359 for (k, v) in sorted_map {
360 map_ser.serialize_entry(&k, &v)?;
361 }
362 map_ser.end()
363 }
364}
365
366impl DeterministicOrdering for SignablePayloadField {}
368
369impl SignablePayloadField {
371 pub fn fallback_text(&self) -> &String {
372 match self {
373 SignablePayloadField::Text { common, .. } => &common.fallback_text,
374 SignablePayloadField::TextV2 { common, .. } => &common.fallback_text,
375 SignablePayloadField::Address { common, .. } => &common.fallback_text,
376 SignablePayloadField::AddressV2 { common, .. } => &common.fallback_text,
377 SignablePayloadField::Number { common, .. } => &common.fallback_text,
378 SignablePayloadField::Amount { common, .. } => &common.fallback_text,
379 SignablePayloadField::AmountV2 { common, .. } => &common.fallback_text,
380 SignablePayloadField::Divider { common, .. } => &common.fallback_text,
381 SignablePayloadField::PreviewLayout { common, .. } => &common.fallback_text,
382 SignablePayloadField::ListLayout { common, .. } => &common.fallback_text,
383 SignablePayloadField::Unknown { common, .. } => &common.fallback_text,
384 }
385 }
386
387 pub fn label(&self) -> &String {
388 match self {
389 SignablePayloadField::Text { common, .. } => &common.label,
390 SignablePayloadField::TextV2 { common, .. } => &common.label,
391 SignablePayloadField::Address { common, .. } => &common.label,
392 SignablePayloadField::AddressV2 { common, .. } => &common.label,
393 SignablePayloadField::Number { common, .. } => &common.label,
394 SignablePayloadField::Amount { common, .. } => &common.label,
395 SignablePayloadField::AmountV2 { common, .. } => &common.label,
396 SignablePayloadField::Divider { common, .. } => &common.label,
397 SignablePayloadField::PreviewLayout { common, .. } => &common.label,
398 SignablePayloadField::ListLayout { common, .. } => &common.label,
399 SignablePayloadField::Unknown { common, .. } => &common.label,
400 }
401 }
402
403 pub fn field_type(&self) -> &str {
404 match self {
405 SignablePayloadField::Text { .. } => "text",
406 SignablePayloadField::TextV2 { .. } => "text_v2",
407 SignablePayloadField::Address { .. } => "address",
408 SignablePayloadField::AddressV2 { .. } => "address_v2",
409 SignablePayloadField::Number { .. } => "number",
410 SignablePayloadField::Amount { .. } => "amount",
411 SignablePayloadField::AmountV2 { .. } => "amount_v2",
412 SignablePayloadField::Divider { .. } => "divider",
413 SignablePayloadField::PreviewLayout { .. } => "preview_layout",
414 SignablePayloadField::ListLayout { .. } => "list_layout",
415 SignablePayloadField::Unknown { .. } => "unknown",
416 }
417 }
418}
419
420#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
422pub struct SignablePayloadFieldPreviewLayout {
423 #[serde(rename = "Title", skip_serializing_if = "Option::is_none")]
424 pub title: Option<SignablePayloadFieldTextV2>,
425 #[serde(rename = "Subtitle", skip_serializing_if = "Option::is_none")]
426 pub subtitle: Option<SignablePayloadFieldTextV2>,
427 #[serde(rename = "Condensed", skip_serializing_if = "Option::is_none")]
428 pub condensed: Option<SignablePayloadFieldListLayout>,
429 #[serde(rename = "Expanded", skip_serializing_if = "Option::is_none")]
430 pub expanded: Option<SignablePayloadFieldListLayout>,
431}
432
433impl DeterministicOrdering for SignablePayloadFieldPreviewLayout {}
435
436impl Serialize for SignablePayloadFieldPreviewLayout {
438 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
439 where
440 S: Serializer,
441 {
442 let mut map = std::collections::BTreeMap::new();
444
445 if let Some(ref condensed) = self.condensed {
448 map.insert(
449 "Condensed",
450 serde_json::to_value(condensed).map_err(serde::ser::Error::custom)?,
451 );
452 }
453 if let Some(ref expanded) = self.expanded {
454 map.insert(
455 "Expanded",
456 serde_json::to_value(expanded).map_err(serde::ser::Error::custom)?,
457 );
458 }
459 if let Some(ref subtitle) = self.subtitle {
460 map.insert(
461 "Subtitle",
462 serde_json::to_value(subtitle).map_err(serde::ser::Error::custom)?,
463 );
464 }
465 if let Some(ref title) = self.title {
466 map.insert(
467 "Title",
468 serde_json::to_value(title).map_err(serde::ser::Error::custom)?,
469 );
470 }
471
472 let mut map_ser = serializer.serialize_map(Some(map.len()))?;
474 for (k, v) in map {
475 map_ser.serialize_entry(&k, &v)?;
476 }
477 map_ser.end()
478 }
479}
480
481#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
482pub struct SignablePayloadFieldListLayout {
483 #[serde(rename = "Fields")]
484 pub fields: Vec<AnnotatedPayloadField>,
485}
486
487impl DeterministicOrdering for SignablePayloadFieldListLayout {}
489
490#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
491pub struct SignablePayloadFieldText {
492 #[serde(rename = "Text")]
493 pub text: String,
494}
495
496impl DeterministicOrdering for SignablePayloadFieldText {}
498
499#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
500pub struct SignablePayloadFieldTextV2 {
501 #[serde(rename = "Text")]
502 pub text: String,
503}
504
505impl DeterministicOrdering for SignablePayloadFieldTextV2 {}
507
508#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
509pub struct SignablePayloadFieldAddress {
510 #[serde(rename = "Address")]
511 pub address: String,
512 #[serde(rename = "Name")]
513 pub name: String,
514}
515
516impl DeterministicOrdering for SignablePayloadFieldAddress {}
518
519#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
520pub struct SignablePayloadFieldAddressV2 {
521 #[serde(rename = "Address")]
522 pub address: String,
523 #[serde(rename = "Name", skip_serializing_if = "is_empty_string")]
524 pub name: String,
525 #[serde(rename = "Memo", skip_serializing_if = "Option::is_none")]
526 pub memo: Option<String>,
527 #[serde(rename = "AssetLabel", skip_serializing_if = "is_empty_string")]
528 pub asset_label: String,
529 #[serde(rename = "BadgeText", skip_serializing_if = "Option::is_none")]
530 pub badge_text: Option<String>,
531}
532
533impl DeterministicOrdering for SignablePayloadFieldAddressV2 {}
535
536#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
537pub struct SignablePayloadFieldNumber {
538 #[serde(rename = "Number")]
539 pub number: String,
540}
541
542impl DeterministicOrdering for SignablePayloadFieldNumber {}
544
545#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
546pub struct SignablePayloadFieldAmount {
547 #[serde(rename = "Amount")]
548 pub amount: String,
549 #[serde(rename = "Abbreviation", skip_serializing_if = "Option::is_none")]
550 pub abbreviation: Option<String>,
551}
552
553impl DeterministicOrdering for SignablePayloadFieldAmount {}
555
556#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
557pub struct SignablePayloadFieldAmountV2 {
558 #[serde(rename = "Amount")]
559 pub amount: String,
560 #[serde(rename = "Abbreviation", skip_serializing_if = "Option::is_none")]
561 pub abbreviation: Option<String>,
562}
563
564impl Serialize for SignablePayloadFieldAmountV2 {
565 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
566 where
567 S: serde::Serializer,
568 {
569 use std::collections::BTreeMap;
570
571 let mut map = BTreeMap::new();
572 map.insert("Amount", &self.amount);
573 if let Some(ref abbreviation) = self.abbreviation {
574 map.insert("Abbreviation", abbreviation);
575 }
576 map.serialize(serializer)
577 }
578}
579
580impl DeterministicOrdering for SignablePayloadFieldAmountV2 {}
582
583#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
584pub struct SignablePayloadFieldDivider {
585 #[serde(rename = "Style")]
586 pub style: DividerStyle,
587}
588
589impl DeterministicOrdering for SignablePayloadFieldDivider {}
591
592#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
593pub struct SignablePayloadFieldUnknown {
594 #[serde(rename = "Data")]
595 pub data: String,
596 #[serde(rename = "Explanation")]
597 pub explanation: String,
598}
599
600impl DeterministicOrdering for SignablePayloadFieldUnknown {}
602
603#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
604pub struct SignablePayloadFieldStaticAnnotation {
605 #[serde(rename = "Text")]
606 pub text: String,
607}
608
609impl DeterministicOrdering for SignablePayloadFieldStaticAnnotation {}
611
612#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
613pub struct SignablePayloadFieldDynamicAnnotation {
614 #[serde(rename = "Type")]
615 pub field_type: String,
616 #[serde(rename = "ID")]
617 pub id: String,
618 #[serde(rename = "Params")]
619 pub params: Vec<String>,
620}
621
622impl Serialize for SignablePayloadFieldDynamicAnnotation {
623 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
624 where
625 S: serde::Serializer,
626 {
627 use serde::ser::SerializeMap;
628
629 let mut map = serializer.serialize_map(Some(3))?;
630 map.serialize_entry("ID", &self.id)?;
631 map.serialize_entry("Params", &self.params)?;
632 map.serialize_entry("Type", &self.field_type)?;
633 map.end()
634 }
635}
636
637impl DeterministicOrdering for SignablePayloadFieldDynamicAnnotation {}
639
640#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
641pub struct AnnotatedPayload {
642 #[serde(rename = "Version")]
643 pub version: String,
644 #[serde(rename = "Title", skip_serializing_if = "Option::is_none")]
645 pub title: Option<String>,
646 #[serde(rename = "Subtitle", skip_serializing_if = "Option::is_none")]
647 pub subtitle: Option<String>,
648 #[serde(rename = "Fields", skip_serializing_if = "Option::is_none")]
649 pub fields: Option<Vec<AnnotatedPayloadField>>,
650}
651
652#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
653pub struct AnnotatedPayloadField {
654 #[serde(flatten)]
655 pub signable_payload_field: SignablePayloadField,
656 #[serde(rename = "StaticAnnotation", skip_serializing_if = "Option::is_none")]
657 pub static_annotation: Option<SignablePayloadFieldStaticAnnotation>,
658 #[serde(rename = "DynamicAnnotation", skip_serializing_if = "Option::is_none")]
659 pub dynamic_annotation: Option<SignablePayloadFieldDynamicAnnotation>,
660}
661
662impl DeterministicOrdering for AnnotatedPayloadField {}
664
665impl Serialize for AnnotatedPayloadField {
667 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
668 where
669 S: Serializer,
670 {
671 let field_map = self
673 .signable_payload_field
674 .serialize_to_map()
675 .map_err(serde::ser::Error::custom)?;
676
677 let mut sorted_map = std::collections::BTreeMap::new();
679
680 for (key, value) in field_map {
682 sorted_map.insert(key, value);
683 }
684
685 if let Some(ref static_annotation) = self.static_annotation {
687 sorted_map.insert(
688 "StaticAnnotation".to_string(),
689 serde_json::to_value(static_annotation).map_err(serde::ser::Error::custom)?,
690 );
691 }
692
693 if let Some(ref dynamic_annotation) = self.dynamic_annotation {
694 sorted_map.insert(
695 "DynamicAnnotation".to_string(),
696 serde_json::to_value(dynamic_annotation).map_err(serde::ser::Error::custom)?,
697 );
698 }
699
700 let mut map_ser = serializer.serialize_map(Some(sorted_map.len()))?;
702 for (k, v) in sorted_map {
703 map_ser.serialize_entry(&k, &v)?;
704 }
705 map_ser.end()
706 }
707}
708
709#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
710pub struct UserIntent {
711 #[serde(rename = "Type")]
712 pub intent_type: String,
713 #[serde(rename = "Payload")]
714 pub payload: Value,
715}
716
717#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
718pub struct DividerStyle(String);
719
720impl DividerStyle {
721 pub const THIN: DividerStyle = DividerStyle(String::new());
722}
723
724impl DeterministicOrdering for SignablePayload {}
726
727impl SignablePayload {
728 pub fn new(
729 version: i64,
730 title: String,
731 subtitle: Option<String>,
732 fields: Vec<SignablePayloadField>,
733 payload_type: String,
734 ) -> Self {
735 SignablePayload {
736 version: version.to_string(),
737 title,
738 subtitle,
739 payload_type,
740 fields,
741 }
742 }
743
744 pub fn new_with_verified_fields<F>(
746 version: i64,
747 title: String,
748 subtitle: Option<String>,
749 fields: Vec<F>,
750 payload_type: String,
751 ) -> Self
752 where
753 F: Into<SignablePayloadField> + DeterministicOrdering,
754 {
755 SignablePayload {
756 version: version.to_string(),
757 title,
758 subtitle,
759 payload_type,
760 fields: fields.into_iter().map(Into::into).collect(),
761 }
762 }
763
764 pub fn verify_field_deterministic_ordering(field: &SignablePayloadField) -> Result<(), String> {
766 match field {
769 SignablePayloadField::PreviewLayout { preview_layout, .. } => {
770 preview_layout.verify_deterministic_ordering()?;
771 if let Some(ref condensed) = preview_layout.condensed {
772 condensed.verify_deterministic_ordering()?;
773 }
774 if let Some(ref expanded) = preview_layout.expanded {
775 expanded.verify_deterministic_ordering()?;
776 }
777 }
778 SignablePayloadField::ListLayout { list_layout, .. } => {
779 list_layout.verify_deterministic_ordering()?;
780 }
781 _ => {}
782 }
783 field.verify_deterministic_ordering()
784 }
785
786 pub fn to_json(&self) -> Result<String, Box<dyn std::error::Error>> {
787 let value = serde_json::to_value(self)?;
789
790 let sorted_value = sort_json_alphabetically(value);
792
793 let mut buf = Vec::new();
795 let formatter = serde_json::ser::CompactFormatter;
796 let mut ser = serde_json::Serializer::with_formatter(&mut buf, formatter);
797 sorted_value.serialize(&mut ser)?;
798
799 Ok(String::from_utf8(buf)?)
801 }
802
803 pub fn to_pretty_json(&self) -> Result<String, Box<dyn std::error::Error>> {
805 let value = serde_json::to_value(self)?;
806 let sorted_value = sort_json_alphabetically(value);
807 Ok(serde_json::to_string_pretty(&sorted_value)?)
808 }
809}
810
811fn sort_json_alphabetically(value: serde_json::Value) -> serde_json::Value {
813 match value {
814 serde_json::Value::Object(map) => {
815 let mut sorted_map = std::collections::BTreeMap::new();
817
818 for (key, val) in map {
820 sorted_map.insert(key, sort_json_alphabetically(val));
821 }
822
823 serde_json::Value::Object(serde_json::Map::from_iter(sorted_map))
825 }
826 serde_json::Value::Array(arr) => {
827 serde_json::Value::Array(arr.into_iter().map(sort_json_alphabetically).collect())
829 }
830 other => other,
832 }
833}
834
835impl SignablePayload {
836 pub fn validate_charset(&self) -> Result<(), VisualSignError> {
840 let json_str = self.to_json().map_err(|e| {
841 VisualSignError::SerializationError(format!("Failed to serialize for validation: {e}"))
842 })?;
843
844 if json_str.contains("\\u") {
846 return Err(VisualSignError::ValidationError(
847 "Restricted Characters Detected".to_string(),
848 ));
849 }
850
851 if !json_str.is_ascii() {
853 return Err(VisualSignError::ValidationError(
854 "Restricted Characters Detected".to_string(),
855 ));
856 }
857
858 for (i, ch) in json_str.char_indices() {
860 if !ch.is_ascii_graphic() && !ch.is_ascii_whitespace() {
861 return Err(VisualSignError::ValidationError(format!(
862 "JSON output contains non-printable character '{}' (U+{:02X}) at position {}",
863 ch.escape_default(),
864 ch as u32,
865 i
866 )));
867 }
868 }
869
870 Ok(())
871 }
872
873 pub fn to_validated_json(&self) -> Result<String, VisualSignError> {
875 self.validate_charset()?;
876 self.to_json()
877 .map_err(|e| VisualSignError::SerializationError(format!("Serialization failed: {e}")))
878 }
879}
880
881#[cfg(test)]
882mod tests {
883 use super::*;
884 use pretty_assertions::assert_eq;
885 use serde_json::json;
886
887 #[test]
888 fn test_signable_payload_to_json() {
889 let fields = vec![
890 SignablePayloadField::Text {
891 common: SignablePayloadFieldCommon {
892 fallback_text: "FallbackText1".to_string(),
893 label: "Label1".to_string(),
894 },
895 text: SignablePayloadFieldText {
896 text: "Text1".to_string(),
897 },
898 },
899 SignablePayloadField::Text {
900 common: SignablePayloadFieldCommon {
901 fallback_text: "FallbackText2".to_string(),
902 label: "Label2".to_string(),
903 },
904 text: SignablePayloadFieldText {
905 text: "Text2".to_string(),
906 },
907 },
908 ];
909
910 let payload = SignablePayload::new(
911 1,
912 "Test Title".to_string(),
913 Some("Test Subtitle".to_string()),
914 fields,
915 "Test Payload Type".to_string(),
916 );
917
918 let json = payload.to_json().unwrap();
919 println!("{json}");
920 }
921
922 #[test]
923 fn test_eth_user_intent_equivalence() {
924 let from_address = "0xYourFromAddress";
926
927 let fields = vec![
928 SignablePayloadField::TextV2 {
929 common: SignablePayloadFieldCommon {
930 fallback_text: "Ethereum Regnet".to_string(),
931 label: "Network".to_string(),
932 },
933 text_v2: SignablePayloadFieldTextV2 {
934 text: "Ethereum Regnet".to_string(),
935 },
936 },
937 SignablePayloadField::AddressV2 {
938 common: SignablePayloadFieldCommon {
939 fallback_text: from_address.to_string(),
940 label: "From".to_string(),
941 },
942 address_v2: SignablePayloadFieldAddressV2 {
943 address: from_address.to_string(),
944 name: "".to_string(),
945 memo: None,
946 asset_label: "".to_string(),
947 badge_text: None,
948 },
949 },
950 SignablePayloadField::AddressV2 {
951 common: SignablePayloadFieldCommon {
952 fallback_text: "0xb06E442b696513d54B05b5De58494E902E6e08Cb".to_string(),
953 label: "Contract Address".to_string(),
954 },
955 address_v2: SignablePayloadFieldAddressV2 {
956 address: "0xb06E442b696513d54B05b5De58494E902E6e08Cb".to_string(),
957 name: "".to_string(),
958 memo: None,
959 asset_label: "".to_string(),
960 badge_text: None,
961 },
962 },
963 SignablePayloadField::TextV2 {
964 common: SignablePayloadFieldCommon {
965 fallback_text: "0x00".to_string(),
966 label: "Data".to_string(),
967 },
968 text_v2: SignablePayloadFieldTextV2 {
969 text: "0x00".to_string(),
970 },
971 },
972 SignablePayloadField::AmountV2 {
973 common: SignablePayloadFieldCommon {
974 fallback_text: "0 ETH_R".to_string(),
975 label: "Value".to_string(),
976 },
977 amount_v2: SignablePayloadFieldAmountV2 {
978 amount: "0".to_string(),
979 abbreviation: Some("ETH_R".to_string()),
980 },
981 },
982 SignablePayloadField::AmountV2 {
983 common: SignablePayloadFieldCommon {
984 fallback_text: "0.000000000000000004 ETH_R".to_string(),
985 label: "Max Fee".to_string(),
986 },
987 amount_v2: SignablePayloadFieldAmountV2 {
988 amount: "0.000000000000000004".to_string(),
989 abbreviation: Some("ETH_R".to_string()),
990 },
991 },
992 ];
993
994 let payload =
995 SignablePayload::new(15, "Withdraw".to_string(), None, fields, "".to_string());
996
997 let json = payload.to_json().unwrap();
998 println!("{json}");
999
1000 let expected_json = json!({
1001 "Version": "15",
1002 "Title": "Withdraw",
1003 "Fields": [
1004 {
1005 "FallbackText": "Ethereum Regnet",
1006 "Type": "text_v2",
1007 "Label": "Network",
1008 "TextV2": {
1009 "Text": "Ethereum Regnet"
1010 }
1011 },
1012 {
1013 "FallbackText": "0xYourFromAddress",
1014 "Type": "address_v2",
1015 "Label": "From",
1016 "AddressV2": {
1017 "Address": "0xYourFromAddress"
1018 }
1019 },
1020 {
1021 "FallbackText": "0xb06E442b696513d54B05b5De58494E902E6e08Cb",
1022 "Type": "address_v2",
1023 "Label": "Contract Address",
1024 "AddressV2": {
1025 "Address": "0xb06E442b696513d54B05b5De58494E902E6e08Cb"
1026 }
1027 },
1028 {
1029 "FallbackText": "0x00",
1030 "Type": "text_v2",
1031 "Label": "Data",
1032 "TextV2": {
1033 "Text": "0x00"
1034 }
1035 },
1036 {
1037 "FallbackText": "0 ETH_R",
1038 "Type": "amount_v2",
1039 "Label": "Value",
1040 "AmountV2": {
1041 "Amount": "0",
1042 "Abbreviation": "ETH_R"
1043 }
1044 },
1045 {
1046 "FallbackText": "0.000000000000000004 ETH_R",
1047 "Type": "amount_v2",
1048 "Label": "Max Fee",
1049 "AmountV2": {
1050 "Amount": "0.000000000000000004",
1051 "Abbreviation": "ETH_R"
1052 }
1053 }
1054 ]
1055 });
1056
1057 let generated_json: Value = serde_json::from_str(&json).unwrap();
1058 assert_eq!(generated_json, expected_json);
1059 }
1060
1061 #[test]
1062 fn test_extensibility_with_new_field_type_requires_deterministic_ordering() {
1063 #[derive(Serialize, Debug, Clone, PartialEq, Eq)]
1068 struct TestCurrencyField {
1069 #[serde(rename = "CurrencyCode")]
1070 currency_code: String,
1071 #[serde(rename = "Symbol")]
1072 symbol: String,
1073 }
1074
1075 #[derive(Debug, Clone, PartialEq, Eq)]
1077 enum ExtendedSignablePayloadField {
1078 TextV2 {
1080 common: SignablePayloadFieldCommon,
1081 text_v2: SignablePayloadFieldTextV2,
1082 },
1083 Currency {
1085 common: SignablePayloadFieldCommon,
1086 currency: TestCurrencyField,
1087 },
1088 }
1089
1090 impl Serialize for ExtendedSignablePayloadField {
1092 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1093 where
1094 S: Serializer,
1095 {
1096 let mut fields = std::collections::HashMap::new();
1097
1098 match self {
1099 ExtendedSignablePayloadField::TextV2 { common, text_v2 } => {
1100 serialize_field_variant!(fields, "text_v2", common, ("TextV2", text_v2));
1101 }
1102 ExtendedSignablePayloadField::Currency { common, currency } => {
1104 serialize_field_variant!(
1105 fields,
1106 "currency",
1107 common,
1108 ("Currency", currency)
1109 );
1110 }
1111 }
1112
1113 let sorted_map: std::collections::BTreeMap<String, serde_json::Value> =
1114 fields.into_iter().collect();
1115 let mut map_ser = serializer.serialize_map(Some(sorted_map.len()))?;
1116 for (k, v) in sorted_map {
1117 map_ser.serialize_entry(&k, &v)?;
1118 }
1119 map_ser.end()
1120 }
1121 }
1122
1123 impl DeterministicOrdering for ExtendedSignablePayloadField {}
1125
1126 fn require_deterministic<T: DeterministicOrdering>(field: &T) -> Result<(), String> {
1128 field.verify_deterministic_ordering()
1129 }
1130
1131 let currency_field = ExtendedSignablePayloadField::Currency {
1133 common: SignablePayloadFieldCommon {
1134 fallback_text: "USD ($)".to_string(),
1135 label: "Payment Currency".to_string(),
1136 },
1137 currency: TestCurrencyField {
1138 currency_code: "USD".to_string(),
1139 symbol: "$".to_string(),
1140 },
1141 };
1142
1143 require_deterministic(¤cy_field).unwrap();
1145
1146 let json = serde_json::to_string(¤cy_field).unwrap();
1147 println!("New Currency Field JSON: {json}");
1148
1149 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1151 if let serde_json::Value::Object(map) = value {
1152 let keys: Vec<_> = map.keys().cloned().collect();
1153 println!("Currency Field Keys in order: {keys:?}");
1154
1155 assert_eq!(keys, vec!["Currency", "FallbackText", "Label", "Type"]);
1157 } else {
1158 panic!("Expected JSON object");
1159 }
1160
1161 let text_field = ExtendedSignablePayloadField::TextV2 {
1163 common: SignablePayloadFieldCommon {
1164 fallback_text: "Test Text".to_string(),
1165 label: "Test Label".to_string(),
1166 },
1167 text_v2: SignablePayloadFieldTextV2 {
1168 text: "Hello World".to_string(),
1169 },
1170 };
1171
1172 let json2 = serde_json::to_string(&text_field).unwrap();
1173 println!("TextV2 Field JSON: {json2}");
1174
1175 let value2: serde_json::Value = serde_json::from_str(&json2).unwrap();
1176 if let serde_json::Value::Object(map) = value2 {
1177 let keys: Vec<_> = map.keys().cloned().collect();
1178 println!("TextV2 Field Keys in order: {keys:?}");
1179
1180 assert_eq!(keys, vec!["FallbackText", "Label", "TextV2", "Type"]);
1182 } else {
1183 panic!("Expected JSON object");
1184 }
1185
1186 println!("✅ Successfully demonstrated adding new field type with automatic alphabetical ordering!");
1187 println!("✅ New field type MUST implement DeterministicOrdering to be used in deterministic contexts!");
1188 }
1189
1190 #[test]
1191 fn test_compile_time_error_without_deterministic_ordering() {
1192 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
1197 struct BadFieldType {
1198 z_field: String, a_field: String,
1200 m_field: String,
1201 }
1202
1203 #[derive(Serialize, Debug, Clone, PartialEq, Eq)]
1205 enum BadSignablePayloadField {
1206 BadVariant {
1207 common: SignablePayloadFieldCommon,
1208 bad_field: BadFieldType,
1209 },
1210 }
1211
1212 #[allow(dead_code)]
1218 fn process_field<T: DeterministicOrdering>(_field: &T) -> String {
1219 "processed".to_string()
1220 }
1221
1222 let bad_field = BadSignablePayloadField::BadVariant {
1224 common: SignablePayloadFieldCommon {
1225 fallback_text: "bad".to_string(),
1226 label: "Bad Field".to_string(),
1227 },
1228 bad_field: BadFieldType {
1229 z_field: "z".to_string(),
1230 a_field: "a".to_string(),
1231 m_field: "m".to_string(),
1232 },
1233 };
1234
1235 let json = serde_json::to_string(&bad_field).unwrap();
1245 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1246
1247 if let serde_json::Value::Object(map) = value {
1249 if let Some(serde_json::Value::Object(bad_field_obj)) = map.get("bad_field") {
1250 let keys: Vec<_> = bad_field_obj.keys().cloned().collect();
1251 println!("Bad field keys (not alphabetical): {keys:?}");
1254 assert_ne!(
1255 keys,
1256 vec!["a_field", "m_field", "z_field"],
1257 "Fields should NOT be alphabetically ordered without custom Serialize"
1258 );
1259 }
1260 }
1261
1262 println!("✅ Demonstrated that types without DeterministicOrdering can't be used in deterministic contexts!");
1263 }
1264
1265 #[test]
1266 fn test_new_field_type_workflow_with_deterministic_ordering() {
1267 #[derive(Deserialize, Debug, Clone, PartialEq)]
1271 struct GeoLocationField {
1272 #[serde(rename = "Latitude")]
1273 latitude: f64,
1274 #[serde(rename = "Longitude")]
1275 longitude: f64,
1276 #[serde(rename = "Accuracy")]
1277 accuracy: Option<f64>,
1278 }
1279
1280 impl Serialize for GeoLocationField {
1281 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1282 where
1283 S: Serializer,
1284 {
1285 use serde::ser::SerializeMap;
1286
1287 let mut map = serializer.serialize_map(Some(3))?;
1288 if let Some(ref accuracy) = self.accuracy {
1289 map.serialize_entry("Accuracy", accuracy)?;
1290 }
1291 map.serialize_entry("Latitude", &self.latitude)?;
1292 map.serialize_entry("Longitude", &self.longitude)?;
1293 map.end()
1294 }
1295 }
1296
1297 #[derive(Debug, Clone, PartialEq)]
1299 enum NewFieldVariant {
1300 GeoLocation {
1301 common: SignablePayloadFieldCommon,
1302 location: GeoLocationField,
1303 },
1304 }
1305
1306 impl Serialize for NewFieldVariant {
1308 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1309 where
1310 S: Serializer,
1311 {
1312 match self {
1313 NewFieldVariant::GeoLocation { common, location } => {
1314 let mut map = std::collections::BTreeMap::new();
1316
1317 map.insert(
1319 "FallbackText",
1320 serde_json::to_value(&common.fallback_text).unwrap(),
1321 );
1322 map.insert("GeoLocation", serde_json::to_value(location).unwrap());
1323 map.insert("Label", serde_json::to_value(&common.label).unwrap());
1324 map.insert(
1325 "Type",
1326 serde_json::Value::String("geo_location".to_string()),
1327 );
1328
1329 let mut map_ser = serializer.serialize_map(Some(map.len()))?;
1331 for (k, v) in map {
1332 map_ser.serialize_entry(&k, &v)?;
1333 }
1334 map_ser.end()
1335 }
1336 }
1337 }
1338 }
1339
1340 impl DeterministicOrdering for NewFieldVariant {}
1342
1343 fn add_to_payload<T: DeterministicOrdering>(field: T) -> Result<String, String> {
1345 field.verify_deterministic_ordering()?;
1347 Ok("Field added to payload".to_string())
1348 }
1349
1350 let geo_field = NewFieldVariant::GeoLocation {
1352 common: SignablePayloadFieldCommon {
1353 fallback_text: "37.7749° N, 122.4194° W".to_string(),
1354 label: "Location".to_string(),
1355 },
1356 location: GeoLocationField {
1357 latitude: 37.7749,
1358 longitude: -122.4194,
1359 accuracy: Some(10.0),
1360 },
1361 };
1362
1363 let result = add_to_payload(geo_field.clone());
1365 assert!(result.is_ok());
1366
1367 let json = serde_json::to_string(&geo_field).unwrap();
1369 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1370
1371 if let serde_json::Value::Object(map) = value {
1372 let keys: Vec<_> = map.keys().cloned().collect();
1373 assert_eq!(
1374 keys,
1375 vec!["FallbackText", "GeoLocation", "Label", "Type"],
1376 "Fields must be in alphabetical order"
1377 );
1378
1379 if let Some(serde_json::Value::Object(geo_obj)) = map.get("GeoLocation") {
1381 let geo_keys: Vec<_> = geo_obj.keys().cloned().collect();
1382 assert_eq!(
1383 geo_keys,
1384 vec!["Accuracy", "Latitude", "Longitude"],
1385 "Nested fields must also be alphabetical"
1386 );
1387 }
1388 }
1389
1390 println!("✅ Complete workflow demonstrated:");
1391 println!(" 1. Define new field type structure");
1392 println!(" 2. Create enum variant");
1393 println!(" 3. Implement custom Serialize with deterministic ordering");
1394 println!(" 4. Implement DeterministicOrdering trait");
1395 println!(" 5. Use in functions requiring deterministic ordering");
1396 println!(" 6. Verify JSON output has correct ordering");
1397 }
1398
1399 #[test]
1400 fn test_multiple_new_field_types_extensibility() {
1401 #[derive(Serialize, Debug, Clone, PartialEq, Eq)]
1405 struct TestDateTimeField {
1406 #[serde(rename = "DateTime")]
1407 date_time: String,
1408 #[serde(rename = "Format")]
1409 format: String,
1410 }
1411
1412 #[derive(Serialize, Debug, Clone, PartialEq, Eq)]
1413 struct TestPercentageField {
1414 #[serde(rename = "Value")]
1415 value: String,
1416 #[serde(rename = "Precision")]
1417 precision: u32,
1418 }
1419
1420 #[derive(Serialize, Debug, Clone, PartialEq, Eq)]
1421 struct TestLocationField {
1422 #[serde(rename = "Latitude")]
1423 latitude: String, #[serde(rename = "Longitude")]
1425 longitude: String,
1426 #[serde(rename = "Address")]
1427 address: String,
1428 }
1429
1430 #[derive(Debug, Clone, PartialEq, Eq)]
1432 enum MultiExtendedSignablePayloadField {
1433 DateTime {
1434 common: SignablePayloadFieldCommon,
1435 date_time: TestDateTimeField,
1436 },
1437 Percentage {
1438 common: SignablePayloadFieldCommon,
1439 percentage: TestPercentageField,
1440 },
1441 Location {
1442 common: SignablePayloadFieldCommon,
1443 location: TestLocationField,
1444 },
1445 }
1446
1447 impl Serialize for MultiExtendedSignablePayloadField {
1448 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1449 where
1450 S: Serializer,
1451 {
1452 let mut fields = std::collections::HashMap::new();
1453
1454 match self {
1455 MultiExtendedSignablePayloadField::DateTime { common, date_time } => {
1457 serialize_field_variant!(
1458 fields,
1459 "date_time",
1460 common,
1461 ("DateTime", date_time)
1462 );
1463 }
1464 MultiExtendedSignablePayloadField::Percentage { common, percentage } => {
1465 serialize_field_variant!(
1466 fields,
1467 "percentage",
1468 common,
1469 ("Percentage", percentage)
1470 );
1471 }
1472 MultiExtendedSignablePayloadField::Location { common, location } => {
1473 serialize_field_variant!(
1474 fields,
1475 "location",
1476 common,
1477 ("Location", location)
1478 );
1479 }
1480 }
1481
1482 let sorted_map: std::collections::BTreeMap<String, serde_json::Value> =
1483 fields.into_iter().collect();
1484 let mut map_ser = serializer.serialize_map(Some(sorted_map.len()))?;
1485 for (k, v) in sorted_map {
1486 map_ser.serialize_entry(&k, &v)?;
1487 }
1488 map_ser.end()
1489 }
1490 }
1491
1492 let datetime_field = MultiExtendedSignablePayloadField::DateTime {
1494 common: SignablePayloadFieldCommon {
1495 fallback_text: "2024-01-15 14:30:00 UTC".to_string(),
1496 label: "Transaction Time".to_string(),
1497 },
1498 date_time: TestDateTimeField {
1499 date_time: "2024-01-15T14:30:00Z".to_string(),
1500 format: "ISO8601".to_string(),
1501 },
1502 };
1503
1504 let json = serde_json::to_string(&datetime_field).unwrap();
1505 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1506 if let serde_json::Value::Object(map) = value {
1507 let keys: Vec<_> = map.keys().cloned().collect();
1508 assert_eq!(keys, vec!["DateTime", "FallbackText", "Label", "Type"]);
1510 }
1511
1512 let percentage_field = MultiExtendedSignablePayloadField::Percentage {
1514 common: SignablePayloadFieldCommon {
1515 fallback_text: "15.50%".to_string(),
1516 label: "Fee Rate".to_string(),
1517 },
1518 percentage: TestPercentageField {
1519 value: "15.50".to_string(),
1520 precision: 2,
1521 },
1522 };
1523
1524 let json2 = serde_json::to_string(&percentage_field).unwrap();
1525 let value2: serde_json::Value = serde_json::from_str(&json2).unwrap();
1526 if let serde_json::Value::Object(map) = value2 {
1527 let keys: Vec<_> = map.keys().cloned().collect();
1528 assert_eq!(keys, vec!["FallbackText", "Label", "Percentage", "Type"]);
1530 }
1531
1532 let location_field = MultiExtendedSignablePayloadField::Location {
1534 common: SignablePayloadFieldCommon {
1535 fallback_text: "New York, NY (40.7128, -74.0060)".to_string(),
1536 label: "Transaction Location".to_string(),
1537 },
1538 location: TestLocationField {
1539 latitude: "40.7128".to_string(),
1540 longitude: "-74.0060".to_string(),
1541 address: "New York, NY".to_string(),
1542 },
1543 };
1544
1545 let json3 = serde_json::to_string(&location_field).unwrap();
1546 let value3: serde_json::Value = serde_json::from_str(&json3).unwrap();
1547 if let serde_json::Value::Object(map) = value3 {
1548 let keys: Vec<_> = map.keys().cloned().collect();
1549 assert_eq!(keys, vec!["FallbackText", "Label", "Location", "Type"]);
1551 }
1552
1553 println!("✅ Successfully demonstrated adding multiple new field types easily!");
1554 println!(" - DateTime field: automatic alphabetical ordering");
1555 println!(" - Percentage field: automatic alphabetical ordering");
1556 println!(" - Location field: automatic alphabetical ordering");
1557 println!(" - Each new type required only ONE line of macro code!");
1558 }
1559
1560 #[test]
1561 fn test_field_completeness_verification() {
1562 #[derive(Debug, Clone, PartialEq, Eq)]
1566 enum IncompleteTestField {
1567 TestVariant {
1568 common: SignablePayloadFieldCommon,
1569 test_data: String,
1570 },
1571 }
1572
1573 impl FieldSerializer for IncompleteTestField {
1575 fn serialize_to_map(
1576 &self,
1577 ) -> Result<std::collections::BTreeMap<String, serde_json::Value>, serde_json::Error>
1578 {
1579 let mut fields = std::collections::HashMap::new();
1580 match self {
1581 IncompleteTestField::TestVariant {
1582 common,
1583 test_data: _,
1584 } => {
1585 fields.insert(
1587 "FallbackText".to_string(),
1588 serde_json::to_value(&common.fallback_text).unwrap(),
1589 );
1590 fields.insert(
1591 "Label".to_string(),
1592 serde_json::to_value(&common.label).unwrap(),
1593 );
1594 fields.insert(
1595 "Type".to_string(),
1596 serde_json::Value::String("test".to_string()),
1597 );
1598 }
1600 }
1601 Ok(fields.into_iter().collect())
1602 }
1603
1604 fn get_expected_fields(&self) -> Vec<&'static str> {
1605 vec!["FallbackText", "Label", "TestData", "Type"] }
1607 }
1608
1609 impl Serialize for IncompleteTestField {
1611 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1612 where
1613 S: Serializer,
1614 {
1615 let sorted_map = self.serialize_to_map().map_err(serde::ser::Error::custom)?;
1616 let expected_fields = self.get_expected_fields();
1617 let actual_fields: Vec<_> = sorted_map.keys().map(|s| s.as_str()).collect();
1618
1619 for expected in &expected_fields {
1621 if !actual_fields.contains(expected) {
1622 return Err(serde::ser::Error::custom(format!(
1623 "Missing expected field '{expected}'. Expected: {expected_fields:?}, Actual: {actual_fields:?}",
1624 )));
1625 }
1626 }
1627
1628 let mut map_ser = serializer.serialize_map(Some(sorted_map.len()))?;
1629 for (k, v) in sorted_map {
1630 map_ser.serialize_entry(&k, &v)?;
1631 }
1632 map_ser.end()
1633 }
1634 }
1635
1636 let incomplete_field = IncompleteTestField::TestVariant {
1638 common: SignablePayloadFieldCommon {
1639 fallback_text: "Test".to_string(),
1640 label: "Test Label".to_string(),
1641 },
1642 test_data: "This should be serialized but isn't".to_string(),
1643 };
1644
1645 let result = serde_json::to_string(&incomplete_field);
1646
1647 assert!(
1649 result.is_err(),
1650 "Expected serialization to fail due to missing TestData field"
1651 );
1652
1653 let error_msg = result.unwrap_err().to_string();
1654 assert!(
1655 error_msg.contains("Missing expected field 'TestData'"),
1656 "Error should mention missing TestData field, got: {error_msg}",
1657 );
1658
1659 println!("✅ Successfully caught missing field serialization!");
1660 println!(" Error: {error_msg}");
1661 }
1662
1663 #[test]
1664 fn test_field_completeness_verification_with_correct_implementation() {
1665 #[derive(Serialize, Debug, Clone, PartialEq, Eq)]
1668 struct TestDataStruct {
1669 #[serde(rename = "Data")]
1670 data: String,
1671 }
1672
1673 #[derive(Debug, Clone, PartialEq, Eq)]
1674 enum CompleteTestField {
1675 TestVariant {
1676 common: SignablePayloadFieldCommon,
1677 test_data: TestDataStruct,
1678 },
1679 }
1680
1681 impl FieldSerializer for CompleteTestField {
1683 fn serialize_to_map(
1684 &self,
1685 ) -> Result<std::collections::BTreeMap<String, serde_json::Value>, serde_json::Error>
1686 {
1687 let mut fields = std::collections::HashMap::new();
1688 match self {
1689 CompleteTestField::TestVariant { common, test_data } => {
1690 serialize_field_variant!(fields, "test", common, ("TestData", test_data));
1692 }
1693 }
1694 Ok(fields.into_iter().collect())
1695 }
1696
1697 fn get_expected_fields(&self) -> Vec<&'static str> {
1698 vec!["FallbackText", "Label", "TestData", "Type"] }
1700 }
1701
1702 impl Serialize for CompleteTestField {
1704 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1705 where
1706 S: Serializer,
1707 {
1708 let sorted_map = self.serialize_to_map().map_err(serde::ser::Error::custom)?;
1709 let expected_fields = self.get_expected_fields();
1710 let actual_fields: Vec<_> = sorted_map.keys().map(|s| s.as_str()).collect();
1711
1712 for expected in &expected_fields {
1713 if !actual_fields.contains(expected) {
1714 return Err(serde::ser::Error::custom(format!(
1715 "Missing expected field '{expected}'. Expected: {expected_fields:?}, Actual: {actual_fields:?}",
1716 )));
1717 }
1718 }
1719
1720 let mut map_ser = serializer.serialize_map(Some(sorted_map.len()))?;
1721 for (k, v) in sorted_map {
1722 map_ser.serialize_entry(&k, &v)?;
1723 }
1724 map_ser.end()
1725 }
1726 }
1727
1728 let complete_field = CompleteTestField::TestVariant {
1730 common: SignablePayloadFieldCommon {
1731 fallback_text: "Test".to_string(),
1732 label: "Test Label".to_string(),
1733 },
1734 test_data: TestDataStruct {
1735 data: "This is properly serialized".to_string(),
1736 },
1737 };
1738
1739 let result = serde_json::to_string(&complete_field);
1740 assert!(
1741 result.is_ok(),
1742 "Expected serialization to succeed: {result:?}",
1743 );
1744
1745 let json = result.unwrap();
1746 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1747 if let serde_json::Value::Object(map) = value {
1748 let keys: Vec<_> = map.keys().cloned().collect();
1749 assert_eq!(keys, vec!["FallbackText", "Label", "TestData", "Type"]);
1751 }
1752
1753 println!("✅ Correctly implemented field serialization passed verification!");
1754 println!(" JSON: {json}");
1755 }
1756
1757 #[test]
1758 fn test_original_signable_payload_field_verification() {
1759 let test_fields = vec![
1763 SignablePayloadField::TextV2 {
1765 common: SignablePayloadFieldCommon {
1766 fallback_text: "Test Text".to_string(),
1767 label: "Text Field".to_string(),
1768 },
1769 text_v2: SignablePayloadFieldTextV2 {
1770 text: "Hello World".to_string(),
1771 },
1772 },
1773 SignablePayloadField::AmountV2 {
1775 common: SignablePayloadFieldCommon {
1776 fallback_text: "100 USD".to_string(),
1777 label: "Amount Field".to_string(),
1778 },
1779 amount_v2: SignablePayloadFieldAmountV2 {
1780 amount: "100".to_string(),
1781 abbreviation: Some("USD".to_string()),
1782 },
1783 },
1784 SignablePayloadField::Address {
1786 common: SignablePayloadFieldCommon {
1787 fallback_text: "0x123...abc".to_string(),
1788 label: "Address Field".to_string(),
1789 },
1790 address: SignablePayloadFieldAddress {
1791 address: "0x123abc".to_string(),
1792 name: "Test Address".to_string(),
1793 },
1794 },
1795 ];
1796
1797 for (i, field) in test_fields.iter().enumerate() {
1798 let result = serde_json::to_string(field);
1800 assert!(
1801 result.is_ok(),
1802 "Field {i} should serialize successfully: {result:?}",
1803 );
1804
1805 let json = result.unwrap();
1806 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1807
1808 if let serde_json::Value::Object(map) = value {
1810 let keys: Vec<_> = map.keys().cloned().collect();
1811 let mut expected_keys = keys.clone();
1812 expected_keys.sort();
1813
1814 assert_eq!(
1815 keys, expected_keys,
1816 "Fields should be in alphabetical order for field {}: got {:?}",
1817 i, keys
1818 );
1819
1820 let expected_field_count = field.get_expected_fields().len();
1822 assert_eq!(
1823 keys.len(),
1824 expected_field_count,
1825 "Field {} should have exactly {} fields: {:?}",
1826 i,
1827 expected_field_count,
1828 keys
1829 );
1830
1831 println!(
1832 "✅ Field {} verified: {} fields in alphabetical order: {:?}",
1833 i,
1834 keys.len(),
1835 keys
1836 );
1837 }
1838 }
1839
1840 println!(
1841 "✅ All SignablePayloadField variants pass verification with alphabetical ordering!"
1842 );
1843 }
1844
1845 #[test]
1846 fn test_field_alphabetical_ordering() {
1847 let field = SignablePayloadField::TextV2 {
1851 common: SignablePayloadFieldCommon {
1852 fallback_text: "Test Fallback".to_string(),
1853 label: "Test Label".to_string(),
1854 },
1855 text_v2: SignablePayloadFieldTextV2 {
1856 text: "Test Text".to_string(),
1857 },
1858 };
1859
1860 let json = serde_json::to_string(&field).unwrap();
1861 println!("TextV2 Field JSON: {json}");
1862
1863 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1865 if let serde_json::Value::Object(map) = value {
1866 let keys: Vec<_> = map.keys().cloned().collect();
1867 println!("Keys in order: {keys:?}");
1868
1869 assert_eq!(keys, vec!["FallbackText", "Label", "TextV2", "Type"]);
1871 } else {
1872 panic!("Expected JSON object");
1873 }
1874
1875 let field2 = SignablePayloadField::AmountV2 {
1877 common: SignablePayloadFieldCommon {
1878 fallback_text: "0 ETH".to_string(),
1879 label: "Value".to_string(),
1880 },
1881 amount_v2: SignablePayloadFieldAmountV2 {
1882 amount: "0".to_string(),
1883 abbreviation: Some("ETH".to_string()),
1884 },
1885 };
1886
1887 let json2 = serde_json::to_string(&field2).unwrap();
1888 println!("AmountV2 Field JSON: {json2}");
1889
1890 let value2: serde_json::Value = serde_json::from_str(&json2).unwrap();
1891 if let serde_json::Value::Object(map) = value2 {
1892 let keys: Vec<_> = map.keys().cloned().collect();
1893 println!("Keys in order: {keys:?}");
1894
1895 assert_eq!(keys, vec!["AmountV2", "FallbackText", "Label", "Type"]);
1897 } else {
1898 panic!("Expected JSON object");
1899 }
1900 }
1901
1902 #[test]
1903 fn test_alphabetical_sorting() {
1904 let payload = SignablePayload::new(
1905 1,
1906 "Z_Title".to_string(), Some("A_Subtitle".to_string()), vec![], "M_PayloadType".to_string(), );
1911
1912 let json = payload.to_json().unwrap();
1913 assert_sorted_alphabetically(json);
1914
1915 let serde_default_json = serde_json::to_string(&payload).unwrap();
1918 assert_sorted_alphabetically(serde_default_json);
1919 }
1920
1921 #[test]
1922 fn test_compile_time_deterministic_ordering_enforcement() {
1923 fn assert_deterministic_ordering<T: DeterministicOrdering>(_: &T) {}
1927
1928 let text_v2 = SignablePayloadFieldTextV2 {
1930 text: "Value".to_string(),
1931 };
1932 assert_deterministic_ordering(&text_v2);
1933
1934 let address = SignablePayloadFieldAddress {
1935 address: "0x123".to_string(),
1936 name: "Test".to_string(),
1937 };
1938 assert_deterministic_ordering(&address);
1939
1940 let amount_v2 = SignablePayloadFieldAmountV2 {
1941 amount: "100".to_string(),
1942 abbreviation: Some("USD".to_string()),
1943 };
1944 assert_deterministic_ordering(&amount_v2);
1945
1946 let preview_layout = SignablePayloadFieldPreviewLayout {
1948 title: Some(text_v2.clone()),
1949 subtitle: None,
1950 condensed: Some(SignablePayloadFieldListLayout { fields: vec![] }),
1951 expanded: None,
1952 };
1953 assert_deterministic_ordering(&preview_layout);
1954
1955 let list_layout = SignablePayloadFieldListLayout { fields: vec![] };
1956 assert_deterministic_ordering(&list_layout);
1957
1958 let static_annotation = SignablePayloadFieldStaticAnnotation {
1960 text: "Note".to_string(),
1961 };
1962 assert_deterministic_ordering(&static_annotation);
1963
1964 let dynamic_annotation = SignablePayloadFieldDynamicAnnotation {
1965 field_type: "type".to_string(),
1966 id: "id".to_string(),
1967 params: vec![],
1968 };
1969 assert_deterministic_ordering(&dynamic_annotation);
1970
1971 let field = SignablePayloadField::TextV2 {
1973 common: SignablePayloadFieldCommon {
1974 fallback_text: "Test".to_string(),
1975 label: "Label".to_string(),
1976 },
1977 text_v2: text_v2.clone(),
1978 };
1979 assert_deterministic_ordering(&field);
1980
1981 let annotated = AnnotatedPayloadField {
1983 signable_payload_field: field.clone(),
1984 static_annotation: Some(static_annotation),
1985 dynamic_annotation: Some(dynamic_annotation),
1986 };
1987 assert_deterministic_ordering(&annotated);
1988
1989 let payload = SignablePayload::new(
1991 1,
1992 "Title".to_string(),
1993 None,
1994 vec![field.clone()],
1995 "Type".to_string(),
1996 );
1997 assert_deterministic_ordering(&payload);
1998
1999 let complex_field = SignablePayloadField::PreviewLayout {
2001 common: SignablePayloadFieldCommon {
2002 fallback_text: "Preview".to_string(),
2003 label: "Complex".to_string(),
2004 },
2005 preview_layout,
2006 };
2007 assert_deterministic_ordering(&complex_field);
2008 assert!(complex_field.verify_deterministic_ordering().is_ok());
2009 }
2010
2011 #[test]
2012 fn test_annotated_payload_field_alphabetical_ordering() {
2013 let annotated_field = AnnotatedPayloadField {
2018 signable_payload_field: SignablePayloadField::AmountV2 {
2019 common: SignablePayloadFieldCommon {
2020 fallback_text: "100 USD".to_string(),
2021 label: "Amount".to_string(),
2022 },
2023 amount_v2: SignablePayloadFieldAmountV2 {
2024 amount: "100".to_string(),
2025 abbreviation: Some("USD".to_string()),
2026 },
2027 },
2028 static_annotation: Some(SignablePayloadFieldStaticAnnotation {
2029 text: "Note: This is a test".to_string(),
2030 }),
2031 dynamic_annotation: Some(SignablePayloadFieldDynamicAnnotation {
2032 field_type: "test_type".to_string(),
2033 id: "test_id".to_string(),
2034 params: vec!["param1".to_string()],
2035 }),
2036 };
2037
2038 let json = serde_json::to_string(&annotated_field).unwrap();
2039 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
2040
2041 assert_json_keys_alphabetical(&value, "AnnotatedPayloadField with all annotations");
2042
2043 let annotated_field_no_annotations = AnnotatedPayloadField {
2045 signable_payload_field: SignablePayloadField::TextV2 {
2046 common: SignablePayloadFieldCommon {
2047 fallback_text: "Test Text".to_string(),
2048 label: "Test Label".to_string(),
2049 },
2050 text_v2: SignablePayloadFieldTextV2 {
2051 text: "Hello World".to_string(),
2052 },
2053 },
2054 static_annotation: None,
2055 dynamic_annotation: None,
2056 };
2057
2058 let json2 = serde_json::to_string(&annotated_field_no_annotations).unwrap();
2059 let value2: serde_json::Value = serde_json::from_str(&json2).unwrap();
2060
2061 assert_json_keys_alphabetical(&value2, "AnnotatedPayloadField without annotations");
2062
2063 let annotated_field_static_only = AnnotatedPayloadField {
2065 signable_payload_field: SignablePayloadField::Address {
2066 common: SignablePayloadFieldCommon {
2067 fallback_text: "0x123".to_string(),
2068 label: "Address".to_string(),
2069 },
2070 address: SignablePayloadFieldAddress {
2071 address: "0x123456".to_string(),
2072 name: "Test Address".to_string(),
2073 },
2074 },
2075 static_annotation: Some(SignablePayloadFieldStaticAnnotation {
2076 text: "Static annotation only".to_string(),
2077 }),
2078 dynamic_annotation: None,
2079 };
2080
2081 let json3 = serde_json::to_string(&annotated_field_static_only).unwrap();
2082 let value3: serde_json::Value = serde_json::from_str(&json3).unwrap();
2083
2084 assert_json_keys_alphabetical(&value3, "AnnotatedPayloadField with static annotation only");
2085 }
2086
2087 #[test]
2088 fn test_preview_layout_field_alphabetical_ordering() {
2089 let preview_layout = SignablePayloadFieldPreviewLayout {
2093 title: Some(SignablePayloadFieldTextV2 {
2094 text: "Test Title".to_string(),
2095 }),
2096 subtitle: Some(SignablePayloadFieldTextV2 {
2097 text: "Test Subtitle".to_string(),
2098 }),
2099 condensed: Some(SignablePayloadFieldListLayout { fields: vec![] }),
2100 expanded: Some(SignablePayloadFieldListLayout { fields: vec![] }),
2101 };
2102
2103 let json = serde_json::to_string(&preview_layout).unwrap();
2105 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
2106
2107 if let serde_json::Value::Object(map) = value {
2108 let keys: Vec<_> = map.keys().cloned().collect();
2109
2110 assert_eq!(
2112 keys,
2113 vec!["Condensed", "Expanded", "Subtitle", "Title"],
2114 "PreviewLayout fields should be in alphabetical order"
2115 );
2116 }
2117
2118 let partial_preview = SignablePayloadFieldPreviewLayout {
2120 title: Some(SignablePayloadFieldTextV2 {
2121 text: "Title Only".to_string(),
2122 }),
2123 subtitle: None,
2124 condensed: Some(SignablePayloadFieldListLayout { fields: vec![] }),
2125 expanded: None,
2126 };
2127
2128 let json2 = serde_json::to_string(&partial_preview).unwrap();
2129 let value2: serde_json::Value = serde_json::from_str(&json2).unwrap();
2130
2131 if let serde_json::Value::Object(map) = value2 {
2132 let keys: Vec<_> = map.keys().cloned().collect();
2133
2134 assert_eq!(
2136 keys,
2137 vec!["Condensed", "Title"],
2138 "Partial PreviewLayout fields should be in alphabetical order"
2139 );
2140 }
2141
2142 let preview_field = SignablePayloadField::PreviewLayout {
2144 common: SignablePayloadFieldCommon {
2145 fallback_text: "Preview".to_string(),
2146 label: "Preview Field".to_string(),
2147 },
2148 preview_layout,
2149 };
2150
2151 let json3 = serde_json::to_string(&preview_field).unwrap();
2152 let value3: serde_json::Value = serde_json::from_str(&json3).unwrap();
2153
2154 if let serde_json::Value::Object(map) = &value3 {
2156 let keys: Vec<_> = map.keys().cloned().collect();
2157 let mut expected_keys = keys.clone();
2158 expected_keys.sort();
2159 assert_eq!(
2160 keys, expected_keys,
2161 "SignablePayloadField with PreviewLayout should have alphabetical keys"
2162 );
2163
2164 if let Some(serde_json::Value::Object(preview_map)) = map.get("PreviewLayout") {
2166 let inner_keys: Vec<_> = preview_map.keys().cloned().collect();
2167 assert_eq!(
2168 inner_keys,
2169 vec!["Condensed", "Expanded", "Subtitle", "Title"],
2170 "Inner PreviewLayout should have alphabetical field ordering"
2171 );
2172 }
2173 }
2174 }
2175
2176 #[test]
2177 fn test_annotated_payload_field_in_condensed_recursive_ordering() {
2178 let condensed_fields = vec![
2182 AnnotatedPayloadField {
2183 signable_payload_field: SignablePayloadField::AmountV2 {
2184 common: SignablePayloadFieldCommon {
2185 fallback_text: "10 SOL".to_string(),
2186 label: "Transfer Amount".to_string(),
2187 },
2188 amount_v2: SignablePayloadFieldAmountV2 {
2189 amount: "10000000000".to_string(),
2190 abbreviation: Some("lamports".to_string()),
2191 },
2192 },
2193 static_annotation: Some(SignablePayloadFieldStaticAnnotation {
2194 text: "Fee warning".to_string(),
2195 }),
2196 dynamic_annotation: None,
2197 },
2198 AnnotatedPayloadField {
2199 signable_payload_field: SignablePayloadField::TextV2 {
2200 common: SignablePayloadFieldCommon {
2201 fallback_text: "Test Text".to_string(),
2202 label: "Test Label".to_string(),
2203 },
2204 text_v2: SignablePayloadFieldTextV2 {
2205 text: "Hello World".to_string(),
2206 },
2207 },
2208 static_annotation: None,
2209 dynamic_annotation: Some(SignablePayloadFieldDynamicAnnotation {
2210 field_type: "dynamic".to_string(),
2211 id: "id123".to_string(),
2212 params: vec![],
2213 }),
2214 },
2215 ];
2216
2217 let preview_field = SignablePayloadField::PreviewLayout {
2218 common: SignablePayloadFieldCommon {
2219 fallback_text: "Preview".to_string(),
2220 label: "Preview Field".to_string(),
2221 },
2222 preview_layout: SignablePayloadFieldPreviewLayout {
2223 title: Some(SignablePayloadFieldTextV2 {
2224 text: "Title Text".to_string(),
2225 }),
2226 subtitle: None,
2227 condensed: Some(SignablePayloadFieldListLayout {
2228 fields: condensed_fields.clone(),
2229 }),
2230 expanded: Some(SignablePayloadFieldListLayout {
2231 fields: vec![AnnotatedPayloadField {
2232 signable_payload_field: SignablePayloadField::Number {
2233 common: SignablePayloadFieldCommon {
2234 fallback_text: "42".to_string(),
2235 label: "Number Field".to_string(),
2236 },
2237 number: SignablePayloadFieldNumber {
2238 number: "42".to_string(),
2239 },
2240 },
2241 static_annotation: None,
2242 dynamic_annotation: None,
2243 }],
2244 }),
2245 },
2246 };
2247
2248 let json = serde_json::to_string(&preview_field).unwrap();
2249 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
2250
2251 assert_json_recursive_alphabetical(&value, "");
2253 }
2254
2255 fn assert_json_keys_alphabetical(value: &serde_json::Value, context: &str) {
2257 if let serde_json::Value::Object(map) = value {
2258 let keys: Vec<_> = map.keys().cloned().collect();
2259 let mut expected_keys = keys.clone();
2260 expected_keys.sort();
2261
2262 assert_eq!(
2263 keys, expected_keys,
2264 "{} should have alphabetically ordered keys. Got: {:?}, Expected: {:?}",
2265 context, keys, expected_keys
2266 );
2267 }
2268 }
2269
2270 fn assert_json_recursive_alphabetical(value: &serde_json::Value, path: &str) {
2272 match value {
2273 serde_json::Value::Object(map) => {
2274 let keys: Vec<_> = map.keys().cloned().collect();
2276 let mut expected_keys = keys.clone();
2277 expected_keys.sort();
2278
2279 assert_eq!(
2280 keys, expected_keys,
2281 "Object at path '{}' should have alphabetically ordered keys. Got: {:?}, Expected: {:?}",
2282 if path.is_empty() { "root" } else { path },
2283 keys, expected_keys
2284 );
2285
2286 for (key, nested_value) in map {
2288 let new_path = if path.is_empty() {
2289 key.clone()
2290 } else {
2291 format!("{path}.{key}")
2292 };
2293 assert_json_recursive_alphabetical(nested_value, &new_path);
2294 }
2295 }
2296 serde_json::Value::Array(arr) => {
2297 for (i, item) in arr.iter().enumerate() {
2299 let new_path = format!("{path}[{i}]");
2300 assert_json_recursive_alphabetical(item, &new_path);
2301 }
2302 }
2303 _ => {} }
2305 }
2306
2307 #[test]
2308 fn test_deterministic_ordering_consistency() {
2309 let preview_field = SignablePayloadField::PreviewLayout {
2314 common: SignablePayloadFieldCommon {
2315 fallback_text: "Complex Preview".to_string(),
2316 label: "Preview".to_string(),
2317 },
2318 preview_layout: SignablePayloadFieldPreviewLayout {
2319 title: Some(SignablePayloadFieldTextV2 {
2320 text: "Title".to_string(),
2321 }),
2322 subtitle: Some(SignablePayloadFieldTextV2 {
2323 text: "Subtitle".to_string(),
2324 }),
2325 condensed: Some(SignablePayloadFieldListLayout {
2326 fields: vec![AnnotatedPayloadField {
2327 signable_payload_field: SignablePayloadField::AmountV2 {
2328 common: SignablePayloadFieldCommon {
2329 fallback_text: "100 USD".to_string(),
2330 label: "Amount".to_string(),
2331 },
2332 amount_v2: SignablePayloadFieldAmountV2 {
2333 amount: "100".to_string(),
2334 abbreviation: Some("USD".to_string()),
2335 },
2336 },
2337 static_annotation: Some(SignablePayloadFieldStaticAnnotation {
2338 text: "Fee".to_string(),
2339 }),
2340 dynamic_annotation: None,
2341 }],
2342 }),
2343 expanded: Some(SignablePayloadFieldListLayout {
2344 fields: vec![AnnotatedPayloadField {
2345 signable_payload_field: SignablePayloadField::AddressV2 {
2346 common: SignablePayloadFieldCommon {
2347 fallback_text: "0x123".to_string(),
2348 label: "Address".to_string(),
2349 },
2350 address_v2: SignablePayloadFieldAddressV2 {
2351 address: "0x123456".to_string(),
2352 name: "".to_string(),
2353 memo: None,
2354 asset_label: "".to_string(),
2355 badge_text: Some("Verified".to_string()),
2356 },
2357 },
2358 static_annotation: None,
2359 dynamic_annotation: Some(SignablePayloadFieldDynamicAnnotation {
2360 field_type: "address".to_string(),
2361 id: "addr_1".to_string(),
2362 params: vec!["param1".to_string()],
2363 }),
2364 }],
2365 }),
2366 },
2367 };
2368
2369 let result = SignablePayload::verify_field_deterministic_ordering(&preview_field);
2371 assert!(
2372 result.is_ok(),
2373 "Complex field should verify deterministic ordering"
2374 );
2375
2376 let json = serde_json::to_string(&preview_field).unwrap();
2378 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
2379
2380 assert_json_recursive_alphabetical(&value, "");
2382
2383 let payload = SignablePayload::new(
2385 1,
2386 "Test Payload".to_string(),
2387 Some("With complex fields".to_string()),
2388 vec![preview_field],
2389 "test".to_string(),
2390 );
2391
2392 assert!(payload.verify_deterministic_ordering().is_ok());
2394
2395 fn require_deterministic<T: DeterministicOrdering>(_: &T) {}
2397
2398 require_deterministic(&SignablePayloadFieldTextV2 {
2400 text: "".to_string(),
2401 });
2402 require_deterministic(&SignablePayloadFieldAmountV2 {
2403 amount: "".to_string(),
2404 abbreviation: None,
2405 });
2406 require_deterministic(&SignablePayloadFieldPreviewLayout {
2407 title: None,
2408 subtitle: None,
2409 condensed: None,
2410 expanded: None,
2411 });
2412 require_deterministic(&SignablePayloadFieldListLayout { fields: vec![] });
2413 require_deterministic(&payload);
2414
2415 println!("✅ All types consistently implement DeterministicOrdering!");
2416 }
2417
2418 fn assert_sorted_alphabetically(json: String) {
2419 println!("Sorted JSON: {json}");
2420 let pos_fields = json.find("Fields").unwrap_or(0);
2422 let pos_payload = json.find("PayloadType").unwrap_or(0);
2423 let pos_subtitle = json.find("Subtitle").unwrap_or(0);
2424 let pos_title = json.find("Title").unwrap_or(0);
2425 let pos_version = json.find("Version").unwrap_or(0);
2426
2427 assert!(
2428 pos_fields < pos_payload,
2429 "Fields should come before PayloadType"
2430 );
2431 assert!(
2432 pos_payload < pos_subtitle,
2433 "PayloadType should come before Subtitle"
2434 );
2435 assert!(
2436 pos_subtitle < pos_title,
2437 "Subtitle should come before Title"
2438 );
2439 assert!(pos_title < pos_version, "Title should come before Version");
2440 }
2441}