r402_protocol/network/
id.rs1use std::fmt;
4use std::str::FromStr;
5
6use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash)]
23pub struct ChainId {
24 namespace: String,
25 reference: String,
26}
27
28impl ChainId {
29 pub fn new<N: Into<String>, R: Into<String>>(namespace: N, reference: R) -> Self {
31 Self {
32 namespace: namespace.into(),
33 reference: reference.into(),
34 }
35 }
36
37 #[must_use]
39 pub fn namespace(&self) -> &str {
40 &self.namespace
41 }
42
43 #[must_use]
45 pub fn reference(&self) -> &str {
46 &self.reference
47 }
48
49 #[must_use]
51 pub fn into_parts(self) -> (String, String) {
52 (self.namespace, self.reference)
53 }
54}
55
56impl fmt::Display for ChainId {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 write!(f, "{}:{}", self.namespace, self.reference)
59 }
60}
61
62impl From<ChainId> for String {
63 fn from(value: ChainId) -> Self {
64 value.to_string()
65 }
66}
67
68#[derive(Debug, thiserror::Error)]
70#[error("Invalid chain id format {0}")]
71pub struct ChainIdFormatError(
72 pub String,
74);
75
76impl FromStr for ChainId {
77 type Err = ChainIdFormatError;
78
79 fn from_str(s: &str) -> Result<Self, Self::Err> {
80 let (namespace, reference) = s
81 .split_once(':')
82 .ok_or_else(|| ChainIdFormatError(s.into()))?;
83 Ok(Self {
84 namespace: namespace.into(),
85 reference: reference.into(),
86 })
87 }
88}
89
90impl Serialize for ChainId {
91 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
92 where
93 S: Serializer,
94 {
95 serializer.serialize_str(&self.to_string())
96 }
97}
98
99impl<'de> Deserialize<'de> for ChainId {
100 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
101 where
102 D: Deserializer<'de>,
103 {
104 let s = String::deserialize(deserializer)?;
105 Self::from_str(&s).map_err(de::Error::custom)
106 }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub struct NetworkInfo {
112 pub name: &'static str,
114 pub namespace: &'static str,
116 pub reference: &'static str,
118}
119
120impl NetworkInfo {
121 #[must_use]
123 pub fn chain_id(&self) -> ChainId {
124 ChainId::new(self.namespace, self.reference)
125 }
126}
127
128#[cfg(test)]
129#[allow(clippy::unwrap_used, reason = "unit tests panic on assertion failure")]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn serialize_eip155() {
135 let chain_id = ChainId::new("eip155", "1");
136 let serialized = serde_json::to_string(&chain_id).unwrap();
137 assert_eq!(serialized, "\"eip155:1\"");
138 }
139
140 #[test]
141 fn serialize_solana() {
142 let chain_id = ChainId::new("solana", "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp");
143 let serialized = serde_json::to_string(&chain_id).unwrap();
144 assert_eq!(serialized, "\"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp\"");
145 }
146
147 #[test]
148 fn deserialize_eip155() {
149 let chain_id: ChainId = serde_json::from_str("\"eip155:1\"").unwrap();
150 assert_eq!(chain_id.namespace(), "eip155");
151 assert_eq!(chain_id.reference(), "1");
152 }
153
154 #[test]
155 fn deserialize_solana() {
156 let chain_id: ChainId =
157 serde_json::from_str("\"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp\"").unwrap();
158 assert_eq!(chain_id.namespace(), "solana");
159 assert_eq!(chain_id.reference(), "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp");
160 }
161
162 #[test]
163 fn roundtrip_eip155() {
164 let original = ChainId::new("eip155", "8453");
165 let serialized = serde_json::to_string(&original).unwrap();
166 let deserialized: ChainId = serde_json::from_str(&serialized).unwrap();
167 assert_eq!(original, deserialized);
168 }
169
170 #[test]
171 fn roundtrip_solana() {
172 let original = ChainId::new("solana", "devnet");
173 let serialized = serde_json::to_string(&original).unwrap();
174 let deserialized: ChainId = serde_json::from_str(&serialized).unwrap();
175 assert_eq!(original, deserialized);
176 }
177
178 #[test]
179 fn deserialize_invalid_format() {
180 let result: Result<ChainId, _> = serde_json::from_str("\"invalid\"");
181 assert!(result.is_err());
182 }
183
184 #[test]
185 fn deserialize_unknown_namespace() {
186 let result: Result<ChainId, _> = serde_json::from_str("\"unknown:1\"");
187 assert!(result.is_ok());
188 }
189}