this repo has no description
1use serde::{Deserialize, Serialize}; 2use std::borrow::Cow; 3use std::fmt; 4use std::ops::Deref; 5use std::str::FromStr; 6 7#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, sqlx::Type)] 8#[serde(transparent)] 9#[sqlx(transparent)] 10pub struct Did(String); 11 12impl<'de> Deserialize<'de> for Did { 13 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 14 where 15 D: serde::Deserializer<'de>, 16 { 17 let s = String::deserialize(deserializer)?; 18 Did::new(&s).map_err(|e| serde::de::Error::custom(e.to_string())) 19 } 20} 21 22impl From<Did> for String { 23 fn from(did: Did) -> Self { 24 did.0 25 } 26} 27 28impl From<String> for Did { 29 fn from(s: String) -> Self { 30 Did(s) 31 } 32} 33 34impl<'a> From<&'a Did> for Cow<'a, str> { 35 fn from(did: &'a Did) -> Self { 36 Cow::Borrowed(&did.0) 37 } 38} 39 40impl Did { 41 pub fn new(s: impl Into<String>) -> Result<Self, DidError> { 42 let s = s.into(); 43 jacquard::types::string::Did::new(&s).map_err(|_| DidError::Invalid(s.clone()))?; 44 Ok(Self(s)) 45 } 46 47 pub fn new_unchecked(s: impl Into<String>) -> Self { 48 Self(s.into()) 49 } 50 51 pub fn as_str(&self) -> &str { 52 &self.0 53 } 54 55 pub fn into_inner(self) -> String { 56 self.0 57 } 58 59 pub fn is_plc(&self) -> bool { 60 self.0.starts_with("did:plc:") 61 } 62 63 pub fn is_web(&self) -> bool { 64 self.0.starts_with("did:web:") 65 } 66} 67 68impl AsRef<str> for Did { 69 fn as_ref(&self) -> &str { 70 &self.0 71 } 72} 73 74impl PartialEq<str> for Did { 75 fn eq(&self, other: &str) -> bool { 76 self.0 == other 77 } 78} 79 80impl PartialEq<&str> for Did { 81 fn eq(&self, other: &&str) -> bool { 82 self.0 == *other 83 } 84} 85 86impl PartialEq<String> for Did { 87 fn eq(&self, other: &String) -> bool { 88 self.0 == *other 89 } 90} 91 92impl PartialEq<Did> for String { 93 fn eq(&self, other: &Did) -> bool { 94 *self == other.0 95 } 96} 97 98impl PartialEq<Did> for &str { 99 fn eq(&self, other: &Did) -> bool { 100 *self == other.0 101 } 102} 103 104impl Deref for Did { 105 type Target = str; 106 107 fn deref(&self) -> &Self::Target { 108 &self.0 109 } 110} 111 112impl fmt::Display for Did { 113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 114 write!(f, "{}", self.0) 115 } 116} 117 118impl FromStr for Did { 119 type Err = DidError; 120 121 fn from_str(s: &str) -> Result<Self, Self::Err> { 122 Self::new(s) 123 } 124} 125 126#[derive(Debug, Clone)] 127pub enum DidError { 128 Invalid(String), 129} 130 131impl fmt::Display for DidError { 132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 133 match self { 134 Self::Invalid(s) => write!(f, "invalid DID: {}", s), 135 } 136 } 137} 138 139impl std::error::Error for DidError {} 140 141#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)] 142#[serde(transparent)] 143#[sqlx(transparent)] 144pub struct Handle(String); 145 146impl From<Handle> for String { 147 fn from(handle: Handle) -> Self { 148 handle.0 149 } 150} 151 152impl From<String> for Handle { 153 fn from(s: String) -> Self { 154 Handle(s) 155 } 156} 157 158impl<'a> From<&'a Handle> for Cow<'a, str> { 159 fn from(handle: &'a Handle) -> Self { 160 Cow::Borrowed(&handle.0) 161 } 162} 163 164impl Handle { 165 pub fn new(s: impl Into<String>) -> Result<Self, HandleError> { 166 let s = s.into(); 167 jacquard::types::string::Handle::new(&s).map_err(|_| HandleError::Invalid(s.clone()))?; 168 Ok(Self(s)) 169 } 170 171 pub fn new_unchecked(s: impl Into<String>) -> Self { 172 Self(s.into()) 173 } 174 175 pub fn as_str(&self) -> &str { 176 &self.0 177 } 178 179 pub fn into_inner(self) -> String { 180 self.0 181 } 182} 183 184impl AsRef<str> for Handle { 185 fn as_ref(&self) -> &str { 186 &self.0 187 } 188} 189 190impl Deref for Handle { 191 type Target = str; 192 193 fn deref(&self) -> &Self::Target { 194 &self.0 195 } 196} 197 198impl PartialEq<str> for Handle { 199 fn eq(&self, other: &str) -> bool { 200 self.0 == other 201 } 202} 203 204impl PartialEq<&str> for Handle { 205 fn eq(&self, other: &&str) -> bool { 206 self.0 == *other 207 } 208} 209 210impl PartialEq<String> for Handle { 211 fn eq(&self, other: &String) -> bool { 212 self.0 == *other 213 } 214} 215 216impl PartialEq<Handle> for String { 217 fn eq(&self, other: &Handle) -> bool { 218 *self == other.0 219 } 220} 221 222impl PartialEq<Handle> for &str { 223 fn eq(&self, other: &Handle) -> bool { 224 *self == other.0 225 } 226} 227 228impl fmt::Display for Handle { 229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 230 write!(f, "{}", self.0) 231 } 232} 233 234impl FromStr for Handle { 235 type Err = HandleError; 236 237 fn from_str(s: &str) -> Result<Self, Self::Err> { 238 Self::new(s) 239 } 240} 241 242#[derive(Debug, Clone)] 243pub enum HandleError { 244 Invalid(String), 245} 246 247impl fmt::Display for HandleError { 248 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 249 match self { 250 Self::Invalid(s) => write!(f, "invalid handle: {}", s), 251 } 252 } 253} 254 255impl std::error::Error for HandleError {} 256 257#[derive(Debug, Clone, PartialEq, Eq, Hash)] 258pub enum AtIdentifier { 259 Did(Did), 260 Handle(Handle), 261} 262 263impl AtIdentifier { 264 pub fn new(s: impl AsRef<str>) -> Result<Self, AtIdentifierError> { 265 let s = s.as_ref(); 266 if s.starts_with("did:") { 267 Did::new(s) 268 .map(AtIdentifier::Did) 269 .map_err(|_| AtIdentifierError::Invalid(s.to_string())) 270 } else { 271 Handle::new(s) 272 .map(AtIdentifier::Handle) 273 .map_err(|_| AtIdentifierError::Invalid(s.to_string())) 274 } 275 } 276 277 pub fn as_str(&self) -> &str { 278 match self { 279 AtIdentifier::Did(d) => d.as_str(), 280 AtIdentifier::Handle(h) => h.as_str(), 281 } 282 } 283 284 pub fn into_inner(self) -> String { 285 match self { 286 AtIdentifier::Did(d) => d.into_inner(), 287 AtIdentifier::Handle(h) => h.into_inner(), 288 } 289 } 290 291 pub fn is_did(&self) -> bool { 292 matches!(self, AtIdentifier::Did(_)) 293 } 294 295 pub fn is_handle(&self) -> bool { 296 matches!(self, AtIdentifier::Handle(_)) 297 } 298 299 pub fn as_did(&self) -> Option<&Did> { 300 match self { 301 AtIdentifier::Did(d) => Some(d), 302 AtIdentifier::Handle(_) => None, 303 } 304 } 305 306 pub fn as_handle(&self) -> Option<&Handle> { 307 match self { 308 AtIdentifier::Handle(h) => Some(h), 309 AtIdentifier::Did(_) => None, 310 } 311 } 312} 313 314impl From<Did> for AtIdentifier { 315 fn from(did: Did) -> Self { 316 AtIdentifier::Did(did) 317 } 318} 319 320impl From<Handle> for AtIdentifier { 321 fn from(handle: Handle) -> Self { 322 AtIdentifier::Handle(handle) 323 } 324} 325 326impl AsRef<str> for AtIdentifier { 327 fn as_ref(&self) -> &str { 328 self.as_str() 329 } 330} 331 332impl Deref for AtIdentifier { 333 type Target = str; 334 335 fn deref(&self) -> &Self::Target { 336 self.as_str() 337 } 338} 339 340impl fmt::Display for AtIdentifier { 341 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 342 write!(f, "{}", self.as_str()) 343 } 344} 345 346impl FromStr for AtIdentifier { 347 type Err = AtIdentifierError; 348 349 fn from_str(s: &str) -> Result<Self, Self::Err> { 350 Self::new(s) 351 } 352} 353 354impl Serialize for AtIdentifier { 355 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 356 where 357 S: serde::Serializer, 358 { 359 serializer.serialize_str(self.as_str()) 360 } 361} 362 363impl<'de> Deserialize<'de> for AtIdentifier { 364 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 365 where 366 D: serde::Deserializer<'de>, 367 { 368 let s = String::deserialize(deserializer)?; 369 AtIdentifier::new(&s).map_err(serde::de::Error::custom) 370 } 371} 372 373#[derive(Debug, Clone)] 374pub enum AtIdentifierError { 375 Invalid(String), 376} 377 378impl fmt::Display for AtIdentifierError { 379 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 380 match self { 381 Self::Invalid(s) => write!(f, "invalid AT identifier: {}", s), 382 } 383 } 384} 385 386impl std::error::Error for AtIdentifierError {} 387 388#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)] 389#[serde(transparent)] 390#[sqlx(type_name = "rkey")] 391pub struct Rkey(String); 392 393impl From<Rkey> for String { 394 fn from(rkey: Rkey) -> Self { 395 rkey.0 396 } 397} 398 399impl From<String> for Rkey { 400 fn from(s: String) -> Self { 401 Rkey(s) 402 } 403} 404 405impl<'a> From<&'a Rkey> for Cow<'a, str> { 406 fn from(rkey: &'a Rkey) -> Self { 407 Cow::Borrowed(&rkey.0) 408 } 409} 410 411impl Rkey { 412 pub fn new(s: impl Into<String>) -> Result<Self, RkeyError> { 413 let s = s.into(); 414 jacquard::types::string::Rkey::new(&s).map_err(|_| RkeyError::Invalid(s.clone()))?; 415 Ok(Self(s)) 416 } 417 418 pub fn new_unchecked(s: impl Into<String>) -> Self { 419 Self(s.into()) 420 } 421 422 pub fn generate() -> Self { 423 use jacquard::types::integer::LimitedU32; 424 Self(jacquard::types::string::Tid::now(LimitedU32::MIN).to_string()) 425 } 426 427 pub fn as_str(&self) -> &str { 428 &self.0 429 } 430 431 pub fn into_inner(self) -> String { 432 self.0 433 } 434 435 pub fn is_tid(&self) -> bool { 436 jacquard::types::string::Tid::from_str(&self.0).is_ok() 437 } 438} 439 440impl AsRef<str> for Rkey { 441 fn as_ref(&self) -> &str { 442 &self.0 443 } 444} 445 446impl Deref for Rkey { 447 type Target = str; 448 449 fn deref(&self) -> &Self::Target { 450 &self.0 451 } 452} 453 454impl PartialEq<str> for Rkey { 455 fn eq(&self, other: &str) -> bool { 456 self.0 == other 457 } 458} 459 460impl PartialEq<&str> for Rkey { 461 fn eq(&self, other: &&str) -> bool { 462 self.0 == *other 463 } 464} 465 466impl PartialEq<String> for Rkey { 467 fn eq(&self, other: &String) -> bool { 468 self.0 == *other 469 } 470} 471 472impl PartialEq<Rkey> for String { 473 fn eq(&self, other: &Rkey) -> bool { 474 *self == other.0 475 } 476} 477 478impl PartialEq<Rkey> for &str { 479 fn eq(&self, other: &Rkey) -> bool { 480 *self == other.0 481 } 482} 483 484impl fmt::Display for Rkey { 485 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 486 write!(f, "{}", self.0) 487 } 488} 489 490impl FromStr for Rkey { 491 type Err = RkeyError; 492 493 fn from_str(s: &str) -> Result<Self, Self::Err> { 494 Self::new(s) 495 } 496} 497 498#[derive(Debug, Clone)] 499pub enum RkeyError { 500 Invalid(String), 501} 502 503impl fmt::Display for RkeyError { 504 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 505 match self { 506 Self::Invalid(s) => write!(f, "invalid rkey: {}", s), 507 } 508 } 509} 510 511impl std::error::Error for RkeyError {} 512 513#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)] 514#[serde(transparent)] 515#[sqlx(type_name = "nsid")] 516pub struct Nsid(String); 517 518impl From<Nsid> for String { 519 fn from(nsid: Nsid) -> Self { 520 nsid.0 521 } 522} 523 524impl From<String> for Nsid { 525 fn from(s: String) -> Self { 526 Nsid(s) 527 } 528} 529 530impl<'a> From<&'a Nsid> for Cow<'a, str> { 531 fn from(nsid: &'a Nsid) -> Self { 532 Cow::Borrowed(&nsid.0) 533 } 534} 535 536impl Nsid { 537 pub fn new(s: impl Into<String>) -> Result<Self, NsidError> { 538 let s = s.into(); 539 jacquard::types::string::Nsid::new(&s).map_err(|_| NsidError::Invalid(s.clone()))?; 540 Ok(Self(s)) 541 } 542 543 pub fn new_unchecked(s: impl Into<String>) -> Self { 544 Self(s.into()) 545 } 546 547 pub fn as_str(&self) -> &str { 548 &self.0 549 } 550 551 pub fn into_inner(self) -> String { 552 self.0 553 } 554 555 pub fn authority(&self) -> Option<&str> { 556 let parts: Vec<&str> = self.0.rsplitn(2, '.').collect(); 557 if parts.len() == 2 { 558 Some(parts[1]) 559 } else { 560 None 561 } 562 } 563 564 pub fn name(&self) -> Option<&str> { 565 self.0.rsplit('.').next() 566 } 567} 568 569impl AsRef<str> for Nsid { 570 fn as_ref(&self) -> &str { 571 &self.0 572 } 573} 574 575impl Deref for Nsid { 576 type Target = str; 577 578 fn deref(&self) -> &Self::Target { 579 &self.0 580 } 581} 582 583impl PartialEq<str> for Nsid { 584 fn eq(&self, other: &str) -> bool { 585 self.0 == other 586 } 587} 588 589impl PartialEq<&str> for Nsid { 590 fn eq(&self, other: &&str) -> bool { 591 self.0 == *other 592 } 593} 594 595impl PartialEq<String> for Nsid { 596 fn eq(&self, other: &String) -> bool { 597 self.0 == *other 598 } 599} 600 601impl PartialEq<Nsid> for String { 602 fn eq(&self, other: &Nsid) -> bool { 603 *self == other.0 604 } 605} 606 607impl PartialEq<Nsid> for &str { 608 fn eq(&self, other: &Nsid) -> bool { 609 *self == other.0 610 } 611} 612 613impl fmt::Display for Nsid { 614 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 615 write!(f, "{}", self.0) 616 } 617} 618 619impl FromStr for Nsid { 620 type Err = NsidError; 621 622 fn from_str(s: &str) -> Result<Self, Self::Err> { 623 Self::new(s) 624 } 625} 626 627#[derive(Debug, Clone)] 628pub enum NsidError { 629 Invalid(String), 630} 631 632impl fmt::Display for NsidError { 633 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 634 match self { 635 Self::Invalid(s) => write!(f, "invalid NSID: {}", s), 636 } 637 } 638} 639 640impl std::error::Error for NsidError {} 641 642#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)] 643#[serde(transparent)] 644#[sqlx(type_name = "at_uri")] 645pub struct AtUri(String); 646 647impl From<AtUri> for String { 648 fn from(uri: AtUri) -> Self { 649 uri.0 650 } 651} 652 653impl From<String> for AtUri { 654 fn from(s: String) -> Self { 655 AtUri(s) 656 } 657} 658 659impl<'a> From<&'a AtUri> for Cow<'a, str> { 660 fn from(uri: &'a AtUri) -> Self { 661 Cow::Borrowed(&uri.0) 662 } 663} 664 665impl AtUri { 666 pub fn new(s: impl Into<String>) -> Result<Self, AtUriError> { 667 let s = s.into(); 668 jacquard::types::string::AtUri::new(&s).map_err(|_| AtUriError::Invalid(s.clone()))?; 669 Ok(Self(s)) 670 } 671 672 pub fn new_unchecked(s: impl Into<String>) -> Self { 673 Self(s.into()) 674 } 675 676 pub fn from_parts(did: &str, collection: &str, rkey: &str) -> Self { 677 Self(format!("at://{}/{}/{}", did, collection, rkey)) 678 } 679 680 pub fn as_str(&self) -> &str { 681 &self.0 682 } 683 684 pub fn into_inner(self) -> String { 685 self.0 686 } 687} 688 689impl AsRef<str> for AtUri { 690 fn as_ref(&self) -> &str { 691 &self.0 692 } 693} 694 695impl Deref for AtUri { 696 type Target = str; 697 698 fn deref(&self) -> &Self::Target { 699 &self.0 700 } 701} 702 703impl PartialEq<str> for AtUri { 704 fn eq(&self, other: &str) -> bool { 705 self.0 == other 706 } 707} 708 709impl PartialEq<&str> for AtUri { 710 fn eq(&self, other: &&str) -> bool { 711 self.0 == *other 712 } 713} 714 715impl PartialEq<String> for AtUri { 716 fn eq(&self, other: &String) -> bool { 717 self.0 == *other 718 } 719} 720 721impl PartialEq<AtUri> for String { 722 fn eq(&self, other: &AtUri) -> bool { 723 *self == other.0 724 } 725} 726 727impl PartialEq<AtUri> for &str { 728 fn eq(&self, other: &AtUri) -> bool { 729 *self == other.0 730 } 731} 732 733impl fmt::Display for AtUri { 734 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 735 write!(f, "{}", self.0) 736 } 737} 738 739impl FromStr for AtUri { 740 type Err = AtUriError; 741 742 fn from_str(s: &str) -> Result<Self, Self::Err> { 743 Self::new(s) 744 } 745} 746 747#[derive(Debug, Clone)] 748pub enum AtUriError { 749 Invalid(String), 750} 751 752impl fmt::Display for AtUriError { 753 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 754 match self { 755 Self::Invalid(s) => write!(f, "invalid AT URI: {}", s), 756 } 757 } 758} 759 760impl std::error::Error for AtUriError {} 761 762#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)] 763#[serde(transparent)] 764#[sqlx(transparent)] 765pub struct Tid(String); 766 767impl From<Tid> for String { 768 fn from(tid: Tid) -> Self { 769 tid.0 770 } 771} 772 773impl From<String> for Tid { 774 fn from(s: String) -> Self { 775 Tid(s) 776 } 777} 778 779impl<'a> From<&'a Tid> for Cow<'a, str> { 780 fn from(tid: &'a Tid) -> Self { 781 Cow::Borrowed(&tid.0) 782 } 783} 784 785impl Tid { 786 pub fn new(s: impl Into<String>) -> Result<Self, TidError> { 787 let s = s.into(); 788 jacquard::types::string::Tid::from_str(&s).map_err(|_| TidError::Invalid(s.clone()))?; 789 Ok(Self(s)) 790 } 791 792 pub fn new_unchecked(s: impl Into<String>) -> Self { 793 Self(s.into()) 794 } 795 796 pub fn now() -> Self { 797 use jacquard::types::integer::LimitedU32; 798 Self(jacquard::types::string::Tid::now(LimitedU32::MIN).to_string()) 799 } 800 801 pub fn as_str(&self) -> &str { 802 &self.0 803 } 804 805 pub fn into_inner(self) -> String { 806 self.0 807 } 808} 809 810impl AsRef<str> for Tid { 811 fn as_ref(&self) -> &str { 812 &self.0 813 } 814} 815 816impl Deref for Tid { 817 type Target = str; 818 819 fn deref(&self) -> &Self::Target { 820 &self.0 821 } 822} 823 824impl PartialEq<str> for Tid { 825 fn eq(&self, other: &str) -> bool { 826 self.0 == other 827 } 828} 829 830impl PartialEq<&str> for Tid { 831 fn eq(&self, other: &&str) -> bool { 832 self.0 == *other 833 } 834} 835 836impl PartialEq<String> for Tid { 837 fn eq(&self, other: &String) -> bool { 838 self.0 == *other 839 } 840} 841 842impl PartialEq<Tid> for String { 843 fn eq(&self, other: &Tid) -> bool { 844 *self == other.0 845 } 846} 847 848impl PartialEq<Tid> for &str { 849 fn eq(&self, other: &Tid) -> bool { 850 *self == other.0 851 } 852} 853 854impl fmt::Display for Tid { 855 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 856 write!(f, "{}", self.0) 857 } 858} 859 860impl FromStr for Tid { 861 type Err = TidError; 862 863 fn from_str(s: &str) -> Result<Self, Self::Err> { 864 Self::new(s) 865 } 866} 867 868#[derive(Debug, Clone)] 869pub enum TidError { 870 Invalid(String), 871} 872 873impl fmt::Display for TidError { 874 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 875 match self { 876 Self::Invalid(s) => write!(f, "invalid TID: {}", s), 877 } 878 } 879} 880 881impl std::error::Error for TidError {} 882 883#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)] 884#[serde(transparent)] 885#[sqlx(transparent)] 886pub struct Datetime(String); 887 888impl From<Datetime> for String { 889 fn from(dt: Datetime) -> Self { 890 dt.0 891 } 892} 893 894impl From<String> for Datetime { 895 fn from(s: String) -> Self { 896 Datetime(s) 897 } 898} 899 900impl<'a> From<&'a Datetime> for Cow<'a, str> { 901 fn from(dt: &'a Datetime) -> Self { 902 Cow::Borrowed(&dt.0) 903 } 904} 905 906impl Datetime { 907 pub fn new(s: impl Into<String>) -> Result<Self, DatetimeError> { 908 let s = s.into(); 909 jacquard::types::string::Datetime::from_str(&s) 910 .map_err(|_| DatetimeError::Invalid(s.clone()))?; 911 Ok(Self(s)) 912 } 913 914 pub fn new_unchecked(s: impl Into<String>) -> Self { 915 Self(s.into()) 916 } 917 918 pub fn now() -> Self { 919 Self(chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string()) 920 } 921 922 pub fn as_str(&self) -> &str { 923 &self.0 924 } 925 926 pub fn into_inner(self) -> String { 927 self.0 928 } 929} 930 931impl AsRef<str> for Datetime { 932 fn as_ref(&self) -> &str { 933 &self.0 934 } 935} 936 937impl Deref for Datetime { 938 type Target = str; 939 940 fn deref(&self) -> &Self::Target { 941 &self.0 942 } 943} 944 945impl PartialEq<str> for Datetime { 946 fn eq(&self, other: &str) -> bool { 947 self.0 == other 948 } 949} 950 951impl PartialEq<&str> for Datetime { 952 fn eq(&self, other: &&str) -> bool { 953 self.0 == *other 954 } 955} 956 957impl PartialEq<String> for Datetime { 958 fn eq(&self, other: &String) -> bool { 959 self.0 == *other 960 } 961} 962 963impl PartialEq<Datetime> for String { 964 fn eq(&self, other: &Datetime) -> bool { 965 *self == other.0 966 } 967} 968 969impl PartialEq<Datetime> for &str { 970 fn eq(&self, other: &Datetime) -> bool { 971 *self == other.0 972 } 973} 974 975impl fmt::Display for Datetime { 976 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 977 write!(f, "{}", self.0) 978 } 979} 980 981impl FromStr for Datetime { 982 type Err = DatetimeError; 983 984 fn from_str(s: &str) -> Result<Self, Self::Err> { 985 Self::new(s) 986 } 987} 988 989#[derive(Debug, Clone)] 990pub enum DatetimeError { 991 Invalid(String), 992} 993 994impl fmt::Display for DatetimeError { 995 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 996 match self { 997 Self::Invalid(s) => write!(f, "invalid datetime: {}", s), 998 } 999 } 1000} 1001 1002impl std::error::Error for DatetimeError {} 1003 1004#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)] 1005#[serde(transparent)] 1006#[sqlx(transparent)] 1007pub struct Language(String); 1008 1009impl From<Language> for String { 1010 fn from(lang: Language) -> Self { 1011 lang.0 1012 } 1013} 1014 1015impl From<String> for Language { 1016 fn from(s: String) -> Self { 1017 Language(s) 1018 } 1019} 1020 1021impl<'a> From<&'a Language> for Cow<'a, str> { 1022 fn from(lang: &'a Language) -> Self { 1023 Cow::Borrowed(&lang.0) 1024 } 1025} 1026 1027impl Language { 1028 pub fn new(s: impl Into<String>) -> Result<Self, LanguageError> { 1029 let s = s.into(); 1030 jacquard::types::string::Language::from_str(&s) 1031 .map_err(|_| LanguageError::Invalid(s.clone()))?; 1032 Ok(Self(s)) 1033 } 1034 1035 pub fn new_unchecked(s: impl Into<String>) -> Self { 1036 Self(s.into()) 1037 } 1038 1039 pub fn as_str(&self) -> &str { 1040 &self.0 1041 } 1042 1043 pub fn into_inner(self) -> String { 1044 self.0 1045 } 1046} 1047 1048impl AsRef<str> for Language { 1049 fn as_ref(&self) -> &str { 1050 &self.0 1051 } 1052} 1053 1054impl Deref for Language { 1055 type Target = str; 1056 1057 fn deref(&self) -> &Self::Target { 1058 &self.0 1059 } 1060} 1061 1062impl PartialEq<str> for Language { 1063 fn eq(&self, other: &str) -> bool { 1064 self.0 == other 1065 } 1066} 1067 1068impl PartialEq<&str> for Language { 1069 fn eq(&self, other: &&str) -> bool { 1070 self.0 == *other 1071 } 1072} 1073 1074impl PartialEq<String> for Language { 1075 fn eq(&self, other: &String) -> bool { 1076 self.0 == *other 1077 } 1078} 1079 1080impl PartialEq<Language> for String { 1081 fn eq(&self, other: &Language) -> bool { 1082 *self == other.0 1083 } 1084} 1085 1086impl PartialEq<Language> for &str { 1087 fn eq(&self, other: &Language) -> bool { 1088 *self == other.0 1089 } 1090} 1091 1092impl fmt::Display for Language { 1093 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 1094 write!(f, "{}", self.0) 1095 } 1096} 1097 1098impl FromStr for Language { 1099 type Err = LanguageError; 1100 1101 fn from_str(s: &str) -> Result<Self, Self::Err> { 1102 Self::new(s) 1103 } 1104} 1105 1106#[derive(Debug, Clone)] 1107pub enum LanguageError { 1108 Invalid(String), 1109} 1110 1111impl fmt::Display for LanguageError { 1112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 1113 match self { 1114 Self::Invalid(s) => write!(f, "invalid language tag: {}", s), 1115 } 1116 } 1117} 1118 1119impl std::error::Error for LanguageError {} 1120 1121#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)] 1122#[serde(transparent)] 1123#[sqlx(transparent)] 1124pub struct CidLink(String); 1125 1126impl From<CidLink> for String { 1127 fn from(cid: CidLink) -> Self { 1128 cid.0 1129 } 1130} 1131 1132impl From<String> for CidLink { 1133 fn from(s: String) -> Self { 1134 CidLink(s) 1135 } 1136} 1137 1138impl<'a> From<&'a CidLink> for Cow<'a, str> { 1139 fn from(cid: &'a CidLink) -> Self { 1140 Cow::Borrowed(&cid.0) 1141 } 1142} 1143 1144impl CidLink { 1145 pub fn new(s: impl Into<String>) -> Result<Self, CidLinkError> { 1146 let s = s.into(); 1147 cid::Cid::from_str(&s).map_err(|_| CidLinkError::Invalid(s.clone()))?; 1148 Ok(Self(s)) 1149 } 1150 1151 pub fn new_unchecked(s: impl Into<String>) -> Self { 1152 Self(s.into()) 1153 } 1154 1155 pub fn as_str(&self) -> &str { 1156 &self.0 1157 } 1158 1159 pub fn into_inner(self) -> String { 1160 self.0 1161 } 1162 1163 pub fn to_cid(&self) -> Result<cid::Cid, cid::Error> { 1164 cid::Cid::from_str(&self.0) 1165 } 1166} 1167 1168impl AsRef<str> for CidLink { 1169 fn as_ref(&self) -> &str { 1170 &self.0 1171 } 1172} 1173 1174impl Deref for CidLink { 1175 type Target = str; 1176 1177 fn deref(&self) -> &Self::Target { 1178 &self.0 1179 } 1180} 1181 1182impl PartialEq<str> for CidLink { 1183 fn eq(&self, other: &str) -> bool { 1184 self.0 == other 1185 } 1186} 1187 1188impl PartialEq<&str> for CidLink { 1189 fn eq(&self, other: &&str) -> bool { 1190 self.0 == *other 1191 } 1192} 1193 1194impl PartialEq<String> for CidLink { 1195 fn eq(&self, other: &String) -> bool { 1196 self.0 == *other 1197 } 1198} 1199 1200impl PartialEq<CidLink> for String { 1201 fn eq(&self, other: &CidLink) -> bool { 1202 *self == other.0 1203 } 1204} 1205 1206impl PartialEq<CidLink> for &str { 1207 fn eq(&self, other: &CidLink) -> bool { 1208 *self == other.0 1209 } 1210} 1211 1212impl fmt::Display for CidLink { 1213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 1214 write!(f, "{}", self.0) 1215 } 1216} 1217 1218impl FromStr for CidLink { 1219 type Err = CidLinkError; 1220 1221 fn from_str(s: &str) -> Result<Self, Self::Err> { 1222 Self::new(s) 1223 } 1224} 1225 1226#[derive(Debug, Clone)] 1227pub enum CidLinkError { 1228 Invalid(String), 1229} 1230 1231impl fmt::Display for CidLinkError { 1232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 1233 match self { 1234 Self::Invalid(s) => write!(f, "invalid CID: {}", s), 1235 } 1236 } 1237} 1238 1239impl std::error::Error for CidLinkError {} 1240 1241#[derive(Debug, Clone, PartialEq, Eq)] 1242pub enum AccountState { 1243 Active, 1244 Deactivated { 1245 at: chrono::DateTime<chrono::Utc>, 1246 }, 1247 TakenDown { 1248 reference: String, 1249 }, 1250 Migrated { 1251 at: chrono::DateTime<chrono::Utc>, 1252 to_pds: String, 1253 }, 1254} 1255 1256impl AccountState { 1257 pub fn from_db_fields( 1258 deactivated_at: Option<chrono::DateTime<chrono::Utc>>, 1259 takedown_ref: Option<String>, 1260 migrated_to_pds: Option<String>, 1261 migrated_at: Option<chrono::DateTime<chrono::Utc>>, 1262 ) -> Self { 1263 if let Some(reference) = takedown_ref { 1264 AccountState::TakenDown { reference } 1265 } else if let (Some(at), Some(to_pds)) = (deactivated_at, migrated_to_pds) { 1266 let migrated_at = migrated_at.unwrap_or(at); 1267 AccountState::Migrated { 1268 at: migrated_at, 1269 to_pds, 1270 } 1271 } else if let Some(at) = deactivated_at { 1272 AccountState::Deactivated { at } 1273 } else { 1274 AccountState::Active 1275 } 1276 } 1277 1278 pub fn is_active(&self) -> bool { 1279 matches!(self, AccountState::Active) 1280 } 1281 1282 pub fn is_deactivated(&self) -> bool { 1283 matches!(self, AccountState::Deactivated { .. }) 1284 } 1285 1286 pub fn is_takendown(&self) -> bool { 1287 matches!(self, AccountState::TakenDown { .. }) 1288 } 1289 1290 pub fn is_migrated(&self) -> bool { 1291 matches!(self, AccountState::Migrated { .. }) 1292 } 1293 1294 pub fn can_login(&self) -> bool { 1295 matches!(self, AccountState::Active) 1296 } 1297 1298 pub fn can_access_repo(&self) -> bool { 1299 matches!(self, AccountState::Active | AccountState::Deactivated { .. }) 1300 } 1301 1302 pub fn status_string(&self) -> &'static str { 1303 match self { 1304 AccountState::Active => "active", 1305 AccountState::Deactivated { .. } => "deactivated", 1306 AccountState::TakenDown { .. } => "takendown", 1307 AccountState::Migrated { .. } => "deactivated", 1308 } 1309 } 1310 1311 pub fn status_for_session(&self) -> Option<&'static str> { 1312 match self { 1313 AccountState::Active => None, 1314 AccountState::Deactivated { .. } => Some("deactivated"), 1315 AccountState::TakenDown { .. } => Some("takendown"), 1316 AccountState::Migrated { .. } => Some("migrated"), 1317 } 1318 } 1319} 1320 1321impl fmt::Display for AccountState { 1322 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 1323 match self { 1324 AccountState::Active => write!(f, "active"), 1325 AccountState::Deactivated { at } => write!(f, "deactivated ({})", at), 1326 AccountState::TakenDown { reference } => write!(f, "takendown ({})", reference), 1327 AccountState::Migrated { to_pds, .. } => write!(f, "migrated to {}", to_pds), 1328 } 1329 } 1330} 1331 1332#[derive(Debug, Clone, Deserialize)] 1333#[serde(transparent)] 1334pub struct PlainPassword(String); 1335 1336impl PlainPassword { 1337 pub fn new(s: impl Into<String>) -> Self { 1338 Self(s.into()) 1339 } 1340 1341 pub fn as_str(&self) -> &str { 1342 &self.0 1343 } 1344 1345 pub fn into_inner(self) -> String { 1346 self.0 1347 } 1348 1349 pub fn is_empty(&self) -> bool { 1350 self.0.is_empty() 1351 } 1352} 1353 1354impl AsRef<str> for PlainPassword { 1355 fn as_ref(&self) -> &str { 1356 &self.0 1357 } 1358} 1359 1360impl AsRef<[u8]> for PlainPassword { 1361 fn as_ref(&self) -> &[u8] { 1362 self.0.as_bytes() 1363 } 1364} 1365 1366impl Deref for PlainPassword { 1367 type Target = str; 1368 1369 fn deref(&self) -> &Self::Target { 1370 &self.0 1371 } 1372} 1373 1374#[derive(Debug, Clone, Serialize, sqlx::Type)] 1375#[serde(transparent)] 1376#[sqlx(transparent)] 1377pub struct PasswordHash(String); 1378 1379impl PasswordHash { 1380 pub fn from_hash(hash: impl Into<String>) -> Self { 1381 Self(hash.into()) 1382 } 1383 1384 pub fn as_str(&self) -> &str { 1385 &self.0 1386 } 1387 1388 pub fn into_inner(self) -> String { 1389 self.0 1390 } 1391} 1392 1393impl AsRef<str> for PasswordHash { 1394 fn as_ref(&self) -> &str { 1395 &self.0 1396 } 1397} 1398 1399impl From<String> for PasswordHash { 1400 fn from(s: String) -> Self { 1401 Self(s) 1402 } 1403} 1404 1405#[derive(Debug, Clone, PartialEq, Eq)] 1406pub enum TokenSource { 1407 Session, 1408 OAuth { client_id: Option<String> }, 1409 ServiceAuth { lxm: Option<String>, aud: Option<String> }, 1410} 1411 1412impl TokenSource { 1413 pub fn is_session(&self) -> bool { 1414 matches!(self, TokenSource::Session) 1415 } 1416 1417 pub fn is_oauth(&self) -> bool { 1418 matches!(self, TokenSource::OAuth { .. }) 1419 } 1420 1421 pub fn is_service_auth(&self) -> bool { 1422 matches!(self, TokenSource::ServiceAuth { .. }) 1423 } 1424} 1425 1426#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 1427#[serde(transparent)] 1428pub struct JwkThumbprint(String); 1429 1430impl JwkThumbprint { 1431 pub fn new(s: impl Into<String>) -> Self { 1432 Self(s.into()) 1433 } 1434 1435 pub fn as_str(&self) -> &str { 1436 &self.0 1437 } 1438 1439 pub fn into_inner(self) -> String { 1440 self.0 1441 } 1442} 1443 1444impl AsRef<str> for JwkThumbprint { 1445 fn as_ref(&self) -> &str { 1446 &self.0 1447 } 1448} 1449 1450impl Deref for JwkThumbprint { 1451 type Target = str; 1452 1453 fn deref(&self) -> &Self::Target { 1454 &self.0 1455 } 1456} 1457 1458impl From<String> for JwkThumbprint { 1459 fn from(s: String) -> Self { 1460 Self(s) 1461 } 1462} 1463 1464impl PartialEq<str> for JwkThumbprint { 1465 fn eq(&self, other: &str) -> bool { 1466 self.0 == other 1467 } 1468} 1469 1470impl PartialEq<String> for JwkThumbprint { 1471 fn eq(&self, other: &String) -> bool { 1472 &self.0 == other 1473 } 1474} 1475 1476impl PartialEq<JwkThumbprint> for String { 1477 fn eq(&self, other: &JwkThumbprint) -> bool { 1478 self == &other.0 1479 } 1480} 1481 1482#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 1483#[serde(transparent)] 1484pub struct DPoPProofId(String); 1485 1486impl DPoPProofId { 1487 pub fn new(s: impl Into<String>) -> Self { 1488 Self(s.into()) 1489 } 1490 1491 pub fn as_str(&self) -> &str { 1492 &self.0 1493 } 1494 1495 pub fn into_inner(self) -> String { 1496 self.0 1497 } 1498} 1499 1500impl AsRef<str> for DPoPProofId { 1501 fn as_ref(&self) -> &str { 1502 &self.0 1503 } 1504} 1505 1506impl Deref for DPoPProofId { 1507 type Target = str; 1508 1509 fn deref(&self) -> &Self::Target { 1510 &self.0 1511 } 1512} 1513 1514impl From<String> for DPoPProofId { 1515 fn from(s: String) -> Self { 1516 Self(s) 1517 } 1518} 1519 1520#[cfg(test)] 1521mod tests { 1522 use super::*; 1523 1524 #[test] 1525 fn test_did_validation() { 1526 assert!(Did::new("did:plc:abc123").is_ok()); 1527 assert!(Did::new("did:web:example.com").is_ok()); 1528 assert!(Did::new("not-a-did").is_err()); 1529 assert!(Did::new("").is_err()); 1530 } 1531 1532 #[test] 1533 fn test_did_methods() { 1534 let plc = Did::new("did:plc:abc123").unwrap(); 1535 assert!(plc.is_plc()); 1536 assert!(!plc.is_web()); 1537 assert_eq!(plc.as_str(), "did:plc:abc123"); 1538 1539 let web = Did::new("did:web:example.com").unwrap(); 1540 assert!(!web.is_plc()); 1541 assert!(web.is_web()); 1542 } 1543 1544 #[test] 1545 fn test_did_conversions() { 1546 let did = Did::new("did:plc:test123").unwrap(); 1547 let s: String = did.clone().into(); 1548 assert_eq!(s, "did:plc:test123"); 1549 assert_eq!(format!("{}", did), "did:plc:test123"); 1550 } 1551 1552 #[test] 1553 fn test_did_serde() { 1554 let did = Did::new("did:plc:test123").unwrap(); 1555 let json = serde_json::to_string(&did).unwrap(); 1556 assert_eq!(json, "\"did:plc:test123\""); 1557 1558 let parsed: Did = serde_json::from_str(&json).unwrap(); 1559 assert_eq!(parsed, did); 1560 } 1561 1562 #[test] 1563 fn test_handle_validation() { 1564 assert!(Handle::new("user.bsky.social").is_ok()); 1565 assert!(Handle::new("test.example.com").is_ok()); 1566 assert!(Handle::new("invalid handle with spaces").is_err()); 1567 } 1568 1569 #[test] 1570 fn test_rkey_validation() { 1571 assert!(Rkey::new("self").is_ok()); 1572 assert!(Rkey::new("3jzfcijpj2z2a").is_ok()); 1573 assert!(Rkey::new("invalid/rkey").is_err()); 1574 } 1575 1576 #[test] 1577 fn test_rkey_generate() { 1578 let rkey = Rkey::generate(); 1579 assert!(rkey.is_tid()); 1580 assert!(!rkey.as_str().is_empty()); 1581 } 1582 1583 #[test] 1584 fn test_nsid_validation() { 1585 assert!(Nsid::new("app.bsky.feed.post").is_ok()); 1586 assert!(Nsid::new("com.atproto.repo.createRecord").is_ok()); 1587 assert!(Nsid::new("invalid").is_err()); 1588 } 1589 1590 #[test] 1591 fn test_nsid_parts() { 1592 let nsid = Nsid::new("app.bsky.feed.post").unwrap(); 1593 assert_eq!(nsid.name(), Some("post")); 1594 } 1595 1596 #[test] 1597 fn test_at_uri_validation() { 1598 assert!(AtUri::new("at://did:plc:abc123/app.bsky.feed.post/xyz").is_ok()); 1599 assert!(AtUri::new("not-an-at-uri").is_err()); 1600 } 1601 1602 #[test] 1603 fn test_at_uri_from_parts() { 1604 let uri = AtUri::from_parts("did:plc:abc123", "app.bsky.feed.post", "xyz"); 1605 assert_eq!(uri.as_str(), "at://did:plc:abc123/app.bsky.feed.post/xyz"); 1606 } 1607 1608 #[test] 1609 fn test_type_safety() { 1610 fn takes_did(_: &Did) {} 1611 fn takes_handle(_: &Handle) {} 1612 1613 let did = Did::new("did:plc:test").unwrap(); 1614 let handle = Handle::new("test.bsky.social").unwrap(); 1615 1616 takes_did(&did); 1617 takes_handle(&handle); 1618 } 1619 1620 #[test] 1621 fn test_tid_validation() { 1622 let tid = Tid::now(); 1623 assert!(!tid.as_str().is_empty()); 1624 assert!(Tid::new(tid.as_str()).is_ok()); 1625 assert!(Tid::new("invalid").is_err()); 1626 } 1627 1628 #[test] 1629 fn test_datetime_validation() { 1630 assert!(Datetime::new("2024-01-15T12:30:45.123Z").is_ok()); 1631 assert!(Datetime::new("not-a-date").is_err()); 1632 let now = Datetime::now(); 1633 assert!(!now.as_str().is_empty()); 1634 } 1635 1636 #[test] 1637 fn test_language_validation() { 1638 assert!(Language::new("en").is_ok()); 1639 assert!(Language::new("en-US").is_ok()); 1640 assert!(Language::new("ja").is_ok()); 1641 } 1642 1643 #[test] 1644 fn test_cidlink_validation() { 1645 assert!(CidLink::new("bafyreib74ckyq525l3y6an5txykwwtb3dgex6ofzakml53di77oxwr5pfe").is_ok()); 1646 assert!(CidLink::new("not-a-cid").is_err()); 1647 } 1648 1649 #[test] 1650 fn test_at_identifier_validation() { 1651 let did_ident = AtIdentifier::new("did:plc:abc123").unwrap(); 1652 assert!(did_ident.is_did()); 1653 assert!(!did_ident.is_handle()); 1654 assert!(did_ident.as_did().is_some()); 1655 assert!(did_ident.as_handle().is_none()); 1656 1657 let handle_ident = AtIdentifier::new("user.bsky.social").unwrap(); 1658 assert!(!handle_ident.is_did()); 1659 assert!(handle_ident.is_handle()); 1660 assert!(handle_ident.as_did().is_none()); 1661 assert!(handle_ident.as_handle().is_some()); 1662 1663 assert!(AtIdentifier::new("invalid identifier").is_err()); 1664 } 1665 1666 #[test] 1667 fn test_at_identifier_serde() { 1668 let ident = AtIdentifier::new("did:plc:test123").unwrap(); 1669 let json = serde_json::to_string(&ident).unwrap(); 1670 assert_eq!(json, "\"did:plc:test123\""); 1671 1672 let parsed: AtIdentifier = serde_json::from_str(&json).unwrap(); 1673 assert_eq!(parsed.as_str(), "did:plc:test123"); 1674 } 1675 1676 #[test] 1677 fn test_account_state_active() { 1678 let state = AccountState::from_db_fields(None, None, None, None); 1679 assert!(state.is_active()); 1680 assert!(!state.is_deactivated()); 1681 assert!(!state.is_takendown()); 1682 assert!(!state.is_migrated()); 1683 assert!(state.can_login()); 1684 assert!(state.can_access_repo()); 1685 assert_eq!(state.status_string(), "active"); 1686 } 1687 1688 #[test] 1689 fn test_account_state_deactivated() { 1690 let now = chrono::Utc::now(); 1691 let state = AccountState::from_db_fields(Some(now), None, None, None); 1692 assert!(!state.is_active()); 1693 assert!(state.is_deactivated()); 1694 assert!(!state.is_takendown()); 1695 assert!(!state.is_migrated()); 1696 assert!(!state.can_login()); 1697 assert!(state.can_access_repo()); 1698 assert_eq!(state.status_string(), "deactivated"); 1699 } 1700 1701 #[test] 1702 fn test_account_state_takendown() { 1703 let state = AccountState::from_db_fields(None, Some("mod-action-123".into()), None, None); 1704 assert!(!state.is_active()); 1705 assert!(!state.is_deactivated()); 1706 assert!(state.is_takendown()); 1707 assert!(!state.is_migrated()); 1708 assert!(!state.can_login()); 1709 assert!(!state.can_access_repo()); 1710 assert_eq!(state.status_string(), "takendown"); 1711 } 1712 1713 #[test] 1714 fn test_account_state_migrated() { 1715 let now = chrono::Utc::now(); 1716 let state = 1717 AccountState::from_db_fields(Some(now), None, Some("https://other.pds".into()), None); 1718 assert!(!state.is_active()); 1719 assert!(!state.is_deactivated()); 1720 assert!(!state.is_takendown()); 1721 assert!(state.is_migrated()); 1722 assert!(!state.can_login()); 1723 assert!(!state.can_access_repo()); 1724 assert_eq!(state.status_string(), "deactivated"); 1725 } 1726 1727 #[test] 1728 fn test_account_state_takedown_priority() { 1729 let now = chrono::Utc::now(); 1730 let state = AccountState::from_db_fields( 1731 Some(now), 1732 Some("mod-action".into()), 1733 Some("https://other.pds".into()), 1734 None, 1735 ); 1736 assert!(state.is_takendown()); 1737 } 1738}