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: std::iter::once((
156 "examples",
157 [
158 #[cfg(feature = "nep141")]
159 Self::Nep141(crate::nep141::Nep141TokenId::new(
160 "ft.near".parse::<AccountId>().unwrap(),
161 )),
162 #[cfg(feature = "nep171")]
163 Self::Nep171(crate::nep171::Nep171TokenId::new(
164 "nft.near".parse::<AccountId>().unwrap(),
165 "token_id1",
166 )),
167 #[cfg(feature = "nep245")]
168 Self::Nep245(crate::nep245::Nep245TokenId::new(
169 "mt.near".parse::<AccountId>().unwrap(),
170 "token_id1",
171 )),
172 #[cfg(feature = "imt")]
173 Self::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 .map(|(k, v)| (k.to_string(), v))
183 .collect(),
184 ..Default::default()
185 }
186 .into()
187 }
188 }
189};
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use defuse_test_utils::random::make_arbitrary;
195 use near_sdk::{borsh, serde_json};
196 use rstest::rstest;
197
198 #[rstest]
199 #[trace]
200 #[cfg_attr(feature = "nep141", case("nep141:abc", "0003000000616263"))]
201 #[cfg_attr(
202 feature = "nep171",
203 case("nep171:abc:xyz", "01030000006162630300000078797a")
204 )]
205 #[cfg_attr(
206 feature = "nep245",
207 case("nep245:abc:xyz", "02030000006162630300000078797a")
208 )]
209 #[cfg_attr(feature = "imt", case("imt:abc:xyz", "03030000006162630300000078797a"))]
210 fn roundtrip_fixed(#[case] token_id_str: &str, #[case] borsh_expected_hex: &str) {
211 let token_id: TokenId = token_id_str.parse().unwrap();
212 let borsh_expected = hex::decode(borsh_expected_hex).unwrap();
213
214 let borsh_ser = borsh::to_vec(&token_id).unwrap();
215 assert_eq!(borsh_ser, borsh_expected);
216
217 let got: TokenId = borsh::from_slice(&borsh_ser).unwrap();
218 assert_eq!(got, token_id);
219 assert_eq!(got.to_string(), token_id_str);
220 }
221
222 #[rstest]
223 #[trace]
224 fn borsh_roundtrip(#[from(make_arbitrary)] token_id: TokenId) {
225 let ser = borsh::to_vec(&token_id).unwrap();
226 let got: TokenId = borsh::from_slice(&ser).unwrap();
227 assert_eq!(got, token_id);
228 }
229
230 #[rstest]
231 #[trace]
232 fn display_from_str_roundtrip(#[from(make_arbitrary)] token_id: TokenId) {
233 let s = token_id.to_string();
234 let got: TokenId = s.parse().unwrap();
235 assert_eq!(got, token_id);
236 }
237
238 #[rstest]
239 #[trace]
240 fn serde_roundtrip(#[from(make_arbitrary)] token_id: TokenId) {
241 let ser = serde_json::to_vec(&token_id).unwrap();
242 let got: TokenId = serde_json::from_slice(&ser).unwrap();
243 assert_eq!(got, token_id);
244 }
245}