endpoint-libs 3.4.3

Launch MCP services fast: describe endpoints once in RON, and endpoint-gen generates the Rust models, docs and MCP tool schemas that this crate serves over WebSocket RPC, with roles and typed errors built in.
Documentation
use std::fmt::Debug;
use std::ops::{Deref, DerefMut};
use std::str::FromStr;

#[doc(hidden)]
pub use alloy_primitives::{Address, B256 as H256, U256};
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize, Serializer};

fn parse_blockchain_scalar<E: Error, T: FromStr>(
    value: &str,
    prefix: &'static str,
    digits: usize,
) -> Result<T, E>
where
    T::Err: std::fmt::Display,
{
    let bytes = value.as_bytes();
    if bytes.len() != prefix.len() + digits
        || !value.starts_with(prefix)
        || !bytes[prefix.len()..].iter().all(u8::is_ascii_hexdigit)
    {
        return Err(E::custom(format!(
            "expected {prefix} followed by exactly {digits} hexadecimal digits"
        )));
    }

    T::from_str(value).map_err(|error| E::custom(error.to_string()))
}

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct BlockchainAddress(pub Address);
impl Debug for BlockchainAddress {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}
#[allow(non_snake_case)]
pub mod WithBlockchainAddress {
    use super::*;

    pub fn serialize<S>(this: &Address, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        format!("{this:?}").serialize(serializer)
    }
    pub fn deserialize<'de, D>(deserializer: D) -> Result<Address, D::Error>
    where
        D: Deserializer<'de>,
    {
        let value = String::deserialize(deserializer)?;
        parse_blockchain_scalar::<D::Error, Address>(&value, "0x", 40)
    }
}
impl Serialize for BlockchainAddress {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        format!("{:?}", self.0).serialize(serializer)
    }
}
impl<'de> Deserialize<'de> for BlockchainAddress {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let value = String::deserialize(deserializer)?;
        parse_blockchain_scalar::<D::Error, Address>(&value, "0x", 40).map(BlockchainAddress)
    }
}

impl From<Address> for BlockchainAddress {
    fn from(address: Address) -> Self {
        BlockchainAddress(address)
    }
}
impl From<BlockchainAddress> for Address {
    fn from(val: BlockchainAddress) -> Self {
        val.0
    }
}

impl Deref for BlockchainAddress {
    type Target = Address;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
impl DerefMut for BlockchainAddress {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct BlockchainTransactionHash(pub H256);
impl Debug for BlockchainTransactionHash {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}
#[allow(non_snake_case)]
pub mod WithBlockchainTransactionHash {
    use super::*;

    pub fn serialize<S>(this: &H256, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        format!("{this:?}").serialize(serializer)
    }
    pub fn deserialize<'de, D>(deserializer: D) -> Result<H256, D::Error>
    where
        D: Deserializer<'de>,
    {
        let value = String::deserialize(deserializer)?;
        parse_blockchain_scalar::<D::Error, H256>(&value, "0x", 64)
    }
}
impl Serialize for BlockchainTransactionHash {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        format!("{:?}", self.0).serialize(serializer)
    }
}
impl<'de> Deserialize<'de> for BlockchainTransactionHash {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let value = String::deserialize(deserializer)?;
        parse_blockchain_scalar::<D::Error, H256>(&value, "0x", 64).map(BlockchainTransactionHash)
    }
}

impl From<H256> for BlockchainTransactionHash {
    fn from(hash: H256) -> Self {
        BlockchainTransactionHash(hash)
    }
}
impl From<BlockchainTransactionHash> for H256 {
    fn from(val: BlockchainTransactionHash) -> Self {
        val.0
    }
}

impl Deref for BlockchainTransactionHash {
    type Target = H256;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
impl DerefMut for BlockchainTransactionHash {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

#[cfg(test)]
mod scalar_decode_tests {
    use super::*;
    use serde::de::value::{Error as DecodeError, StrDeserializer};

    #[test]
    fn scalar_entry_points_decode_hex_values_and_reject_malformed_input() {
        let address = format!("0x{}", "aB".repeat(20));
        let hash = format!("0x{}", "aB".repeat(32));
        let decode = |value| StrDeserializer::<DecodeError>::new(value);
        assert_eq!(
            WithBlockchainAddress::deserialize(decode(&address)).unwrap(),
            Address::from_str(&address).unwrap()
        );
        assert_eq!(
            BlockchainAddress::deserialize(decode(&address)).unwrap().0,
            Address::from_str(&address).unwrap()
        );
        assert_eq!(
            WithBlockchainTransactionHash::deserialize(decode(&hash)).unwrap(),
            H256::from_str(&hash).unwrap()
        );
        assert_eq!(
            BlockchainTransactionHash::deserialize(decode(&hash))
                .unwrap()
                .0,
            H256::from_str(&hash).unwrap()
        );
        for invalid in [
            "",
            "0x",
            "é",
            "0x🦀",
            &"0".repeat(40),
            &format!("0x{}", "g".repeat(64)),
            &format!("0X{}", "a".repeat(40)),
            &format!("0x{}", "a".repeat(65)),
        ] {
            assert!(WithBlockchainAddress::deserialize(decode(invalid)).is_err());
            assert!(BlockchainAddress::deserialize(decode(invalid)).is_err());
            assert!(WithBlockchainTransactionHash::deserialize(decode(invalid)).is_err());
            assert!(BlockchainTransactionHash::deserialize(decode(invalid)).is_err());
        }
    }
}