1mod error;
2
3#[cfg(feature = "imt")]
4pub mod imt;
5#[cfg(feature = "nep141")]
6pub mod nep141;
7#[cfg(feature = "nep171")]
8pub mod nep171;
9#[cfg(feature = "nep245")]
10pub mod nep245;
11
12#[cfg(not(any(
13 feature = "nep141",
14 feature = "nep171",
15 feature = "nep245",
16 feature = "imt"
17)))]
18compile_error!(
19 r#"At least one of these features should be enabled:
20- `nep141`
21- `nep171`
22- `nep245`
23- `imt`
24"#
25);
26
27use core::{
28 fmt::{self, Debug, Display},
29 str::FromStr,
30};
31use strum::{EnumDiscriminants, EnumIter, EnumString};
32
33pub use self::error::TokenIdError;
34
35pub(crate) type Nep245TokenId = String;
37
38#[cfg_attr(any(feature = "arbitrary", test), derive(arbitrary::Arbitrary))]
39#[cfg_attr(
40 feature = "borsh",
41 derive(::borsh::BorshSerialize, ::borsh::BorshDeserialize),
42 cfg_attr(feature = "abi", derive(::borsh::BorshSchema)),
43 borsh(use_discriminant = true)
44)]
45#[cfg_attr(
46 feature = "serde",
47 derive(::serde_with::SerializeDisplay, ::serde_with::DeserializeFromStr)
48)]
49#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumDiscriminants, derive_more::From)]
50#[strum_discriminants(
51 name(TokenIdType),
52 cfg_attr(
53 feature = "serde",
54 derive(::serde_with::SerializeDisplay, ::serde_with::DeserializeFromStr),
55 cfg_attr(
56 feature = "abi",
57 derive(::schemars::JsonSchema),
58 schemars(with = "String"),
59 )
60 ),
61 derive(strum::Display, EnumString, EnumIter),
62 strum(serialize_all = "snake_case"),
63 vis(pub)
64)]
65#[repr(u8)]
66pub enum TokenId {
68 #[cfg(feature = "nep141")]
69 Nep141(crate::nep141::Nep141TokenId) = 0,
70 #[cfg(feature = "nep171")]
71 Nep171(crate::nep171::Nep171TokenId) = 1,
72 #[cfg(feature = "nep245")]
73 Nep245(crate::nep245::Nep245TokenId) = 2,
74 #[cfg(feature = "imt")]
75 Imt(crate::imt::ImtTokenId) = 3,
76}
77
78impl Debug for TokenId {
79 #[inline]
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 match self {
82 #[cfg(feature = "nep141")]
83 Self::Nep141(token_id) => {
84 write!(f, "{}:{}", TokenIdType::Nep141, token_id)
85 }
86 #[cfg(feature = "nep171")]
87 Self::Nep171(token_id) => {
88 write!(f, "{}:{}", TokenIdType::Nep171, token_id)
89 }
90 #[cfg(feature = "nep245")]
91 Self::Nep245(token_id) => {
92 write!(f, "{}:{}", TokenIdType::Nep245, token_id)
93 }
94 #[cfg(feature = "imt")]
95 Self::Imt(token_id) => {
96 write!(f, "{}:{}", TokenIdType::Imt, token_id)
97 }
98 }
99 }
100}
101
102impl Display for TokenId {
103 #[inline]
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 fmt::Debug::fmt(&self, f)
106 }
107}
108
109impl FromStr for TokenId {
110 type Err = TokenIdError;
111
112 #[inline]
113 fn from_str(s: &str) -> Result<Self, Self::Err> {
114 let (typ, data) = s
115 .split_once(':')
116 .ok_or(strum::ParseError::VariantNotFound)?;
117 match typ.parse()? {
118 #[cfg(feature = "nep141")]
119 TokenIdType::Nep141 => data.parse().map(Self::Nep141),
120 #[cfg(feature = "nep171")]
121 TokenIdType::Nep171 => data.parse().map(Self::Nep171),
122 #[cfg(feature = "nep245")]
123 TokenIdType::Nep245 => data.parse().map(Self::Nep245),
124 #[cfg(feature = "imt")]
125 TokenIdType::Imt => data.parse().map(Self::Imt),
126 }
127 }
128}
129
130#[cfg(feature = "abi")]
131const _: () = {
132 use schemars::{
133 JsonSchema,
134 r#gen::SchemaGenerator,
135 schema::{InstanceType, Schema, SchemaObject},
136 };
137
138 impl JsonSchema for TokenId {
139 fn schema_name() -> String {
140 stringify!(TokenId).to_string()
141 }
142
143 fn json_schema(_gen: &mut SchemaGenerator) -> Schema {
144 use near_account_id::AccountId;
145
146 SchemaObject {
147 instance_type: Some(InstanceType::String.into()),
148 extensions: std::iter::once((
149 "examples",
150 [
151 #[cfg(feature = "nep141")]
152 Self::Nep141(crate::nep141::Nep141TokenId::new(
153 "ft.near".parse::<AccountId>().unwrap(),
154 )),
155 #[cfg(feature = "nep171")]
156 Self::Nep171(crate::nep171::Nep171TokenId::new(
157 "nft.near".parse::<AccountId>().unwrap(),
158 "token_id1",
159 )),
160 #[cfg(feature = "nep245")]
161 Self::Nep245(crate::nep245::Nep245TokenId::new(
162 "mt.near".parse::<AccountId>().unwrap(),
163 "token_id1",
164 )),
165 #[cfg(feature = "imt")]
166 Self::Imt(crate::imt::ImtTokenId::new(
167 "imt.near".parse::<AccountId>().unwrap(),
168 "token_id1",
169 )),
170 ]
171 .map(|s| s.to_string())
172 .to_vec()
173 .into(),
174 ))
175 .map(|(k, v)| (k.to_string(), v))
176 .collect(),
177 ..Default::default()
178 }
179 .into()
180 }
181 }
182};
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use defuse_test_utils::random::make_arbitrary;
188 use rstest::rstest;
189
190 #[cfg(feature = "borsh")]
191 #[rstest]
192 #[trace]
193 #[cfg_attr(feature = "nep141", case("nep141:abc", "0003000000616263"))]
194 #[cfg_attr(
195 feature = "nep171",
196 case("nep171:abc:xyz", "01030000006162630300000078797a")
197 )]
198 #[cfg_attr(
199 feature = "nep245",
200 case("nep245:abc:xyz", "02030000006162630300000078797a")
201 )]
202 #[cfg_attr(feature = "imt", case("imt:abc:xyz", "03030000006162630300000078797a"))]
203 fn roundtrip_fixed(#[case] token_id_str: &str, #[case] borsh_expected_hex: &str) {
204 let token_id: TokenId = token_id_str.parse().unwrap();
205 let borsh_expected = hex::decode(borsh_expected_hex).unwrap();
206
207 let borsh_ser = borsh::to_vec(&token_id).unwrap();
208 assert_eq!(borsh_ser, borsh_expected);
209
210 let got: TokenId = borsh::from_slice(&borsh_ser).unwrap();
211 assert_eq!(got, token_id);
212 assert_eq!(got.to_string(), token_id_str);
213 }
214
215 #[cfg(feature = "borsh")]
216 #[rstest]
217 #[trace]
218 fn borsh_roundtrip(#[from(make_arbitrary)] token_id: TokenId) {
219 let ser = borsh::to_vec(&token_id).unwrap();
220 let got: TokenId = borsh::from_slice(&ser).unwrap();
221 assert_eq!(got, token_id);
222 }
223
224 #[rstest]
225 #[trace]
226 fn display_from_str_roundtrip(#[from(make_arbitrary)] token_id: TokenId) {
227 let s = token_id.to_string();
228 let got: TokenId = s.parse().unwrap();
229 assert_eq!(got, token_id);
230 }
231
232 #[cfg(feature = "serde")]
233 #[rstest]
234 #[trace]
235 fn serde_roundtrip(#[from(make_arbitrary)] token_id: TokenId) {
236 let ser = serde_json::to_vec(&token_id).unwrap();
237 let got: TokenId = serde_json::from_slice(&ser).unwrap();
238 assert_eq!(got, token_id);
239 }
240}