pub use defuse_nep245::TokenId; use std::{fmt, str::FromStr}; use near_sdk::{AccountId, AccountIdRef, near}; use serde_with::{DeserializeFromStr, SerializeDisplay}; #[cfg(any(feature = "arbitrary", test))] use arbitrary_with::{Arbitrary, As}; #[cfg(any(feature = "arbitrary", test))] use defuse_near_utils::arbitrary::ArbitraryAccountId; use crate::{TokenIdType, error::TokenIdError}; #[cfg_attr(any(feature = "arbitrary", test), derive(Arbitrary))] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, SerializeDisplay, DeserializeFromStr)] #[near(serializers = [borsh])] pub struct Nep245TokenId { #[cfg_attr( any(feature = "arbitrary", test), arbitrary(with = As::::arbitrary), )] contract_id: AccountId, #[cfg_attr( all(not(feature = "unbounded"), any(feature = "arbitrary", test)), arbitrary(with = As::<::arbitrary_with::LimitLen<{crate::MAX_ALLOWED_TOKEN_ID_LEN}>>::arbitrary), )] mt_token_id: TokenId, } impl Nep245TokenId { #[cfg(not(feature = "unbounded"))] pub fn new(contract_id: AccountId, mt_token_id: TokenId) -> Result { if mt_token_id.len() > crate::MAX_ALLOWED_TOKEN_ID_LEN { return Err(TokenIdError::TokenIdTooLarge(mt_token_id.len())); } Ok(Self { contract_id, mt_token_id, }) } #[cfg(feature = "unbounded")] pub fn new(contract_id: AccountId, mt_token_id: TokenId) -> Self { Self { contract_id, mt_token_id, } } #[allow(clippy::missing_const_for_fn)] pub fn contract_id(&self) -> &AccountIdRef { &self.contract_id } pub const fn mt_token_id(&self) -> &TokenId { &self.mt_token_id } pub fn into_contract_id_and_mt_token_id(self) -> (AccountId, TokenId) { (self.contract_id, self.mt_token_id) } } impl std::fmt::Debug for Nep245TokenId { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}:{}", self.contract_id(), self.mt_token_id()) } } impl std::fmt::Display for Nep245TokenId { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self, f) } } impl FromStr for Nep245TokenId { type Err = TokenIdError; fn from_str(data: &str) -> Result { let (contract_id, token_id) = data .split_once(':') .ok_or(strum::ParseError::VariantNotFound)?; let r = Self::new(contract_id.parse()?, token_id.to_string()); #[cfg(feature = "unbounded")] return Ok(r); #[cfg(not(feature = "unbounded"))] return r; } } impl From<&Nep245TokenId> for TokenIdType { #[inline] fn from(_: &Nep245TokenId) -> Self { Self::Nep245 } } #[cfg(test)] mod tests { use super::*; use defuse_test_utils::random::make_arbitrary; #[cfg(not(feature = "unbounded"))] use defuse_test_utils::random::random_bytes; use rstest::rstest; #[rstest] #[trace] fn display_from_str_roundtrip(#[from(make_arbitrary)] token_id: Nep245TokenId) { let s = token_id.to_string(); let got: Nep245TokenId = s.parse().unwrap(); assert_eq!(got, token_id); } #[cfg(not(feature = "unbounded"))] #[rstest] fn token_id_length(random_bytes: Vec) { use arbitrary::Unstructured; use arbitrary_with::UnstructuredExt; let mut u = Unstructured::new(&random_bytes); let contract_id = u.arbitrary_as::<_, ArbitraryAccountId>().unwrap(); let token_id: String = u.arbitrary().unwrap(); let r = Nep245TokenId::new(contract_id, token_id.clone()); if token_id.len() > crate::MAX_ALLOWED_TOKEN_ID_LEN { assert!(matches!(r.unwrap_err(), TokenIdError::TokenIdTooLarge(_))); } else { r.unwrap(); } } }