defuse_core/token_id/
nep245.rs

1use 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}