monero_rpc/
util.rs

1// Copyright 2019-2023 Artem Vorotnikov and Monero Rust Contributors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//   http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use serde::{Deserialize, Deserializer, Serialize};
16use std::fmt::{self, Display};
17
18/// Get bytes and parse from `str` interface.
19pub trait HashType: Sized {
20    /// Get bytes representation.
21    fn bytes(&self) -> &[u8]
22    where
23        Self: AsRef<[u8]>,
24    {
25        self.as_ref()
26    }
27    /// Parse from `str`.
28    fn from_str(v: &str) -> anyhow::Result<Self>;
29}
30
31macro_rules! hash_type_impl {
32    ($name:ty) => {
33        impl HashType for $name {
34            fn from_str(v: &str) -> anyhow::Result<Self> {
35                Ok(v.parse()?)
36            }
37        }
38    };
39}
40
41hash_type_impl!(monero::util::address::PaymentId);
42hash_type_impl!(monero::cryptonote::hash::Hash);
43
44impl HashType for Vec<u8> {
45    fn from_str(v: &str) -> anyhow::Result<Self> {
46        let v = v.strip_prefix("0x").unwrap_or(v);
47        Ok(hex::decode(v)?)
48    }
49}
50
51/// Wrapper type to help serializating types through string.
52#[derive(Clone, Debug, Eq, PartialEq)]
53pub struct HashString<T>(pub T);
54
55impl<T> Display for HashString<T>
56where
57    T: HashType + AsRef<[u8]>,
58{
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        write!(f, "{}", hex::encode(self.0.bytes()))
61    }
62}
63
64impl<T> Serialize for HashString<T>
65where
66    T: HashType + AsRef<[u8]>,
67{
68    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
69    where
70        S: serde::ser::Serializer,
71    {
72        serializer.serialize_str(&self.to_string())
73    }
74}
75
76impl<'de, T> Deserialize<'de> for HashString<T>
77where
78    T: HashType,
79{
80    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
81    where
82        D: Deserializer<'de>,
83    {
84        let s = String::deserialize(deserializer)?;
85        Ok(Self(T::from_str(&s).map_err(serde::de::Error::custom)?))
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use serde_test::{assert_tokens, Token};
93
94    #[test]
95    fn trait_hash_type_for_payment_id() {
96        use monero::util::address::PaymentId;
97
98        let payment_id = PaymentId([0, 1, 2, 3, 4, 5, 6, 7]);
99
100        assert_eq!(payment_id.bytes(), &[0, 1, 2, 3, 4, 5, 6, 7]);
101
102        assert!(<PaymentId as HashType>::from_str("")
103            .unwrap_err()
104            .is::<rustc_hex::FromHexError>());
105        assert!(<PaymentId as HashType>::from_str("0x01234567")
106            .unwrap_err()
107            .is::<rustc_hex::FromHexError>());
108        assert!(<PaymentId as HashType>::from_str("0xgg")
109            .unwrap_err()
110            .is::<rustc_hex::FromHexError>());
111
112        assert_eq!(
113            <PaymentId as HashType>::from_str("0x0001020304050607").unwrap(),
114            payment_id
115        );
116        assert_eq!(
117            <PaymentId as HashType>::from_str("0001020304050607").unwrap(),
118            payment_id
119        );
120    }
121
122    #[test]
123    fn trait_hash_type_for_cryptonote_hash() {
124        use monero::cryptonote::hash::Hash;
125
126        let hash = Hash([250; 32]);
127
128        assert_eq!(hash.bytes(), [250; 32].as_slice());
129
130        assert!(<Hash as HashType>::from_str("")
131            .unwrap_err()
132            .is::<rustc_hex::FromHexError>());
133        assert!(<Hash as HashType>::from_str("0x01234567")
134            .unwrap_err()
135            .is::<rustc_hex::FromHexError>());
136        assert!(<Hash as HashType>::from_str("0xgg")
137            .unwrap_err()
138            .is::<rustc_hex::FromHexError>());
139
140        let hash_str = "fa".repeat(32);
141        assert_eq!(<Hash as HashType>::from_str(&hash_str).unwrap(), hash);
142
143        let hash_str = format!("0x{}", hash_str);
144        assert_eq!(<Hash as HashType>::from_str(&hash_str).unwrap(), hash);
145    }
146
147    #[test]
148    fn trait_hash_type_for_vec_u8() {
149        let vec_non_empty = vec![0, 1, 2, 3, 4];
150
151        assert_eq!(vec_non_empty.bytes(), &[0, 1, 2, 3, 4]);
152
153        assert_eq!(
154            <Vec<u8> as HashType>::from_str("").unwrap(),
155            Vec::<u8>::new()
156        );
157        assert!(<Vec<u8> as HashType>::from_str("0xgg")
158            .unwrap_err()
159            .is::<hex::FromHexError>());
160
161        assert_eq!(
162            <Vec<u8> as HashType>::from_str("0x0001020304").unwrap(),
163            vec_non_empty
164        );
165        assert_eq!(
166            <Vec<u8> as HashType>::from_str("0001020304").unwrap(),
167            vec_non_empty
168        );
169    }
170
171    #[test]
172    fn display_for_hash_string() {
173        let vec = vec![0, 1, 2, 3, 4];
174        let hash_string = HashString(vec);
175        assert_eq!(hash_string.to_string(), "0001020304");
176    }
177
178    #[test]
179    fn se_de_for_hash_string() {
180        let vec = vec![0, 1, 2, 3, 4];
181        let hash_string = HashString(vec);
182
183        assert_tokens(&hash_string, &[Token::Str("0001020304")]);
184    }
185}