cat_gateway/service/common/types/cardano/
transaction_id.rs1use std::sync::LazyLock;
4
5use cardano_chain_follower::hashes::{TransactionId, BLAKE_2B256_SIZE};
6use const_format::concatcp;
7use poem_openapi::{
8 registry::{MetaSchema, MetaSchemaRef},
9 types::{Example, ParseError, ParseFromJSON, ParseFromParameter, ParseResult, ToJSON, Type},
10};
11use regex::Regex;
12use serde_json::Value;
13
14use crate::service::{common::types::string_types::impl_string_types, utilities::as_hex_string};
15
16const TITLE: &str = "Transaction Id/Hash";
18const DESCRIPTION: &str = "The Blake2b-256 hash of the transaction.";
20const EXAMPLE: &str = "0x27d0350039fb3d068cccfae902bf2e72583fc553e0aafb960bd9d76d5bff777b";
22const ENCODED_LENGTH: usize = EXAMPLE.len();
24const HASH_LENGTH: usize = BLAKE_2B256_SIZE;
26const PATTERN: &str = concatcp!("^0x", "[A-Fa-f0-9]{", HASH_LENGTH * 2, "}$");
28
29#[allow(clippy::cast_lossless)]
31static SCHEMA: LazyLock<MetaSchema> = LazyLock::new(|| {
32 MetaSchema {
33 title: Some(TITLE.to_owned()),
34 description: Some(DESCRIPTION),
35 example: Some(EXAMPLE.into()),
36 max_length: Some(ENCODED_LENGTH),
37 min_length: Some(ENCODED_LENGTH),
38 pattern: Some(PATTERN.to_string()),
39 ..poem_openapi::registry::MetaSchema::ANY
40 }
41});
42
43fn is_valid(hash: &str) -> bool {
45 #[allow(clippy::unwrap_used)] static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(PATTERN).unwrap());
48
49 if RE.is_match(hash) {
50 if let Some(h) = hash.strip_prefix("0x") {
51 return hex::decode(h).is_ok();
52 }
53 }
54 false
55}
56
57impl_string_types!(
58 TxnId,
59 "string",
60 "hex:hash(32)",
61 Some(SCHEMA.clone()),
62 is_valid
63);
64
65impl Example for TxnId {
66 fn example() -> Self {
67 Self(EXAMPLE.to_string())
68 }
69}
70
71impl TryFrom<Vec<u8>> for TxnId {
72 type Error = anyhow::Error;
73
74 fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
75 if value.len() != HASH_LENGTH {
76 anyhow::bail!("Hash Length Invalid.")
77 }
78 Ok(Self(as_hex_string(&value)))
79 }
80}
81
82impl From<TransactionId> for TxnId {
83 fn from(value: TransactionId) -> Self {
84 Self(value.to_string())
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn test_txn_id() {
94 assert!(TxnId::parse_from_parameter(EXAMPLE).is_ok());
95
96 let invalid = [
97 "0x27d0350039fb3d068cccfae902bf2e72583fc5",
98 "0x27d0350039fb3d068cccfae902bf2e72583fc553e0aafb960bd9d76d5bff777b0",
99 "0x",
100 "0xqw",
101 ];
102 for v in &invalid {
103 assert!(TxnId::parse_from_parameter(v).is_err());
104 }
105 }
106}