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