chainlink_data_streams_report/
feed_id.rs

1use hex::{FromHex, ToHex};
2use serde::{Deserialize, Serialize};
3use std::fmt;
4use std::str::FromStr;
5use thiserror::Error;
6
7#[derive(Debug, Error, PartialEq)]
8pub enum IDError {
9    #[error("Missing '0x' prefix")]
10    MissingPrefix,
11
12    #[error("Invalid length for FeedID")]
13    InvalidLength,
14
15    #[error("Failed to decode FeedID")]
16    DecodeError(#[from] hex::FromHexError),
17}
18
19/// Represents a 32-byte identifier.
20///
21/// The `ID` struct encapsulates a 32-byte array and provides methods for
22/// parsing from and converting to hexadecimal strings, as well as extracting
23/// the feed version from the identifier.
24///
25/// # Examples
26///
27/// ```rust
28/// use chainlink_data_streams_report::feed_id::ID;
29///
30/// let id = ID::from_hex_str("0x00016b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472").unwrap();
31/// println!("ID: {}", id);
32/// ```
33#[derive(Clone, Copy, PartialEq, Eq, Hash)]
34pub struct ID(pub [u8; 32]);
35
36impl ID {
37    /// Parses an `ID` from a hexadecimal string with a "0x" prefix.
38    ///
39    /// # Arguments
40    ///
41    /// * `s` - A string slice that holds the hexadecimal representation of the ID.
42    ///
43    /// # Returns
44    ///
45    /// * `Ok(ID)` if parsing is successful.
46    /// * `IDError` if the input string is invalid.
47    ///
48    /// # Errors
49    ///
50    /// Returns an error if:
51    /// - The string does not start with "0x" or "0X".
52    /// - The string length after the prefix is not exactly 64 characters (32 bytes).
53    /// - The string contains invalid hexadecimal characters.
54    ///
55    /// # Examples
56    ///
57    /// ```rust
58    /// use chainlink_data_streams_report::feed_id::ID;
59    ///
60    /// let id = ID::from_hex_str("0x00016b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472").unwrap();
61    /// ```
62    pub fn from_hex_str(s: &str) -> Result<Self, IDError> {
63        let s = s.trim();
64
65        if !s.starts_with("0x") && !s.starts_with("0X") {
66            return Err(IDError::MissingPrefix);
67        }
68
69        let hex_str = &s[2..];
70
71        if hex_str.len() != 64 {
72            return Err(IDError::InvalidLength);
73        }
74
75        let bytes = <[u8; 32]>::from_hex(hex_str)?;
76        Ok(ID(bytes))
77    }
78
79    /// Returns the hexadecimal string representation prefixed with "0x".
80    ///
81    /// # Returns
82    ///
83    /// A `String` containing the hexadecimal representation of the `ID`, prefixed with "0x".
84    ///
85    /// # Examples
86    ///
87    /// ```rust
88    /// use chainlink_data_streams_report::feed_id::ID;
89    ///
90    /// let id = ID::from_hex_str("0x00016b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472").unwrap();
91    /// let hex_string = id.to_hex_string();
92    /// assert_eq!(hex_string, "0x00016b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472");
93    /// ```
94    pub fn to_hex_string(&self) -> String {
95        format!("0x{}", self.0.encode_hex::<String>())
96    }
97}
98
99impl FromStr for ID {
100    type Err = IDError;
101
102    /// Parses an `ID` from a string using `from_hex_str`.
103    ///
104    /// # Arguments
105    ///
106    /// * `s` - A string slice containing the hexadecimal representation of the `ID`.
107    ///
108    /// # Returns
109    ///
110    /// * `Ok(ID)` if parsing is successful.
111    /// * `IDError` if the input string is invalid.
112    ///
113    /// # Examples
114    ///
115    /// ```rust
116    /// use chainlink_data_streams_report::feed_id::ID;
117    /// use std::str::FromStr;
118    ///
119    /// let id = ID::from_str("0x00016b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472").unwrap();
120    /// ```
121    fn from_str(s: &str) -> Result<Self, Self::Err> {
122        ID::from_hex_str(s)
123    }
124}
125
126impl fmt::Display for ID {
127    /// Formats the `ID` using its hexadecimal string representation.
128    ///
129    /// # Examples
130    ///
131    /// ```rust
132    /// use chainlink_data_streams_report::feed_id::ID;
133    ///
134    /// let id = ID::from_hex_str("0x00016b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472").unwrap();
135    /// println!("{}", id); // Outputs: 0x00016b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472
136    /// ```
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        f.write_str(&self.to_hex_string())
139    }
140}
141
142impl fmt::Debug for ID {
143    /// Formats the `ID` using its hexadecimal string representation.
144    ///
145    /// # Examples
146    ///
147    /// ```rust
148    /// use chainlink_data_streams_report::feed_id::ID;
149    ///
150    /// let id = ID::from_hex_str("0x00016b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472").unwrap();
151    /// println!("{:?}", id); // Outputs: 0x00016b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472
152    /// ```
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        f.write_str(&self.to_hex_string())
155    }
156}
157
158impl Serialize for ID {
159    /// Serializes the `ID` as a string.
160    ///
161    /// This method is used by Serde to serialize the `ID` into formats like JSON.
162    ///
163    /// # Examples
164    ///
165    /// ```rust
166    /// use chainlink_data_streams_report::feed_id::ID;
167    /// use serde_json;
168    ///
169    /// let id = ID::from_hex_str("0x00016b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472").unwrap();
170    /// let json = serde_json::to_string(&id).unwrap();
171    /// ```
172    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
173    where
174        S: serde::Serializer,
175    {
176        serializer.serialize_str(&self.to_hex_string())
177    }
178}
179
180impl<'de> Deserialize<'de> for ID {
181    /// Deserializes the `ID` from a string.
182    ///
183    /// This method is used by Serde to deserialize the `ID` from formats like JSON.
184    ///
185    /// # Examples
186    ///
187    /// ```rust
188    /// use chainlink_data_streams_report::feed_id::ID;
189    /// use serde_json;
190    ///
191    /// let json = "\"0x00016b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472\"";
192    /// let id: ID = serde_json::from_str(json).unwrap();
193    /// ```
194    fn deserialize<D>(deserializer: D) -> Result<ID, D::Error>
195    where
196        D: serde::Deserializer<'de>,
197    {
198        let s = String::deserialize(deserializer)?;
199        ID::from_str(&s).map_err(serde::de::Error::custom)
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206
207    const V1_FEED_ID: ID = ID([
208        0, 1, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58,
209        163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114,
210    ]);
211    const V2_FEED_ID: ID = ID([
212        00, 02, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58,
213        163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114,
214    ]);
215    const V3_FEED_ID: ID = ID([
216        00, 03, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58,
217        163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114,
218    ]);
219    const V4_FEED_ID: ID = ID([
220        00, 04, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58,
221        163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114,
222    ]);
223
224    const V1_FEED_ID_STR: &str =
225        "0x00016b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472";
226    const V2_FEED_ID_STR: &str =
227        "0x00026b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472";
228    const V3_FEED_ID_STR: &str =
229        "0x00036b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472";
230    const V4_FEED_ID_STR: &str =
231        "0x00046b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472";
232
233    #[test]
234    fn test_from_hex_str() {
235        assert_eq!(ID::from_hex_str(V1_FEED_ID_STR), Ok(V1_FEED_ID));
236        assert_eq!(ID::from_hex_str(V2_FEED_ID_STR), Ok(V2_FEED_ID));
237        assert_eq!(ID::from_hex_str(V3_FEED_ID_STR), Ok(V3_FEED_ID));
238        assert_eq!(ID::from_hex_str(V4_FEED_ID_STR), Ok(V4_FEED_ID));
239    }
240
241    #[test]
242    fn test_from_str() {
243        assert_eq!(ID::from_str(V1_FEED_ID_STR), Ok(V1_FEED_ID));
244        assert_eq!(ID::from_str(V2_FEED_ID_STR), Ok(V2_FEED_ID));
245        assert_eq!(ID::from_str(V3_FEED_ID_STR), Ok(V3_FEED_ID));
246        assert_eq!(ID::from_str(V4_FEED_ID_STR), Ok(V4_FEED_ID));
247    }
248
249    #[test]
250    fn test_to_hex_string() {
251        assert_eq!(V1_FEED_ID.to_hex_string(), V1_FEED_ID_STR);
252        assert_eq!(V2_FEED_ID.to_hex_string(), V2_FEED_ID_STR);
253        assert_eq!(V3_FEED_ID.to_hex_string(), V3_FEED_ID_STR);
254        assert_eq!(V4_FEED_ID.to_hex_string(), V4_FEED_ID_STR);
255    }
256
257    #[test]
258    fn test_revert_if_missing_prefix() {
259        let hex_str = &V1_FEED_ID_STR[2..];
260        let result = ID::from_hex_str(hex_str);
261        assert!(matches!(result, Err(IDError::MissingPrefix)));
262    }
263
264    #[test]
265    fn test_revert_if_invalid_length() {
266        let hex_str = "0x309";
267        let result = ID::from_hex_str(hex_str);
268        assert!(matches!(result, Err(IDError::InvalidLength)));
269    }
270
271    #[test]
272    fn test_revert_if_failed_to_decode() {
273        let hex_str = "0xZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";
274        let result = ID::from_hex_str(hex_str);
275        assert!(matches!(result, Err(IDError::DecodeError(_))));
276    }
277}