defuse_core/token_id/
nep245.rs1use std::{fmt, str::FromStr};
2
3use near_sdk::{AccountId, AccountIdRef, near};
4use serde_with::{DeserializeFromStr, SerializeDisplay};
5
6use crate::token_id::{MAX_ALLOWED_TOKEN_ID_LEN, error::TokenIdError};
7
8#[cfg(any(feature = "arbitrary", test))]
9use arbitrary_with::{Arbitrary, As, LimitLen};
10#[cfg(any(feature = "arbitrary", test))]
11use defuse_near_utils::arbitrary::ArbitraryAccountId;
12
13#[cfg_attr(any(feature = "arbitrary", test), derive(Arbitrary))]
14#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, SerializeDisplay, DeserializeFromStr)]
15#[near(serializers = [borsh])]
16pub struct Nep245TokenId {
17 #[cfg_attr(
18 any(feature = "arbitrary", test),
19 arbitrary(with = As::<ArbitraryAccountId>::arbitrary),
20 )]
21 contract_id: AccountId,
22
23 #[cfg_attr(
24 any(feature = "arbitrary", test),
25 arbitrary(with = As::<LimitLen<MAX_ALLOWED_TOKEN_ID_LEN>>::arbitrary),
26 )]
27 mt_token_id: defuse_nep245::TokenId,
28}
29
30impl Nep245TokenId {
31 pub fn new(
32 contract_id: AccountId,
33 mt_token_id: defuse_nep245::TokenId,
34 ) -> Result<Self, TokenIdError> {
35 if mt_token_id.len() > MAX_ALLOWED_TOKEN_ID_LEN {
36 return Err(TokenIdError::TokenIdTooLarge(mt_token_id.len()));
37 }
38
39 Ok(Self {
40 contract_id,
41 mt_token_id,
42 })
43 }
44
45 #[allow(clippy::missing_const_for_fn)]
46 pub fn contract_id(&self) -> &AccountIdRef {
47 &self.contract_id
48 }
49
50 pub const fn mt_token_id(&self) -> &defuse_nep245::TokenId {
51 &self.mt_token_id
52 }
53
54 pub fn into_contract_id_and_mt_token_id(self) -> (AccountId, defuse_nep245::TokenId) {
55 (self.contract_id, self.mt_token_id)
56 }
57}
58
59impl std::fmt::Debug for Nep245TokenId {
60 #[inline]
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 write!(f, "{}:{}", self.contract_id(), self.mt_token_id())
63 }
64}
65
66impl std::fmt::Display for Nep245TokenId {
67 #[inline]
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 fmt::Debug::fmt(&self, f)
70 }
71}
72
73impl FromStr for Nep245TokenId {
74 type Err = TokenIdError;
75
76 fn from_str(data: &str) -> Result<Self, Self::Err> {
77 let (contract_id, token_id) = data
78 .split_once(':')
79 .ok_or(strum::ParseError::VariantNotFound)?;
80 Self::new(contract_id.parse()?, token_id.to_string())
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 use arbitrary::Unstructured;
89 use arbitrary_with::UnstructuredExt;
90 use defuse_test_utils::random::{make_arbitrary, random_bytes};
91 use rstest::rstest;
92
93 #[rstest]
94 #[trace]
95 fn display_from_str_roundtrip(#[from(make_arbitrary)] token_id: Nep245TokenId) {
96 let s = token_id.to_string();
97 let got: Nep245TokenId = s.parse().unwrap();
98 assert_eq!(got, token_id);
99 }
100
101 #[rstest]
102 fn token_id_length(random_bytes: Vec<u8>) {
103 let mut u = Unstructured::new(&random_bytes);
104 let contract_id = u.arbitrary_as::<_, ArbitraryAccountId>().unwrap();
105 let token_id: String = u.arbitrary().unwrap();
106
107 let r = Nep245TokenId::new(contract_id, token_id.clone());
108 if token_id.len() > MAX_ALLOWED_TOKEN_ID_LEN {
109 assert!(matches!(r.unwrap_err(), TokenIdError::TokenIdTooLarge(_)));
110 } else {
111 r.unwrap();
112 }
113 }
114}