thegraph_core/collection_id.rs
1use alloy::primitives::{Address, FixedBytes};
2
3use crate::allocation_id::AllocationId;
4
5/// A unique identifier for a collection: the Graph Tally's payment identifier.
6///
7/// This is a "new-type" wrapper around [`FixedBytes<32>`] to provide type safety.
8///
9/// ## Formatting
10///
11/// The `CollectionId` type implements the following formatting traits:
12///
13/// - Use [`std::fmt::Display`] for formatting the `CollectionId` as a raw lower-case hexadecimal string.
14/// - Use [`std::fmt::LowerHex`] (or [`std::fmt::UpperHex`]) for formatting the `CollectionId` as
15/// a hexadecimal string.
16///
17/// See the [`Display`], [`LowerHex`], and [`UpperHex`] trait implementations for usage examples.
18///
19/// ## Generating test data
20///
21/// The `CollectionId` type implements the [`fake`] crate's [`fake::Dummy`] trait, allowing you to
22/// generate random `CollectionId` values for testing.
23///
24/// Note that the `fake` feature must be enabled to use this functionality.
25///
26/// See the [`Dummy`] trait impl for usage examples.
27///
28/// [`Display`]: #impl-Display-for-CollectionId
29/// [`LowerHex`]: #impl-LowerHex-for-CollectionId
30/// [`UpperHex`]: #impl-UpperHex-for-CollectionId
31/// [`Dummy`]: #impl-Dummy<Faker>-for-CollectionId
32#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
33pub struct CollectionId(FixedBytes<32>);
34
35impl CollectionId {
36 /// Create a new [`CollectionId`].
37 pub const fn new(collection: FixedBytes<32>) -> Self {
38 CollectionId(collection)
39 }
40
41 /// Return the internal representation.
42 pub fn into_inner(self) -> FixedBytes<32> {
43 self.0
44 }
45
46 /// Converts this `CollectionId` into an `Address`, assuming it was originally derived from a
47 /// left-padded address (i.e., the last 20 bytes are the address).
48 ///
49 /// ```rust
50 /// use thegraph_core::{
51 /// alloy::primitives::Address,
52 /// alloy::primitives::address,
53 /// collection_id, CollectionId,
54 /// };
55 ///
56 /// let collection_id: CollectionId = collection_id!("0000000000000000000000003e1f9c2ab4c7f1b3d7e839ebe6ae451c8a0b1d24");
57 /// assert_eq!(collection_id.as_address(), address!("3e1f9c2ab4c7f1b3d7e839ebe6ae451c8a0b1d24"));
58 /// ```
59 pub fn as_address(&self) -> Address {
60 Address::from_slice(&self.0.as_slice()[12..])
61 }
62}
63
64impl std::fmt::Display for CollectionId {
65 /// Formats the `CollectionId` using its raw lower-case hexadecimal representation.
66 ///
67 /// See [`LowerHex`] (and [`UpperHex`]) for formatting the `CollectionId` as a hexadecimal
68 /// string.
69 ///
70 /// [`LowerHex`]: struct.CollectionId.html#impl-LowerHex-for-CollectionId
71 /// [`UpperHex`]: struct.CollectionId.html#impl-UpperHex-for-CollectionId
72 ///
73 /// ```rust
74 /// # use thegraph_core::{collection_id, CollectionId};
75 /// const ID: CollectionId = collection_id!("8f2c4a779f66bde2e9c3d81d4315e91db8a42afee0d5f9947c20ab54be73e611");
76 ///
77 /// assert_eq!(format!("{}", ID), "0x8f2c4a779f66bde2e9c3d81d4315e91db8a42afee0d5f9947c20ab54be73e611");
78 /// ```
79 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
80 std::fmt::Display::fmt(&self.0, f)
81 }
82}
83
84impl std::fmt::Debug for CollectionId {
85 /// Formats the `CollectionId` using its raw lower-case hexadecimal representation.
86 ///
87 /// It is advised to use the [`LowerHex`] (and [`UpperHex`]) format trait implementation over
88 /// the [`Debug`](std::fmt::Debug) implementation to format the `CollectionId` as a lower-case
89 /// hexadecimal string.
90 ///
91 /// This implementation matches `alloy_primitives::FixedBytes`'s `Debug` implementation.
92 ///
93 /// [`LowerHex`]: struct.CollectionId.html#impl-LowerHex-for-CollectionId
94 /// [`UpperHex`]: struct.CollectionId.html#impl-UpperHex-for-CollectionId
95 ///
96 /// ```rust
97 /// # use thegraph_core::{collection_id, CollectionId};
98 /// const ID: CollectionId = collection_id!("8f2c4a779f66bde2e9c3d81d4315e91db8a42afee0d5f9947c20ab54be73e611");
99 ///
100 /// assert_eq!(format!("{:?}", ID), "0x8f2c4a779f66bde2e9c3d81d4315e91db8a42afee0d5f9947c20ab54be73e611");
101 /// ```
102 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
103 std::fmt::Debug::fmt(&self.0, f)
104 }
105}
106
107impl std::fmt::LowerHex for CollectionId {
108 /// Lowercase hex representation of the `CollectionId`.
109 ///
110 /// Note that the alternate flag, `#`, adds a `0x` in front of the output.
111 ///
112 /// ```rust
113 /// # use thegraph_core::{collection_id, CollectionId};
114 /// const ID: CollectionId = collection_id!("8f2c4a779f66bde2e9c3d81d4315e91db8a42afee0d5f9947c20ab54be73e611");
115 ///
116 /// // Lower hex
117 /// assert_eq!(format!("{:x}", ID), "8f2c4a779f66bde2e9c3d81d4315e91db8a42afee0d5f9947c20ab54be73e611");
118 ///
119 /// // Lower hex with alternate flag
120 /// assert_eq!(format!("{:#x}", ID), "0x8f2c4a779f66bde2e9c3d81d4315e91db8a42afee0d5f9947c20ab54be73e611");
121 /// ```
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 std::fmt::LowerHex::fmt(&self.0, f)
124 }
125}
126
127impl std::fmt::UpperHex for CollectionId {
128 /// Uppercase hex representation of the `CollectionId`.
129 ///
130 /// Note that the alternate flag, `#`, adds a `0x` in front of the output.
131 ///
132 /// ```rust
133 /// # use thegraph_core::{collection_id, CollectionId};
134 /// const ID: CollectionId = collection_id!("8f2c4a779f66bde2e9c3d81d4315e91db8a42afee0d5f9947c20ab54be73e611");
135 ///
136 /// // Upper hex
137 /// assert_eq!(format!("{:X}", ID), "8F2C4A779F66BDE2E9C3D81D4315E91DB8A42AFEE0D5F9947C20AB54BE73E611");
138 ///
139 /// // Upper hex with alternate flag
140 /// assert_eq!(format!("{:#X}", ID), "0x8F2C4A779F66BDE2E9C3D81D4315E91DB8A42AFEE0D5F9947C20AB54BE73E611");
141 /// ```
142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 std::fmt::UpperHex::fmt(&self.0, f)
144 }
145}
146
147impl From<FixedBytes<32>> for CollectionId {
148 fn from(collection: FixedBytes<32>) -> Self {
149 CollectionId(collection)
150 }
151}
152
153/// Convert an [`Address`] into a [`CollectionId`] by zero padding the address to 32 bytes.
154///
155/// ```rust
156/// use thegraph_core::{
157/// alloy::primitives::Address,
158/// alloy::primitives::address,
159/// collection_id, CollectionId,
160/// };
161/// let address: Address = address!("3e1f9c2aB4C7F1b3d7E839EbE6Ae451c8A0b1d24");
162/// let collection_id: CollectionId = address.into();
163/// assert_eq!(format!("{:?}", collection_id), "0x0000000000000000000000003e1f9c2ab4c7f1b3d7e839ebe6ae451c8a0b1d24");
164///
165/// let collection_id2: CollectionId = CollectionId::from(address!("3e1f9c2aB4C7F1b3d7E839EbE6Ae451c8A0b1d24"));
166/// assert_eq!(format!("{:?}", collection_id2), "0x0000000000000000000000003e1f9c2ab4c7f1b3d7e839ebe6ae451c8a0b1d24");
167/// ```
168impl From<Address> for CollectionId {
169 fn from(address: Address) -> Self {
170 let mut buf = [0u8; 32];
171 buf[12..].copy_from_slice(address.as_slice());
172 CollectionId(FixedBytes::<32>::from(buf))
173 }
174}
175
176/// Convert an [`AllocationId`] into a [`CollectionId`] by zero padding the allocation id to 32 bytes.
177///
178/// ```rust
179/// use thegraph_core::{
180/// alloy::primitives::Address,
181/// alloy::primitives::address,
182/// collection_id, CollectionId,
183/// allocation_id, AllocationId
184/// };
185///
186/// let allocation_id: AllocationId = allocation_id!("3e1f9c2aB4C7F1b3d7E839EbE6Ae451c8A0b1d24");
187/// let collection_id: CollectionId = allocation_id.into();
188/// assert_eq!(format!("{:?}", collection_id), "0x0000000000000000000000003e1f9c2ab4c7f1b3d7e839ebe6ae451c8a0b1d24");
189///
190/// let collection_id2: CollectionId = CollectionId::from(allocation_id!("3e1f9c2aB4C7F1b3d7E839EbE6Ae451c8A0b1d24"));
191/// assert_eq!(format!("{:?}", collection_id2), "0x0000000000000000000000003e1f9c2ab4c7f1b3d7e839ebe6ae451c8a0b1d24");
192/// ```
193impl From<AllocationId> for CollectionId {
194 fn from(allocation: AllocationId) -> Self {
195 let mut buf = [0u8; 32];
196 buf[12..].copy_from_slice(allocation.as_slice());
197 CollectionId(FixedBytes::<32>::from(buf))
198 }
199}
200
201impl std::str::FromStr for CollectionId {
202 type Err = <FixedBytes<32> as std::str::FromStr>::Err;
203
204 fn from_str(s: &str) -> Result<Self, Self::Err> {
205 let collection = std::str::FromStr::from_str(s)?;
206 Ok(CollectionId(collection))
207 }
208}
209
210impl PartialEq<FixedBytes<32>> for CollectionId {
211 fn eq(&self, other: &FixedBytes<32>) -> bool {
212 self.0.eq(other)
213 }
214}
215
216impl AsRef<FixedBytes<32>> for CollectionId {
217 fn as_ref(&self) -> &FixedBytes<32> {
218 &self.0
219 }
220}
221
222impl std::ops::Deref for CollectionId {
223 type Target = FixedBytes<32>;
224
225 fn deref(&self) -> &Self::Target {
226 &self.0
227 }
228}
229
230#[cfg(feature = "serde")]
231impl<'de> serde::Deserialize<'de> for CollectionId {
232 fn deserialize<D>(deserializer: D) -> Result<CollectionId, D::Error>
233 where
234 D: serde::Deserializer<'de>,
235 {
236 let collection = FixedBytes::<32>::deserialize(deserializer)?;
237 Ok(CollectionId(collection))
238 }
239}
240
241#[cfg(feature = "serde")]
242impl serde::Serialize for CollectionId {
243 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
244 where
245 S: serde::Serializer,
246 {
247 self.0.serialize(serializer)
248 }
249}
250
251#[cfg(feature = "fake")]
252/// To use the [`fake`] crate to generate random [`CollectionId`] values, **the `fake` feature must
253/// be enabled.**
254///
255/// ```rust
256/// # use thegraph_core::CollectionId;
257/// # use fake::Fake;
258/// let collection_id = fake::Faker.fake::<CollectionId>();
259///
260/// println!("CollectionId: {:#x}", collection_id);
261/// ```
262impl fake::Dummy<fake::Faker> for CollectionId {
263 fn dummy_with_rng<R: fake::Rng + ?Sized>(_: &fake::Faker, rng: &mut R) -> Self {
264 use crate::fake_impl::alloy::Alloy;
265 Self(FixedBytes::<32>::dummy_with_rng(&Alloy, rng))
266 }
267}
268
269/// Converts a sequence of string literals containing hex-encoded data into a new [`CollectionId`]
270/// at compile time.
271///
272/// To create a `CollectionId` from a string literal (no `0x` prefix) at compile time:
273///
274/// ```rust
275/// use thegraph_core::{collection_id, CollectionId};
276///
277/// const COLLECTION_ID: CollectionId = collection_id!("8f2c4a779f66bde2e9c3d81d4315e91db8a42afee0d5f9947c20ab54be73e611");
278/// ```
279///
280/// If no argument is provided, the macro will create a `CollectionId` with the zero address:
281///
282/// ```rust
283/// use thegraph_core::{
284/// alloy::primitives::FixedBytes,
285/// collection_id, CollectionId
286/// };
287///
288/// const COLLECTION_ID: CollectionId = collection_id!();
289///
290/// assert_eq!(COLLECTION_ID, FixedBytes::<32>::ZERO);
291/// ```
292#[macro_export]
293#[doc(hidden)]
294macro_rules! __collection_id {
295 () => {
296 $crate::CollectionId::new($crate::alloy::primitives::FixedBytes::<32>::ZERO)
297 };
298 ($value:tt) => {
299 $crate::CollectionId::new($crate::alloy::primitives::fixed_bytes!($value))
300 };
301}