proto_blue_syntax/
at_identifier.rs1use std::fmt;
6use std::str::FromStr;
7
8use crate::did::Did;
9use crate::handle::Handle;
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13pub enum AtIdentifier {
14 Did(Did),
16 Handle(Handle),
18}
19
20#[derive(Debug, Clone, thiserror::Error)]
22#[error("Invalid AT identifier: {reason}")]
23pub struct InvalidAtIdentifierError {
24 pub reason: String,
25}
26
27impl AtIdentifier {
28 pub fn new(s: &str) -> Result<Self, InvalidAtIdentifierError> {
30 if s.starts_with("did:") {
31 Did::new(s)
32 .map(AtIdentifier::Did)
33 .map_err(|e| InvalidAtIdentifierError {
34 reason: e.to_string(),
35 })
36 } else {
37 Handle::new(s)
38 .map(AtIdentifier::Handle)
39 .map_err(|e| InvalidAtIdentifierError {
40 reason: e.to_string(),
41 })
42 }
43 }
44
45 #[must_use]
47 pub fn is_valid(s: &str) -> bool {
48 Self::new(s).is_ok()
49 }
50
51 #[must_use]
53 pub fn as_str(&self) -> &str {
54 match self {
55 Self::Did(d) => d.as_str(),
56 Self::Handle(h) => h.as_str(),
57 }
58 }
59
60 #[must_use]
62 pub const fn is_did(&self) -> bool {
63 matches!(self, Self::Did(_))
64 }
65
66 #[must_use]
68 pub const fn is_handle(&self) -> bool {
69 matches!(self, Self::Handle(_))
70 }
71}
72
73impl fmt::Display for AtIdentifier {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 match self {
76 Self::Did(d) => d.fmt(f),
77 Self::Handle(h) => h.fmt(f),
78 }
79 }
80}
81
82impl FromStr for AtIdentifier {
83 type Err = InvalidAtIdentifierError;
84 fn from_str(s: &str) -> Result<Self, Self::Err> {
85 Self::new(s)
86 }
87}
88
89impl From<Did> for AtIdentifier {
90 fn from(d: Did) -> Self {
91 Self::Did(d)
92 }
93}
94
95impl From<Handle> for AtIdentifier {
96 fn from(h: Handle) -> Self {
97 Self::Handle(h)
98 }
99}
100
101impl serde::Serialize for AtIdentifier {
102 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
103 self.as_str().serialize(serializer)
104 }
105}
106
107impl<'de> serde::Deserialize<'de> for AtIdentifier {
108 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
109 let s = String::deserialize(deserializer)?;
110 Self::new(&s).map_err(serde::de::Error::custom)
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn parses_dids() {
120 let id = AtIdentifier::new("did:plc:asdf123").unwrap();
121 assert!(id.is_did());
122 assert!(!id.is_handle());
123 }
124
125 #[test]
126 fn parses_handles() {
127 let id = AtIdentifier::new("alice.bsky.social").unwrap();
128 assert!(id.is_handle());
129 assert!(!id.is_did());
130 }
131
132 #[test]
133 fn invalid() {
134 assert!(AtIdentifier::new("").is_err());
135 assert!(AtIdentifier::new("not-valid").is_err());
136 }
137
138 #[test]
139 fn display() {
140 let id = AtIdentifier::new("did:plc:asdf123").unwrap();
141 assert_eq!(id.to_string(), "did:plc:asdf123");
142
143 let id = AtIdentifier::new("alice.bsky.social").unwrap();
144 assert_eq!(id.to_string(), "alice.bsky.social");
145 }
146}