diff --git a/src/models/results/account_tx.rs b/src/models/results/account_tx.rs index 35d57834..4fe8c122 100644 --- a/src/models/results/account_tx.rs +++ b/src/models/results/account_tx.rs @@ -7,9 +7,8 @@ use serde_json::Value; use crate::models::requests::Marker; use crate::models::{XRPLModelException, XRPLModelResult}; -use super::{ - exceptions::XRPLResultException, metadata::TransactionMetadata, XRPLResponse, XRPLResult, -}; +use super::{exceptions::XRPLResultException, XRPLResponse, XRPLResult}; +use crate::models::transactions::metadata::TransactionMetadata; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(untagged)] diff --git a/src/models/results/metadata.rs b/src/models/results/metadata.rs deleted file mode 100644 index a730b98e..00000000 --- a/src/models/results/metadata.rs +++ /dev/null @@ -1,223 +0,0 @@ -use alloc::borrow::Cow; - -use serde::{Deserialize, Serialize}; -use serde_json::Value; - -/// See Metadata: -/// `` -#[serde_with::skip_serializing_none] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)] -#[serde(rename_all = "PascalCase")] -pub struct TransactionMetadata<'a> { - /// The transaction's position within the ledger that included it. - #[serde(rename = "TransactionIndex")] - pub transaction_index: u64, - /// The transaction's result code. - #[serde(rename = "TransactionResult")] - pub transaction_result: Cow<'a, str>, - /// Array of objects describing changes to ledger entries this - /// transaction made. - #[serde(rename = "AffectedNodes")] - pub affected_nodes: Cow<'a, [AffectedNode<'a>]>, - /// The currency amount actually delivered to the destination for Payment - /// transactions. Contains "unavailable" for partial payments before - /// 2014-01-20. - pub delivered_amount: Option, - /// (Optional) NFTokenID for NFTokenMint and NFTokenAcceptOffer - /// transactions. - #[serde(rename = "nftoken_id")] - pub nftoken_id: Option>, - /// (Optional) Array of NFTokenIDs for NFTokenCancelOffer transactions. - #[serde(rename = "nftoken_ids")] - pub nftoken_ids: Option]>>, - /// (Optional) OfferID for NFTokenCreateOffer transactions. - #[serde(rename = "offer_id")] - pub offer_id: Option>, - /// (Optional) MPTokenIssuanceID for MPTokenIssuanceCreate transactions. - #[serde(rename = "mpt_issuance_id")] - pub mpt_issuance_id: Option>, -} - -#[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>, - #[serde(rename = "ModifiedNode")] - pub modified_node: Option>, - #[serde(rename = "DeletedNode")] - pub deleted_node: Option>, -} - -#[serde_with::skip_serializing_none] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)] -#[serde(rename_all = "PascalCase")] -pub struct LedgerNode<'a> { - /// The type of ledger object this node represents. - pub ledger_entry_type: Cow<'a, str>, - /// The ID of this ledger entry in the ledger's state tree. - pub ledger_index: Cow<'a, str>, - /// The content fields of the ledger entry after changes. - pub final_fields: Option, - /// The previous values for changed fields. - pub previous_fields: Option, - /// The content fields of a newly created ledger entry. - pub new_fields: Option, - /// The identifying hash of the previous transaction to modify this - /// ledger entry. - pub previous_txn_id: Option>, - /// The Ledger Index of the ledger containing the previous transaction. - #[serde(rename = "PreviousTxnLgrSeq")] - pub previous_txn_lgr_seq: Option, - /// The node in the directory chain. - pub book_node: Option>, - /// The node in the owner directory chain. - pub owner_node: Option>, - /// The exchange rate, used in offer directory nodes. - pub exchange_rate: Option>, - /// The root index of the directory. - pub root_index: Option>, -} - -#[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); - - // Test first affected node (ModifiedNode) - 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"); - } - - // Test second affected node (DeletedNode) - 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()); - } -} diff --git a/src/models/results/mod.rs b/src/models/results/mod.rs index f82bc44f..aebdba6e 100644 --- a/src/models/results/mod.rs +++ b/src/models/results/mod.rs @@ -20,7 +20,6 @@ pub mod ledger_current; pub mod ledger_data; pub mod ledger_entry; pub mod manifest; -pub mod metadata; pub mod nft_buy_offers; pub mod nft_info; pub mod nft_offer; diff --git a/src/models/results/nftoken.rs b/src/models/results/nftoken.rs index 93f6e9bb..d1c7f4c8 100644 --- a/src/models/results/nftoken.rs +++ b/src/models/results/nftoken.rs @@ -3,7 +3,8 @@ use core::convert::TryFrom; use serde::{Deserialize, Serialize}; -use super::{metadata::TransactionMetadata, tx::TxVersionMap}; +use super::tx::TxVersionMap; +use crate::models::transactions::metadata::TransactionMetadata; use crate::models::{XRPLModelException, XRPLModelResult}; /// Result type for NFTokenMint transaction diff --git a/src/models/results/transaction_entry.rs b/src/models/results/transaction_entry.rs index b1e860ef..8cfbdef5 100644 --- a/src/models/results/transaction_entry.rs +++ b/src/models/results/transaction_entry.rs @@ -2,7 +2,7 @@ use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; -use super::metadata::TransactionMetadata; +use crate::models::transactions::metadata::TransactionMetadata; /// Response format for the transaction_entry method. /// diff --git a/src/models/results/tx.rs b/src/models/results/tx.rs index c7c3b839..4636f076 100644 --- a/src/models/results/tx.rs +++ b/src/models/results/tx.rs @@ -9,7 +9,8 @@ use crate::models::{ XRPLModelException, XRPLModelResult, }; -use super::{metadata::TransactionMetadata, XRPLResponse, XRPLResult}; +use super::{XRPLResponse, XRPLResult}; +use crate::models::transactions::metadata::TransactionMetadata; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(untagged)] diff --git a/src/models/transactions/metadata.rs b/src/models/transactions/metadata.rs index 1e45c85d..f4e0e498 100644 --- a/src/models/transactions/metadata.rs +++ b/src/models/transactions/metadata.rs @@ -23,6 +23,7 @@ pub struct NFTokenMetadataFields<'a> { #[serde(rename = "URI")] pub uri: Cow<'a, str>, } + #[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] @@ -52,24 +53,39 @@ pub struct Fields<'a> { pub enum AffectedNode<'a> { #[serde(rename_all = "PascalCase")] CreatedNode { + /// The type of ledger object this node represents. ledger_entry_type: LedgerEntryType, + /// The ID of this ledger entry in the ledger's state tree. ledger_index: LedgerIndex<'a>, + /// The content fields of a newly created ledger entry. new_fields: Fields<'a>, }, #[serde(rename_all = "PascalCase")] ModifiedNode { + /// The type of ledger object this node represents. ledger_entry_type: LedgerEntryType, + /// The ID of this ledger entry in the ledger's state tree. ledger_index: LedgerIndex<'a>, + /// The content fields of the ledger entry after changes. final_fields: Option>, + /// The previous values for changed fields. previous_fields: Option>, + /// The identifying hash of the previous transaction to modify this + /// ledger entry. + #[serde(rename = "PreviousTxnID")] previous_txn_id: Option>, + /// The Ledger Index of the ledger containing the previous transaction. previous_txn_lgr_seq: Option, }, #[serde(rename_all = "PascalCase")] DeletedNode { + /// The type of ledger object this node represents. ledger_entry_type: LedgerEntryType, + /// The ID of this ledger entry in the ledger's state tree. ledger_index: LedgerIndex<'a>, + /// The content fields of the ledger entry after changes. final_fields: Fields<'a>, + /// The previous values for changed fields. previous_fields: Option>, }, } @@ -82,18 +98,42 @@ pub enum NodeType { } #[skip_serializing_none] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct TransactionMetadata<'a> { + /// Array of objects describing changes to ledger entries this + /// transaction made. pub affected_nodes: Vec>, + /// The transaction's position within the ledger that included it. pub transaction_index: u32, - pub transaction_result: Amount<'a>, + /// The transaction's result code. + pub transaction_result: Cow<'a, str>, + /// The currency amount actually delivered to the destination for Payment + /// transactions. Contains "unavailable" for partial payments before + /// 2014-01-20. #[serde(rename = "delivered_amount")] pub delivered_amount: Option>, + /// (Optional) NFTokenID for NFTokenMint and NFTokenAcceptOffer + /// transactions. + #[serde(rename = "nftoken_id")] + pub nftoken_id: Option>, + /// (Optional) Array of NFTokenIDs for NFTokenCancelOffer transactions. + #[serde(rename = "nftoken_ids")] + pub nftoken_ids: Option]>>, + /// (Optional) OfferID for NFTokenCreateOffer transactions. + #[serde(rename = "offer_id")] + pub offer_id: Option>, + /// (Optional) MPTokenIssuanceID for MPTokenIssuanceCreate transactions. + #[serde(rename = "mpt_issuance_id")] + pub mpt_issuance_id: Option>, } #[cfg(test)] mod test_serde { + use crate::models::{ledger::objects::LedgerEntryType, requests::LedgerIndex}; + + use super::AffectedNode; + #[test] fn test_deserialize_deleted_node() { let json = r#" @@ -120,9 +160,29 @@ mod test_serde { } } "#; - let deleted_node = serde_json::from_str::(json); - - assert!(deleted_node.is_ok()); + let deleted_node = serde_json::from_str::(json).unwrap(); + if let AffectedNode::DeletedNode { + ledger_entry_type, + ledger_index, + final_fields, + previous_fields, + } = deleted_node + { + assert_eq!(ledger_entry_type, LedgerEntryType::Offer); + assert_eq!( + ledger_index, + LedgerIndex::Str( + "D11F69DE8A8CACB130F2E2B9893E5C97B9EE4136759C66C1F3497C8575FF5ED0".into() + ) + ); + assert_eq!( + final_fields.account, + Some("rHzKtpcB1KC1YuU4PBhk9m2abqrf2kZsfV".into()) + ); + assert!(previous_fields.is_none()); + } else { + panic!("expected deleted node") + } } #[test] @@ -148,9 +208,39 @@ mod test_serde { } } "#; - let modified_node = serde_json::from_str::(json); - - assert!(modified_node.is_ok()); + let modified_node = serde_json::from_str::(json).unwrap(); + if let AffectedNode::ModifiedNode { + ledger_entry_type, + ledger_index, + final_fields, + previous_fields, + previous_txn_id, + previous_txn_lgr_seq, + } = modified_node + { + assert_eq!(ledger_entry_type, LedgerEntryType::AccountRoot); + assert_eq!( + ledger_index, + LedgerIndex::Str( + "991ED60C316200D33B2EA3E56E505433394DBA7FF5E7ADE8C8850D02BEF1F53A".into() + ) + ); + assert_eq!( + final_fields.map(|f| f.account), + Some(Some("rHzKtpcB1KC1YuU4PBhk9m2abqrf2kZsfV".into())) + ); + assert_eq!( + previous_fields.map(|f| f.balance), + Some(Some("5000542904".into())) + ); + assert_eq!( + previous_txn_id, + Some("960FAFAF9CA0465B7475F888946F0D58F9CF49B18F3991D826B03A5025368DDE".into()) + ); + assert_eq!(previous_txn_lgr_seq, Some(92173588)); + } else { + panic!("expected modified node") + } } #[test] @@ -170,8 +260,123 @@ mod test_serde { } } "#; - let created_node = serde_json::from_str::(json); + let created_node = serde_json::from_str::(json).unwrap(); + if let AffectedNode::CreatedNode { + ledger_entry_type, + ledger_index, + new_fields, + } = created_node + { + assert_eq!(ledger_entry_type, LedgerEntryType::AccountRoot); + assert_eq!( + ledger_index, + LedgerIndex::Str( + "991ED60C316200D33B2EA3E56E505433394DBA7FF5E7ADE8C8850D02BEF1F53A".into() + ) + ); + assert_eq!( + new_fields.account, + Some("rHzKtpcB1KC1YuU4PBhk9m2abqrf2kZsfV".into()) + ); + } else { + panic!("expected created node") + } + } + + #[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: super::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); + + // Test first affected node (ModifiedNode) + let first_node = &metadata.affected_nodes[0]; + if let AffectedNode::ModifiedNode { + ledger_entry_type, + ledger_index, + previous_txn_lgr_seq, + .. + } = &first_node + { + assert_eq!(*ledger_entry_type, LedgerEntryType::AccountRoot); + assert_eq!( + *ledger_index, + LedgerIndex::Str( + "1ED8DDFD80F275CB1CE7F18BB9D906655DE8029805D8B95FB9020B30425821EB".into() + ) + ); + assert_eq!(*previous_txn_lgr_seq, Some(86724072)); + } else { + panic!("Expected ModifiedNode"); + } - assert!(created_node.is_ok()); + // Test second affected node (DeletedNode) + let second_node = &metadata.affected_nodes[1]; + if let AffectedNode::DeletedNode { + ledger_entry_type, + ledger_index, + .. + } = &second_node + { + assert_eq!(*ledger_entry_type, LedgerEntryType::Offer); + assert_eq!( + *ledger_index, + LedgerIndex::Str( + "348AF66EBD872FBF2BD23085D3FB4A200E15509451475027C4A5EE8D8B77C623".into() + ) + ); + } else { + panic!("Expected DeletedNode"); + } } }