use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ChainId {
namespace: String,
reference: String,
}
impl ChainId {
pub fn new<N: Into<String>, R: Into<String>>(namespace: N, reference: R) -> Self {
Self {
namespace: namespace.into(),
reference: reference.into(),
}
}
#[must_use]
pub fn namespace(&self) -> &str {
&self.namespace
}
#[must_use]
pub fn reference(&self) -> &str {
&self.reference
}
#[must_use]
pub fn into_parts(self) -> (String, String) {
(self.namespace, self.reference)
}
}
impl fmt::Display for ChainId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.namespace, self.reference)
}
}
impl From<ChainId> for String {
fn from(value: ChainId) -> Self {
value.to_string()
}
}
#[derive(Debug, thiserror::Error)]
#[error("Invalid chain id format {0}")]
pub struct ChainIdFormatError(
pub String,
);
impl FromStr for ChainId {
type Err = ChainIdFormatError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (namespace, reference) = s
.split_once(':')
.ok_or_else(|| ChainIdFormatError(s.into()))?;
Ok(Self {
namespace: namespace.into(),
reference: reference.into(),
})
}
}
impl Serialize for ChainId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for ChainId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Self::from_str(&s).map_err(de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NetworkInfo {
pub name: &'static str,
pub namespace: &'static str,
pub reference: &'static str,
}
impl NetworkInfo {
#[must_use]
pub fn chain_id(&self) -> ChainId {
ChainId::new(self.namespace, self.reference)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, reason = "unit tests panic on assertion failure")]
mod tests {
use super::*;
#[test]
fn serialize_eip155() {
let chain_id = ChainId::new("eip155", "1");
let serialized = serde_json::to_string(&chain_id).unwrap();
assert_eq!(serialized, "\"eip155:1\"");
}
#[test]
fn serialize_solana() {
let chain_id = ChainId::new("solana", "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp");
let serialized = serde_json::to_string(&chain_id).unwrap();
assert_eq!(serialized, "\"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp\"");
}
#[test]
fn deserialize_eip155() {
let chain_id: ChainId = serde_json::from_str("\"eip155:1\"").unwrap();
assert_eq!(chain_id.namespace(), "eip155");
assert_eq!(chain_id.reference(), "1");
}
#[test]
fn deserialize_solana() {
let chain_id: ChainId =
serde_json::from_str("\"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp\"").unwrap();
assert_eq!(chain_id.namespace(), "solana");
assert_eq!(chain_id.reference(), "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp");
}
#[test]
fn roundtrip_eip155() {
let original = ChainId::new("eip155", "8453");
let serialized = serde_json::to_string(&original).unwrap();
let deserialized: ChainId = serde_json::from_str(&serialized).unwrap();
assert_eq!(original, deserialized);
}
#[test]
fn roundtrip_solana() {
let original = ChainId::new("solana", "devnet");
let serialized = serde_json::to_string(&original).unwrap();
let deserialized: ChainId = serde_json::from_str(&serialized).unwrap();
assert_eq!(original, deserialized);
}
#[test]
fn deserialize_invalid_format() {
let result: Result<ChainId, _> = serde_json::from_str("\"invalid\"");
assert!(result.is_err());
}
#[test]
fn deserialize_unknown_namespace() {
let result: Result<ChainId, _> = serde_json::from_str("\"unknown:1\"");
assert!(result.is_ok());
}
}