PDS software with bells & whistles you didn’t even know you needed. will move this to its own account when ready.
at main 38 kB view raw
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( 920 chrono::Utc::now() 921 .format("%Y-%m-%dT%H:%M:%S%.3fZ") 922 .to_string(), 923 ) 924 } 925 926 pub fn as_str(&self) -> &str { 927 &self.0 928 } 929 930 pub fn into_inner(self) -> String { 931 self.0 932 } 933} 934 935impl AsRef<str> for Datetime { 936 fn as_ref(&self) -> &str { 937 &self.0 938 } 939} 940 941impl Deref for Datetime { 942 type Target = str; 943 944 fn deref(&self) -> &Self::Target { 945 &self.0 946 } 947} 948 949impl PartialEq<str> for Datetime { 950 fn eq(&self, other: &str) -> bool { 951 self.0 == other 952 } 953} 954 955impl PartialEq<&str> for Datetime { 956 fn eq(&self, other: &&str) -> bool { 957 self.0 == *other 958 } 959} 960 961impl PartialEq<String> for Datetime { 962 fn eq(&self, other: &String) -> bool { 963 self.0 == *other 964 } 965} 966 967impl PartialEq<Datetime> for String { 968 fn eq(&self, other: &Datetime) -> bool { 969 *self == other.0 970 } 971} 972 973impl PartialEq<Datetime> for &str { 974 fn eq(&self, other: &Datetime) -> bool { 975 *self == other.0 976 } 977} 978 979impl fmt::Display for Datetime { 980 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 981 write!(f, "{}", self.0) 982 } 983} 984 985impl FromStr for Datetime { 986 type Err = DatetimeError; 987 988 fn from_str(s: &str) -> Result<Self, Self::Err> { 989 Self::new(s) 990 } 991} 992 993#[derive(Debug, Clone)] 994pub enum DatetimeError { 995 Invalid(String), 996} 997 998impl fmt::Display for DatetimeError { 999 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 1000 match self { 1001 Self::Invalid(s) => write!(f, "invalid datetime: {}", s), 1002 } 1003 } 1004} 1005 1006impl std::error::Error for DatetimeError {} 1007 1008#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)] 1009#[serde(transparent)] 1010#[sqlx(transparent)] 1011pub struct Language(String); 1012 1013impl From<Language> for String { 1014 fn from(lang: Language) -> Self { 1015 lang.0 1016 } 1017} 1018 1019impl From<String> for Language { 1020 fn from(s: String) -> Self { 1021 Language(s) 1022 } 1023} 1024 1025impl<'a> From<&'a Language> for Cow<'a, str> { 1026 fn from(lang: &'a Language) -> Self { 1027 Cow::Borrowed(&lang.0) 1028 } 1029} 1030 1031impl Language { 1032 pub fn new(s: impl Into<String>) -> Result<Self, LanguageError> { 1033 let s = s.into(); 1034 jacquard::types::string::Language::from_str(&s) 1035 .map_err(|_| LanguageError::Invalid(s.clone()))?; 1036 Ok(Self(s)) 1037 } 1038 1039 pub fn new_unchecked(s: impl Into<String>) -> Self { 1040 Self(s.into()) 1041 } 1042 1043 pub fn as_str(&self) -> &str { 1044 &self.0 1045 } 1046 1047 pub fn into_inner(self) -> String { 1048 self.0 1049 } 1050} 1051 1052impl AsRef<str> for Language { 1053 fn as_ref(&self) -> &str { 1054 &self.0 1055 } 1056} 1057 1058impl Deref for Language { 1059 type Target = str; 1060 1061 fn deref(&self) -> &Self::Target { 1062 &self.0 1063 } 1064} 1065 1066impl PartialEq<str> for Language { 1067 fn eq(&self, other: &str) -> bool { 1068 self.0 == other 1069 } 1070} 1071 1072impl PartialEq<&str> for Language { 1073 fn eq(&self, other: &&str) -> bool { 1074 self.0 == *other 1075 } 1076} 1077 1078impl PartialEq<String> for Language { 1079 fn eq(&self, other: &String) -> bool { 1080 self.0 == *other 1081 } 1082} 1083 1084impl PartialEq<Language> for String { 1085 fn eq(&self, other: &Language) -> bool { 1086 *self == other.0 1087 } 1088} 1089 1090impl PartialEq<Language> for &str { 1091 fn eq(&self, other: &Language) -> bool { 1092 *self == other.0 1093 } 1094} 1095 1096impl fmt::Display for Language { 1097 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 1098 write!(f, "{}", self.0) 1099 } 1100} 1101 1102impl FromStr for Language { 1103 type Err = LanguageError; 1104 1105 fn from_str(s: &str) -> Result<Self, Self::Err> { 1106 Self::new(s) 1107 } 1108} 1109 1110#[derive(Debug, Clone)] 1111pub enum LanguageError { 1112 Invalid(String), 1113} 1114 1115impl fmt::Display for LanguageError { 1116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 1117 match self { 1118 Self::Invalid(s) => write!(f, "invalid language tag: {}", s), 1119 } 1120 } 1121} 1122 1123impl std::error::Error for LanguageError {} 1124 1125#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)] 1126#[serde(transparent)] 1127#[sqlx(transparent)] 1128pub struct CidLink(String); 1129 1130impl From<CidLink> for String { 1131 fn from(cid: CidLink) -> Self { 1132 cid.0 1133 } 1134} 1135 1136impl From<String> for CidLink { 1137 fn from(s: String) -> Self { 1138 CidLink(s) 1139 } 1140} 1141 1142impl<'a> From<&'a CidLink> for Cow<'a, str> { 1143 fn from(cid: &'a CidLink) -> Self { 1144 Cow::Borrowed(&cid.0) 1145 } 1146} 1147 1148impl CidLink { 1149 pub fn new(s: impl Into<String>) -> Result<Self, CidLinkError> { 1150 let s = s.into(); 1151 cid::Cid::from_str(&s).map_err(|_| CidLinkError::Invalid(s.clone()))?; 1152 Ok(Self(s)) 1153 } 1154 1155 pub fn new_unchecked(s: impl Into<String>) -> Self { 1156 Self(s.into()) 1157 } 1158 1159 pub fn as_str(&self) -> &str { 1160 &self.0 1161 } 1162 1163 pub fn into_inner(self) -> String { 1164 self.0 1165 } 1166 1167 pub fn to_cid(&self) -> Result<cid::Cid, cid::Error> { 1168 cid::Cid::from_str(&self.0) 1169 } 1170} 1171 1172impl AsRef<str> for CidLink { 1173 fn as_ref(&self) -> &str { 1174 &self.0 1175 } 1176} 1177 1178impl Deref for CidLink { 1179 type Target = str; 1180 1181 fn deref(&self) -> &Self::Target { 1182 &self.0 1183 } 1184} 1185 1186impl PartialEq<str> for CidLink { 1187 fn eq(&self, other: &str) -> bool { 1188 self.0 == other 1189 } 1190} 1191 1192impl PartialEq<&str> for CidLink { 1193 fn eq(&self, other: &&str) -> bool { 1194 self.0 == *other 1195 } 1196} 1197 1198impl PartialEq<String> for CidLink { 1199 fn eq(&self, other: &String) -> bool { 1200 self.0 == *other 1201 } 1202} 1203 1204impl PartialEq<CidLink> for String { 1205 fn eq(&self, other: &CidLink) -> bool { 1206 *self == other.0 1207 } 1208} 1209 1210impl PartialEq<CidLink> for &str { 1211 fn eq(&self, other: &CidLink) -> bool { 1212 *self == other.0 1213 } 1214} 1215 1216impl fmt::Display for CidLink { 1217 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 1218 write!(f, "{}", self.0) 1219 } 1220} 1221 1222impl FromStr for CidLink { 1223 type Err = CidLinkError; 1224 1225 fn from_str(s: &str) -> Result<Self, Self::Err> { 1226 Self::new(s) 1227 } 1228} 1229 1230#[derive(Debug, Clone)] 1231pub enum CidLinkError { 1232 Invalid(String), 1233} 1234 1235impl fmt::Display for CidLinkError { 1236 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 1237 match self { 1238 Self::Invalid(s) => write!(f, "invalid CID: {}", s), 1239 } 1240 } 1241} 1242 1243impl std::error::Error for CidLinkError {} 1244 1245#[derive(Debug, Clone, PartialEq, Eq)] 1246pub enum AccountState { 1247 Active, 1248 Deactivated { 1249 at: chrono::DateTime<chrono::Utc>, 1250 }, 1251 TakenDown { 1252 reference: String, 1253 }, 1254 Migrated { 1255 at: chrono::DateTime<chrono::Utc>, 1256 to_pds: String, 1257 }, 1258} 1259 1260impl AccountState { 1261 pub fn from_db_fields( 1262 deactivated_at: Option<chrono::DateTime<chrono::Utc>>, 1263 takedown_ref: Option<String>, 1264 migrated_to_pds: Option<String>, 1265 migrated_at: Option<chrono::DateTime<chrono::Utc>>, 1266 ) -> Self { 1267 if let Some(reference) = takedown_ref { 1268 AccountState::TakenDown { reference } 1269 } else if let (Some(at), Some(to_pds)) = (deactivated_at, migrated_to_pds) { 1270 let migrated_at = migrated_at.unwrap_or(at); 1271 AccountState::Migrated { 1272 at: migrated_at, 1273 to_pds, 1274 } 1275 } else if let Some(at) = deactivated_at { 1276 AccountState::Deactivated { at } 1277 } else { 1278 AccountState::Active 1279 } 1280 } 1281 1282 pub fn is_active(&self) -> bool { 1283 matches!(self, AccountState::Active) 1284 } 1285 1286 pub fn is_deactivated(&self) -> bool { 1287 matches!(self, AccountState::Deactivated { .. }) 1288 } 1289 1290 pub fn is_takendown(&self) -> bool { 1291 matches!(self, AccountState::TakenDown { .. }) 1292 } 1293 1294 pub fn is_migrated(&self) -> bool { 1295 matches!(self, AccountState::Migrated { .. }) 1296 } 1297 1298 pub fn can_login(&self) -> bool { 1299 matches!(self, AccountState::Active) 1300 } 1301 1302 pub fn can_access_repo(&self) -> bool { 1303 matches!( 1304 self, 1305 AccountState::Active | AccountState::Deactivated { .. } 1306 ) 1307 } 1308 1309 pub fn status_string(&self) -> &'static str { 1310 match self { 1311 AccountState::Active => "active", 1312 AccountState::Deactivated { .. } => "deactivated", 1313 AccountState::TakenDown { .. } => "takendown", 1314 AccountState::Migrated { .. } => "deactivated", 1315 } 1316 } 1317 1318 pub fn status_for_session(&self) -> Option<&'static str> { 1319 match self { 1320 AccountState::Active => None, 1321 AccountState::Deactivated { .. } => Some("deactivated"), 1322 AccountState::TakenDown { .. } => Some("takendown"), 1323 AccountState::Migrated { .. } => Some("migrated"), 1324 } 1325 } 1326} 1327 1328impl fmt::Display for AccountState { 1329 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 1330 match self { 1331 AccountState::Active => write!(f, "active"), 1332 AccountState::Deactivated { at } => write!(f, "deactivated ({})", at), 1333 AccountState::TakenDown { reference } => write!(f, "takendown ({})", reference), 1334 AccountState::Migrated { to_pds, .. } => write!(f, "migrated to {}", to_pds), 1335 } 1336 } 1337} 1338 1339#[derive(Debug, Clone, Deserialize)] 1340#[serde(transparent)] 1341pub struct PlainPassword(String); 1342 1343impl PlainPassword { 1344 pub fn new(s: impl Into<String>) -> Self { 1345 Self(s.into()) 1346 } 1347 1348 pub fn as_str(&self) -> &str { 1349 &self.0 1350 } 1351 1352 pub fn into_inner(self) -> String { 1353 self.0 1354 } 1355 1356 pub fn is_empty(&self) -> bool { 1357 self.0.is_empty() 1358 } 1359} 1360 1361impl AsRef<str> for PlainPassword { 1362 fn as_ref(&self) -> &str { 1363 &self.0 1364 } 1365} 1366 1367impl AsRef<[u8]> for PlainPassword { 1368 fn as_ref(&self) -> &[u8] { 1369 self.0.as_bytes() 1370 } 1371} 1372 1373impl Deref for PlainPassword { 1374 type Target = str; 1375 1376 fn deref(&self) -> &Self::Target { 1377 &self.0 1378 } 1379} 1380 1381#[derive(Debug, Clone, Serialize, sqlx::Type)] 1382#[serde(transparent)] 1383#[sqlx(transparent)] 1384pub struct PasswordHash(String); 1385 1386impl PasswordHash { 1387 pub fn from_hash(hash: impl Into<String>) -> Self { 1388 Self(hash.into()) 1389 } 1390 1391 pub fn as_str(&self) -> &str { 1392 &self.0 1393 } 1394 1395 pub fn into_inner(self) -> String { 1396 self.0 1397 } 1398} 1399 1400impl AsRef<str> for PasswordHash { 1401 fn as_ref(&self) -> &str { 1402 &self.0 1403 } 1404} 1405 1406impl From<String> for PasswordHash { 1407 fn from(s: String) -> Self { 1408 Self(s) 1409 } 1410} 1411 1412#[derive(Debug, Clone, PartialEq, Eq)] 1413pub enum TokenSource { 1414 Session, 1415 OAuth { 1416 client_id: Option<String>, 1417 }, 1418 ServiceAuth { 1419 lxm: Option<String>, 1420 aud: Option<String>, 1421 }, 1422} 1423 1424impl TokenSource { 1425 pub fn is_session(&self) -> bool { 1426 matches!(self, TokenSource::Session) 1427 } 1428 1429 pub fn is_oauth(&self) -> bool { 1430 matches!(self, TokenSource::OAuth { .. }) 1431 } 1432 1433 pub fn is_service_auth(&self) -> bool { 1434 matches!(self, TokenSource::ServiceAuth { .. }) 1435 } 1436} 1437 1438#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 1439#[serde(transparent)] 1440pub struct JwkThumbprint(String); 1441 1442impl JwkThumbprint { 1443 pub fn new(s: impl Into<String>) -> Self { 1444 Self(s.into()) 1445 } 1446 1447 pub fn as_str(&self) -> &str { 1448 &self.0 1449 } 1450 1451 pub fn into_inner(self) -> String { 1452 self.0 1453 } 1454} 1455 1456impl AsRef<str> for JwkThumbprint { 1457 fn as_ref(&self) -> &str { 1458 &self.0 1459 } 1460} 1461 1462impl Deref for JwkThumbprint { 1463 type Target = str; 1464 1465 fn deref(&self) -> &Self::Target { 1466 &self.0 1467 } 1468} 1469 1470impl From<String> for JwkThumbprint { 1471 fn from(s: String) -> Self { 1472 Self(s) 1473 } 1474} 1475 1476impl PartialEq<str> for JwkThumbprint { 1477 fn eq(&self, other: &str) -> bool { 1478 self.0 == other 1479 } 1480} 1481 1482impl PartialEq<String> for JwkThumbprint { 1483 fn eq(&self, other: &String) -> bool { 1484 &self.0 == other 1485 } 1486} 1487 1488impl PartialEq<JwkThumbprint> for String { 1489 fn eq(&self, other: &JwkThumbprint) -> bool { 1490 self == &other.0 1491 } 1492} 1493 1494#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 1495#[serde(transparent)] 1496pub struct DPoPProofId(String); 1497 1498impl DPoPProofId { 1499 pub fn new(s: impl Into<String>) -> Self { 1500 Self(s.into()) 1501 } 1502 1503 pub fn as_str(&self) -> &str { 1504 &self.0 1505 } 1506 1507 pub fn into_inner(self) -> String { 1508 self.0 1509 } 1510} 1511 1512impl AsRef<str> for DPoPProofId { 1513 fn as_ref(&self) -> &str { 1514 &self.0 1515 } 1516} 1517 1518impl Deref for DPoPProofId { 1519 type Target = str; 1520 1521 fn deref(&self) -> &Self::Target { 1522 &self.0 1523 } 1524} 1525 1526impl From<String> for DPoPProofId { 1527 fn from(s: String) -> Self { 1528 Self(s) 1529 } 1530} 1531 1532#[cfg(test)] 1533mod tests { 1534 use super::*; 1535 1536 #[test] 1537 fn test_did_validation() { 1538 assert!(Did::new("did:plc:abc123").is_ok()); 1539 assert!(Did::new("did:web:example.com").is_ok()); 1540 assert!(Did::new("not-a-did").is_err()); 1541 assert!(Did::new("").is_err()); 1542 } 1543 1544 #[test] 1545 fn test_did_methods() { 1546 let plc = Did::new("did:plc:abc123").unwrap(); 1547 assert!(plc.is_plc()); 1548 assert!(!plc.is_web()); 1549 assert_eq!(plc.as_str(), "did:plc:abc123"); 1550 1551 let web = Did::new("did:web:example.com").unwrap(); 1552 assert!(!web.is_plc()); 1553 assert!(web.is_web()); 1554 } 1555 1556 #[test] 1557 fn test_did_conversions() { 1558 let did = Did::new("did:plc:test123").unwrap(); 1559 let s: String = did.clone().into(); 1560 assert_eq!(s, "did:plc:test123"); 1561 assert_eq!(format!("{}", did), "did:plc:test123"); 1562 } 1563 1564 #[test] 1565 fn test_did_serde() { 1566 let did = Did::new("did:plc:test123").unwrap(); 1567 let json = serde_json::to_string(&did).unwrap(); 1568 assert_eq!(json, "\"did:plc:test123\""); 1569 1570 let parsed: Did = serde_json::from_str(&json).unwrap(); 1571 assert_eq!(parsed, did); 1572 } 1573 1574 #[test] 1575 fn test_handle_validation() { 1576 assert!(Handle::new("user.bsky.social").is_ok()); 1577 assert!(Handle::new("test.example.com").is_ok()); 1578 assert!(Handle::new("invalid handle with spaces").is_err()); 1579 } 1580 1581 #[test] 1582 fn test_rkey_validation() { 1583 assert!(Rkey::new("self").is_ok()); 1584 assert!(Rkey::new("3jzfcijpj2z2a").is_ok()); 1585 assert!(Rkey::new("invalid/rkey").is_err()); 1586 } 1587 1588 #[test] 1589 fn test_rkey_generate() { 1590 let rkey = Rkey::generate(); 1591 assert!(rkey.is_tid()); 1592 assert!(!rkey.as_str().is_empty()); 1593 } 1594 1595 #[test] 1596 fn test_nsid_validation() { 1597 assert!(Nsid::new("app.bsky.feed.post").is_ok()); 1598 assert!(Nsid::new("com.atproto.repo.createRecord").is_ok()); 1599 assert!(Nsid::new("invalid").is_err()); 1600 } 1601 1602 #[test] 1603 fn test_nsid_parts() { 1604 let nsid = Nsid::new("app.bsky.feed.post").unwrap(); 1605 assert_eq!(nsid.name(), Some("post")); 1606 } 1607 1608 #[test] 1609 fn test_at_uri_validation() { 1610 assert!(AtUri::new("at://did:plc:abc123/app.bsky.feed.post/xyz").is_ok()); 1611 assert!(AtUri::new("not-an-at-uri").is_err()); 1612 } 1613 1614 #[test] 1615 fn test_at_uri_from_parts() { 1616 let uri = AtUri::from_parts("did:plc:abc123", "app.bsky.feed.post", "xyz"); 1617 assert_eq!(uri.as_str(), "at://did:plc:abc123/app.bsky.feed.post/xyz"); 1618 } 1619 1620 #[test] 1621 fn test_type_safety() { 1622 fn takes_did(_: &Did) {} 1623 fn takes_handle(_: &Handle) {} 1624 1625 let did = Did::new("did:plc:test").unwrap(); 1626 let handle = Handle::new("test.bsky.social").unwrap(); 1627 1628 takes_did(&did); 1629 takes_handle(&handle); 1630 } 1631 1632 #[test] 1633 fn test_tid_validation() { 1634 let tid = Tid::now(); 1635 assert!(!tid.as_str().is_empty()); 1636 assert!(Tid::new(tid.as_str()).is_ok()); 1637 assert!(Tid::new("invalid").is_err()); 1638 } 1639 1640 #[test] 1641 fn test_datetime_validation() { 1642 assert!(Datetime::new("2024-01-15T12:30:45.123Z").is_ok()); 1643 assert!(Datetime::new("not-a-date").is_err()); 1644 let now = Datetime::now(); 1645 assert!(!now.as_str().is_empty()); 1646 } 1647 1648 #[test] 1649 fn test_language_validation() { 1650 assert!(Language::new("en").is_ok()); 1651 assert!(Language::new("en-US").is_ok()); 1652 assert!(Language::new("ja").is_ok()); 1653 } 1654 1655 #[test] 1656 fn test_cidlink_validation() { 1657 assert!( 1658 CidLink::new("bafyreib74ckyq525l3y6an5txykwwtb3dgex6ofzakml53di77oxwr5pfe").is_ok() 1659 ); 1660 assert!(CidLink::new("not-a-cid").is_err()); 1661 } 1662 1663 #[test] 1664 fn test_at_identifier_validation() { 1665 let did_ident = AtIdentifier::new("did:plc:abc123").unwrap(); 1666 assert!(did_ident.is_did()); 1667 assert!(!did_ident.is_handle()); 1668 assert!(did_ident.as_did().is_some()); 1669 assert!(did_ident.as_handle().is_none()); 1670 1671 let handle_ident = AtIdentifier::new("user.bsky.social").unwrap(); 1672 assert!(!handle_ident.is_did()); 1673 assert!(handle_ident.is_handle()); 1674 assert!(handle_ident.as_did().is_none()); 1675 assert!(handle_ident.as_handle().is_some()); 1676 1677 assert!(AtIdentifier::new("invalid identifier").is_err()); 1678 } 1679 1680 #[test] 1681 fn test_at_identifier_serde() { 1682 let ident = AtIdentifier::new("did:plc:test123").unwrap(); 1683 let json = serde_json::to_string(&ident).unwrap(); 1684 assert_eq!(json, "\"did:plc:test123\""); 1685 1686 let parsed: AtIdentifier = serde_json::from_str(&json).unwrap(); 1687 assert_eq!(parsed.as_str(), "did:plc:test123"); 1688 } 1689 1690 #[test] 1691 fn test_account_state_active() { 1692 let state = AccountState::from_db_fields(None, None, None, None); 1693 assert!(state.is_active()); 1694 assert!(!state.is_deactivated()); 1695 assert!(!state.is_takendown()); 1696 assert!(!state.is_migrated()); 1697 assert!(state.can_login()); 1698 assert!(state.can_access_repo()); 1699 assert_eq!(state.status_string(), "active"); 1700 } 1701 1702 #[test] 1703 fn test_account_state_deactivated() { 1704 let now = chrono::Utc::now(); 1705 let state = AccountState::from_db_fields(Some(now), None, None, None); 1706 assert!(!state.is_active()); 1707 assert!(state.is_deactivated()); 1708 assert!(!state.is_takendown()); 1709 assert!(!state.is_migrated()); 1710 assert!(!state.can_login()); 1711 assert!(state.can_access_repo()); 1712 assert_eq!(state.status_string(), "deactivated"); 1713 } 1714 1715 #[test] 1716 fn test_account_state_takendown() { 1717 let state = AccountState::from_db_fields(None, Some("mod-action-123".into()), None, 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(), "takendown"); 1725 } 1726 1727 #[test] 1728 fn test_account_state_migrated() { 1729 let now = chrono::Utc::now(); 1730 let state = 1731 AccountState::from_db_fields(Some(now), None, Some("https://other.pds".into()), None); 1732 assert!(!state.is_active()); 1733 assert!(!state.is_deactivated()); 1734 assert!(!state.is_takendown()); 1735 assert!(state.is_migrated()); 1736 assert!(!state.can_login()); 1737 assert!(!state.can_access_repo()); 1738 assert_eq!(state.status_string(), "deactivated"); 1739 } 1740 1741 #[test] 1742 fn test_account_state_takedown_priority() { 1743 let now = chrono::Utc::now(); 1744 let state = AccountState::from_db_fields( 1745 Some(now), 1746 Some("mod-action".into()), 1747 Some("https://other.pds".into()), 1748 None, 1749 ); 1750 assert!(state.is_takendown()); 1751 } 1752}