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}