kind of like tap but different and in rust
at main 395 lines 11 kB view raw
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}