forked from
lewis.moe/bspds-sandbox
PDS software with bells & whistles you didn’t even know you needed. will move this to its own account when ready.
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}