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