use crate::models::ledger::objects::LedgerEntryType;
use crate::models::FlagCollection;
use crate::models::Model;
use alloc::borrow::Cow;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use serde_with::skip_serializing_none;
use strum_macros::{AsRefStr, Display, EnumIter};
use super::{CommonFields, LedgerObject};
#[derive(
Debug, Eq, PartialEq, Clone, Serialize_repr, Deserialize_repr, Display, AsRefStr, EnumIter,
)]
#[repr(u32)]
pub enum CredentialFlag {
LsfAccepted = 0x00010000,
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct Credential<'a> {
#[serde(flatten)]
pub common_fields: CommonFields<'a, CredentialFlag>,
pub subject: Cow<'a, str>,
pub issuer: Cow<'a, str>,
pub credential_type: Cow<'a, str>,
pub expiration: Option<u32>,
#[serde(rename = "URI")]
pub uri: Option<Cow<'a, str>>,
pub subject_node: Option<Cow<'a, str>>,
pub issuer_node: Cow<'a, str>,
#[serde(rename = "PreviousTxnID")]
pub previous_txn_id: Cow<'a, str>,
pub previous_txn_lgr_seq: u32,
}
impl<'a> Model for Credential<'a> {}
impl<'a> LedgerObject<CredentialFlag> for Credential<'a> {
fn get_ledger_entry_type(&self) -> LedgerEntryType {
self.common_fields.get_ledger_entry_type()
}
}
impl<'a> Credential<'a> {
#[allow(clippy::too_many_arguments)]
pub fn new(
index: Option<Cow<'a, str>>,
ledger_index: Option<Cow<'a, str>>,
subject: Cow<'a, str>,
issuer: Cow<'a, str>,
credential_type: Cow<'a, str>,
expiration: Option<u32>,
uri: Option<Cow<'a, str>>,
subject_node: Option<Cow<'a, str>>,
issuer_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::Credential,
index,
ledger_index,
},
subject,
issuer,
credential_type,
expiration,
uri,
subject_node,
issuer_node,
previous_txn_id,
previous_txn_lgr_seq,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_serde() {
let credential = Credential::new(
Some(Cow::from(
"DD40031C6C21164E7673A47C35513D52A6B0F1349A873EE0D188D8994CD4D001",
)),
None,
Cow::from("rALICE1111111111111111111111111111"),
Cow::from("rISABEL111111111111111111111111111"),
Cow::from("4B5943"),
Some(789004799),
Some(Cow::from(
"69736162656C2E636F6D2F63726564656E7469616C732F6B79632F616C696365",
)),
Some(Cow::from("0000000000000000")),
Cow::from("0000000000000000"),
Cow::from("3E8964D5A86B3CD6B9ECB33310D4E073D64C865A5B866200AD2B7E29F8326702"),
8,
);
let serialized = serde_json::to_string(&credential).unwrap();
let deserialized: Credential = serde_json::from_str(&serialized).unwrap();
assert_eq!(credential, deserialized);
}
#[test]
fn test_serde_round_trip_all_fields() {
let credential = Credential {
common_fields: CommonFields {
flags: FlagCollection::default(),
ledger_entry_type: LedgerEntryType::Credential,
index: Some(Cow::from(
"DD40031C6C21164E7673A47C35513D52A6B0F1349A873EE0D188D8994CD4D001",
)),
ledger_index: Some(Cow::from("42")),
},
subject: Cow::from("rALICE1111111111111111111111111111"),
issuer: Cow::from("rISABEL111111111111111111111111111"),
credential_type: Cow::from("4B5943"),
expiration: Some(789004799),
uri: Some(Cow::from(
"69736162656C2E636F6D2F63726564656E7469616C732F6B79632F616C696365",
)),
subject_node: Some(Cow::from("0000000000000000")),
issuer_node: Cow::from("0000000000000001"),
previous_txn_id: Cow::from(
"3E8964D5A86B3CD6B9ECB33310D4E073D64C865A5B866200AD2B7E29F8326702",
),
previous_txn_lgr_seq: 8,
};
let json = serde_json::to_string(&credential).unwrap();
let deserialized: Credential = serde_json::from_str(&json).unwrap();
assert_eq!(credential, deserialized);
}
#[test]
fn test_serde_round_trip_optional_fields_omitted() {
let credential = Credential {
common_fields: CommonFields {
flags: FlagCollection::default(),
ledger_entry_type: LedgerEntryType::Credential,
index: Some(Cow::from(
"DD40031C6C21164E7673A47C35513D52A6B0F1349A873EE0D188D8994CD4D001",
)),
ledger_index: None,
},
subject: Cow::from("rALICE1111111111111111111111111111"),
issuer: Cow::from("rISABEL111111111111111111111111111"),
credential_type: Cow::from("4B5943"),
expiration: None,
uri: None,
subject_node: Some(Cow::from("0000000000000000")),
issuer_node: Cow::from("0000000000000000"),
previous_txn_id: Cow::from(
"3E8964D5A86B3CD6B9ECB33310D4E073D64C865A5B866200AD2B7E29F8326702",
),
previous_txn_lgr_seq: 5,
};
let json = serde_json::to_string(&credential).unwrap();
assert!(!json.contains("Expiration"));
assert!(!json.contains("URI"));
let deserialized: Credential = serde_json::from_str(&json).unwrap();
assert_eq!(credential, deserialized);
assert!(deserialized.expiration.is_none());
assert!(deserialized.uri.is_none());
}
#[test]
fn test_self_issued_credential_allows_missing_subject_node() {
let json = r#"{
"LedgerEntryType":"Credential",
"Flags":65536,
"Subject":"rSELF11111111111111111111111111111",
"Issuer":"rSELF11111111111111111111111111111",
"CredentialType":"4B5943",
"IssuerNode":"0000000000000000",
"PreviousTxnID":"3E8964D5A86B3CD6B9ECB33310D4E073D64C865A5B866200AD2B7E29F8326702",
"PreviousTxnLgrSeq":10
}"#;
let credential: Credential = serde_json::from_str(json).unwrap();
assert!(credential.subject_node.is_none());
assert_eq!(credential.subject, credential.issuer);
}
#[test]
fn test_lsf_accepted_flag_value() {
assert_eq!(CredentialFlag::LsfAccepted as u32, 0x00010000);
}
#[test]
fn test_serde_with_accepted_flag() {
let mut flags = FlagCollection::default();
flags.0.push(CredentialFlag::LsfAccepted);
let credential = Credential {
common_fields: CommonFields {
flags,
ledger_entry_type: LedgerEntryType::Credential,
index: Some(Cow::from(
"DD40031C6C21164E7673A47C35513D52A6B0F1349A873EE0D188D8994CD4D001",
)),
ledger_index: None,
},
subject: Cow::from("rALICE1111111111111111111111111111"),
issuer: Cow::from("rISABEL111111111111111111111111111"),
credential_type: Cow::from("4B5943"),
expiration: None,
uri: None,
subject_node: Some(Cow::from("0000000000000000")),
issuer_node: Cow::from("0000000000000000"),
previous_txn_id: Cow::from(
"3E8964D5A86B3CD6B9ECB33310D4E073D64C865A5B866200AD2B7E29F8326702",
),
previous_txn_lgr_seq: 10,
};
let json = serde_json::to_string(&credential).unwrap();
let deserialized: Credential = serde_json::from_str(&json).unwrap();
assert_eq!(credential, deserialized);
}
}