xrpl/models/amount/
mpt_amount.rs1use crate::models::transactions::mptoken_issuance_set::validate_mptoken_issuance_id;
2use crate::models::{Model, XRPLModelResult};
3use alloc::{borrow::Cow, string::ToString};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]
17#[serde(deny_unknown_fields)]
18pub struct MPTAmount<'a> {
19 pub value: Cow<'a, str>,
21 pub mpt_issuance_id: Cow<'a, str>,
24}
25
26impl<'a> Model for MPTAmount<'a> {
27 fn get_errors(&self) -> XRPLModelResult<()> {
28 if self.value.is_empty() || !self.value.bytes().all(|b| b.is_ascii_digit()) {
32 return Err(crate::models::XRPLModelException::InvalidValueFormat {
33 field: "value".into(),
34 format: "unsigned integer string".into(),
35 found: self.value.to_string(),
36 });
37 }
38 let n: u64 = self.value.parse()?;
39 if n > i64::MAX as u64 {
40 return Err(crate::models::XRPLModelException::InvalidValue {
41 field: "value".into(),
42 expected: alloc::format!("MPT amount <= {} (i64::MAX)", i64::MAX),
43 found: self.value.to_string(),
44 });
45 }
46 validate_mptoken_issuance_id(self.mpt_issuance_id.as_ref())?;
47 Ok(())
48 }
49}
50
51impl<'a> MPTAmount<'a> {
52 pub fn new(value: Cow<'a, str>, mpt_issuance_id: Cow<'a, str>) -> Self {
53 Self {
54 value,
55 mpt_issuance_id,
56 }
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use crate::models::Model;
63
64 use super::*;
65
66 const VALID_ID: &str = "00000001A407AF5856CEFBF81F3D4A0000000000A407AF58";
67
68 #[test]
69 fn test_mpt_amount_serde_roundtrip() {
70 let amount = MPTAmount::new("100".into(), VALID_ID.into());
71 let json = serde_json::to_string(&amount).unwrap();
72 let decoded: MPTAmount = serde_json::from_str(&json).unwrap();
73 assert_eq!(amount, decoded);
74 }
75
76 #[test]
77 fn test_mpt_amount_get_errors_valid() {
78 let amount = MPTAmount::new("9223372036854775807".into(), VALID_ID.into());
79 assert!(amount.get_errors().is_ok());
80 }
81
82 #[test]
83 fn test_mpt_amount_get_errors_zero() {
84 let amount = MPTAmount::new("0".into(), VALID_ID.into());
85 assert!(amount.get_errors().is_ok());
86 }
87
88 #[test]
89 fn test_mpt_amount_get_errors_bad_value_decimal() {
90 let amount = MPTAmount::new("1.5".into(), VALID_ID.into());
91 assert!(amount.get_errors().is_err());
92 }
93
94 #[test]
95 fn test_mpt_amount_get_errors_bad_value_negative() {
96 let amount = MPTAmount::new("-1".into(), VALID_ID.into());
97 assert!(amount.get_errors().is_err());
98 }
99
100 #[test]
101 fn test_mpt_amount_get_errors_bad_value_plus_prefix() {
102 let amount = MPTAmount::new("+1".into(), VALID_ID.into());
103 assert!(amount.get_errors().is_err());
104 }
105
106 #[test]
107 fn test_mpt_amount_get_errors_rejects_above_i64_max() {
108 let amount = MPTAmount::new("9223372036854775808".into(), VALID_ID.into());
110 assert!(amount.get_errors().is_err());
111 }
112
113 #[test]
114 fn test_mpt_amount_get_errors_bad_id_too_short() {
115 let amount = MPTAmount::new("100".into(), "DEAD".into());
116 assert!(amount.get_errors().is_err());
117 }
118
119 #[test]
120 fn test_mpt_amount_get_errors_bad_id_non_hex() {
121 let bad_id = "Z".repeat(48);
122 let amount = MPTAmount::new("100".into(), bad_id.as_str().into());
123 assert!(amount.get_errors().is_err());
124 }
125}