use crate::models::transactions::validate_credential_type;
use crate::models::CredentialAuthorization;
use crate::models::FlagCollection;
use crate::models::Model;
use crate::models::{ledger::objects::LedgerEntryType, NoFlags};
use crate::models::{XRPLModelException, XRPLModelResult};
use alloc::borrow::Cow;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use super::{CommonFields, LedgerObject};
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct DepositPreauth<'a> {
#[serde(flatten)]
pub common_fields: CommonFields<'a, NoFlags>,
pub account: Cow<'a, str>,
pub authorize: Option<Cow<'a, str>>,
pub authorize_credentials: Option<Vec<CredentialAuthorization<'a>>>,
pub owner_node: Cow<'a, str>,
#[serde(rename = "PreviousTxnID")]
pub previous_txn_id: Cow<'a, str>,
pub previous_txn_lgr_seq: u32,
}
impl<'a> Model for DepositPreauth<'a> {
fn get_errors(&self) -> XRPLModelResult<()> {
self._get_authorization_error()
}
}
impl<'a> LedgerObject<NoFlags> for DepositPreauth<'a> {
fn get_ledger_entry_type(&self) -> LedgerEntryType {
self.common_fields.get_ledger_entry_type()
}
}
impl<'a> DepositPreauth<'a> {
pub fn new(
index: Option<Cow<'a, str>>,
ledger_index: Option<Cow<'a, str>>,
account: Cow<'a, str>,
authorize: Cow<'a, str>,
owner_node: Cow<'a, str>,
previous_txn_id: Cow<'a, str>,
previous_txn_lgr_seq: u32,
) -> Self {
Self {
common_fields: CommonFields {
flags: FlagCollection::default(),
ledger_entry_type: LedgerEntryType::DepositPreauth,
index,
ledger_index,
},
account,
authorize: Some(authorize),
authorize_credentials: None,
owner_node,
previous_txn_id,
previous_txn_lgr_seq,
}
}
pub fn new_with_authorize_credentials(
index: Option<Cow<'a, str>>,
ledger_index: Option<Cow<'a, str>>,
account: Cow<'a, str>,
authorize_credentials: Vec<CredentialAuthorization<'a>>,
owner_node: Cow<'a, str>,
previous_txn_id: Cow<'a, str>,
previous_txn_lgr_seq: u32,
) -> Self {
Self {
common_fields: CommonFields {
flags: FlagCollection::default(),
ledger_entry_type: LedgerEntryType::DepositPreauth,
index,
ledger_index,
},
account,
authorize: None,
authorize_credentials: Some(authorize_credentials),
owner_node,
previous_txn_id,
previous_txn_lgr_seq,
}
}
}
impl<'a> DepositPreauthError for DepositPreauth<'a> {
fn _get_authorization_error(&self) -> XRPLModelResult<()> {
let count = [
self.authorize.is_some(),
self.authorize_credentials.is_some(),
]
.iter()
.filter(|x| **x)
.count();
if count != 1 {
return Err(XRPLModelException::InvalidFieldCombination {
field: "authorize",
other_fields: &["authorize_credentials"],
});
}
if let Some(creds) = &self.authorize_credentials {
if creds.is_empty() {
return Err(XRPLModelException::ValueTooShort {
field: "authorize_credentials".into(),
min: 1,
found: 0,
});
}
if creds.len() > 8 {
return Err(XRPLModelException::ValueTooLong {
field: "authorize_credentials".into(),
max: 8,
found: creds.len(),
});
}
for (i, cred) in creds.iter().enumerate() {
validate_credential_type(&cred.credential.credential_type)?;
if creds[..i].iter().any(|prev| {
prev.credential.issuer == cred.credential.issuer
&& prev
.credential
.credential_type
.eq_ignore_ascii_case(&cred.credential.credential_type)
}) {
return Err(XRPLModelException::ValueEqualsValue {
field1: "authorize_credentials".into(),
field2: "authorize_credentials (duplicate entry)".into(),
});
}
}
}
Ok(())
}
}
pub trait DepositPreauthError {
fn _get_authorization_error(&self) -> XRPLModelResult<()>;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::CredentialAuthorizationFields;
use crate::models::{Model, XRPLModelException};
use alloc::vec;
#[test]
fn test_serde() {
let deposit_preauth = DepositPreauth::new(
Some(Cow::from(
"4A255038CC3ADCC1A9C91509279B59908251728D0DAADB248FFE297D0F7E068C",
)),
None,
Cow::from("rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8"),
Cow::from("rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de"),
Cow::from("0000000000000000"),
Cow::from("3E8964D5A86B3CD6B9ECB33310D4E073D64C865A5B866200AD2B7E29F8326702"),
7,
);
let serialized = serde_json::to_string(&deposit_preauth).unwrap();
let deserialized: DepositPreauth = serde_json::from_str(&serialized).unwrap();
assert_eq!(deposit_preauth, deserialized);
}
#[test]
fn test_serde_with_authorize_credentials() {
let deposit_preauth = DepositPreauth::new_with_authorize_credentials(
None,
None,
Cow::from("rOwner1111111111111111111111111111"),
vec![CredentialAuthorization::new(
CredentialAuthorizationFields::new(
Cow::from("rIssuer111111111111111111111111111"),
Cow::from("4B5943"),
),
)],
Cow::from("0000000000000001"),
Cow::from("3E8964D5A86B3CD6B9ECB33310D4E073D64C865A5B866200AD2B7E29F8326702"),
8,
);
let serialized = serde_json::to_string(&deposit_preauth).unwrap();
let deserialized: DepositPreauth = serde_json::from_str(&serialized).unwrap();
assert_eq!(deposit_preauth, deserialized);
}
#[test]
fn test_invalid_empty_authorize_credentials() {
let deposit_preauth = DepositPreauth {
common_fields: CommonFields {
flags: FlagCollection::default(),
ledger_entry_type: LedgerEntryType::DepositPreauth,
index: None,
ledger_index: None,
},
account: Cow::from("rOwner1111111111111111111111111111"),
authorize: None,
authorize_credentials: Some(vec![]),
owner_node: Cow::from("0000000000000001"),
previous_txn_id: Cow::from(
"3E8964D5A86B3CD6B9ECB33310D4E073D64C865A5B866200AD2B7E29F8326702",
),
previous_txn_lgr_seq: 8,
};
assert_eq!(
deposit_preauth.get_errors().unwrap_err(),
XRPLModelException::ValueTooShort {
field: "authorize_credentials".into(),
min: 1,
found: 0,
}
);
}
#[test]
fn test_invalid_oversized_authorize_credentials() {
let creds: Vec<CredentialAuthorization<'_>> = (0..9)
.map(|i| {
CredentialAuthorization::new(CredentialAuthorizationFields::new(
Cow::from(alloc::format!("rIssuer{i}1111111111111111111111111")),
Cow::from("4B5943"),
))
})
.collect();
let deposit_preauth = DepositPreauth {
common_fields: CommonFields {
flags: FlagCollection::default(),
ledger_entry_type: LedgerEntryType::DepositPreauth,
index: None,
ledger_index: None,
},
account: Cow::from("rOwner1111111111111111111111111111"),
authorize: None,
authorize_credentials: Some(creds),
owner_node: Cow::from("0000000000000001"),
previous_txn_id: Cow::from(
"3E8964D5A86B3CD6B9ECB33310D4E073D64C865A5B866200AD2B7E29F8326702",
),
previous_txn_lgr_seq: 8,
};
assert_eq!(
deposit_preauth.get_errors().unwrap_err(),
XRPLModelException::ValueTooLong {
field: "authorize_credentials".into(),
max: 8,
found: 9,
}
);
}
#[test]
fn test_invalid_without_authorization_fields() {
let deposit_preauth = DepositPreauth {
common_fields: CommonFields {
flags: FlagCollection::default(),
ledger_entry_type: LedgerEntryType::DepositPreauth,
index: None,
ledger_index: None,
},
account: Cow::from("rOwner1111111111111111111111111111"),
authorize: None,
authorize_credentials: None,
owner_node: Cow::from("0000000000000001"),
previous_txn_id: Cow::from(
"3E8964D5A86B3CD6B9ECB33310D4E073D64C865A5B866200AD2B7E29F8326702",
),
previous_txn_lgr_seq: 8,
};
assert_eq!(
deposit_preauth.get_errors().unwrap_err(),
XRPLModelException::InvalidFieldCombination {
field: "authorize",
other_fields: &["authorize_credentials"],
}
);
}
#[test]
fn test_invalid_both_authorize_and_authorize_credentials() {
let deposit_preauth = DepositPreauth {
common_fields: CommonFields {
flags: FlagCollection::default(),
ledger_entry_type: LedgerEntryType::DepositPreauth,
index: None,
ledger_index: None,
},
account: Cow::from("rOwner1111111111111111111111111111"),
authorize: Some(Cow::from("rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de")),
authorize_credentials: Some(vec![CredentialAuthorization::new(
CredentialAuthorizationFields::new(
Cow::from("rIssuer111111111111111111111111111"),
Cow::from("4B5943"),
),
)]),
owner_node: Cow::from("0000000000000001"),
previous_txn_id: Cow::from(
"3E8964D5A86B3CD6B9ECB33310D4E073D64C865A5B866200AD2B7E29F8326702",
),
previous_txn_lgr_seq: 8,
};
assert_eq!(
deposit_preauth.get_errors().unwrap_err(),
XRPLModelException::InvalidFieldCombination {
field: "authorize",
other_fields: &["authorize_credentials"],
}
);
}
#[test]
fn test_authorize_credentials_exactly_eight_ok() {
let creds: Vec<CredentialAuthorization<'_>> = (0..8)
.map(|i| {
CredentialAuthorization::new(CredentialAuthorizationFields::new(
Cow::from(alloc::format!("rIssuer{i}1111111111111111111111111")),
Cow::from("4B5943"),
))
})
.collect();
let deposit_preauth = DepositPreauth {
common_fields: CommonFields {
flags: FlagCollection::default(),
ledger_entry_type: LedgerEntryType::DepositPreauth,
index: None,
ledger_index: None,
},
account: Cow::from("rOwner1111111111111111111111111111"),
authorize: None,
authorize_credentials: Some(creds),
owner_node: Cow::from("0000000000000001"),
previous_txn_id: Cow::from(
"3E8964D5A86B3CD6B9ECB33310D4E073D64C865A5B866200AD2B7E29F8326702",
),
previous_txn_lgr_seq: 8,
};
assert!(deposit_preauth.get_errors().is_ok());
}
#[test]
fn test_authorize_credentials_non_hex_credential_type_error() {
let deposit_preauth = DepositPreauth {
common_fields: CommonFields {
flags: FlagCollection::default(),
ledger_entry_type: LedgerEntryType::DepositPreauth,
index: None,
ledger_index: None,
},
account: Cow::from("rOwner1111111111111111111111111111"),
authorize: None,
authorize_credentials: Some(vec![CredentialAuthorization::new(
CredentialAuthorizationFields::new(
Cow::from("rIssuer111111111111111111111111111"),
Cow::from("NOTHEX"),
),
)]),
owner_node: Cow::from("0000000000000001"),
previous_txn_id: Cow::from(
"3E8964D5A86B3CD6B9ECB33310D4E073D64C865A5B866200AD2B7E29F8326702",
),
previous_txn_lgr_seq: 8,
};
assert_eq!(
deposit_preauth.get_errors().unwrap_err(),
XRPLModelException::InvalidValueFormat {
field: "credential_type".into(),
format: "hexadecimal".into(),
found: "NOTHEX".into(),
}
);
}
#[test]
fn test_authorize_credentials_exactly_one_ok() {
let deposit_preauth = DepositPreauth {
common_fields: CommonFields {
flags: FlagCollection::default(),
ledger_entry_type: LedgerEntryType::DepositPreauth,
index: None,
ledger_index: None,
},
account: Cow::from("rOwner1111111111111111111111111111"),
authorize: None,
authorize_credentials: Some(vec![CredentialAuthorization::new(
CredentialAuthorizationFields::new(
Cow::from("rIssuer111111111111111111111111111"),
Cow::from("4B5943"),
),
)]),
owner_node: Cow::from("0000000000000001"),
previous_txn_id: Cow::from(
"3E8964D5A86B3CD6B9ECB33310D4E073D64C865A5B866200AD2B7E29F8326702",
),
previous_txn_lgr_seq: 8,
};
assert!(deposit_preauth.get_errors().is_ok());
}
#[test]
fn test_authorize_credentials_nine_entries_error() {
let creds: Vec<CredentialAuthorization<'_>> = (0..9)
.map(|i| {
CredentialAuthorization::new(CredentialAuthorizationFields::new(
Cow::from(alloc::format!("rIssuer{i}1111111111111111111111111")),
Cow::from("4B5943"),
))
})
.collect();
let deposit_preauth = DepositPreauth {
common_fields: CommonFields {
flags: FlagCollection::default(),
ledger_entry_type: LedgerEntryType::DepositPreauth,
index: None,
ledger_index: None,
},
account: Cow::from("rOwner1111111111111111111111111111"),
authorize: None,
authorize_credentials: Some(creds),
owner_node: Cow::from("0000000000000001"),
previous_txn_id: Cow::from(
"3E8964D5A86B3CD6B9ECB33310D4E073D64C865A5B866200AD2B7E29F8326702",
),
previous_txn_lgr_seq: 8,
};
assert_eq!(
deposit_preauth.get_errors().unwrap_err(),
XRPLModelException::ValueTooLong {
field: "authorize_credentials".into(),
max: 8,
found: 9,
}
);
}
}