1use crate::error::TaiError;
9use serde::{Deserialize, Serialize};
10use std::fmt;
11use std::str::FromStr;
12
13pub const SUI_ADDR_LEN: usize = 32;
15
16#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
18#[serde(transparent)]
19pub struct SuiAddress(#[serde(with = "hex_prefixed")] pub [u8; SUI_ADDR_LEN]);
20
21#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
23#[serde(transparent)]
24pub struct ObjectId(#[serde(with = "hex_prefixed")] pub [u8; SUI_ADDR_LEN]);
25
26impl fmt::Debug for SuiAddress {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 write!(f, "0x{}", hex::encode(self.0))
29 }
30}
31
32impl fmt::Display for SuiAddress {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 write!(f, "0x{}", hex::encode(self.0))
35 }
36}
37
38impl fmt::Debug for ObjectId {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 write!(f, "0x{}", hex::encode(self.0))
41 }
42}
43
44impl fmt::Display for ObjectId {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 write!(f, "0x{}", hex::encode(self.0))
47 }
48}
49
50impl FromStr for SuiAddress {
51 type Err = TaiError;
52 fn from_str(s: &str) -> Result<Self, Self::Err> {
53 Ok(SuiAddress(parse_32_hex(s)?))
54 }
55}
56
57impl FromStr for ObjectId {
58 type Err = TaiError;
59 fn from_str(s: &str) -> Result<Self, Self::Err> {
60 Ok(ObjectId(parse_32_hex(s)?))
61 }
62}
63
64impl SuiAddress {
65 pub const ZERO: SuiAddress = SuiAddress([0u8; SUI_ADDR_LEN]);
68
69 pub fn from_bytes(b: [u8; SUI_ADDR_LEN]) -> Self {
71 SuiAddress(b)
72 }
73
74 pub fn as_bytes(&self) -> &[u8; SUI_ADDR_LEN] {
76 &self.0
77 }
78}
79
80impl ObjectId {
81 pub fn from_bytes(b: [u8; SUI_ADDR_LEN]) -> Self {
83 ObjectId(b)
84 }
85
86 pub fn as_bytes(&self) -> &[u8; SUI_ADDR_LEN] {
88 &self.0
89 }
90}
91
92fn parse_32_hex(s: &str) -> Result<[u8; SUI_ADDR_LEN], TaiError> {
93 let s = s.strip_prefix("0x").unwrap_or(s);
94 let padded: String = if s.len() < 64 {
97 format!("{:0>64}", s)
98 } else {
99 s.to_string()
100 };
101 if padded.len() != 64 {
102 return Err(TaiError::InvalidAddress(format!(
103 "expected 32 bytes (64 hex chars), got {}",
104 s.len()
105 )));
106 }
107 let bytes =
108 hex::decode(&padded).map_err(|e| TaiError::InvalidAddress(format!("hex decode: {e}")))?;
109 let mut out = [0u8; SUI_ADDR_LEN];
110 out.copy_from_slice(&bytes);
111 Ok(out)
112}
113
114mod hex_prefixed {
116 use serde::{Deserialize, Deserializer, Serializer};
117
118 pub fn serialize<S: Serializer>(b: &[u8; 32], s: S) -> Result<S::Ok, S::Error> {
119 s.serialize_str(&format!("0x{}", hex::encode(b)))
120 }
121
122 pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<[u8; 32], D::Error> {
123 let s = String::deserialize(d)?;
124 let s = s.strip_prefix("0x").unwrap_or(&s);
125 let padded: String = if s.len() < 64 {
126 format!("{:0>64}", s)
127 } else {
128 s.to_string()
129 };
130 let bytes = hex::decode(&padded).map_err(serde::de::Error::custom)?;
131 if bytes.len() != 32 {
132 return Err(serde::de::Error::custom("expected 32 bytes"));
133 }
134 let mut out = [0u8; 32];
135 out.copy_from_slice(&bytes);
136 Ok(out)
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn parse_full_64_hex() {
146 let s = "0x7d41072ae77b18b752292b47468e07e6332cd9a6ef9b052752f98f22d9844f8d";
147 let id: ObjectId = s.parse().unwrap();
148 assert_eq!(id.to_string(), s);
149 }
150
151 #[test]
152 fn parse_short_hex_is_left_padded() {
153 let id: ObjectId = "0x6".parse().unwrap();
154 assert_eq!(
155 id.to_string(),
156 "0x0000000000000000000000000000000000000000000000000000000000000006"
157 );
158 }
159
160 #[test]
161 fn parse_without_prefix() {
162 let id: SuiAddress = "ad".parse().unwrap();
163 assert_eq!(
164 id.to_string(),
165 "0x00000000000000000000000000000000000000000000000000000000000000ad"
166 );
167 }
168
169 #[test]
170 fn rejects_too_long() {
171 let s = "0x".to_string() + &"a".repeat(65);
172 assert!(s.parse::<ObjectId>().is_err());
173 }
174
175 #[test]
176 fn zero_address_constant() {
177 assert_eq!(
178 SuiAddress::ZERO.to_string(),
179 "0x0000000000000000000000000000000000000000000000000000000000000000"
180 );
181 }
182
183 #[test]
184 fn json_roundtrip() {
185 let id: ObjectId = "0x7d41072ae77b18b752292b47468e07e6332cd9a6ef9b052752f98f22d9844f8d"
186 .parse()
187 .unwrap();
188 let json = serde_json::to_string(&id).unwrap();
189 assert_eq!(
190 json,
191 "\"0x7d41072ae77b18b752292b47468e07e6332cd9a6ef9b052752f98f22d9844f8d\""
192 );
193 let id2: ObjectId = serde_json::from_str(&json).unwrap();
194 assert_eq!(id, id2);
195 }
196}