ibc_app_nft_transfer_types/
token.rs1use core::fmt::{self, Display};
3use core::str::FromStr;
4
5use http::Uri;
6use ibc_core::host::types::error::DecodingError;
7use ibc_core::primitives::prelude::*;
8#[cfg(feature = "serde")]
9use ibc_core::primitives::serializers;
10
11use crate::data::Data;
12
13#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
15#[cfg_attr(
16 feature = "parity-scale-codec",
17 derive(
18 parity_scale_codec::Encode,
19 parity_scale_codec::Decode,
20 scale_info::TypeInfo
21 )
22)]
23#[cfg_attr(
24 feature = "borsh",
25 derive(borsh::BorshSerialize, borsh::BorshDeserialize)
26)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
29#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
30pub struct TokenId(String);
31
32impl AsRef<str> for TokenId {
33 fn as_ref(&self) -> &str {
34 &self.0
35 }
36}
37
38impl Display for TokenId {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 write!(f, "{}", self.0)
41 }
42}
43
44impl FromStr for TokenId {
45 type Err = DecodingError;
46
47 fn from_str(token_id: &str) -> Result<Self, Self::Err> {
48 if token_id.trim().is_empty() {
49 Err(DecodingError::missing_raw_data("empty token ID"))
50 } else {
51 Ok(Self(token_id.to_string()))
52 }
53 }
54}
55
56#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
57#[cfg_attr(
58 feature = "parity-scale-codec",
59 derive(
60 parity_scale_codec::Encode,
61 parity_scale_codec::Decode,
62 scale_info::TypeInfo
63 )
64)]
65#[cfg_attr(
66 feature = "borsh",
67 derive(borsh::BorshSerialize, borsh::BorshDeserialize)
68)]
69#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
70#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
71#[derive(Clone, Debug, PartialEq, Eq)]
72pub struct TokenIds(pub Vec<TokenId>);
73
74impl TokenIds {
75 pub fn as_ref(&self) -> Vec<&TokenId> {
76 self.0.iter().collect()
77 }
78}
79
80impl Display for TokenIds {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 write!(
83 f,
84 "{}",
85 self.0
86 .iter()
87 .map(|t| t.to_string())
88 .collect::<Vec<String>>()
89 .join(",")
90 )
91 }
92}
93
94impl TryFrom<Vec<String>> for TokenIds {
95 type Error = DecodingError;
96
97 fn try_from(token_ids: Vec<String>) -> Result<Self, Self::Error> {
98 if token_ids.is_empty() {
99 return Err(DecodingError::missing_raw_data("empty token IDs"));
100 }
101
102 let ids: Result<Vec<TokenId>, _> = token_ids.iter().map(|t| t.parse()).collect();
103 let mut ids = ids?;
104
105 ids.sort();
106 ids.dedup();
107
108 if ids.len() != token_ids.len() {
109 return Err(DecodingError::invalid_raw_data(format!(
110 "mismatched number of token IDs: expected {}, actual {}",
111 token_ids.len(),
112 ids.len()
113 )));
114 }
115
116 Ok(Self(ids))
117 }
118}
119
120#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
122#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
123#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
124#[derive(Clone, Debug, PartialEq, Eq)]
125pub struct TokenUri(
126 #[cfg_attr(feature = "arbitrary", arbitrary(with = crate::arb_uri))]
127 #[cfg_attr(feature = "serde", serde(with = "serializers"))]
128 #[cfg_attr(feature = "schema", schemars(with = "String"))]
129 Uri,
130);
131
132#[cfg(feature = "borsh")]
133impl borsh::BorshSerialize for TokenUri {
134 fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
135 borsh::BorshSerialize::serialize(&self.to_string(), writer)
136 }
137}
138
139#[cfg(feature = "borsh")]
140impl borsh::BorshDeserialize for TokenUri {
141 fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Self> {
142 let uri = String::deserialize_reader(reader)?;
143 Ok(TokenUri::from_str(&uri).map_err(|_| borsh::io::ErrorKind::Other)?)
144 }
145}
146
147#[cfg(feature = "parity-scale-codec")]
148impl parity_scale_codec::Encode for TokenUri {
149 fn encode_to<T: parity_scale_codec::Output + ?Sized>(&self, writer: &mut T) {
150 self.to_string().encode_to(writer);
151 }
152}
153
154#[cfg(feature = "parity-scale-codec")]
155impl parity_scale_codec::Decode for TokenUri {
156 fn decode<I: parity_scale_codec::Input>(
157 input: &mut I,
158 ) -> Result<Self, parity_scale_codec::Error> {
159 let uri = String::decode(input)?;
160 TokenUri::from_str(&uri).map_err(|_| parity_scale_codec::Error::from("from str error"))
161 }
162}
163
164#[cfg(feature = "parity-scale-codec")]
165impl scale_info::TypeInfo for TokenUri {
166 type Identity = Self;
167
168 fn type_info() -> scale_info::Type {
169 scale_info::Type::builder()
170 .path(scale_info::Path::new("TokenUri", module_path!()))
171 .composite(
172 scale_info::build::Fields::unnamed()
173 .field(|f| f.ty::<String>().type_name("String")),
174 )
175 }
176}
177
178impl Display for TokenUri {
179 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180 write!(f, "{}", self.0)
181 }
182}
183
184impl FromStr for TokenUri {
185 type Err = DecodingError;
186
187 fn from_str(token_uri: &str) -> Result<Self, Self::Err> {
188 let token_uri = Uri::from_str(token_uri).map_err(DecodingError::invalid_raw_data)?;
189 Ok(Self(token_uri))
190 }
191}
192
193#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
195#[cfg_attr(
196 feature = "parity-scale-codec",
197 derive(
198 parity_scale_codec::Encode,
199 parity_scale_codec::Decode,
200 scale_info::TypeInfo
201 )
202)]
203#[cfg_attr(
204 feature = "borsh",
205 derive(borsh::BorshSerialize, borsh::BorshDeserialize)
206)]
207#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
208#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
209#[derive(Clone, Debug, PartialEq, Eq, derive_more::AsRef)]
210pub struct TokenData(Data);
211
212impl Display for TokenData {
213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214 write!(f, "{}", self.0)
215 }
216}
217
218impl FromStr for TokenData {
219 type Err = DecodingError;
220
221 fn from_str(token_data: &str) -> Result<Self, Self::Err> {
222 let data = Data::from_str(token_data)?;
223 Ok(Self(data))
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 #[cfg(feature = "serde")]
232 #[test]
233 fn test_serde_json_roundtrip() {
234 fn serde_roundtrip(token_uri: TokenUri) {
235 let serialized =
236 serde_json::to_string(&token_uri).expect("failed to serialize TokenUri");
237 let deserialized = serde_json::from_str::<TokenUri>(&serialized)
238 .expect("failed to deserialize TokenUri");
239
240 assert_eq!(deserialized, token_uri);
241 }
242
243 let uri = "/foo/bar?baz".parse::<Uri>().unwrap();
244 serde_roundtrip(TokenUri(uri));
245
246 let uri = "https://www.rust-lang.org/install.html"
247 .parse::<Uri>()
248 .unwrap();
249 serde_roundtrip(TokenUri(uri));
250 }
251
252 #[cfg(feature = "borsh")]
253 #[test]
254 fn test_borsh_roundtrip() {
255 fn borsh_roundtrip(token_uri: TokenUri) {
256 let token_uri_bytes = borsh::to_vec(&token_uri).unwrap();
257 let res = borsh::from_slice::<TokenUri>(&token_uri_bytes).unwrap();
258
259 assert_eq!(token_uri, res);
260 }
261
262 let uri = "/foo/bar?baz".parse::<Uri>().unwrap();
263 borsh_roundtrip(TokenUri(uri));
264
265 let uri = "https://www.rust-lang.org/install.html"
266 .parse::<Uri>()
267 .unwrap();
268 borsh_roundtrip(TokenUri(uri));
269 }
270}