From 40cc835628347772ca1cc71b9d860e2a4c3b1214 Mon Sep 17 00:00:00 2001 From: iamjpotts <8704475+iamjpotts@users.noreply.github.com> Date: Thu, 29 Aug 2024 19:45:57 +0000 Subject: [PATCH] feat: parse transaction IDs from mirror api responses Co-authored-by: Ricky Saechao <76449893+RickyLB@users.noreply.github.com> --- src/transaction_id.rs | 50 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/src/transaction_id.rs b/src/transaction_id.rs index e5cbdfda8..670912368 100644 --- a/src/transaction_id.rs +++ b/src/transaction_id.rs @@ -152,39 +152,49 @@ impl ToProtobuf for TransactionId { } } -// TODO: add unit tests to prove parsing // TODO: potentially improve parsing with `nom` or `combine` impl FromStr for TransactionId { type Err = Error; fn from_str(s: &str) -> Result { - const EXPECTED: &str = "expecting @[?scheduled][/]"; + const EXPECTED: &str = "expecting @[?scheduled][/] or -[?scheduled][/]"; // parse route: - // split_once('@') -> ("", "[?scheduled][/]") + // split_once('@') or split_once('-') -> ("", "[?scheduled][/]") // rsplit_once('/') -> Either ("[?scheduled]", "") or ("[?scheduled]") // .strip_suffix("?scheduled") -> ("") and the suffix was either removed or not. // (except it's better ux to do a `split_once('?')`... Except it doesn't matter that much) - let (account_id, s) = s.split_once('@').ok_or_else(|| Error::basic_parse(EXPECTED))?; + let (account_id, seconds, remainder) = s + .split_once('@') + .and_then(|(account_id, remainder)| { + remainder + .split_once('.') + .map(|(vs_secs, remainder)| (account_id, vs_secs, remainder)) + }) + .or_else(|| { + s.split_once('-').and_then(|(account_id, remainder)| { + remainder + .split_once('-') + .map(|(vs_secs, remainder)| (account_id, vs_secs, remainder)) + }) + }) + .ok_or_else(|| Error::basic_parse(EXPECTED))?; let account_id: AccountId = account_id.parse()?; - let (s, nonce) = match s.rsplit_once('/') { + let (s, nonce) = match remainder.rsplit_once('/') { Some((s, nonce)) => (s, Some(nonce)), - None => (s, None), + None => (remainder, None), }; let nonce = nonce.map(i32::from_str).transpose().map_err(Error::basic_parse)?; - let (valid_start, scheduled) = match s.strip_suffix("?scheduled") { + let (nanos, scheduled) = match s.strip_suffix("?scheduled") { Some(rest) => (rest, true), None => (s, false), }; let valid_start = { - let (seconds, nanos) = - valid_start.split_once('.').ok_or_else(|| Error::basic_parse(EXPECTED))?; - let seconds = i64::from_str(seconds).map_err(Error::basic_parse)?; let nanos = i64::from_str(nanos).map_err(Error::basic_parse)?; @@ -345,4 +355,24 @@ mod tests { } ) } + + /// Parse a transaction ID returned by the Hedera mirror api. + /// + /// Test case was an output of this mirror request: + /// curl 'https://mainnet.mirrornode.hedera.com/api/v1/accounts/2?transactionType=cryptotransfer' + #[test] + fn parse_from_mirror() { + let transaction_id = TransactionId::from_str("0.0.2247604-1691870420-078765024").unwrap(); + + assert_eq!( + transaction_id, + TransactionId { + account_id: AccountId::new(0, 0, 2247604), + valid_start: OffsetDateTime::from_unix_timestamp_nanos(1691870420078765024) + .unwrap(), + nonce: None, + scheduled: false + } + ) + } }