thegraph_core/
subgraph_id.rs1use alloy::primitives::B256;
2
3#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
5pub enum ParseSubgraphIdError {
6 #[error("invalid length {length}: {value} (length must be <=44)")]
8 InvalidLength { value: String, length: usize },
9
10 #[error("invalid character \"{value}\": {error}")]
12 InvalidCharacter { value: String, error: String },
13}
14
15#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
28#[cfg_attr(
29 feature = "serde",
30 derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr)
31)]
32#[repr(transparent)]
33pub struct SubgraphId(B256);
34
35impl SubgraphId {
36 pub const ZERO: Self = Self(B256::ZERO);
41
42 pub const fn new(value: B256) -> Self {
44 Self(value)
45 }
46
47 pub fn as_bytes(&self) -> &[u8; 32] {
49 self.0.as_ref()
50 }
51}
52
53impl AsRef<B256> for SubgraphId {
54 fn as_ref(&self) -> &B256 {
55 &self.0
56 }
57}
58
59impl AsRef<[u8]> for SubgraphId {
60 fn as_ref(&self) -> &[u8] {
61 self.0.as_ref()
62 }
63}
64
65impl AsRef<[u8; 32]> for SubgraphId {
66 fn as_ref(&self) -> &[u8; 32] {
67 self.0.as_ref()
68 }
69}
70
71impl std::borrow::Borrow<[u8]> for SubgraphId {
72 fn borrow(&self) -> &[u8] {
73 self.0.borrow()
74 }
75}
76
77impl std::borrow::Borrow<[u8; 32]> for SubgraphId {
78 fn borrow(&self) -> &[u8; 32] {
79 self.0.borrow()
80 }
81}
82
83impl std::ops::Deref for SubgraphId {
84 type Target = B256;
85
86 fn deref(&self) -> &Self::Target {
87 &self.0
88 }
89}
90
91impl From<B256> for SubgraphId {
92 fn from(bytes: B256) -> Self {
93 Self(bytes)
94 }
95}
96
97impl From<[u8; 32]> for SubgraphId {
98 fn from(value: [u8; 32]) -> Self {
99 Self(value.into())
100 }
101}
102
103impl<'a> From<&'a [u8; 32]> for SubgraphId {
104 fn from(value: &'a [u8; 32]) -> Self {
105 Self(value.into())
106 }
107}
108
109impl<'a> TryFrom<&'a [u8]> for SubgraphId {
110 type Error = <B256 as TryFrom<&'a [u8]>>::Error;
111
112 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
113 value.try_into().map(Self)
114 }
115}
116
117impl From<SubgraphId> for B256 {
118 fn from(id: SubgraphId) -> Self {
119 id.0
120 }
121}
122
123impl From<&SubgraphId> for B256 {
124 fn from(id: &SubgraphId) -> Self {
125 id.0
126 }
127}
128
129impl std::str::FromStr for SubgraphId {
130 type Err = ParseSubgraphIdError;
131
132 fn from_str(value: &str) -> Result<Self, Self::Err> {
134 let mut buffer = [0_u8; 32];
135
136 let len = bs58::decode(value)
138 .onto(&mut buffer)
139 .map_err(|err| match err {
140 bs58::decode::Error::BufferTooSmall => ParseSubgraphIdError::InvalidLength {
141 value: value.to_string(),
142 length: value.len(),
143 },
144 bs58::decode::Error::InvalidCharacter { .. } => {
145 ParseSubgraphIdError::InvalidCharacter {
146 value: value.to_string(),
147 error: err.to_string(),
148 }
149 }
150 bs58::decode::Error::NonAsciiCharacter { .. } => {
151 ParseSubgraphIdError::InvalidCharacter {
152 value: value.to_string(),
153 error: err.to_string(),
154 }
155 }
156 _ => unreachable!(),
157 })?;
158
159 if len < 32 {
162 buffer.rotate_right(32 - len);
163 }
164
165 Ok(Self::from(buffer))
166 }
167}
168
169impl std::fmt::Display for SubgraphId {
170 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179 let leading_zeroes = self.0.iter().take_while(|b| **b == 0).count();
180 f.write_str(&bs58::encode(&self.0[leading_zeroes..]).into_string())
181 }
182}
183
184impl std::fmt::Debug for SubgraphId {
185 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194 write!(f, "SubgraphId({self})")
195 }
196}
197
198#[cfg(feature = "fake")]
199impl fake::Dummy<fake::Faker> for SubgraphId {
210 fn dummy_with_rng<R: fake::Rng + ?Sized>(config: &fake::Faker, rng: &mut R) -> Self {
211 <[u8; 32]>::dummy_with_rng(config, rng).into()
212 }
213}
214
215#[macro_export]
234#[doc(hidden)]
235macro_rules! __subgraph_id {
236 () => {
237 $crate::SubgraphId::ZERO
238 };
239 ($id:tt) => {
240 $crate::SubgraphId::new($crate::__parse_subgraph_id_const($id))
241 };
242}
243
244#[doc(hidden)]
246pub const fn __parse_subgraph_id_const(value: &str) -> B256 {
247 let data = value.as_bytes();
248 let bytes = bs58::decode(data).into_array_const_unwrap::<32>();
249 B256::new(bytes)
250}
251
252#[cfg(test)]
253mod tests {
254 use alloy::primitives::{B256, b256};
255
256 use super::{ParseSubgraphIdError, SubgraphId};
257 use crate::subgraph_id;
258
259 const VALID_SUBGRAPH_ID: &str = "7xB3yxxD8okmq4dZPky3eP1nYRgLfZrwMyUQBGo32t4U";
260
261 const EXPECTED_ID_BYTES: B256 =
262 b256!("67486e65165b1474898247760a4b852d70d95782c6325960e5b6b4fd82fed1bd");
263
264 const EXPECTED_ID: SubgraphId = subgraph_id!(VALID_SUBGRAPH_ID);
265
266 #[test]
267 fn parse_valid_string() {
268 let valid_id = VALID_SUBGRAPH_ID;
270
271 let result = valid_id.parse::<SubgraphId>();
273
274 let id = result.expect("invalid subgraph ID");
276 assert_eq!(id, EXPECTED_ID);
277 assert_eq!(id.0, EXPECTED_ID_BYTES);
278 }
279
280 #[test]
281 fn parse_failure_on_invalid_string() {
282 let invalid_id = "invalid";
285
286 let result = invalid_id.parse::<SubgraphId>();
288
289 let err = result.expect_err("expected an error");
291 assert_eq!(
292 err,
293 ParseSubgraphIdError::InvalidCharacter {
294 value: invalid_id.to_string(),
295 error: "provided string contained invalid character 'l' at byte 4".to_string(),
296 }
297 );
298 }
299
300 #[test]
301 fn format_subgraph_id_display() {
302 let valid_id = EXPECTED_ID;
304
305 let result = format!("{}", valid_id);
307
308 assert_eq!(result, VALID_SUBGRAPH_ID);
310 }
311
312 #[test]
313 fn format_subgraph_id_debug() {
314 let expected_debug_repr = format!("SubgraphId({})", VALID_SUBGRAPH_ID);
316
317 let valid_id = EXPECTED_ID;
318
319 let result = format!("{:?}", valid_id);
321
322 assert_eq!(result, expected_debug_repr);
324 }
325
326 #[test]
327 fn serialize_leading_zeroes() {
328 let input = "4JruhWH1ZdwvUuMg2xCmtnZQYYHvmEq6cmTcZkpM6pW";
329 let output: SubgraphId = input.parse().unwrap();
330 assert_eq!(output.to_string(), input.to_string());
331 }
332}