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