use alloc::borrow::Cow;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "PascalCase")]
pub struct TransactionMetadata<'a> {
#[serde(rename = "TransactionIndex")]
pub transaction_index: u64,
#[serde(rename = "TransactionResult")]
pub transaction_result: Cow<'a, str>,
#[serde(rename = "AffectedNodes")]
pub affected_nodes: Cow<'a, [AffectedNode<'a>]>,
pub delivered_amount: Option<Value>,
#[serde(rename = "nftoken_id")]
pub nftoken_id: Option<Cow<'a, str>>,
#[serde(rename = "nftoken_ids")]
pub nftoken_ids: Option<Cow<'a, [Cow<'a, str>]>>,
#[serde(rename = "offer_id")]
pub offer_id: Option<Cow<'a, str>>,
#[serde(rename = "mpt_issuance_id")]
pub mpt_issuance_id: Option<Cow<'a, str>>,
}
#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "PascalCase")]
pub struct AffectedNode<'a> {
#[serde(rename = "CreatedNode")]
pub created_node: Option<LedgerNode<'a>>,
#[serde(rename = "ModifiedNode")]
pub modified_node: Option<LedgerNode<'a>>,
#[serde(rename = "DeletedNode")]
pub deleted_node: Option<LedgerNode<'a>>,
}
#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "PascalCase")]
pub struct LedgerNode<'a> {
pub ledger_entry_type: Cow<'a, str>,
pub ledger_index: Cow<'a, str>,
pub final_fields: Option<Value>,
pub previous_fields: Option<Value>,
pub new_fields: Option<Value>,
pub previous_txn_id: Option<Cow<'a, str>>,
#[serde(rename = "PreviousTxnLgrSeq")]
pub previous_txn_lgr_seq: Option<u32>,
pub book_node: Option<Cow<'a, str>>,
pub owner_node: Option<Cow<'a, str>>,
pub exchange_rate: Option<Cow<'a, str>>,
pub root_index: Option<Cow<'a, str>>,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_transaction_metadata_deserialize() {
let json = r#"{
"AffectedNodes": [
{
"ModifiedNode": {
"FinalFields": {
"Account": "rBTwLga3i2gz3doX6Gva3MgEV8ZCD8jjah",
"Balance": "27724423128",
"Flags": 0,
"OwnerCount": 14,
"Sequence": 129693478
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "1ED8DDFD80F275CB1CE7F18BB9D906655DE8029805D8B95FB9020B30425821EB",
"PreviousFields": {
"Balance": "27719423228",
"Sequence": 129693477
},
"PreviousTxnID": "3110F983CDC090750B45C9BFB74B8CE629CA80F57C35612402B2760153822BA5",
"PreviousTxnLgrSeq": 86724072
}
},
{
"DeletedNode": {
"FinalFields": {
"Account": "rPx6Rbh8fStXeP3LwECBisownN2ZyMyzYS",
"BookDirectory": "DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4E1566CBCC208000",
"BookNode": "0",
"Flags": 0,
"OwnerNode": "0",
"PreviousTxnID": "DCB061EC44BBF73BBC20CE0432E9D8D7C4B8B28ABA8AE5A5BA687476E7A796EF",
"PreviousTxnLgrSeq": 86724050,
"Sequence": 86586865,
"TakerGets": "0",
"TakerPays": {
"currency": "USD",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"value": "0"
}
},
"LedgerEntryType": "Offer",
"LedgerIndex": "348AF66EBD872FBF2BD23085D3FB4A200E15509451475027C4A5EE8D8B77C623"
}
}
],
"TransactionIndex": 5,
"TransactionResult": "tesSUCCESS"
}"#;
let metadata: TransactionMetadata = serde_json::from_str(json).unwrap();
assert_eq!(metadata.transaction_index, 5);
assert_eq!(metadata.transaction_result, "tesSUCCESS");
assert_eq!(metadata.affected_nodes.len(), 2);
let first_node = &metadata.affected_nodes[0];
if let Some(modified) = &first_node.modified_node {
assert_eq!(modified.ledger_entry_type, "AccountRoot");
assert_eq!(
modified.ledger_index,
"1ED8DDFD80F275CB1CE7F18BB9D906655DE8029805D8B95FB9020B30425821EB"
);
assert_eq!(modified.previous_txn_lgr_seq, Some(86724072));
} else {
panic!("Expected ModifiedNode");
}
let second_node = &metadata.affected_nodes[1];
if let Some(deleted) = &second_node.deleted_node {
assert_eq!(deleted.ledger_entry_type, "Offer");
assert_eq!(
deleted.ledger_index,
"348AF66EBD872FBF2BD23085D3FB4A200E15509451475027C4A5EE8D8B77C623"
);
} else {
panic!("Expected DeletedNode");
}
}
#[test]
fn test_affected_node_variants() {
let created_json = json!({
"CreatedNode": {
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "ABCD",
"NewFields": {
"Account": "rXXX",
"Balance": "1000000"
}
}
});
let modified_json = json!({
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "DEFG",
"FinalFields": {
"Account": "rYYY",
"Balance": "2000000"
},
"PreviousFields": {
"Balance": "1000000"
}
}
});
let deleted_json = json!({
"DeletedNode": {
"LedgerEntryType": "Offer",
"LedgerIndex": "HIJK",
"FinalFields": {
"Account": "rZZZ"
}
}
});
let created: AffectedNode = serde_json::from_value(created_json).unwrap();
let modified: AffectedNode = serde_json::from_value(modified_json).unwrap();
let deleted: AffectedNode = serde_json::from_value(deleted_json).unwrap();
assert!(created.created_node.is_some());
assert!(created.modified_node.is_none());
assert!(created.deleted_node.is_none());
assert!(modified.created_node.is_none());
assert!(modified.modified_node.is_some());
assert!(modified.deleted_node.is_none());
assert!(deleted.created_node.is_none());
assert!(deleted.modified_node.is_none());
assert!(deleted.deleted_node.is_some());
}
}