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