1use crate::data::manifest::ManifestCustomValueKind;
2use crate::data::scrypto::model::*;
3use crate::data::scrypto::*;
4use crate::*;
5use radix_rust::copy_u8_array;
6use sbor::rust::prelude::*;
7use sbor::*;
8
9pub const NON_FUNGIBLE_LOCAL_ID_MAX_LENGTH: usize = 64;
10
11pub trait IsNonFungibleLocalId: Into<NonFungibleLocalId> {
13 fn id_type() -> NonFungibleIdType;
14}
15
16impl IsNonFungibleLocalId for StringNonFungibleLocalId {
17 fn id_type() -> NonFungibleIdType {
18 NonFungibleIdType::String
19 }
20}
21impl IsNonFungibleLocalId for IntegerNonFungibleLocalId {
22 fn id_type() -> NonFungibleIdType {
23 NonFungibleIdType::Integer
24 }
25}
26impl IsNonFungibleLocalId for BytesNonFungibleLocalId {
27 fn id_type() -> NonFungibleIdType {
28 NonFungibleIdType::Bytes
29 }
30}
31impl IsNonFungibleLocalId for RUIDNonFungibleLocalId {
32 fn id_type() -> NonFungibleIdType {
33 NonFungibleIdType::RUID
34 }
35}
36
37pub trait IsNonAutoGeneratedNonFungibleLocalId: IsNonFungibleLocalId {}
39
40impl IsNonAutoGeneratedNonFungibleLocalId for StringNonFungibleLocalId {}
41impl IsNonAutoGeneratedNonFungibleLocalId for IntegerNonFungibleLocalId {}
42impl IsNonAutoGeneratedNonFungibleLocalId for BytesNonFungibleLocalId {}
43
44impl TryFrom<String> for NonFungibleLocalId {
45 type Error = ContentValidationError;
46
47 fn try_from(value: String) -> Result<Self, Self::Error> {
48 Ok(StringNonFungibleLocalId::new(value)?.into())
49 }
50}
51
52impl From<u64> for NonFungibleLocalId {
53 fn from(value: u64) -> Self {
54 IntegerNonFungibleLocalId::new(value).into()
55 }
56}
57
58impl TryFrom<Vec<u8>> for NonFungibleLocalId {
59 type Error = ContentValidationError;
60
61 fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
62 Ok(BytesNonFungibleLocalId::new(value)?.into())
63 }
64}
65
66impl From<[u8; 32]> for NonFungibleLocalId {
67 fn from(value: [u8; 32]) -> Self {
68 Self::RUID(value.into())
69 }
70}
71
72#[cfg_attr(
74 feature = "fuzzing",
75 derive(::arbitrary::Arbitrary, ::serde::Serialize, ::serde::Deserialize)
76)]
77#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
78pub enum NonFungibleLocalId {
79 String(StringNonFungibleLocalId),
83 Integer(IntegerNonFungibleLocalId),
87 Bytes(BytesNonFungibleLocalId),
91 RUID(RUIDNonFungibleLocalId),
95}
96
97impl NonFungibleLocalId {
98 pub fn string<T: AsRef<str>>(value: T) -> Result<Self, ContentValidationError> {
99 StringNonFungibleLocalId::new(value).map(Self::String)
100 }
101
102 pub const fn integer(value: u64) -> Self {
103 Self::Integer(IntegerNonFungibleLocalId(value))
104 }
105
106 pub fn bytes<T: Into<Vec<u8>>>(value: T) -> Result<Self, ContentValidationError> {
107 value.into().try_into()
108 }
109
110 pub const fn ruid(value: [u8; 32]) -> Self {
111 Self::RUID(RUIDNonFungibleLocalId(value))
112 }
113
114 pub fn to_key(&self) -> Vec<u8> {
115 scrypto_encode(self).expect("Failed to encode non-fungible local id")
116 }
117}
118
119impl NonFungibleLocalId {
128 pub const fn const_string(value: &'static str) -> Result<Self, ContentValidationError> {
129 match StringNonFungibleLocalId::validate_slice(value.as_bytes()) {
130 Ok(()) => Ok(Self::String(StringNonFungibleLocalId(Cow::Borrowed(value)))),
131 Err(error) => Err(error),
132 }
133 }
134
135 pub const fn const_integer(value: u64) -> Self {
136 Self::integer(value)
137 }
138
139 pub const fn const_bytes(value: &'static [u8]) -> Result<Self, ContentValidationError> {
140 match BytesNonFungibleLocalId::validate(value) {
141 Ok(()) => Ok(Self::Bytes(BytesNonFungibleLocalId(Cow::Borrowed(value)))),
142 Err(error) => Err(error),
143 }
144 }
145
146 pub const fn const_ruid(value: [u8; 32]) -> Self {
147 Self::ruid(value)
148 }
149}
150
151impl From<StringNonFungibleLocalId> for NonFungibleLocalId {
152 fn from(value: StringNonFungibleLocalId) -> Self {
153 Self::String(value)
154 }
155}
156
157impl From<IntegerNonFungibleLocalId> for NonFungibleLocalId {
158 fn from(value: IntegerNonFungibleLocalId) -> Self {
159 Self::Integer(value)
160 }
161}
162
163impl From<BytesNonFungibleLocalId> for NonFungibleLocalId {
164 fn from(value: BytesNonFungibleLocalId) -> Self {
165 Self::Bytes(value)
166 }
167}
168
169impl From<RUIDNonFungibleLocalId> for NonFungibleLocalId {
170 fn from(value: RUIDNonFungibleLocalId) -> Self {
171 Self::RUID(value)
172 }
173}
174
175#[cfg_attr(feature = "fuzzing", derive(::serde::Serialize, ::serde::Deserialize))]
177#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
178pub struct StringNonFungibleLocalId(Cow<'static, str>);
179
180impl StringNonFungibleLocalId {
181 pub fn new<S: AsRef<str>>(id: S) -> Result<Self, ContentValidationError> {
182 Self::validate_slice(id.as_ref().as_bytes())
183 .map(|_| Self(Cow::Owned(id.as_ref().to_owned())))
184 }
185
186 pub const fn validate_slice(slice: &[u8]) -> Result<(), ContentValidationError> {
187 if slice.is_empty() {
188 return Err(ContentValidationError::Empty);
189 }
190 if slice.len() > NON_FUNGIBLE_LOCAL_ID_MAX_LENGTH {
191 return Err(ContentValidationError::TooLong);
192 }
193
194 let mut index = 0usize;
199 let slice_len = slice.len();
200 while index < slice_len {
201 let byte = slice[index];
202
203 if !(byte >= b'a' && byte <= b'z'
204 || byte >= b'A' && byte <= b'Z'
205 || byte >= b'0' && byte <= b'9'
206 || byte == b'_')
207 {
208 return Err(ContentValidationError::ContainsBadCharacter);
209 }
210
211 index += 1;
212 }
213
214 Ok(())
215 }
216
217 pub fn value(&self) -> &str {
218 self.0.as_ref()
219 }
220
221 pub fn as_bytes(&self) -> &[u8] {
222 self.value().as_bytes()
223 }
224}
225
226#[cfg(feature = "fuzzing")]
227impl<'a> ::arbitrary::Arbitrary<'a> for StringNonFungibleLocalId {
228 fn arbitrary(u: &mut ::arbitrary::Unstructured<'a>) -> ::arbitrary::Result<Self> {
229 let charset: Vec<char> =
230 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWZYZ012345678989_"
231 .chars()
232 .collect();
233 let len: u8 = u
234 .int_in_range(1..=NON_FUNGIBLE_LOCAL_ID_MAX_LENGTH as u8)
235 .unwrap();
236 let s: String = (0..len).map(|_| *u.choose(&charset[..]).unwrap()).collect();
237
238 Ok(Self(Cow::Owned(s)))
239 }
240}
241
242impl TryFrom<String> for StringNonFungibleLocalId {
243 type Error = ContentValidationError;
244
245 fn try_from(value: String) -> Result<Self, Self::Error> {
246 Self::new(value)
247 }
248}
249
250impl TryFrom<&str> for StringNonFungibleLocalId {
251 type Error = ContentValidationError;
252
253 fn try_from(value: &str) -> Result<Self, Self::Error> {
254 Self::new(value)
255 }
256}
257
258#[cfg_attr(
260 feature = "fuzzing",
261 derive(::arbitrary::Arbitrary, ::serde::Serialize, ::serde::Deserialize)
262)]
263#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
264pub struct IntegerNonFungibleLocalId(u64);
265
266impl IntegerNonFungibleLocalId {
267 pub fn new(id: u64) -> Self {
268 Self(id)
269 }
270
271 pub fn value(&self) -> u64 {
272 self.0
273 }
274}
275
276impl From<u64> for IntegerNonFungibleLocalId {
277 fn from(value: u64) -> Self {
278 IntegerNonFungibleLocalId::new(value)
279 }
280}
281
282#[cfg_attr(feature = "fuzzing", derive(::serde::Serialize, ::serde::Deserialize))]
284#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
285pub struct BytesNonFungibleLocalId(Cow<'static, [u8]>);
286
287impl BytesNonFungibleLocalId {
288 pub fn new(id: Vec<u8>) -> Result<Self, ContentValidationError> {
289 Self::validate(&id).map(|_| Self(Cow::Owned(id)))
290 }
291
292 pub const fn validate(slice: &[u8]) -> Result<(), ContentValidationError> {
293 if slice.is_empty() {
294 return Err(ContentValidationError::Empty);
295 }
296 if slice.len() > NON_FUNGIBLE_LOCAL_ID_MAX_LENGTH {
297 return Err(ContentValidationError::TooLong);
298 }
299 Ok(())
300 }
301
302 pub fn value(&self) -> &[u8] {
303 self.0.as_ref()
304 }
305}
306
307#[cfg(feature = "fuzzing")]
308impl<'a> ::arbitrary::Arbitrary<'a> for BytesNonFungibleLocalId {
309 fn arbitrary(u: &mut ::arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
310 let len: u8 = u
311 .int_in_range(1..=NON_FUNGIBLE_LOCAL_ID_MAX_LENGTH as u8)
312 .unwrap();
313 let s = (0..len).map(|_| u8::arbitrary(u).unwrap()).collect();
314
315 Ok(Self(s))
316 }
317}
318
319impl TryFrom<Vec<u8>> for BytesNonFungibleLocalId {
320 type Error = ContentValidationError;
321
322 fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
323 Self::new(value)
324 }
325}
326
327#[cfg_attr(
328 feature = "fuzzing",
329 derive(::arbitrary::Arbitrary, ::serde::Serialize, ::serde::Deserialize)
330)]
331#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
333pub struct RUIDNonFungibleLocalId([u8; 32]);
334
335impl RUIDNonFungibleLocalId {
336 pub fn new(id: [u8; 32]) -> Self {
337 Self(id)
338 }
339
340 pub fn value(&self) -> &[u8; 32] {
341 &self.0
342 }
343}
344
345impl From<[u8; 32]> for RUIDNonFungibleLocalId {
346 fn from(value: [u8; 32]) -> Self {
347 Self::new(value)
348 }
349}
350
351#[derive(Debug, Clone, PartialEq, Eq)]
352pub enum ContentValidationError {
353 TooLong,
354 Empty,
355 ContainsBadCharacter,
356}
357
358impl NonFungibleLocalId {
359 pub fn id_type(&self) -> NonFungibleIdType {
360 match self {
361 NonFungibleLocalId::String(..) => NonFungibleIdType::String,
362 NonFungibleLocalId::Integer(..) => NonFungibleIdType::Integer,
363 NonFungibleLocalId::Bytes(..) => NonFungibleIdType::Bytes,
364 NonFungibleLocalId::RUID(..) => NonFungibleIdType::RUID,
365 }
366 }
367
368 pub fn encode_body_common<X: CustomValueKind, E: Encoder<X>>(
369 &self,
370 encoder: &mut E,
371 ) -> Result<(), EncodeError> {
372 match self {
373 NonFungibleLocalId::String(v) => {
374 encoder.write_discriminator(0)?;
375 encoder.write_size(v.0.len())?;
376 encoder.write_slice(v.as_bytes())?;
377 }
378 NonFungibleLocalId::Integer(v) => {
379 encoder.write_discriminator(1)?;
380 encoder.write_slice(&v.0.to_be_bytes())?; }
382 NonFungibleLocalId::Bytes(v) => {
383 encoder.write_discriminator(2)?;
384 encoder.write_size(v.0.len())?;
385 encoder.write_slice(v.0.as_ref())?;
386 }
387 NonFungibleLocalId::RUID(v) => {
388 encoder.write_discriminator(3)?;
389 encoder.write_slice(v.value().as_slice())?;
390 }
391 }
392 Ok(())
393 }
394
395 pub fn to_vec(&self) -> Vec<u8> {
396 let mut buffer = Vec::new();
397 let mut encoder = ScryptoEncoder::new(&mut buffer, 1);
398 self.encode_body_common(&mut encoder).unwrap();
399 buffer
400 }
401
402 pub fn decode_body_common<X: CustomValueKind, D: Decoder<X>>(
403 decoder: &mut D,
404 ) -> Result<Self, DecodeError> {
405 match decoder.read_discriminator()? {
406 0 => {
407 let size = decoder.read_size()?;
408 let slice = decoder.read_slice(size)?;
409 let str =
410 core::str::from_utf8(slice).map_err(|_| DecodeError::InvalidCustomValue)?;
411 Self::string(str).map_err(|_| DecodeError::InvalidCustomValue)
412 }
413 1 => Ok(Self::integer(u64::from_be_bytes(copy_u8_array(
414 decoder.read_slice(8)?,
415 )))),
416 2 => {
417 let size = decoder.read_size()?;
418 Self::bytes(decoder.read_slice(size)?.to_vec())
419 .map_err(|_| DecodeError::InvalidCustomValue)
420 }
421 3 => Ok(Self::ruid(copy_u8_array(decoder.read_slice(32)?))),
422 _ => Err(DecodeError::InvalidCustomValue),
423 }
424 }
425}
426
427#[derive(Debug, Clone, PartialEq, Eq)]
433pub enum ParseNonFungibleLocalIdError {
434 UnknownType,
435 InvalidInteger,
436 InvalidBytes,
437 InvalidRUID,
438 ContentValidationError(ContentValidationError),
439}
440
441#[cfg(not(feature = "alloc"))]
442impl std::error::Error for ParseNonFungibleLocalIdError {}
443
444#[cfg(not(feature = "alloc"))]
445impl fmt::Display for ParseNonFungibleLocalIdError {
446 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
447 write!(f, "{:?}", self)
448 }
449}
450
451impl Categorize<ScryptoCustomValueKind> for NonFungibleLocalId {
456 #[inline]
457 fn value_kind() -> ValueKind<ScryptoCustomValueKind> {
458 ValueKind::Custom(ScryptoCustomValueKind::NonFungibleLocalId)
459 }
460}
461
462impl<E: Encoder<ScryptoCustomValueKind>> Encode<ScryptoCustomValueKind, E> for NonFungibleLocalId {
463 #[inline]
464 fn encode_value_kind(&self, encoder: &mut E) -> Result<(), EncodeError> {
465 encoder.write_value_kind(Self::value_kind())
466 }
467
468 #[inline]
469 fn encode_body(&self, encoder: &mut E) -> Result<(), EncodeError> {
470 self.encode_body_common(encoder)
471 }
472}
473
474impl<D: Decoder<ScryptoCustomValueKind>> Decode<ScryptoCustomValueKind, D> for NonFungibleLocalId {
475 fn decode_body_with_value_kind(
476 decoder: &mut D,
477 value_kind: ValueKind<ScryptoCustomValueKind>,
478 ) -> Result<Self, DecodeError> {
479 decoder.check_preloaded_value_kind(value_kind, Self::value_kind())?;
480 Self::decode_body_common(decoder)
481 }
482}
483
484impl Categorize<ManifestCustomValueKind> for NonFungibleLocalId {
489 #[inline]
490 fn value_kind() -> ValueKind<ManifestCustomValueKind> {
491 ValueKind::Custom(ManifestCustomValueKind::NonFungibleLocalId)
492 }
493}
494
495impl<E: Encoder<ManifestCustomValueKind>> Encode<ManifestCustomValueKind, E>
496 for NonFungibleLocalId
497{
498 #[inline]
499 fn encode_value_kind(&self, encoder: &mut E) -> Result<(), EncodeError> {
500 encoder.write_value_kind(Self::value_kind())
501 }
502
503 #[inline]
504 fn encode_body(&self, encoder: &mut E) -> Result<(), EncodeError> {
505 self.encode_body_common(encoder)
506 }
507}
508
509impl<D: Decoder<ManifestCustomValueKind>> Decode<ManifestCustomValueKind, D>
510 for NonFungibleLocalId
511{
512 fn decode_body_with_value_kind(
513 decoder: &mut D,
514 value_kind: ValueKind<ManifestCustomValueKind>,
515 ) -> Result<Self, DecodeError> {
516 decoder.check_preloaded_value_kind(value_kind, Self::value_kind())?;
517 Self::decode_body_common(decoder)
518 }
519}
520
521impl Describe<ScryptoCustomTypeKind> for NonFungibleLocalId {
522 const TYPE_ID: RustTypeId =
523 RustTypeId::WellKnown(well_known_scrypto_custom_types::NON_FUNGIBLE_LOCAL_ID_TYPE);
524
525 fn type_data() -> TypeData<ScryptoCustomTypeKind, RustTypeId> {
526 well_known_scrypto_custom_types::non_fungible_local_id_type_data()
527 }
528}
529
530fn is_canonically_formatted_integer(digits: &str) -> bool {
539 if digits == "0" {
540 return true;
541 }
542 let mut chars = digits.chars();
543 let first_char = chars.next();
545 match first_char {
546 None => {
547 return false;
548 }
549 Some('1'..='9') => {}
550 _ => {
551 return false;
552 }
553 }
554 for char in chars {
556 if !char.is_ascii_digit() {
557 return false;
558 }
559 }
560 true
561}
562
563impl FromStr for NonFungibleLocalId {
564 type Err = ParseNonFungibleLocalIdError;
565
566 fn from_str(s: &str) -> Result<Self, Self::Err> {
567 let local_id = if s.starts_with('<') && s.ends_with('>') {
568 Self::string(&s[1..s.len() - 1])
569 .map_err(ParseNonFungibleLocalIdError::ContentValidationError)?
570 } else if s.len() > 1 && s.starts_with('#') && s.ends_with('#') {
571 let digits = &s[1..s.len() - 1];
572 if !is_canonically_formatted_integer(digits) {
573 return Err(ParseNonFungibleLocalIdError::InvalidInteger);
574 }
575 NonFungibleLocalId::integer(
576 s[1..s.len() - 1]
577 .parse::<u64>()
578 .map_err(|_| ParseNonFungibleLocalIdError::InvalidInteger)?,
579 )
580 } else if s.starts_with('[') && s.ends_with(']') {
581 NonFungibleLocalId::bytes(
582 hex::decode(&s[1..s.len() - 1])
583 .map_err(|_| ParseNonFungibleLocalIdError::InvalidBytes)?,
584 )
585 .map_err(ParseNonFungibleLocalIdError::ContentValidationError)?
586 } else if s.starts_with('{') && s.ends_with('}') {
587 let chars: Vec<char> = s[1..s.len() - 1].chars().collect();
588 if chars.len() == 32 * 2 + 3 && chars[16] == '-' && chars[33] == '-' && chars[50] == '-'
589 {
590 let hyphen_stripped: String = chars.into_iter().filter(|c| *c != '-').collect();
591 if hyphen_stripped.len() == 64 {
592 NonFungibleLocalId::RUID(RUIDNonFungibleLocalId(
593 hex::decode(&hyphen_stripped)
594 .map_err(|_| ParseNonFungibleLocalIdError::InvalidRUID)?
595 .try_into()
596 .unwrap(),
597 ))
598 } else {
599 return Err(ParseNonFungibleLocalIdError::InvalidRUID);
600 }
601 } else {
602 return Err(ParseNonFungibleLocalIdError::InvalidRUID);
603 }
604 } else {
605 return Err(ParseNonFungibleLocalIdError::UnknownType);
606 };
607
608 Ok(local_id)
609 }
610}
611
612impl fmt::Display for NonFungibleLocalId {
613 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
614 match self {
615 NonFungibleLocalId::String(v) => write!(f, "<{}>", v.value()),
616 NonFungibleLocalId::Integer(IntegerNonFungibleLocalId(v)) => write!(f, "#{}#", v),
617 NonFungibleLocalId::Bytes(BytesNonFungibleLocalId(v)) => {
618 write!(f, "[{}]", hex::encode(v))
619 }
620 NonFungibleLocalId::RUID(RUIDNonFungibleLocalId(v)) => {
621 let hex = hex::encode(v.as_slice());
622 write!(
623 f,
624 "{{{}-{}-{}-{}}}",
625 &hex[0..16],
626 &hex[16..32],
627 &hex[32..48],
628 &hex[48..64]
629 )
630 }
631 }
632 }
633}
634
635impl fmt::Debug for NonFungibleLocalId {
636 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
637 write!(f, "{}", self)
638 }
639}
640
641#[cfg(test)]
642mod tests {
643 use super::*;
644 use crate::internal_prelude::*;
645
646 #[test]
647 fn test_non_fungible_length_validation() {
648 let validation_result = NonFungibleLocalId::bytes([0; NON_FUNGIBLE_LOCAL_ID_MAX_LENGTH]);
650 assert_matches!(validation_result, Ok(_));
651 let validation_result =
652 NonFungibleLocalId::bytes([0; 1 + NON_FUNGIBLE_LOCAL_ID_MAX_LENGTH]);
653 assert_eq!(validation_result, Err(ContentValidationError::TooLong));
654 let validation_result = NonFungibleLocalId::bytes(vec![]);
655 assert_eq!(validation_result, Err(ContentValidationError::Empty));
656
657 let validation_result =
659 NonFungibleLocalId::string(string_of_length(NON_FUNGIBLE_LOCAL_ID_MAX_LENGTH));
660 assert_matches!(validation_result, Ok(_));
661 let validation_result =
662 NonFungibleLocalId::string(string_of_length(1 + NON_FUNGIBLE_LOCAL_ID_MAX_LENGTH));
663 assert_eq!(validation_result, Err(ContentValidationError::TooLong));
664 let validation_result = NonFungibleLocalId::string("");
665 assert_eq!(validation_result, Err(ContentValidationError::Empty));
666
667 let validation_result =
668 NonFungibleLocalId::from_str("{--------------4----8---------------1}");
669 assert_eq!(
670 validation_result,
671 Err(ParseNonFungibleLocalIdError::InvalidRUID)
672 );
673 }
674
675 fn string_of_length(size: usize) -> String {
676 let mut str_buf = String::new();
677 for _ in 0..size {
678 str_buf.push('a');
679 }
680 str_buf
681 }
682
683 #[test]
684 fn test_non_fungible_string_validation() {
685 let valid_id_string = "abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWZYZ_0123456789";
686 let validation_result = NonFungibleLocalId::string(valid_id_string);
687 assert!(validation_result.is_ok());
688
689 test_invalid_char('.');
690 test_invalid_char('`');
691 test_invalid_char('\\');
692 test_invalid_char('"');
693 test_invalid_char(' ');
694 test_invalid_char('\r');
695 test_invalid_char('\n');
696 test_invalid_char('\t');
697 test_invalid_char('\u{0000}'); test_invalid_char('\u{0301}'); test_invalid_char('\u{2764}'); test_invalid_char('\u{000C}'); test_invalid_char('\u{202D}'); test_invalid_char('\u{202E}'); test_invalid_char('\u{1F600}'); test_invalid_string("`HelloWorld"); test_invalid_string("Hello`World"); test_invalid_string("HelloWorld`"); }
709
710 fn test_invalid_char(char: char) {
711 let validation_result = NonFungibleLocalId::string(format!("valid_{}", char));
712 assert_eq!(
713 validation_result,
714 Err(ContentValidationError::ContainsBadCharacter)
715 );
716 }
717
718 fn test_invalid_string(string: &str) {
719 let validation_result = NonFungibleLocalId::string(string);
720 assert_eq!(
721 validation_result,
722 Err(ContentValidationError::ContainsBadCharacter)
723 );
724 }
725
726 #[test]
727 fn test_from_str() {
728 assert_eq!(
730 NonFungibleLocalId::from_str("#"),
731 Err(ParseNonFungibleLocalIdError::UnknownType)
732 );
733 assert_eq!(
734 NonFungibleLocalId::from_str("{"),
735 Err(ParseNonFungibleLocalIdError::UnknownType)
736 );
737 assert_eq!(
738 NonFungibleLocalId::from_str("<"),
739 Err(ParseNonFungibleLocalIdError::UnknownType)
740 );
741 assert_eq!(
742 NonFungibleLocalId::from_str("["),
743 Err(ParseNonFungibleLocalIdError::UnknownType)
744 );
745 assert_eq!(
747 NonFungibleLocalId::from_str("#1#").unwrap(),
748 NonFungibleLocalId::integer(1)
749 );
750 assert_eq!(
751 NonFungibleLocalId::from_str("#10#").unwrap(),
752 NonFungibleLocalId::integer(10)
753 );
754 assert_eq!(
755 NonFungibleLocalId::from_str("#0#").unwrap(),
756 NonFungibleLocalId::integer(0)
757 );
758 assert_eq!(
760 NonFungibleLocalId::from_str("##"),
761 Err(ParseNonFungibleLocalIdError::InvalidInteger)
762 );
763 assert_eq!(
764 NonFungibleLocalId::from_str("#+10#"),
765 Err(ParseNonFungibleLocalIdError::InvalidInteger)
766 );
767 assert_eq!(
768 NonFungibleLocalId::from_str("#010#"),
769 Err(ParseNonFungibleLocalIdError::InvalidInteger)
770 );
771 assert_eq!(
772 NonFungibleLocalId::from_str("# 10#"),
773 Err(ParseNonFungibleLocalIdError::InvalidInteger)
774 );
775 assert_eq!(
776 NonFungibleLocalId::from_str("#000#"),
777 Err(ParseNonFungibleLocalIdError::InvalidInteger)
778 );
779 assert_eq!(
780 NonFungibleLocalId::from_str("#-10#"),
781 Err(ParseNonFungibleLocalIdError::InvalidInteger)
782 );
783 assert_eq!(
784 NonFungibleLocalId::from_str(
785 "{1111111111111111-1111111111111111-1111111111111111-1111111111111111}"
786 )
787 .unwrap(),
788 NonFungibleLocalId::ruid([0x11; 32])
789 );
790 assert_eq!(
791 NonFungibleLocalId::from_str("<test>").unwrap(),
792 NonFungibleLocalId::string("test").unwrap()
793 );
794 assert_eq!(
795 NonFungibleLocalId::from_str("[010a]").unwrap(),
796 NonFungibleLocalId::bytes(vec![1, 10]).unwrap()
797 );
798 }
799
800 #[test]
801 fn test_to_string() {
802 assert_eq!(NonFungibleLocalId::integer(0).to_string(), "#0#",);
803 assert_eq!(NonFungibleLocalId::integer(1).to_string(), "#1#",);
804 assert_eq!(NonFungibleLocalId::integer(10).to_string(), "#10#",);
805 assert_eq!(
806 NonFungibleLocalId::ruid([
807 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
808 0x22, 0x22, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x44, 0x44, 0x44, 0x44,
809 0x44, 0x44, 0x44, 0x44,
810 ])
811 .to_string(),
812 "{1111111111111111-2222222222222222-3333333333333333-4444444444444444}",
813 );
814 assert_eq!(
815 NonFungibleLocalId::string("test").unwrap().to_string(),
816 "<test>"
817 );
818 assert_eq!(
819 NonFungibleLocalId::bytes(vec![1, 10]).unwrap().to_string(),
820 "[010a]"
821 );
822 }
823
824 #[test]
825 fn const_non_fungible_local_ids_can_be_created() {
826 const _INTEGER: NonFungibleLocalId = NonFungibleLocalId::const_integer(1);
827 const _RUID: NonFungibleLocalId = NonFungibleLocalId::const_ruid([
828 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
829 0x22, 0x22, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x44, 0x44, 0x44, 0x44,
830 0x44, 0x44, 0x44, 0x44,
831 ]);
832 const STRING: Result<NonFungibleLocalId, ContentValidationError> =
833 NonFungibleLocalId::const_string("HelloWorld");
834 const BYTES: Result<NonFungibleLocalId, ContentValidationError> =
835 NonFungibleLocalId::const_bytes(&[
836 110, 101, 118, 101, 114, 32, 103, 111, 110, 110, 97, 32, 103, 105, 118, 101, 32,
837 121, 111, 117, 32, 117, 112,
838 ]);
839
840 assert!(STRING.is_ok());
841 assert!(BYTES.is_ok());
842 }
843}