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}