forked from
ptr.pet/hydrant
kind of like tap but different and in rust
1use data_encoding::BASE32_NOPAD;
2use fjall::UserKey;
3use jacquard::{CowStr, IntoStatic};
4use jacquard_common::types::string::Did;
5use jacquard_common::types::tid::Tid;
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7use smol_str::{SmolStr, SmolStrBuilder, format_smolstr};
8use std::fmt::Display;
9
10const S32_CHAR: &str = "234567abcdefghijklmnopqrstuvwxyz";
11
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub enum TrimmedDid<'s> {
14 Plc([u8; 15]),
15 Web(CowStr<'s>),
16 Other(CowStr<'s>),
17}
18
19const TAG_PLC: u8 = 1;
20const TAG_WEB: u8 = 2;
21
22impl<'s> TrimmedDid<'s> {
23 pub fn len(&self) -> usize {
24 match self {
25 TrimmedDid::Plc(_) => 16,
26 TrimmedDid::Web(s) => 1 + s.len(),
27 TrimmedDid::Other(s) => s.len(),
28 }
29 }
30
31 pub fn into_static(self) -> TrimmedDid<'static> {
32 match self {
33 TrimmedDid::Plc(bytes) => TrimmedDid::Plc(bytes),
34 TrimmedDid::Web(s) => TrimmedDid::Web(s.into_static()),
35 TrimmedDid::Other(s) => TrimmedDid::Other(s.into_static()),
36 }
37 }
38
39 pub fn to_did(&self) -> Did<'static> {
40 match self {
41 TrimmedDid::Plc(_) => {
42 let s = self.to_string();
43 Did::new_owned(format_smolstr!("did:{}", s)).expect("valid did from plc")
44 }
45 TrimmedDid::Web(s) => {
46 Did::new_owned(format_smolstr!("did:web:{}", s)).expect("valid did from web")
47 }
48 TrimmedDid::Other(s) => {
49 Did::new_owned(format_smolstr!("did:{}", s)).expect("valid did from other")
50 }
51 }
52 }
53
54 pub fn write_to_vec(&self, buf: &mut Vec<u8>) {
55 match self {
56 TrimmedDid::Plc(bytes) => {
57 buf.push(TAG_PLC);
58 buf.extend_from_slice(bytes);
59 }
60 TrimmedDid::Web(s) => {
61 buf.push(TAG_WEB);
62 buf.extend_from_slice(s.as_bytes());
63 }
64 TrimmedDid::Other(s) => buf.extend_from_slice(s.as_bytes()),
65 }
66 }
67
68 pub fn to_string(&self) -> String {
69 match self {
70 TrimmedDid::Plc(bytes) => {
71 let mut s = String::with_capacity(28);
72 s.push_str("plc:");
73 s.push_str(&BASE32_NOPAD.encode(bytes).to_ascii_lowercase());
74 s
75 }
76 TrimmedDid::Web(s) => {
77 format!("web:{}", s)
78 }
79 TrimmedDid::Other(s) => s.to_string(),
80 }
81 }
82}
83
84impl Display for TrimmedDid<'_> {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 match self {
87 TrimmedDid::Plc(bytes) => {
88 f.write_str("plc:")?;
89 f.write_str(&BASE32_NOPAD.encode(bytes).to_ascii_lowercase())
90 }
91 TrimmedDid::Web(s) => {
92 f.write_str("web:")?;
93 f.write_str(s)
94 }
95 TrimmedDid::Other(s) => f.write_str(s),
96 }
97 }
98}
99
100impl<'a> From<&'a Did<'a>> for TrimmedDid<'a> {
101 fn from(did: &'a Did<'a>) -> Self {
102 let s = did.as_str();
103 if let Some(rest) = s.strip_prefix("did:plc:") {
104 if rest.len() == 24 {
105 // decode
106 if let Ok(bytes) = BASE32_NOPAD.decode(rest.to_ascii_uppercase().as_bytes()) {
107 if bytes.len() == 15 {
108 return TrimmedDid::Plc(bytes.try_into().unwrap());
109 }
110 }
111 }
112 } else if let Some(rest) = s.strip_prefix("did:web:") {
113 return TrimmedDid::Web(CowStr::Borrowed(rest));
114 }
115 TrimmedDid::Other(CowStr::Borrowed(s.trim_start_matches("did:")))
116 }
117}
118
119impl<'a> TryFrom<&'a [u8]> for TrimmedDid<'a> {
120 type Error = miette::Report;
121
122 fn try_from(value: &'a [u8]) -> miette::Result<Self> {
123 if value.is_empty() {
124 miette::bail!("empty did key");
125 }
126 match value[0] {
127 TAG_PLC => {
128 if value.len() == 16 {
129 let mut arr = [0u8; 15];
130 arr.copy_from_slice(&value[1..]);
131 return Ok(TrimmedDid::Plc(arr));
132 }
133 miette::bail!("invalid length for tagged plc did");
134 }
135 TAG_WEB => {
136 if let Ok(s) = std::str::from_utf8(&value[1..]) {
137 return Ok(TrimmedDid::Web(CowStr::Borrowed(s)));
138 }
139 miette::bail!("invalid utf8 for tagged web did");
140 }
141 _ => {
142 if let Ok(s) = std::str::from_utf8(value) {
143 return Ok(TrimmedDid::Other(CowStr::Borrowed(s)));
144 }
145 miette::bail!("invalid utf8 for other did");
146 }
147 }
148 }
149}
150
151impl<'a> Into<UserKey> for TrimmedDid<'a> {
152 fn into(self) -> UserKey {
153 let mut vec = Vec::with_capacity(32);
154 self.write_to_vec(&mut vec);
155 UserKey::new(&vec)
156 }
157}
158
159impl<'a> Into<UserKey> for &TrimmedDid<'a> {
160 fn into(self) -> UserKey {
161 let mut vec = Vec::with_capacity(32);
162 self.write_to_vec(&mut vec);
163 UserKey::new(&vec)
164 }
165}
166
167impl Serialize for TrimmedDid<'_> {
168 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
169 let mut vec = Vec::with_capacity(32);
170 self.write_to_vec(&mut vec);
171 serializer.serialize_bytes(&vec)
172 }
173}
174
175impl<'de> Deserialize<'de> for TrimmedDid<'de> {
176 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
177 struct DidVisitor;
178 impl<'de> serde::de::Visitor<'de> for DidVisitor {
179 type Value = TrimmedDid<'de>;
180 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
181 formatter.write_str("bytes (tagged) or string (legacy)")
182 }
183
184 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
185 where
186 E: serde::de::Error,
187 {
188 TrimmedDid::try_from(v)
189 .map(|td| td.into_static())
190 .map_err(E::custom)
191 }
192
193 fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
194 where
195 E: serde::de::Error,
196 {
197 TrimmedDid::try_from(v).map_err(E::custom)
198 }
199 }
200 deserializer.deserialize_any(DidVisitor)
201 }
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
205pub struct DbTid(#[serde(with = "serde_bytes")] [u8; 8]);
206
207impl DbTid {
208 pub fn new_from_bytes(bytes: [u8; 8]) -> Self {
209 Self(bytes)
210 }
211
212 pub fn len(&self) -> usize {
213 self.0.len()
214 }
215
216 pub fn as_bytes(&self) -> &[u8] {
217 &self.0
218 }
219
220 pub fn to_tid(&self) -> Tid {
221 Tid::raw(self.to_smolstr())
222 }
223
224 fn to_smolstr(&self) -> SmolStr {
225 let mut i = u64::from_be_bytes(self.0);
226 let mut s = SmolStrBuilder::new();
227 for _ in 0..13 {
228 let c = i & 0x1F;
229 s.push(S32_CHAR.chars().nth(c as usize).unwrap());
230 i >>= 5;
231 }
232
233 let mut builder = SmolStrBuilder::new();
234 for c in s.finish().chars().rev() {
235 builder.push(c);
236 }
237 builder.finish()
238 }
239}
240
241pub fn s32decode(s: &str) -> u64 {
242 let mut i: usize = 0;
243 for c in s.chars() {
244 i = i * 32 + S32_CHAR.chars().position(|x| x == c).unwrap();
245 }
246 i as u64
247}
248
249impl From<&Tid> for DbTid {
250 fn from(tid: &Tid) -> Self {
251 DbTid(s32decode(tid.as_str()).to_be_bytes())
252 }
253}
254
255impl From<DbTid> for Tid {
256 fn from(val: DbTid) -> Self {
257 val.to_tid()
258 }
259}
260
261impl Display for DbTid {
262 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
263 write!(f, "{}", self.to_smolstr())
264 }
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
268#[repr(u8)]
269pub enum DbAction {
270 Create = 0,
271 Update = 1,
272 Delete = 2,
273}
274
275impl DbAction {
276 pub fn as_str(&self) -> &'static str {
277 match self {
278 DbAction::Create => "create",
279 DbAction::Update => "update",
280 DbAction::Delete => "delete",
281 }
282 }
283}
284
285impl<'a> TryFrom<&'a str> for DbAction {
286 type Error = miette::Report;
287
288 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
289 match s {
290 "create" => Ok(DbAction::Create),
291 "update" => Ok(DbAction::Update),
292 "delete" => Ok(DbAction::Delete),
293 _ => miette::bail!("invalid action: {}", s),
294 }
295 }
296}
297
298impl Display for DbAction {
299 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300 write!(f, "{}", self.as_str())
301 }
302}
303
304#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
305#[serde(untagged)]
306pub enum DbRkey {
307 Tid(DbTid),
308 Str(SmolStr),
309}
310
311impl DbRkey {
312 pub fn new(s: &str) -> Self {
313 if let Ok(tid) = Tid::new(s) {
314 DbRkey::Tid(DbTid::from(&tid))
315 } else {
316 DbRkey::Str(SmolStr::from(s))
317 }
318 }
319
320 pub fn len(&self) -> usize {
321 match self {
322 DbRkey::Tid(tid) => tid.len(),
323 DbRkey::Str(s) => s.len(),
324 }
325 }
326
327 pub fn to_smolstr(&self) -> SmolStr {
328 match self {
329 DbRkey::Tid(tid) => tid.to_smolstr(),
330 DbRkey::Str(s) => s.clone(),
331 }
332 }
333}
334
335impl From<&str> for DbRkey {
336 fn from(s: &str) -> Self {
337 Self::new(s)
338 }
339}
340
341impl From<String> for DbRkey {
342 fn from(s: String) -> Self {
343 Self::new(&s)
344 }
345}
346
347impl From<SmolStr> for DbRkey {
348 fn from(s: SmolStr) -> Self {
349 Self::new(s.as_str())
350 }
351}
352
353impl Display for DbRkey {
354 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
355 write!(f, "{}", self.to_smolstr())
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362 use jacquard_common::types::tid::Tid;
363
364 #[test]
365 fn test_dbtid_roundtrip() {
366 let tid_str = "3jzfcijpj2z2a";
367 let tid = Tid::new(tid_str).unwrap();
368 let db_tid = DbTid::from(&tid);
369 assert_eq!(db_tid.to_tid().as_str(), tid_str);
370
371 let tid_str_2 = "2222222222222";
372 let tid = Tid::new(tid_str_2).unwrap();
373 let db_tid = DbTid::from(&tid);
374 assert_eq!(db_tid.to_tid().as_str(), tid_str_2);
375 }
376
377 #[test]
378 fn test_dbrkey() {
379 let tid_str = "3jzfcijpj2z2a";
380 let rkey = DbRkey::new(tid_str);
381 if let DbRkey::Tid(t) = rkey {
382 assert_eq!(t.to_tid().as_str(), tid_str);
383 } else {
384 panic!("expected tid");
385 }
386
387 let str_val = "self";
388 let rkey = DbRkey::new(str_val);
389 if let DbRkey::Str(s) = rkey {
390 assert_eq!(s, str_val);
391 } else {
392 panic!("expected str");
393 }
394 }
395}