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