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