use alloy::primitives::B256;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ParseSubgraphIdError {
#[error("invalid length {length}: {value} (length must be <=44)")]
InvalidLength { value: String, length: usize },
#[error("invalid character \"{value}\": {error}")]
InvalidCharacter { value: String, error: String },
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
feature = "serde",
derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr)
)]
#[repr(transparent)]
pub struct SubgraphId(B256);
impl SubgraphId {
pub const ZERO: Self = Self(B256::ZERO);
pub const fn new(value: B256) -> Self {
Self(value)
}
pub fn as_bytes(&self) -> &[u8; 32] {
self.0.as_ref()
}
}
impl AsRef<B256> for SubgraphId {
fn as_ref(&self) -> &B256 {
&self.0
}
}
impl AsRef<[u8]> for SubgraphId {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl AsRef<[u8; 32]> for SubgraphId {
fn as_ref(&self) -> &[u8; 32] {
self.0.as_ref()
}
}
impl std::borrow::Borrow<[u8]> for SubgraphId {
fn borrow(&self) -> &[u8] {
self.0.borrow()
}
}
impl std::borrow::Borrow<[u8; 32]> for SubgraphId {
fn borrow(&self) -> &[u8; 32] {
self.0.borrow()
}
}
impl std::ops::Deref for SubgraphId {
type Target = B256;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<B256> for SubgraphId {
fn from(bytes: B256) -> Self {
Self(bytes)
}
}
impl From<[u8; 32]> for SubgraphId {
fn from(value: [u8; 32]) -> Self {
Self(value.into())
}
}
impl<'a> From<&'a [u8; 32]> for SubgraphId {
fn from(value: &'a [u8; 32]) -> Self {
Self(value.into())
}
}
impl<'a> TryFrom<&'a [u8]> for SubgraphId {
type Error = <B256 as TryFrom<&'a [u8]>>::Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
value.try_into().map(Self)
}
}
impl From<SubgraphId> for B256 {
fn from(id: SubgraphId) -> Self {
id.0
}
}
impl From<&SubgraphId> for B256 {
fn from(id: &SubgraphId) -> Self {
id.0
}
}
impl std::str::FromStr for SubgraphId {
type Err = ParseSubgraphIdError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let mut buffer = [0_u8; 32];
let len = bs58::decode(value)
.onto(&mut buffer)
.map_err(|err| match err {
bs58::decode::Error::BufferTooSmall => ParseSubgraphIdError::InvalidLength {
value: value.to_string(),
length: value.len(),
},
bs58::decode::Error::InvalidCharacter { .. } => {
ParseSubgraphIdError::InvalidCharacter {
value: value.to_string(),
error: err.to_string(),
}
}
bs58::decode::Error::NonAsciiCharacter { .. } => {
ParseSubgraphIdError::InvalidCharacter {
value: value.to_string(),
error: err.to_string(),
}
}
_ => unreachable!(),
})?;
if len < 32 {
buffer.rotate_right(32 - len);
}
Ok(Self::from(buffer))
}
}
impl std::fmt::Display for SubgraphId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let leading_zeroes = self.0.iter().take_while(|b| **b == 0).count();
f.write_str(&bs58::encode(&self.0[leading_zeroes..]).into_string())
}
}
impl std::fmt::Debug for SubgraphId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SubgraphId({self})")
}
}
#[cfg(feature = "fake")]
impl fake::Dummy<fake::Faker> for SubgraphId {
fn dummy_with_rng<R: fake::Rng + ?Sized>(config: &fake::Faker, rng: &mut R) -> Self {
<[u8; 32]>::dummy_with_rng(config, rng).into()
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! __subgraph_id {
() => {
$crate::SubgraphId::ZERO
};
($id:tt) => {
$crate::SubgraphId::new($crate::__parse_subgraph_id_const($id))
};
}
#[doc(hidden)]
pub const fn __parse_subgraph_id_const(value: &str) -> B256 {
let data = value.as_bytes();
let bytes = bs58::decode(data).into_array_const_unwrap::<32>();
B256::new(bytes)
}
#[cfg(test)]
mod tests {
use alloy::primitives::{B256, b256};
use super::{ParseSubgraphIdError, SubgraphId};
use crate::subgraph_id;
const VALID_SUBGRAPH_ID: &str = "7xB3yxxD8okmq4dZPky3eP1nYRgLfZrwMyUQBGo32t4U";
const EXPECTED_ID_BYTES: B256 =
b256!("67486e65165b1474898247760a4b852d70d95782c6325960e5b6b4fd82fed1bd");
const EXPECTED_ID: SubgraphId = subgraph_id!(VALID_SUBGRAPH_ID);
#[test]
fn parse_valid_string() {
let valid_id = VALID_SUBGRAPH_ID;
let result = valid_id.parse::<SubgraphId>();
let id = result.expect("invalid subgraph ID");
assert_eq!(id, EXPECTED_ID);
assert_eq!(id.0, EXPECTED_ID_BYTES);
}
#[test]
fn parse_failure_on_invalid_string() {
let invalid_id = "invalid";
let result = invalid_id.parse::<SubgraphId>();
let err = result.expect_err("expected an error");
assert_eq!(
err,
ParseSubgraphIdError::InvalidCharacter {
value: invalid_id.to_string(),
error: "provided string contained invalid character 'l' at byte 4".to_string(),
}
);
}
#[test]
fn format_subgraph_id_display() {
let valid_id = EXPECTED_ID;
let result = format!("{}", valid_id);
assert_eq!(result, VALID_SUBGRAPH_ID);
}
#[test]
fn format_subgraph_id_debug() {
let expected_debug_repr = format!("SubgraphId({})", VALID_SUBGRAPH_ID);
let valid_id = EXPECTED_ID;
let result = format!("{:?}", valid_id);
assert_eq!(result, expected_debug_repr);
}
#[test]
fn serialize_leading_zeroes() {
let input = "4JruhWH1ZdwvUuMg2xCmtnZQYYHvmEq6cmTcZkpM6pW";
let output: SubgraphId = input.parse().unwrap();
assert_eq!(output.to_string(), input.to_string());
}
}