xrpl/models/ledger/objects/
permissioned_domain.rs1use crate::models::ledger::objects::LedgerEntryType;
2use crate::models::transactions::Credential;
3use crate::models::FlagCollection;
4use crate::models::NoFlags;
5use alloc::borrow::Cow;
6use alloc::vec::Vec;
7use serde::{Deserialize, Serialize};
8use serde_with::skip_serializing_none;
9
10use super::{CommonFields, LedgerObject};
11
12#[skip_serializing_none]
19#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
20#[serde(rename_all = "PascalCase")]
21pub struct PermissionedDomain<'a> {
22 #[serde(flatten)]
27 pub common_fields: CommonFields<'a, NoFlags>,
28 pub owner: Cow<'a, str>,
30 pub accepted_credentials: Vec<Credential>,
32 pub sequence: u32,
35 pub owner_node: Cow<'a, str>,
38 #[serde(rename = "PreviousTxnID")]
41 pub previous_txn_id: Cow<'a, str>,
42 pub previous_txn_lgr_seq: u32,
45}
46
47impl<'a> LedgerObject<NoFlags> for PermissionedDomain<'a> {
48 fn get_ledger_entry_type(&self) -> LedgerEntryType {
49 self.common_fields.get_ledger_entry_type()
50 }
51}
52
53impl<'a> crate::models::Model for PermissionedDomain<'a> {
54 fn get_errors(&self) -> crate::models::XRPLModelResult<()> {
55 use crate::core::addresscodec::is_valid_classic_address;
56 use crate::models::exceptions::XRPLModelException;
57 use crate::models::transactions::permissioned_domain_set::validate_accepted_credentials;
58
59 if !is_valid_classic_address(&self.owner) {
60 return Err(XRPLModelException::InvalidValue {
61 field: "owner".into(),
62 expected: "valid classic XRPL address".into(),
63 found: self.owner.clone().into_owned(),
64 });
65 }
66 validate_accepted_credentials(&self.accepted_credentials)
69 }
70}
71
72impl<'a> PermissionedDomain<'a> {
73 pub fn new(
74 index: Option<Cow<'a, str>>,
75 ledger_index: Option<Cow<'a, str>>,
76 owner: Cow<'a, str>,
77 accepted_credentials: Vec<Credential>,
78 sequence: u32,
79 owner_node: Cow<'a, str>,
80 previous_txn_id: Cow<'a, str>,
81 previous_txn_lgr_seq: u32,
82 ) -> Self {
83 Self {
84 common_fields: CommonFields {
85 flags: FlagCollection::default(),
86 ledger_entry_type: LedgerEntryType::PermissionedDomain,
87 index,
88 ledger_index,
89 },
90 owner,
91 accepted_credentials,
92 sequence,
93 owner_node,
94 previous_txn_id,
95 previous_txn_lgr_seq,
96 }
97 }
98}
99
100#[cfg(test)]
101mod test_serde {
102 use super::*;
103 use alloc::borrow::Cow;
104 use alloc::string::ToString;
105 use alloc::vec;
106
107 const TEST_ACCOUNT: &str = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
109
110 #[test]
111 fn test_serialize() {
112 let domain = PermissionedDomain {
113 common_fields: CommonFields {
114 flags: FlagCollection::default(),
115 ledger_entry_type: LedgerEntryType::PermissionedDomain,
116 index: Some(Cow::from("ForTest")),
117 ledger_index: None,
118 },
119 owner: Cow::from(TEST_ACCOUNT),
120 accepted_credentials: vec![
121 Credential {
122 issuer: "rIssuerA".to_string(),
123 credential_type: "4B5943".to_string(), },
125 Credential {
126 issuer: "rIssuerB".to_string(),
127 credential_type: "414D4C".to_string(), },
129 ],
130 sequence: 1,
131 owner_node: Cow::from("0"),
132 previous_txn_id: Cow::from(
133 "A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2",
134 ),
135 previous_txn_lgr_seq: 1000,
136 };
137
138 let serialized = serde_json::to_string(&domain).unwrap();
139
140 assert!(serialized.contains("\"AcceptedCredentials\""));
142 assert!(serialized.contains("\"PreviousTxnID\""));
143 assert!(serialized.contains("\"PreviousTxnLgrSeq\""));
144 assert!(serialized.contains("\"Owner\""));
145 assert!(serialized.contains("\"OwnerNode\""));
146 assert!(serialized.contains("\"Sequence\""));
147 assert!(serialized.contains("\"LedgerEntryType\""));
148
149 let deserialized: PermissionedDomain = serde_json::from_str(&serialized).unwrap();
150 assert_eq!(domain, deserialized);
151 }
152
153 #[test]
154 fn test_ledger_entry_type() {
155 let domain = PermissionedDomain {
156 common_fields: CommonFields {
157 flags: FlagCollection::default(),
158 ledger_entry_type: LedgerEntryType::PermissionedDomain,
159 index: None,
160 ledger_index: None,
161 },
162 owner: Cow::from("rOwner"),
163 accepted_credentials: vec![Credential {
164 issuer: "rIssuer".to_string(),
165 credential_type: "4B5943".to_string(),
166 }],
167 sequence: 1,
168 owner_node: Cow::from("0"),
169 previous_txn_id: Cow::from(
170 "0000000000000000000000000000000000000000000000000000000000000000",
171 ),
172 previous_txn_lgr_seq: 1,
173 };
174
175 assert_eq!(
176 domain.get_ledger_entry_type(),
177 LedgerEntryType::PermissionedDomain
178 );
179 }
180
181 #[test]
182 fn test_fields() {
183 let domain = PermissionedDomain {
184 common_fields: CommonFields {
185 flags: FlagCollection::default(),
186 ledger_entry_type: LedgerEntryType::PermissionedDomain,
187 index: Some(Cow::from("TestIndex")),
188 ledger_index: Some(Cow::from("TestLedgerIndex")),
189 },
190 owner: Cow::from("rOwnerXYZ"),
191 accepted_credentials: vec![Credential {
192 issuer: "rIssuerXYZ".to_string(),
193 credential_type: "41434352454449544544".to_string(), }],
195 sequence: 42,
196 owner_node: Cow::from("7"),
197 previous_txn_id: Cow::from(
198 "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF",
199 ),
200 previous_txn_lgr_seq: 999,
201 };
202
203 assert_eq!(domain.owner, "rOwnerXYZ");
204 assert_eq!(domain.sequence, 42);
205 assert_eq!(domain.owner_node, Cow::from("7"));
206 assert_eq!(
207 domain.previous_txn_id,
208 "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF"
209 );
210 assert_eq!(domain.previous_txn_lgr_seq, 999);
211 assert_eq!(domain.accepted_credentials.len(), 1);
212 assert_eq!(domain.common_fields.index, Some(Cow::from("TestIndex")));
213 assert_eq!(
214 domain.common_fields.ledger_index,
215 Some(Cow::from("TestLedgerIndex"))
216 );
217 }
218
219 use crate::models::exceptions::XRPLModelException;
220 use crate::models::Model;
221
222 fn valid_domain(credentials: Vec<Credential>) -> PermissionedDomain<'static> {
224 PermissionedDomain {
225 common_fields: CommonFields {
226 flags: FlagCollection::default(),
227 ledger_entry_type: LedgerEntryType::PermissionedDomain,
228 index: None,
229 ledger_index: None,
230 },
231 owner: Cow::from(TEST_ACCOUNT),
232 accepted_credentials: credentials,
233 sequence: 1,
234 owner_node: Cow::from("0"),
235 previous_txn_id: Cow::from(
236 "A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2",
237 ),
238 previous_txn_lgr_seq: 1000,
239 }
240 }
241
242 fn kyc() -> Credential {
243 Credential {
244 issuer: TEST_ACCOUNT.to_string(),
245 credential_type: "4B5943".to_string(),
246 }
247 }
248
249 #[test]
250 fn test_get_errors_valid() {
251 assert!(valid_domain(vec![kyc()]).get_errors().is_ok());
252 }
253
254 #[test]
255 fn test_get_errors_invalid_owner_rejected() {
256 let mut domain = valid_domain(vec![kyc()]);
257 domain.owner = Cow::from("not-an-address");
258 assert!(matches!(
259 domain.get_errors(),
260 Err(XRPLModelException::InvalidValue { .. })
261 ));
262 }
263
264 #[test]
265 fn test_get_errors_empty_credentials_rejected() {
266 assert!(matches!(
267 valid_domain(vec![]).get_errors(),
268 Err(XRPLModelException::MissingField(_))
269 ));
270 }
271
272 #[test]
273 fn test_get_errors_duplicate_credentials_rejected() {
274 let dup_lower = Credential {
276 issuer: TEST_ACCOUNT.to_string(),
277 credential_type: "4b5943".to_string(),
278 };
279 assert!(matches!(
280 valid_domain(vec![kyc(), dup_lower]).get_errors(),
281 Err(XRPLModelException::InvalidValue { .. })
282 ));
283 }
284
285 #[test]
286 fn test_get_errors_too_many_credentials_rejected() {
287 let creds: Vec<Credential> = (0..11)
288 .map(|i| Credential {
289 issuer: TEST_ACCOUNT.to_string(),
290 credential_type: alloc::format!("{:06X}", i),
291 })
292 .collect();
293 assert!(matches!(
294 valid_domain(creds).get_errors(),
295 Err(XRPLModelException::ValueTooLong { .. })
296 ));
297 }
298}