id.rs (48426B)
1 #![forbid(unsafe_code)] 2 3 #[cfg(not(feature = "std"))] 4 use alloc::{format, string::String, string::ToString, vec::Vec}; 5 6 #[cfg(feature = "std")] 7 use std::{string::String, vec::Vec}; 8 9 use core::{ 10 fmt::{self, Write as _}, 11 str::FromStr, 12 }; 13 use radroots_identity::PublicKey; 14 use url_nostd::Url; 15 16 use crate::envelope::kind::KIND_CLASSIFIED_LISTING; 17 18 #[derive(Clone, Debug, PartialEq, Eq)] 19 pub enum ParseError { 20 Empty, 21 InvalidFormat, 22 InvalidLength { expected: usize, actual: usize }, 23 InvalidCharacter, 24 InvalidPublicKey, 25 UnexpectedKind { expected: u32, actual: u32 }, 26 TooLong { max: usize, actual: usize }, 27 } 28 29 impl fmt::Display for ParseError { 30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 31 match self { 32 Self::Empty => write!(f, "identifier is empty"), 33 Self::InvalidFormat => write!(f, "identifier has invalid format"), 34 Self::InvalidLength { expected, actual } => { 35 write!( 36 f, 37 "identifier length {actual} does not match required length {expected}" 38 ) 39 } 40 Self::InvalidCharacter => write!(f, "identifier contains an invalid character"), 41 Self::InvalidPublicKey => { 42 write!(f, "identifier is not a valid secp256k1 x-only public key") 43 } 44 Self::UnexpectedKind { expected, actual } => { 45 write!( 46 f, 47 "identifier kind {actual} does not match required kind {expected}" 48 ) 49 } 50 Self::TooLong { max, actual } => { 51 write!(f, "identifier length {actual} exceeds maximum length {max}") 52 } 53 } 54 } 55 } 56 57 #[cfg(feature = "std")] 58 impl std::error::Error for ParseError {} 59 60 macro_rules! validated_string_id { 61 ($name:ident, $validator:ident) => { 62 #[cfg_attr(all(test, feature = "std"), derive(dto_bindgen::Dto))] 63 #[cfg_attr(all(test, feature = "std"), dto(as = "string"))] 64 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 65 pub struct $name(String); 66 67 impl $name { 68 pub fn parse(value: impl AsRef<str>) -> Result<Self, ParseError> { 69 $validator(value.as_ref()).map(Self) 70 } 71 72 #[inline] 73 pub fn as_str(&self) -> &str { 74 self.0.as_str() 75 } 76 77 #[inline] 78 pub fn into_string(self) -> String { 79 self.0 80 } 81 } 82 83 impl AsRef<str> for $name { 84 #[inline] 85 fn as_ref(&self) -> &str { 86 self.as_str() 87 } 88 } 89 90 impl From<$name> for String { 91 #[inline] 92 fn from(value: $name) -> Self { 93 value.into_string() 94 } 95 } 96 97 impl fmt::Display for $name { 98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 99 f.write_str(self.as_str()) 100 } 101 } 102 103 impl FromStr for $name { 104 type Err = ParseError; 105 106 fn from_str(value: &str) -> Result<Self, Self::Err> { 107 Self::parse(value) 108 } 109 } 110 111 impl TryFrom<&str> for $name { 112 type Error = ParseError; 113 114 fn try_from(value: &str) -> Result<Self, Self::Error> { 115 Self::parse(value) 116 } 117 } 118 119 impl TryFrom<String> for $name { 120 type Error = ParseError; 121 122 fn try_from(value: String) -> Result<Self, Self::Error> { 123 Self::parse(value) 124 } 125 } 126 127 #[cfg(any(feature = "serde", test))] 128 impl serde::Serialize for $name { 129 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 130 where 131 S: serde::Serializer, 132 { 133 serializer.serialize_str(self.as_str()) 134 } 135 } 136 137 #[cfg(any(feature = "serde", test))] 138 impl<'de> serde::Deserialize<'de> for $name { 139 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 140 where 141 D: serde::Deserializer<'de>, 142 { 143 let value = String::deserialize(deserializer)?; 144 Self::parse(value).map_err(serde::de::Error::custom) 145 } 146 } 147 }; 148 } 149 150 macro_rules! validated_hex_id { 151 ($name:ident, $byte_len:expr) => { 152 #[cfg_attr(all(test, feature = "std"), derive(dto_bindgen::Dto))] 153 #[cfg_attr(all(test, feature = "std"), dto(as = "string"))] 154 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 155 pub struct $name([u8; $byte_len]); 156 157 impl $name { 158 pub fn parse(value: impl AsRef<str>) -> Result<Self, ParseError> { 159 decode_hex::<$byte_len>(value.as_ref()).map(Self) 160 } 161 162 #[inline] 163 pub const fn from_bytes(bytes: [u8; $byte_len]) -> Self { 164 Self(bytes) 165 } 166 167 #[inline] 168 pub const fn as_bytes(&self) -> &[u8; $byte_len] { 169 &self.0 170 } 171 172 #[inline] 173 pub const fn into_bytes(self) -> [u8; $byte_len] { 174 self.0 175 } 176 177 pub fn to_hex(&self) -> String { 178 encode_hex(self.0.as_slice()) 179 } 180 181 #[inline] 182 pub fn into_string(self) -> String { 183 self.to_hex() 184 } 185 } 186 187 impl AsRef<[u8]> for $name { 188 #[inline] 189 fn as_ref(&self) -> &[u8] { 190 self.0.as_slice() 191 } 192 } 193 194 impl From<[u8; $byte_len]> for $name { 195 #[inline] 196 fn from(bytes: [u8; $byte_len]) -> Self { 197 Self::from_bytes(bytes) 198 } 199 } 200 201 impl From<$name> for [u8; $byte_len] { 202 #[inline] 203 fn from(value: $name) -> Self { 204 value.into_bytes() 205 } 206 } 207 208 impl fmt::Display for $name { 209 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 210 write_hex(formatter, self.0.as_slice()) 211 } 212 } 213 214 impl FromStr for $name { 215 type Err = ParseError; 216 217 fn from_str(value: &str) -> Result<Self, Self::Err> { 218 Self::parse(value) 219 } 220 } 221 222 impl TryFrom<&str> for $name { 223 type Error = ParseError; 224 225 fn try_from(value: &str) -> Result<Self, Self::Error> { 226 Self::parse(value) 227 } 228 } 229 230 impl TryFrom<String> for $name { 231 type Error = ParseError; 232 233 fn try_from(value: String) -> Result<Self, Self::Error> { 234 Self::parse(value) 235 } 236 } 237 238 #[cfg(any(feature = "serde", test))] 239 impl serde::Serialize for $name { 240 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 241 where 242 S: serde::Serializer, 243 { 244 serializer.collect_str(self) 245 } 246 } 247 248 #[cfg(any(feature = "serde", test))] 249 impl<'de> serde::Deserialize<'de> for $name { 250 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 251 where 252 D: serde::Deserializer<'de>, 253 { 254 let value = String::deserialize(deserializer)?; 255 Self::parse(value).map_err(serde::de::Error::custom) 256 } 257 } 258 }; 259 } 260 261 validated_hex_id!(EventId, 32); 262 validated_hex_id!(EventSignature, 64); 263 validated_hex_id!(TradeId, 16); 264 validated_hex_id!(CandidateId, 32); 265 validated_hex_id!(MutationId, 32); 266 validated_string_id!(DTag, validate_d_tag); 267 validated_string_id!(AddressableCoordinate, validate_addressable_coordinate); 268 validated_string_id!( 269 ClassifiedListingAddress, 270 validate_classified_listing_address 271 ); 272 validated_string_id!(OrderId, validate_commercial_id); 273 validated_string_id!(OrderQuoteId, validate_commercial_id); 274 validated_string_id!(InventoryBinId, validate_commercial_id); 275 validated_string_id!(EconomicsDigest, validate_economics_digest); 276 validated_hex_id!(EventPointer, 32); 277 validated_string_id!(RelayUrl, validate_relay_url); 278 279 pub(crate) fn parse_public_key(value: impl AsRef<str>) -> Result<PublicKey, ParseError> { 280 PublicKey::from_hex(value.as_ref()).map_err(|error| match error { 281 radroots_identity::Error::InvalidHexLength { expected, actual } 282 | radroots_identity::Error::InvalidByteLength { expected, actual } => { 283 ParseError::InvalidLength { expected, actual } 284 } 285 radroots_identity::Error::InvalidHexCharacter { .. } => ParseError::InvalidCharacter, 286 _ => ParseError::InvalidPublicKey, 287 }) 288 } 289 290 /// Radroots tag-element policy for a NIP-01 coordinate. 291 /// 292 /// NIP-01 does not define this resource limit. 293 pub const RADROOTS_NIP01_COORDINATE_MAX_BYTES: usize = 294 crate::wire::v1::DEFAULT_TAG_ELEMENT_MAX_BYTES; 295 296 #[non_exhaustive] 297 #[derive(Clone, Debug, PartialEq, Eq)] 298 pub enum Nip01CoordinateParseError { 299 Empty, 300 InvalidFormat, 301 Pubkey(ParseError), 302 UnsupportedKind { actual: u32 }, 303 IdentifierMustBeEmpty { kind: u32 }, 304 TooLong { max: usize, actual: usize }, 305 } 306 307 impl fmt::Display for Nip01CoordinateParseError { 308 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 309 match self { 310 Self::Empty => formatter.write_str("NIP-01 coordinate is empty"), 311 Self::InvalidFormat => formatter.write_str("NIP-01 coordinate has invalid format"), 312 Self::Pubkey(error) => { 313 write!(formatter, "NIP-01 coordinate pubkey is invalid: {error}") 314 } 315 Self::UnsupportedKind { actual } => write!( 316 formatter, 317 "NIP-01 coordinate kind {actual} is not replaceable or addressable" 318 ), 319 Self::IdentifierMustBeEmpty { kind } => write!( 320 formatter, 321 "NIP-01 coordinate identifier must be empty for replaceable kind {kind}" 322 ), 323 Self::TooLong { max, actual } => write!( 324 formatter, 325 "NIP-01 coordinate is {actual} bytes; Radroots tag-element max is {max}" 326 ), 327 } 328 } 329 } 330 331 #[cfg(feature = "std")] 332 impl std::error::Error for Nip01CoordinateParseError { 333 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 334 match self { 335 Self::Pubkey(error) => Some(error), 336 _ => None, 337 } 338 } 339 } 340 341 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 342 pub struct Nip01CoordinateParts { 343 pub kind: u32, 344 pub pubkey: PublicKey, 345 pub identifier: String, 346 } 347 348 impl Nip01CoordinateParts { 349 pub fn parse(value: impl AsRef<str>) -> Result<Self, Nip01CoordinateParseError> { 350 Nip01Coordinate::parse(value).map(Nip01Coordinate::into_parts) 351 } 352 } 353 354 /// A canonical NIP-01 replaceable or addressable event coordinate. 355 /// 356 /// Parsing splits only the first two `:` delimiters. The remaining identifier 357 /// is opaque protocol data and is preserved byte-for-byte. 358 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 359 pub struct Nip01Coordinate { 360 canonical: String, 361 kind: u32, 362 pubkey: PublicKey, 363 identifier: String, 364 } 365 366 impl Nip01Coordinate { 367 pub fn parse(value: impl AsRef<str>) -> Result<Self, Nip01CoordinateParseError> { 368 let value = value.as_ref(); 369 if value.is_empty() { 370 return Err(Nip01CoordinateParseError::Empty); 371 } 372 if value.len() > RADROOTS_NIP01_COORDINATE_MAX_BYTES { 373 return Err(Nip01CoordinateParseError::TooLong { 374 max: RADROOTS_NIP01_COORDINATE_MAX_BYTES, 375 actual: value.len(), 376 }); 377 } 378 379 let (kind, remainder) = value 380 .split_once(':') 381 .ok_or(Nip01CoordinateParseError::InvalidFormat)?; 382 let (pubkey, identifier) = remainder 383 .split_once(':') 384 .ok_or(Nip01CoordinateParseError::InvalidFormat)?; 385 let kind = kind 386 .parse::<u32>() 387 .map_err(|_| Nip01CoordinateParseError::InvalidFormat)?; 388 let requires_empty_identifier = matches!(kind, 0 | 3) || (10_000..=19_999).contains(&kind); 389 if !requires_empty_identifier && !(30_000..=39_999).contains(&kind) { 390 return Err(Nip01CoordinateParseError::UnsupportedKind { actual: kind }); 391 } 392 if requires_empty_identifier && !identifier.is_empty() { 393 return Err(Nip01CoordinateParseError::IdentifierMustBeEmpty { kind }); 394 } 395 396 let pubkey = parse_public_key(pubkey).map_err(Nip01CoordinateParseError::Pubkey)?; 397 let identifier = identifier.to_string(); 398 let canonical = format!("{kind}:{pubkey}:{identifier}"); 399 Ok(Self { 400 canonical, 401 kind, 402 pubkey, 403 identifier, 404 }) 405 } 406 407 #[inline] 408 pub fn as_str(&self) -> &str { 409 self.canonical.as_str() 410 } 411 412 #[inline] 413 pub const fn kind(&self) -> u32 { 414 self.kind 415 } 416 417 #[inline] 418 pub const fn pubkey(&self) -> &PublicKey { 419 &self.pubkey 420 } 421 422 #[inline] 423 pub fn identifier(&self) -> &str { 424 self.identifier.as_str() 425 } 426 427 #[inline] 428 pub fn parts(&self) -> Nip01CoordinateParts { 429 Nip01CoordinateParts { 430 kind: self.kind, 431 pubkey: self.pubkey, 432 identifier: self.identifier.clone(), 433 } 434 } 435 436 #[inline] 437 pub fn into_parts(self) -> Nip01CoordinateParts { 438 Nip01CoordinateParts { 439 kind: self.kind, 440 pubkey: self.pubkey, 441 identifier: self.identifier, 442 } 443 } 444 445 #[inline] 446 pub fn into_string(self) -> String { 447 self.canonical 448 } 449 } 450 451 impl AsRef<str> for Nip01Coordinate { 452 #[inline] 453 fn as_ref(&self) -> &str { 454 self.as_str() 455 } 456 } 457 458 impl From<Nip01Coordinate> for String { 459 #[inline] 460 fn from(value: Nip01Coordinate) -> Self { 461 value.into_string() 462 } 463 } 464 465 impl fmt::Display for Nip01Coordinate { 466 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 467 formatter.write_str(self.as_str()) 468 } 469 } 470 471 impl FromStr for Nip01Coordinate { 472 type Err = Nip01CoordinateParseError; 473 474 fn from_str(value: &str) -> Result<Self, Self::Err> { 475 Self::parse(value) 476 } 477 } 478 479 impl TryFrom<&str> for Nip01Coordinate { 480 type Error = Nip01CoordinateParseError; 481 482 fn try_from(value: &str) -> Result<Self, Self::Error> { 483 Self::parse(value) 484 } 485 } 486 487 impl TryFrom<String> for Nip01Coordinate { 488 type Error = Nip01CoordinateParseError; 489 490 fn try_from(value: String) -> Result<Self, Self::Error> { 491 Self::parse(value) 492 } 493 } 494 495 #[cfg(any(feature = "serde", test))] 496 impl serde::Serialize for Nip01Coordinate { 497 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 498 where 499 S: serde::Serializer, 500 { 501 serializer.serialize_str(self.as_str()) 502 } 503 } 504 505 #[cfg(any(feature = "serde", test))] 506 impl<'de> serde::Deserialize<'de> for Nip01Coordinate { 507 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 508 where 509 D: serde::Deserializer<'de>, 510 { 511 let value = <String as serde::Deserialize>::deserialize(deserializer)?; 512 Self::parse(value).map_err(serde::de::Error::custom) 513 } 514 } 515 516 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 517 pub struct AddressableCoordinateParts { 518 pub kind: u32, 519 pub pubkey: PublicKey, 520 pub d_tag: DTag, 521 } 522 523 impl AddressableCoordinateParts { 524 pub fn parse(value: impl AsRef<str>) -> Result<Self, ParseError> { 525 parse_addressable_coordinate_parts(value.as_ref()) 526 } 527 } 528 529 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 530 pub struct EventEnvelopePointer { 531 pub event_id: EventId, 532 pub relays: Vec<String>, 533 } 534 535 impl EventEnvelopePointer { 536 pub fn new<I, S>(event_id: EventId, relays: I) -> Result<Self, ParseError> 537 where 538 I: IntoIterator<Item = S>, 539 S: Into<String>, 540 { 541 let mut canonical_relays = Vec::new(); 542 for relay in relays { 543 let relay = relay.into(); 544 RelayUrl::parse(relay.as_str())?; 545 canonical_relays.push(relay); 546 } 547 Ok(Self { 548 event_id, 549 relays: canonical_relays, 550 }) 551 } 552 } 553 554 fn decode_hex<const N: usize>(value: &str) -> Result<[u8; N], ParseError> { 555 let expected = N * 2; 556 if value.len() != expected { 557 return Err(ParseError::InvalidLength { 558 expected, 559 actual: value.len(), 560 }); 561 } 562 563 let mut decoded = [0_u8; N]; 564 for (index, pair) in value.as_bytes().chunks_exact(2).enumerate() { 565 decoded[index] = (decode_hex_nibble(pair[0])? << 4) | decode_hex_nibble(pair[1])?; 566 } 567 Ok(decoded) 568 } 569 570 fn decode_hex_nibble(value: u8) -> Result<u8, ParseError> { 571 match value { 572 b'0'..=b'9' => Ok(value - b'0'), 573 b'a'..=b'f' => Ok(value - b'a' + 10), 574 b'A'..=b'F' => Ok(value - b'A' + 10), 575 _ => Err(ParseError::InvalidCharacter), 576 } 577 } 578 579 fn encode_hex(bytes: &[u8]) -> String { 580 const LOWER_HEX: &[u8; 16] = b"0123456789abcdef"; 581 let mut encoded = String::with_capacity(bytes.len() * 2); 582 for byte in bytes { 583 encoded.push(LOWER_HEX[usize::from(byte >> 4)] as char); 584 encoded.push(LOWER_HEX[usize::from(byte & 0x0f)] as char); 585 } 586 encoded 587 } 588 589 fn write_hex(formatter: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result { 590 for byte in bytes { 591 formatter.write_char(lower_hex_digit(byte >> 4))?; 592 formatter.write_char(lower_hex_digit(byte & 0x0f))?; 593 } 594 Ok(()) 595 } 596 597 const fn lower_hex_digit(value: u8) -> char { 598 match value { 599 0..=9 => (b'0' + value) as char, 600 _ => (b'a' + value - 10) as char, 601 } 602 } 603 604 fn validate_hex(value: &str, expected_len: usize) -> Result<String, ParseError> { 605 if value.len() != expected_len { 606 return Err(ParseError::InvalidLength { 607 expected: expected_len, 608 actual: value.len(), 609 }); 610 } 611 612 let mut canonical = String::with_capacity(expected_len); 613 for byte in value.bytes() { 614 match byte { 615 b'0'..=b'9' => canonical.push(byte as char), 616 b'a'..=b'f' => canonical.push(byte as char), 617 b'A'..=b'F' => canonical.push((byte + 32) as char), 618 _ => return Err(ParseError::InvalidCharacter), 619 } 620 } 621 Ok(canonical) 622 } 623 624 fn validate_d_tag(value: &str) -> Result<String, ParseError> { 625 validate_visible_token(value, 512) 626 } 627 628 fn validate_commercial_id(value: &str) -> Result<String, ParseError> { 629 validate_visible_token(value, 128) 630 } 631 632 fn validate_economics_digest(value: &str) -> Result<String, ParseError> { 633 if let Some(hex) = value.strip_prefix("sha256:") { 634 validate_hex(hex, 64)?; 635 return Ok(value.to_string()); 636 } 637 validate_visible_token(value, 128) 638 } 639 640 fn validate_addressable_coordinate(value: &str) -> Result<String, ParseError> { 641 parse_addressable_coordinate_parts(value)?; 642 Ok(value.to_string()) 643 } 644 645 fn validate_classified_listing_address(value: &str) -> Result<String, ParseError> { 646 let parts = parse_addressable_coordinate_parts(value)?; 647 if parts.kind != KIND_CLASSIFIED_LISTING { 648 return Err(ParseError::UnexpectedKind { 649 expected: KIND_CLASSIFIED_LISTING, 650 actual: parts.kind, 651 }); 652 } 653 Ok(format!( 654 "{}:{}:{}", 655 KIND_CLASSIFIED_LISTING, parts.pubkey, parts.d_tag 656 )) 657 } 658 659 fn parse_addressable_coordinate_parts( 660 value: &str, 661 ) -> Result<AddressableCoordinateParts, ParseError> { 662 let (kind, remainder) = value.split_once(':').ok_or(ParseError::InvalidFormat)?; 663 let (pubkey, d_tag) = remainder.split_once(':').ok_or(ParseError::InvalidFormat)?; 664 let kind = kind.parse::<u32>().map_err(|_| ParseError::InvalidFormat)?; 665 let pubkey = parse_public_key(pubkey)?; 666 let d_tag = DTag::parse(d_tag)?; 667 Ok(AddressableCoordinateParts { 668 kind, 669 pubkey, 670 d_tag, 671 }) 672 } 673 674 fn validate_visible_token(value: &str, max_len: usize) -> Result<String, ParseError> { 675 if value.is_empty() { 676 return Err(ParseError::Empty); 677 } 678 if value.len() > max_len { 679 return Err(ParseError::TooLong { 680 max: max_len, 681 actual: value.len(), 682 }); 683 } 684 if value.trim() != value 685 || value 686 .chars() 687 .any(|character| character.is_control() || character.is_whitespace()) 688 { 689 return Err(ParseError::InvalidCharacter); 690 } 691 Ok(value.to_string()) 692 } 693 694 pub fn relay_url_is_valid(value: &str) -> bool { 695 validate_relay_url(value).is_ok() 696 } 697 698 fn validate_relay_url(value: &str) -> Result<String, ParseError> { 699 if value.is_empty() { 700 return Err(ParseError::Empty); 701 } 702 if value 703 .chars() 704 .any(|character| character.is_control() || character.is_whitespace()) 705 { 706 return Err(ParseError::InvalidCharacter); 707 } 708 if !(value.starts_with("ws://") || value.starts_with("wss://")) { 709 return Err(ParseError::InvalidFormat); 710 } 711 712 let parsed = Url::parse(value).map_err(|_| ParseError::InvalidFormat)?; 713 if !matches!(parsed.scheme(), "ws" | "wss") 714 || parsed.host_str().is_none_or(str::is_empty) 715 || !parsed.username().is_empty() 716 || parsed.password().is_some() 717 || parsed.fragment().is_some() 718 || parsed.port() == Some(0) 719 { 720 return Err(ParseError::InvalidFormat); 721 } 722 723 // `Url::username` cannot distinguish absent userinfo from an empty userinfo field. 724 let authority = value 725 .split_once("://") 726 .map(|(_, remainder)| remainder.split(['/', '?', '#']).next().unwrap_or(remainder)) 727 .ok_or(ParseError::InvalidFormat)?; 728 if authority.contains('@') { 729 return Err(ParseError::InvalidFormat); 730 } 731 732 Ok(value.to_string()) 733 } 734 735 #[cfg(test)] 736 #[cfg_attr(coverage_nightly, coverage(off))] 737 mod tests { 738 use super::*; 739 740 macro_rules! assert_string_identifier_impls { 741 ($ty:ty, $value:expr) => {{ 742 let value = $value.to_owned(); 743 let value = value.as_str(); 744 let id = <$ty>::parse(value).expect("parse"); 745 746 assert_eq!(id.as_str(), value); 747 assert_eq!(id.as_ref(), value); 748 assert_eq!(id.to_string(), value); 749 assert_eq!( 750 <$ty as core::str::FromStr>::from_str(value).expect("from str"), 751 id 752 ); 753 assert_eq!( 754 <$ty as TryFrom<&str>>::try_from(value).expect("try from str"), 755 id 756 ); 757 assert_eq!( 758 <$ty as TryFrom<String>>::try_from(value.to_owned()).expect("try from string"), 759 id 760 ); 761 let id = <$ty>::parse(value).expect("parse"); 762 let converted: String = String::from(id.clone()); 763 assert_eq!(converted, value); 764 assert_eq!(id.into_string(), value.to_owned()); 765 766 #[cfg(any(feature = "serde", test))] 767 { 768 let id = <$ty>::parse(value).expect("parse"); 769 let encoded = serde_json::to_string(&id).expect("serialize"); 770 let decoded: $ty = serde_json::from_str(&encoded).expect("deserialize"); 771 assert_eq!(decoded.as_str(), value); 772 } 773 }}; 774 } 775 776 macro_rules! assert_hex_identifier_impls { 777 ($ty:ty, $value:expr, $byte_len:expr) => {{ 778 let value = $value.to_owned(); 779 let value = value.as_str(); 780 let id = <$ty>::parse(value).expect("parse"); 781 782 assert_eq!(id.to_hex(), value); 783 assert_eq!(id.as_bytes().len(), $byte_len); 784 assert_eq!(id.as_ref(), id.as_bytes()); 785 assert_eq!(id.to_string(), value); 786 assert_eq!( 787 <$ty as core::str::FromStr>::from_str(value).expect("from str"), 788 id 789 ); 790 assert_eq!( 791 <$ty as TryFrom<&str>>::try_from(value).expect("try from str"), 792 id 793 ); 794 assert_eq!( 795 <$ty as TryFrom<String>>::try_from(value.to_owned()).expect("try from string"), 796 id 797 ); 798 799 let bytes = id.into_bytes(); 800 assert_eq!(<$ty>::from_bytes(bytes), id); 801 assert_eq!(id.into_string(), value.to_owned()); 802 803 #[cfg(any(feature = "serde", test))] 804 { 805 let encoded = serde_json::to_string(&id).expect("serialize"); 806 let decoded: $ty = serde_json::from_str(&encoded).expect("deserialize"); 807 assert_eq!(decoded.to_hex(), value); 808 } 809 }}; 810 } 811 812 fn hex_64(character: char) -> String { 813 crate::test_valid_hex_64(character) 814 } 815 816 fn hex_32(character: char) -> String { 817 core::iter::repeat_n(character, 32).collect() 818 } 819 820 fn hex_128(character: char) -> String { 821 core::iter::repeat_n(character, 128).collect() 822 } 823 824 #[test] 825 fn public_keys_and_event_ids_require_64_hex_chars() { 826 let upper = "585591529DA0BAB31B3B1B1F986611CF5F435DCA84F978C89EE8A40CCA7103DF"; 827 let public_key = parse_public_key(upper).expect("public key"); 828 assert_eq!( 829 public_key.to_hex(), 830 "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df" 831 ); 832 833 let event_id = EventId::parse(hex_64('f')).expect("event id"); 834 assert_eq!(event_id.to_hex(), hex_64('f')); 835 assert_eq!( 836 EventId::parse(" ".repeat(64)).unwrap_err(), 837 ParseError::InvalidCharacter 838 ); 839 assert_eq!( 840 EventId::parse("a".repeat(63)).unwrap_err(), 841 ParseError::InvalidLength { 842 expected: 64, 843 actual: 63 844 } 845 ); 846 } 847 848 #[test] 849 fn id_parse_errors_have_stable_display_messages() { 850 let errors = [ 851 ParseError::Empty, 852 ParseError::InvalidFormat, 853 ParseError::InvalidLength { 854 expected: 64, 855 actual: 7, 856 }, 857 ParseError::InvalidCharacter, 858 ParseError::InvalidPublicKey, 859 ParseError::UnexpectedKind { 860 expected: KIND_CLASSIFIED_LISTING, 861 actual: 30023, 862 }, 863 ParseError::TooLong { 864 max: 128, 865 actual: 129, 866 }, 867 ]; 868 869 for error in errors { 870 assert!(!error.to_string().is_empty()); 871 } 872 assert_eq!( 873 ParseError::UnexpectedKind { 874 expected: KIND_CLASSIFIED_LISTING, 875 actual: 30023, 876 } 877 .to_string(), 878 "identifier kind 30023 does not match required kind 30402" 879 ); 880 } 881 882 #[test] 883 fn signatures_require_128_hex_chars() { 884 let signature = EventSignature::parse(hex_128('B')).expect("signature"); 885 assert_eq!(signature.to_hex(), "b".repeat(128)); 886 assert_eq!( 887 EventSignature::parse(hex_64('b')).unwrap_err(), 888 ParseError::InvalidLength { 889 expected: 128, 890 actual: 64 891 } 892 ); 893 } 894 895 #[test] 896 fn trade_semantic_ids_use_protocol_sized_hex() { 897 let trade_id = TradeId::parse(hex_32('A')).expect("trade id"); 898 assert_eq!(trade_id.to_hex(), hex_32('a')); 899 assert_eq!( 900 TradeId::parse(hex_64('a')).unwrap_err(), 901 ParseError::InvalidLength { 902 expected: 32, 903 actual: 64 904 } 905 ); 906 assert_hex_identifier_impls!(TradeId, &hex_32('a'), 16); 907 assert_hex_identifier_impls!(CandidateId, &hex_64('b'), 32); 908 assert_hex_identifier_impls!(MutationId, &hex_64('c'), 32); 909 } 910 911 #[test] 912 fn d_tags_reject_empty_control_and_whitespace() { 913 assert_eq!(DTag::parse("").unwrap_err(), ParseError::Empty); 914 assert_eq!( 915 DTag::parse(" listing").unwrap_err(), 916 ParseError::InvalidCharacter 917 ); 918 assert_eq!( 919 DTag::parse("listing\none").unwrap_err(), 920 ParseError::InvalidCharacter 921 ); 922 assert_eq!( 923 DTag::parse("farm:farm-1:members").expect("d tag").as_str(), 924 "farm:farm-1:members" 925 ); 926 } 927 928 #[test] 929 fn addressable_coordinates_validate_kind_pubkey_and_d_tag() { 930 let addr = format!("30402:{}:listing-1", hex_64('0')); 931 assert_eq!( 932 AddressableCoordinate::parse(&addr) 933 .expect("coordinate") 934 .as_str(), 935 addr 936 ); 937 assert_eq!( 938 ClassifiedListingAddress::parse("30402:not_hex:listing-1").unwrap_err(), 939 ParseError::InvalidLength { 940 expected: 64, 941 actual: 7 942 } 943 ); 944 assert_eq!( 945 ClassifiedListingAddress::parse(format!("30023:{}:listing-1", hex_64('0'))) 946 .unwrap_err(), 947 ParseError::UnexpectedKind { 948 expected: KIND_CLASSIFIED_LISTING, 949 actual: 30023, 950 } 951 ); 952 let canonical_pubkey = hex_64('a'); 953 for noncanonical_kind in ["030402", "+30402"] { 954 assert_eq!( 955 ClassifiedListingAddress::parse(format!( 956 "{noncanonical_kind}:{}:listing-1", 957 hex_64('A') 958 )) 959 .expect("classified listing coordinate") 960 .as_str(), 961 format!("30402:{canonical_pubkey}:listing-1") 962 ); 963 } 964 assert_eq!( 965 AddressableCoordinate::parse("30402").unwrap_err(), 966 ParseError::InvalidFormat 967 ); 968 assert_eq!( 969 AddressableCoordinate::parse(format!("bad:{}:listing-1", hex_64('a'))).unwrap_err(), 970 ParseError::InvalidFormat 971 ); 972 assert_eq!( 973 AddressableCoordinate::parse(format!("30402:{}:bad d", hex_64('0'))).unwrap_err(), 974 ParseError::InvalidCharacter 975 ); 976 977 let noncanonical = format!("030402:{}:listing-1", hex_64('A')); 978 assert_eq!( 979 AddressableCoordinate::parse(&noncanonical) 980 .expect("validated addressable coordinate") 981 .as_str(), 982 noncanonical 983 ); 984 } 985 986 #[test] 987 fn binary_identifier_order_matches_canonical_hex_order() { 988 let lower = EventId::from_bytes([0_u8; 32]); 989 let mut upper_bytes = [0_u8; 32]; 990 upper_bytes[31] = 1; 991 let upper = EventId::from_bytes(upper_bytes); 992 993 assert!(lower < upper); 994 assert!(lower.to_hex() < upper.to_hex()); 995 996 let mut identifiers = [upper, lower]; 997 identifiers.sort(); 998 assert_eq!(identifiers, [lower, upper]); 999 } 1000 1001 #[test] 1002 fn addressable_coordinate_parts_parse_kind_pubkey_and_d_tag() { 1003 let addr = format!("30402:{}:farm:farm-1:members", hex_64('A')); 1004 let parts = AddressableCoordinateParts::parse(&addr).expect("coordinate parts"); 1005 assert_eq!(parts.kind, 30402); 1006 assert_eq!(parts.pubkey.to_hex(), hex_64('a')); 1007 assert_eq!(parts.d_tag.as_str(), "farm:farm-1:members"); 1008 } 1009 1010 #[test] 1011 fn nip01_coordinates_cover_replaceable_and_addressable_kinds() { 1012 for kind in [0, 3, 10_000, 19_999] { 1013 let coordinate = Nip01Coordinate::parse(format!("+0{kind}:{}:", hex_64('A'))) 1014 .expect("replaceable coordinate"); 1015 assert_eq!(coordinate.kind(), kind); 1016 assert_eq!(coordinate.pubkey().to_hex(), hex_64('a')); 1017 assert_eq!(coordinate.identifier(), ""); 1018 assert_eq!(coordinate.as_str(), format!("{kind}:{}:", hex_64('a'))); 1019 } 1020 1021 for kind in [30_000, 39_999] { 1022 let coordinate = Nip01Coordinate::parse(format!("{kind}:{}:", hex_64('b'))) 1023 .expect("empty addressable coordinate"); 1024 assert_eq!(coordinate.kind(), kind); 1025 assert_eq!(coordinate.identifier(), ""); 1026 } 1027 } 1028 1029 #[test] 1030 fn nip01_coordinate_identifier_is_opaque_after_second_colon() { 1031 let identifier = " victoria:\u{0000}seed:\u{2603}\n"; 1032 let coordinate = Nip01Coordinate::parse(format!("030402:{}:{identifier}", hex_64('A'))) 1033 .expect("opaque addressable coordinate"); 1034 1035 assert_eq!(coordinate.kind(), 30_402); 1036 assert_eq!(coordinate.pubkey().to_hex(), hex_64('a')); 1037 assert_eq!(coordinate.identifier().as_bytes(), identifier.as_bytes()); 1038 assert_eq!( 1039 coordinate.as_str().as_bytes(), 1040 format!("30402:{}:{identifier}", hex_64('a')).as_bytes() 1041 ); 1042 let parts = coordinate.parts(); 1043 assert_eq!(parts.kind, 30_402); 1044 assert_eq!(parts.pubkey.to_hex(), hex_64('a')); 1045 assert_eq!(parts.identifier.as_bytes(), identifier.as_bytes()); 1046 assert_eq!( 1047 Nip01CoordinateParts::parse(coordinate.as_str()).expect("parts"), 1048 parts 1049 ); 1050 } 1051 1052 #[test] 1053 fn nip01_coordinates_reject_unsupported_shapes_and_kinds() { 1054 let pubkey = hex_64('a'); 1055 assert_eq!( 1056 Nip01Coordinate::parse("").unwrap_err(), 1057 Nip01CoordinateParseError::Empty 1058 ); 1059 assert_eq!( 1060 Nip01Coordinate::parse("30000").unwrap_err(), 1061 Nip01CoordinateParseError::InvalidFormat 1062 ); 1063 assert_eq!( 1064 Nip01Coordinate::parse("30000:bad:identifier").unwrap_err(), 1065 Nip01CoordinateParseError::Pubkey(ParseError::InvalidLength { 1066 expected: 64, 1067 actual: 3 1068 }) 1069 ); 1070 for kind in [1, 9_999, 20_000, 29_999, 40_000, u32::MAX] { 1071 assert_eq!( 1072 Nip01Coordinate::parse(format!("{kind}:{pubkey}:")).unwrap_err(), 1073 Nip01CoordinateParseError::UnsupportedKind { actual: kind } 1074 ); 1075 } 1076 for kind in [0, 3, 10_000, 19_999] { 1077 assert_eq!( 1078 Nip01Coordinate::parse(format!("{kind}:{pubkey}:not-empty")).unwrap_err(), 1079 Nip01CoordinateParseError::IdentifierMustBeEmpty { kind } 1080 ); 1081 } 1082 assert_eq!( 1083 Nip01Coordinate::parse(format!("30000:{pubkey}")).unwrap_err(), 1084 Nip01CoordinateParseError::InvalidFormat 1085 ); 1086 } 1087 1088 #[test] 1089 fn nip01_coordinate_errors_and_policy_limit_are_explicit() { 1090 assert_eq!( 1091 RADROOTS_NIP01_COORDINATE_MAX_BYTES, 1092 crate::wire::v1::DEFAULT_TAG_ELEMENT_MAX_BYTES 1093 ); 1094 let errors = [ 1095 Nip01CoordinateParseError::Empty, 1096 Nip01CoordinateParseError::InvalidFormat, 1097 Nip01CoordinateParseError::Pubkey(ParseError::InvalidCharacter), 1098 Nip01CoordinateParseError::UnsupportedKind { actual: 20_000 }, 1099 Nip01CoordinateParseError::IdentifierMustBeEmpty { kind: 10_000 }, 1100 Nip01CoordinateParseError::TooLong { 1101 max: RADROOTS_NIP01_COORDINATE_MAX_BYTES, 1102 actual: RADROOTS_NIP01_COORDINATE_MAX_BYTES + 1, 1103 }, 1104 ]; 1105 for error in errors { 1106 assert!(!error.to_string().is_empty()); 1107 } 1108 } 1109 1110 #[test] 1111 fn nip01_coordinate_enforces_entire_element_byte_limit() { 1112 let prefix = format!("30000:{}:", hex_64('a')); 1113 let exact = format!( 1114 "{prefix}{}", 1115 "x".repeat(RADROOTS_NIP01_COORDINATE_MAX_BYTES - prefix.len()) 1116 ); 1117 let coordinate = Nip01Coordinate::parse(&exact).expect("exact coordinate byte limit"); 1118 assert_eq!( 1119 coordinate.as_str().len(), 1120 RADROOTS_NIP01_COORDINATE_MAX_BYTES 1121 ); 1122 1123 assert_eq!( 1124 Nip01Coordinate::parse(format!("{exact}x")).unwrap_err(), 1125 Nip01CoordinateParseError::TooLong { 1126 max: RADROOTS_NIP01_COORDINATE_MAX_BYTES, 1127 actual: RADROOTS_NIP01_COORDINATE_MAX_BYTES + 1, 1128 } 1129 ); 1130 } 1131 1132 #[test] 1133 fn nip01_coordinate_enforces_multibyte_boundary_by_utf8_bytes() { 1134 let prefix = format!("30000:{}:", hex_64('a')); 1135 let remaining = RADROOTS_NIP01_COORDINATE_MAX_BYTES - prefix.len(); 1136 let identifier = format!("{}x", "\u{00e9}".repeat((remaining - 1) / 2)); 1137 let exact = format!("{prefix}{identifier}"); 1138 assert_eq!(exact.len(), RADROOTS_NIP01_COORDINATE_MAX_BYTES); 1139 Nip01Coordinate::parse(&exact).expect("exact multibyte coordinate limit"); 1140 1141 let overflow = format!("{exact}x"); 1142 assert_eq!( 1143 Nip01Coordinate::parse(&overflow).unwrap_err(), 1144 Nip01CoordinateParseError::TooLong { 1145 max: RADROOTS_NIP01_COORDINATE_MAX_BYTES, 1146 actual: RADROOTS_NIP01_COORDINATE_MAX_BYTES + 1, 1147 } 1148 ); 1149 } 1150 1151 #[test] 1152 fn nip01_coordinate_exposes_explicit_text_and_validating_serde() { 1153 let value = format!("30000:{}:farm:victoria", hex_64('a')); 1154 let coordinate = Nip01Coordinate::parse(&value).expect("coordinate"); 1155 assert_eq!(coordinate.as_ref(), value); 1156 assert_eq!(coordinate.to_string(), value); 1157 assert_eq!( 1158 Nip01Coordinate::from_str(&value).expect("from str"), 1159 coordinate 1160 ); 1161 assert_eq!( 1162 Nip01Coordinate::try_from(value.clone()).expect("try from string"), 1163 coordinate 1164 ); 1165 assert_eq!(String::from(coordinate.clone()), value); 1166 1167 let encoded = serde_json::to_string(&coordinate).expect("serialize"); 1168 assert_eq!( 1169 serde_json::from_str::<Nip01Coordinate>(&encoded).expect("deserialize"), 1170 coordinate 1171 ); 1172 assert!(serde_json::from_str::<Nip01Coordinate>("\"1:bad:\"").is_err()); 1173 } 1174 1175 #[test] 1176 fn commercial_ids_reject_empty_whitespace_control_and_long_values() { 1177 assert_eq!( 1178 OrderId::parse("order-1").expect("order id").as_str(), 1179 "order-1" 1180 ); 1181 assert_eq!( 1182 InventoryBinId::parse("a".repeat(129)).unwrap_err(), 1183 ParseError::TooLong { 1184 max: 128, 1185 actual: 129 1186 } 1187 ); 1188 } 1189 1190 #[test] 1191 fn economics_digest_accepts_sha256_and_existing_wire_tokens() { 1192 let digest = format!("sha256:{}", hex_64('c')); 1193 assert_eq!( 1194 EconomicsDigest::parse(&digest).expect("digest").as_str(), 1195 digest 1196 ); 1197 assert_eq!( 1198 EconomicsDigest::parse("digest-1") 1199 .expect("wire v1 digest") 1200 .as_str(), 1201 "digest-1" 1202 ); 1203 assert_eq!( 1204 EconomicsDigest::parse("sha256:not-hex").unwrap_err(), 1205 ParseError::InvalidLength { 1206 expected: 64, 1207 actual: 7 1208 } 1209 ); 1210 } 1211 1212 #[test] 1213 fn validated_types_do_not_offer_infallible_string_conversion() { 1214 let id = OrderQuoteId::try_from(String::from("quote-1")).expect("quote id"); 1215 assert_eq!(id.as_ref(), "quote-1"); 1216 let parsed: EventPointer = hex_64('d').parse().expect("event pointer"); 1217 assert_eq!(parsed.to_hex(), hex_64('d')); 1218 } 1219 1220 #[test] 1221 fn validated_identifier_wrappers_expose_consistent_traits() { 1222 let addressable = format!("30402:{}:listing-1", hex_64('0')); 1223 1224 assert_hex_identifier_impls!(EventId, hex_64('b').as_str(), 32); 1225 assert_hex_identifier_impls!(EventSignature, hex_128('c').as_str(), 64); 1226 assert_string_identifier_impls!(DTag, "listing-1"); 1227 assert_string_identifier_impls!(AddressableCoordinate, addressable.as_str()); 1228 assert_string_identifier_impls!(ClassifiedListingAddress, addressable.as_str()); 1229 assert_string_identifier_impls!(OrderId, "order-1"); 1230 assert_string_identifier_impls!(OrderQuoteId, "quote-1"); 1231 assert_string_identifier_impls!(InventoryBinId, "bin-1"); 1232 assert_string_identifier_impls!(EconomicsDigest, "digest-1"); 1233 assert_hex_identifier_impls!(EventPointer, hex_64('d').as_str(), 32); 1234 assert_string_identifier_impls!(RelayUrl, "wss://relay.example.com"); 1235 } 1236 1237 #[test] 1238 fn relay_urls_require_valid_websocket_urls() { 1239 assert_eq!( 1240 RelayUrl::parse("ws://relay.example.com") 1241 .expect("relay url") 1242 .as_str(), 1243 "ws://relay.example.com" 1244 ); 1245 assert_eq!( 1246 RelayUrl::parse("wss://relay.example.com") 1247 .expect("relay url") 1248 .as_str(), 1249 "wss://relay.example.com" 1250 ); 1251 assert!(relay_url_is_valid("wss://relay.example.com")); 1252 assert_eq!(RelayUrl::parse("").unwrap_err(), ParseError::Empty); 1253 assert_eq!( 1254 RelayUrl::parse("http://relay.example.com").unwrap_err(), 1255 ParseError::InvalidFormat 1256 ); 1257 assert_eq!( 1258 RelayUrl::parse("WSS://relay.example.com").unwrap_err(), 1259 ParseError::InvalidFormat 1260 ); 1261 assert_eq!( 1262 RelayUrl::parse("ws://").unwrap_err(), 1263 ParseError::InvalidFormat 1264 ); 1265 assert_eq!( 1266 RelayUrl::parse("wss://").unwrap_err(), 1267 ParseError::InvalidFormat 1268 ); 1269 assert_eq!( 1270 RelayUrl::parse(" wss://relay.example.com").unwrap_err(), 1271 ParseError::InvalidCharacter 1272 ); 1273 assert_eq!( 1274 RelayUrl::parse("wss://relay.example.com\nmiddle").unwrap_err(), 1275 ParseError::InvalidCharacter 1276 ); 1277 assert_eq!( 1278 RelayUrl::parse("wss://user@relay.example.com").unwrap_err(), 1279 ParseError::InvalidFormat 1280 ); 1281 assert_eq!( 1282 RelayUrl::parse("wss://user:secret@relay.example.com").unwrap_err(), 1283 ParseError::InvalidFormat 1284 ); 1285 assert_eq!( 1286 RelayUrl::parse("wss://relay.example.com#read").unwrap_err(), 1287 ParseError::InvalidFormat 1288 ); 1289 assert_eq!( 1290 RelayUrl::parse("wss://relay.example.com:0").unwrap_err(), 1291 ParseError::InvalidFormat 1292 ); 1293 assert_eq!( 1294 RelayUrl::parse("wss://relay.example.com/nostr/v1?region=ca-bc") 1295 .expect("relay URL with path and query") 1296 .as_str(), 1297 "wss://relay.example.com/nostr/v1?region=ca-bc" 1298 ); 1299 } 1300 1301 #[test] 1302 fn nostr_event_pointers_validate_relay_values() { 1303 let event_id = EventId::parse(hex_64('e')).expect("event id"); 1304 let pointer = EventEnvelopePointer::new( 1305 event_id, 1306 ["wss://relay.one.example", "wss://relay.two.example"], 1307 ) 1308 .expect("pointer"); 1309 1310 assert_eq!(pointer.event_id, event_id); 1311 assert_eq!( 1312 pointer.relays, 1313 vec![ 1314 "wss://relay.one.example".to_owned(), 1315 "wss://relay.two.example".to_owned() 1316 ] 1317 ); 1318 1319 assert_eq!( 1320 EventEnvelopePointer::new(EventId::parse(hex_64('e')).expect("event id"), [""],) 1321 .unwrap_err(), 1322 ParseError::Empty 1323 ); 1324 assert_eq!( 1325 EventEnvelopePointer::new( 1326 EventId::parse(hex_64('e')).expect("event id"), 1327 ["http://relay.example"], 1328 ) 1329 .unwrap_err(), 1330 ParseError::InvalidFormat 1331 ); 1332 assert_eq!( 1333 EventEnvelopePointer::new( 1334 EventId::parse(hex_64('e')).expect("event id"), 1335 [" wss://relay.example"], 1336 ) 1337 .unwrap_err(), 1338 ParseError::InvalidCharacter 1339 ); 1340 assert_eq!( 1341 EventEnvelopePointer::new( 1342 EventId::parse(hex_64('e')).expect("event id"), 1343 ["wss://relay.example\n"], 1344 ) 1345 .unwrap_err(), 1346 ParseError::InvalidCharacter 1347 ); 1348 } 1349 1350 #[cfg(feature = "serde")] 1351 #[test] 1352 fn serde_deserialization_validates_identifiers() { 1353 let encoded = format!("\"{}\"", hex_64('E')); 1354 let event_id: EventId = serde_json::from_str(&encoded).expect("event id"); 1355 assert_eq!(event_id.to_hex(), hex_64('e')); 1356 1357 let invalid = serde_json::from_str::<OrderId>("\"bad id\""); 1358 assert!(invalid.is_err()); 1359 assert_eq!( 1360 serde_json::to_string(&event_id).expect("json"), 1361 format!("\"{}\"", hex_64('e')) 1362 ); 1363 } 1364 1365 #[cfg(feature = "serde")] 1366 #[test] 1367 fn serde_missing_fields_exercise_identifier_deserializers() { 1368 #[allow(dead_code)] 1369 #[derive(Debug, serde::Deserialize)] 1370 struct MissingPublicKey { 1371 value: PublicKey, 1372 } 1373 #[allow(dead_code)] 1374 #[derive(Debug, serde::Deserialize)] 1375 struct MissingEventId { 1376 value: EventId, 1377 } 1378 #[allow(dead_code)] 1379 #[derive(Debug, serde::Deserialize)] 1380 struct MissingEventSignature { 1381 value: EventSignature, 1382 } 1383 #[allow(dead_code)] 1384 #[derive(Debug, serde::Deserialize)] 1385 struct MissingTradeId { 1386 value: TradeId, 1387 } 1388 #[allow(dead_code)] 1389 #[derive(Debug, serde::Deserialize)] 1390 struct MissingTradeCandidateId { 1391 value: CandidateId, 1392 } 1393 #[allow(dead_code)] 1394 #[derive(Debug, serde::Deserialize)] 1395 struct MissingTradeMutationId { 1396 value: MutationId, 1397 } 1398 #[allow(dead_code)] 1399 #[derive(Debug, serde::Deserialize)] 1400 struct MissingDTag { 1401 value: DTag, 1402 } 1403 #[allow(dead_code)] 1404 #[derive(Debug, serde::Deserialize)] 1405 struct MissingClassifiedListingAddress { 1406 value: ClassifiedListingAddress, 1407 } 1408 #[allow(dead_code)] 1409 #[derive(Debug, serde::Deserialize)] 1410 struct MissingAddressableCoordinate { 1411 value: AddressableCoordinate, 1412 } 1413 #[allow(dead_code)] 1414 #[derive(Debug, serde::Deserialize)] 1415 struct MissingOrderId { 1416 value: OrderId, 1417 } 1418 #[allow(dead_code)] 1419 #[derive(Debug, serde::Deserialize)] 1420 struct MissingOrderQuoteId { 1421 value: OrderQuoteId, 1422 } 1423 #[allow(dead_code)] 1424 #[derive(Debug, serde::Deserialize)] 1425 struct MissingInventoryBinId { 1426 value: InventoryBinId, 1427 } 1428 1429 fn missing_field_message<T>() -> String 1430 where 1431 T: serde::de::DeserializeOwned + core::fmt::Debug, 1432 { 1433 serde_json::from_str::<T>("{}") 1434 .expect_err("missing field") 1435 .to_string() 1436 } 1437 1438 let missing = "missing field `value` at line 1 column 2"; 1439 assert_eq!(missing_field_message::<MissingPublicKey>(), missing); 1440 assert_eq!(missing_field_message::<MissingEventId>(), missing); 1441 assert_eq!(missing_field_message::<MissingEventSignature>(), missing); 1442 assert_eq!(missing_field_message::<MissingTradeId>(), missing); 1443 assert_eq!(missing_field_message::<MissingTradeCandidateId>(), missing); 1444 assert_eq!(missing_field_message::<MissingTradeMutationId>(), missing); 1445 assert_eq!(missing_field_message::<MissingDTag>(), missing); 1446 assert_eq!( 1447 missing_field_message::<MissingClassifiedListingAddress>(), 1448 missing 1449 ); 1450 assert_eq!( 1451 missing_field_message::<MissingAddressableCoordinate>(), 1452 missing 1453 ); 1454 assert_eq!(missing_field_message::<MissingOrderId>(), missing); 1455 assert_eq!(missing_field_message::<MissingOrderQuoteId>(), missing); 1456 assert_eq!(missing_field_message::<MissingInventoryBinId>(), missing); 1457 1458 let order: OrderId = 1459 serde_json::from_value(serde_json::json!("order-1")).expect("order from value"); 1460 let listing: ClassifiedListingAddress = serde_json::from_value(serde_json::json!(format!( 1461 "30402:{}:listing-1", 1462 hex_64('a') 1463 ))) 1464 .expect("listing from value"); 1465 let quote: OrderQuoteId = 1466 serde_json::from_value(serde_json::json!("quote-1")).expect("quote from value"); 1467 assert_eq!(order.as_str(), "order-1"); 1468 assert_eq!(listing.as_str().split(':').next(), Some("30402")); 1469 assert_eq!(quote.as_str(), "quote-1"); 1470 } 1471 }