xrpl/models/transactions/
credential_accept.rs1use alloc::borrow::Cow;
2use alloc::vec::Vec;
3use serde::{Deserialize, Serialize};
4use serde_with::skip_serializing_none;
5
6use crate::models::amount::XRPAmount;
7use crate::models::transactions::CommonFields;
8use crate::models::{
9 transactions::{Memo, Signer, Transaction, TransactionType},
10 Model, XRPLModelResult,
11};
12use crate::models::{FlagCollection, NoFlags};
13
14use super::CommonTransactionBuilder;
15
16#[skip_serializing_none]
21#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone)]
22#[serde(rename_all = "PascalCase")]
23pub struct CredentialAccept<'a> {
24 #[serde(flatten)]
26 pub common_fields: CommonFields<'a, NoFlags>,
27 pub issuer: Cow<'a, str>,
29 pub credential_type: Cow<'a, str>,
31}
32
33impl<'a> Model for CredentialAccept<'a> {
34 fn get_errors(&self) -> XRPLModelResult<()> {
35 self._get_credential_type_error()
36 }
37}
38
39impl<'a> Transaction<'a, NoFlags> for CredentialAccept<'a> {
40 fn get_transaction_type(&self) -> &TransactionType {
41 self.common_fields.get_transaction_type()
42 }
43
44 fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> {
45 self.common_fields.get_common_fields()
46 }
47
48 fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> {
49 self.common_fields.get_mut_common_fields()
50 }
51}
52
53impl<'a> CommonTransactionBuilder<'a, NoFlags> for CredentialAccept<'a> {
54 fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> {
55 &mut self.common_fields
56 }
57
58 fn into_self(self) -> Self {
59 self
60 }
61}
62
63impl<'a> CredentialAccept<'a> {
64 #[allow(clippy::too_many_arguments)]
65 pub fn new(
66 account: Cow<'a, str>,
67 account_txn_id: Option<Cow<'a, str>>,
68 fee: Option<XRPAmount<'a>>,
69 last_ledger_sequence: Option<u32>,
70 memos: Option<Vec<Memo>>,
71 sequence: Option<u32>,
72 signers: Option<Vec<Signer>>,
73 source_tag: Option<u32>,
74 ticket_sequence: Option<u32>,
75 issuer: Cow<'a, str>,
76 credential_type: Cow<'a, str>,
77 ) -> Self {
78 Self {
79 common_fields: CommonFields::new(
80 account,
81 TransactionType::CredentialAccept,
82 account_txn_id,
83 fee,
84 Some(FlagCollection::default()),
85 last_ledger_sequence,
86 memos,
87 None,
88 sequence,
89 signers,
90 None,
91 source_tag,
92 ticket_sequence,
93 None,
94 ),
95 issuer,
96 credential_type,
97 }
98 }
99}
100
101impl<'a> CredentialAcceptError for CredentialAccept<'a> {
102 fn _get_credential_type_error(&self) -> XRPLModelResult<()> {
103 super::validate_credential_type(&self.credential_type)
104 }
105}
106
107pub trait CredentialAcceptError {
108 fn _get_credential_type_error(&self) -> XRPLModelResult<()>;
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use crate::models::{Model, XRPLModelException};
115 use alloc::borrow::Cow;
116 use alloc::format;
117 use proptest::prelude::*;
118
119 #[test]
120 fn test_serde() {
121 let default_txn = CredentialAccept {
122 common_fields: CommonFields {
123 account: "rSubject11111111111111111111111111".into(),
124 transaction_type: TransactionType::CredentialAccept,
125 fee: Some("10".into()),
126 sequence: Some(8),
127 signing_pub_key: Some("".into()),
128 ..Default::default()
129 },
130 issuer: "rIssuer111111111111111111111111111".into(),
131 credential_type: "4B5943".into(),
132 };
133
134 let default_json_str = r#"{"Account":"rSubject11111111111111111111111111","TransactionType":"CredentialAccept","Fee":"10","Flags":0,"Sequence":8,"SigningPubKey":"","Issuer":"rIssuer111111111111111111111111111","CredentialType":"4B5943"}"#;
135
136 let default_json_value = serde_json::to_value(default_json_str).unwrap();
137 let serialized_string = serde_json::to_string(&default_txn).unwrap();
138 let serialized_value = serde_json::to_value(&serialized_string).unwrap();
139 assert_eq!(serialized_value, default_json_value);
140
141 let deserialized: CredentialAccept = serde_json::from_str(default_json_str).unwrap();
142 assert_eq!(default_txn, deserialized);
143 }
144
145 #[test]
146 fn test_credential_type_empty_error() {
147 let tx = CredentialAccept {
148 common_fields: CommonFields {
149 account: "rSubject11111111111111111111111111".into(),
150 transaction_type: TransactionType::CredentialAccept,
151 ..Default::default()
152 },
153 issuer: "rIssuer111111111111111111111111111".into(),
154 credential_type: Cow::from(""),
155 };
156 assert_eq!(
157 tx.get_errors().unwrap_err(),
158 XRPLModelException::ValueTooShort {
159 field: "credential_type".into(),
160 min: 1,
161 found: 0,
162 }
163 );
164 }
165
166 #[test]
167 fn test_credential_type_too_long_error() {
168 let too_long: Cow<'_, str> = Cow::from("A".repeat(129));
170 let tx = CredentialAccept {
171 common_fields: CommonFields {
172 account: "rSubject11111111111111111111111111".into(),
173 transaction_type: TransactionType::CredentialAccept,
174 ..Default::default()
175 },
176 issuer: "rIssuer111111111111111111111111111".into(),
177 credential_type: too_long,
178 };
179 assert_eq!(
180 tx.get_errors().unwrap_err(),
181 XRPLModelException::ValueTooLong {
182 field: "credential_type".into(),
183 max: 128,
184 found: 129,
185 }
186 );
187 }
188
189 #[test]
190 fn test_credential_type_non_hex_error() {
191 let tx = CredentialAccept {
192 common_fields: CommonFields {
193 account: "rSubject11111111111111111111111111".into(),
194 transaction_type: TransactionType::CredentialAccept,
195 ..Default::default()
196 },
197 issuer: "rIssuer111111111111111111111111111".into(),
198 credential_type: "NOTHEX".into(),
199 };
200 assert_eq!(
201 tx.get_errors().unwrap_err(),
202 XRPLModelException::InvalidValueFormat {
203 field: "credential_type".into(),
204 format: "hexadecimal".into(),
205 found: "NOTHEX".into(),
206 }
207 );
208 }
209
210 #[test]
212 fn test_credential_type_at_max_128_ok() {
213 let max_hex: Cow<'_, str> = Cow::from("A".repeat(128));
214 let tx = CredentialAccept {
215 common_fields: CommonFields {
216 account: "rSubject11111111111111111111111111".into(),
217 transaction_type: TransactionType::CredentialAccept,
218 ..Default::default()
219 },
220 issuer: "rIssuer111111111111111111111111111".into(),
221 credential_type: max_hex,
222 };
223 assert!(tx.get_errors().is_ok());
224 }
225
226 #[test]
228 fn test_valid_minimal_accept() {
229 let tx = CredentialAccept {
230 common_fields: CommonFields {
231 account: "rSubject11111111111111111111111111".into(),
232 transaction_type: TransactionType::CredentialAccept,
233 ..Default::default()
234 },
235 issuer: "rIssuer111111111111111111111111111".into(),
236 credential_type: "4B5943".into(),
237 };
238 assert!(tx.get_errors().is_ok());
239 }
240
241 proptest! {
242 #![proptest_config(ProptestConfig::with_cases(200))]
243
244 #[test]
245 fn prop_credential_type_valid_length(len in 1_usize..=64) {
246 let ct = "AB".repeat(len); let tx = CredentialAccept {
248 common_fields: CommonFields {
249 account: "rSubject11111111111111111111111111".into(),
250 transaction_type: TransactionType::CredentialAccept,
251 ..Default::default()
252 },
253 issuer: "rIssuer111111111111111111111111111".into(),
254 credential_type: Cow::Owned(ct),
255 };
256 prop_assert!(tx.get_errors().is_ok(), "len {} should be valid", len);
257 }
258
259 #[test]
260 fn prop_credential_type_too_long(extra in 1_usize..=100) {
261 let len = 64 + extra; let ct = "AB".repeat(len);
263 let tx = CredentialAccept {
264 common_fields: CommonFields {
265 account: "rSubject11111111111111111111111111".into(),
266 transaction_type: TransactionType::CredentialAccept,
267 ..Default::default()
268 },
269 issuer: "rIssuer111111111111111111111111111".into(),
270 credential_type: Cow::Owned(ct),
271 };
272 prop_assert!(tx.get_errors().is_err(), "len {} should be rejected", len);
273 }
274
275 #[test]
276 fn prop_serde_roundtrip(ct in "[0-9A-F]{2,128}") {
277 let tx = CredentialAccept {
278 common_fields: CommonFields {
279 account: "rSubject11111111111111111111111111".into(),
280 transaction_type: TransactionType::CredentialAccept,
281 fee: Some("10".into()),
282 sequence: Some(1),
283 signing_pub_key: Some(Cow::Borrowed("")),
284 ..Default::default()
285 },
286 issuer: "rIssuer111111111111111111111111111".into(),
287 credential_type: Cow::Owned(ct),
288 };
289 let json = serde_json::to_string(&tx)
290 .map_err(|e| TestCaseError::fail(format!("serialize: {e}")))?;
291 let rt: CredentialAccept = serde_json::from_str(&json)
292 .map_err(|e| TestCaseError::fail(format!("deserialize: {e}")))?;
293 prop_assert_eq!(&tx, &rt);
294 }
295 }
296}