don't
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(atproto): consolidate impls for basic types

Make the implementations for Did, Handle, and Nsid more consistent by
perfoming most of their implementation in shared macros.

This also adds serde and sqlx support to Handle and Nsid.

Signed-off-by: tjh <x@tjh.dev>

tjh 5774be8e 1785efbb

+645 -831
+13 -8
crates/atproto/src/aturi.rs
··· 17 17 } 18 18 19 19 impl<'a> AtUri<'a> { 20 - #[must_use] 20 + #[must_use] 21 21 pub const fn did(&self) -> Option<&'a Did> { 22 22 match self.authority { 23 23 Authority::Did(did) => Some(did), 24 - _ => None, 24 + Authority::Handle(_) => None, 25 25 } 26 26 } 27 27 28 - #[must_use] 28 + #[must_use] 29 29 pub const fn handle(&self) -> Option<&'a Handle> { 30 30 match self.authority { 31 31 Authority::Handle(handle) => Some(handle), 32 - _ => None, 32 + Authority::Did(_) => None, 33 33 } 34 34 } 35 35 36 - #[must_use] 36 + #[must_use] 37 37 pub const fn collection_str(&self) -> Option<&str> { 38 38 match self.collection { 39 39 Some(collection) => Some(collection.as_str()), ··· 49 49 } 50 50 51 51 impl Authority<'_> { 52 - #[must_use] 52 + #[must_use] 53 53 pub const fn is_did(&self) -> bool { 54 54 matches!(self, Self::Did(_)) 55 55 } 56 56 57 - #[must_use] 57 + #[must_use] 58 58 pub const fn is_handle(&self) -> bool { 59 59 matches!(self, Self::Handle(_)) 60 60 } 61 61 62 - #[must_use] 62 + #[must_use] 63 63 pub const fn as_str(&self) -> &str { 64 64 match self { 65 65 Self::Did(did) => did.as_str(), ··· 87 87 } 88 88 89 89 impl<'a> AtUri<'a> { 90 + /// 91 + /// # Errors 92 + /// 93 + /// Returns an error if `uri` is not a valid AT-URI. 94 + /// 90 95 pub fn parse(uri: &'a str) -> Result<Self, Error> { 91 96 let uri = uri.strip_prefix("at://").ok_or(Error::InvalidScheme)?; 92 97 let mut parts = uri.split('/');
+33 -286
crates/atproto/src/did.rs
··· 1 - //! `ATmosphere` specific DID. 2 - //! 3 - use core::{borrow, fmt, mem, ops, ptr}; 1 + use core::{borrow, fmt, ops}; 4 2 5 3 #[cfg(feature = "serde")] 6 - pub(crate) use serde_impl::DidVisitor; 4 + pub(crate) use __serde_impl::Visitor as DidVisitor; 7 5 8 - /// A slice of an ATmosphere-specific DID. 6 + /// An Atmosphere DID. 9 7 /// 10 8 /// This is an _unsized_ type similiar to [`str`], so must always be used behind a pointer like 11 - /// `&` or `Box`. For an owned variant of this type use [`OwnedDid`]. 9 + /// `&` or [`Box`]. 10 + /// 11 + /// For an owned variant use [`OwnedDid`]. 12 12 /// 13 13 /// See: <https://atproto.com/specs/did> 14 14 #[derive(Hash, PartialEq, Eq, PartialOrd, Ord)] ··· 18 18 } 19 19 20 20 impl Did { 21 - fn new<D: AsRef<str> + ?Sized>(did: &D) -> &Self { 22 - unsafe { &*(ptr::from_ref::<str>(did.as_ref()) as *const Self) } 23 - } 24 - 25 - const fn new_boxed(did: Box<str>) -> Box<Self> { 26 - unsafe { mem::transmute::<Box<str>, Box<Self>>(did) } 27 - } 28 - 29 21 /// Construct a `&'static Did` from a `&'static str`. 30 22 /// 31 23 /// # Panics 32 24 /// 33 - /// Panics if `s` is not a valid DID. 25 + /// Panics if `did` is not a valid DID. 34 26 /// 35 27 #[must_use] 36 28 pub fn from_static(did: &'static str) -> &'static Self { 37 - validate_did(did).expect("hard-coded did should be valid"); 29 + validate_did(did).expect("Hard-coded did should be valid"); 38 30 Self::new(did) 39 31 } 40 32 ··· 42 50 /// assert_eq!(did.method(), "plc"); 43 51 /// assert_eq!(did.ident(), "65gha4t3avpfpzmvpbwovss7"); 44 52 /// ``` 53 + /// 54 + /// # Errors 55 + /// 56 + /// Returns an error if `did` is not a valid DID. 45 57 /// 46 58 pub fn parse<D: AsRef<str> + ?Sized>(did: &D) -> Result<&Self, Error> { 47 59 validate_did(did.as_ref())?; ··· 64 68 /// ``` 65 69 /// 66 70 #[must_use] 71 + #[allow(clippy::missing_panics_doc)] 67 72 pub fn typ(&self) -> &'static str { 68 73 assert_eq!( 69 74 &self.inner[..4], ··· 112 115 &self.inner[1 + self.method_terminator()..] 113 116 } 114 117 115 - /// Get the DID as a string slice. 116 - #[inline] 117 - #[must_use] 118 - pub const fn as_str(&self) -> &str { 119 - &self.inner 120 - } 121 - 122 - /// Convert to a `Box<Did>`. 123 - #[must_use] 124 - pub fn into_boxed(&self) -> Box<Self> { 125 - Self::new_boxed(self.inner.to_owned().into_boxed_str()) 126 - } 127 - 118 + #[allow(clippy::missing_panics_doc)] 128 119 fn method_terminator(&self) -> usize { 129 120 4 + self.inner[4..] 130 121 .find(':') ··· 120 135 } 121 136 } 122 137 123 - impl fmt::Debug for Did { 124 - #[inline] 125 - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 126 - fmt::Debug::fmt(&self.inner, f) 127 - } 128 - } 129 - 130 - impl fmt::Display for Did { 131 - #[inline] 132 - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 133 - fmt::Display::fmt(&self.inner, f) 134 - } 135 - } 136 - 137 - impl AsRef<str> for Did { 138 - #[inline] 139 - fn as_ref(&self) -> &str { 140 - Self::as_str(self) 141 - } 142 - } 143 - 144 - impl ops::Deref for Did { 145 - type Target = str; 146 - #[inline] 147 - fn deref(&self) -> &Self::Target { 148 - &self.inner 149 - } 150 - } 151 - 152 - impl<'a> TryFrom<&'a str> for &'a Did { 153 - type Error = Error; 154 - #[inline] 155 - fn try_from(value: &'a str) -> Result<Self, Self::Error> { 156 - validate_did(value)?; 157 - Ok(Did::new(value)) 158 - } 159 - } 160 - 161 - impl TryFrom<Box<str>> for Box<Did> { 162 - type Error = Error; 163 - #[inline] 164 - /// Convert a `Box<str>` to a `Box<Did>` without copying. 165 - fn try_from(value: Box<str>) -> Result<Self, Self::Error> { 166 - validate_did(&value)?; 167 - Ok(Did::new_boxed(value)) 168 - } 169 - } 170 - 171 - impl TryFrom<String> for Box<Did> { 172 - type Error = Error; 173 - #[inline] 174 - /// Convert a [`String`] to a `Box<Did>` without copying. 175 - fn try_from(value: String) -> Result<Self, Self::Error> { 176 - validate_did(&value)?; 177 - Ok(Did::new_boxed(value.into_boxed_str())) 178 - } 179 - } 180 - 181 - impl std::str::FromStr for Box<Did> { 182 - type Err = Error; 183 - #[inline] 184 - fn from_str(s: &str) -> Result<Self, Self::Err> { 185 - validate_did(s)?; 186 - Ok(Did::new_boxed(s.into())) 187 - } 188 - } 189 - 190 - impl PartialEq<Did> for str { 191 - #[inline] 192 - fn eq(&self, other: &Did) -> bool { 193 - self == other.as_str() 194 - } 195 - } 196 - 197 - impl PartialEq<str> for Did { 198 - #[inline] 199 - fn eq(&self, other: &str) -> bool { 200 - self.as_str() == other 201 - } 202 - } 203 - 204 - impl From<Box<Did>> for Box<str> { 205 - fn from(value: Box<Did>) -> Self { 206 - unsafe { mem::transmute::<Box<Did>, Self>(value) } 207 - } 208 - } 138 + impl_str_newtype!(Did, validate_did); 209 139 210 140 #[derive(Debug, PartialEq, Eq, thiserror::Error)] 211 141 pub enum Error { ··· 195 295 Self { inner } 196 296 } 197 297 298 + /// Construct an `OwnedDid` from a `&'static str`. 299 + /// 300 + /// # Panics 301 + /// 302 + /// Panics if `did` is not a valid DID. 303 + /// 198 304 #[must_use] 199 305 pub fn from_static(did: &'static str) -> Self { 200 306 validate_did(did).expect("hard-coded did should be valid"); ··· 220 314 /// assert_eq!(did.method(), "plc"); 221 315 /// assert_eq!(did.ident(), "65gha4t3avpfpzmvpbwovss7"); 222 316 /// ``` 317 + /// 318 + /// # Errors 319 + /// 320 + /// Returns an error if `did` is not a valid DID. 223 321 /// 224 322 pub fn parse<D: AsRef<str> + Into<SmallDid>>(did: D) -> Result<Self, Error> { 225 323 validate_did(did.as_ref())?; ··· 290 380 } 291 381 292 382 impl_owned_tryfrom!(&str); 293 - impl_owned_tryfrom!(Box<str>); 294 383 impl_owned_tryfrom!(String); 384 + impl_owned_tryfrom!(Box<str>); 295 385 296 386 impl std::str::FromStr for OwnedDid { 297 387 type Err = Error; ··· 324 414 } 325 415 } 326 416 327 - macro_rules! impl_partial_eq { 328 - ($rhs:ty) => { 329 - impl PartialEq<$rhs> for &str { 330 - #[inline] 331 - fn eq(&self, other: &$rhs) -> bool { 332 - *self == other.as_str() 333 - } 334 - } 335 - 336 - impl PartialEq<&str> for $rhs { 337 - #[inline] 338 - fn eq(&self, other: &&str) -> bool { 339 - self.as_str() == *other 340 - } 341 - } 342 - 343 - impl PartialEq<$rhs> for &Did { 344 - #[inline] 345 - fn eq(&self, other: &$rhs) -> bool { 346 - self.as_str() == other.as_str() 347 - } 348 - } 349 - 350 - impl PartialEq<&Did> for $rhs { 351 - #[inline] 352 - fn eq(&self, other: &&Did) -> bool { 353 - self.as_str() == other.as_str() 354 - } 355 - } 356 - }; 357 - } 358 - 359 - impl_partial_eq!(Box<Did>); 360 - impl_partial_eq!(OwnedDid); 417 + impl_partial_eq!(OwnedDid, &Did); 418 + impl_partial_eq!(OwnedDid, &str); 361 419 362 420 #[cfg(feature = "serde")] 363 - mod serde_impl { 364 - use std::borrow::Cow; 365 - 366 - use super::{Did, OwnedDid}; 367 - 368 - #[derive(Default)] 369 - pub struct DidVisitor<'a>(std::marker::PhantomData<&'a Did>); 370 - 371 - impl<'de: 'a, 'a> serde::de::Visitor<'de> for DidVisitor<'a> { 372 - type Value = Cow<'a, Did>; 373 - 374 - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 375 - formatter.write_str("ATproto DID") 376 - } 377 - 378 - fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 379 - where 380 - E: serde::de::Error, 381 - { 382 - let did = v.try_into().map_err(serde::de::Error::custom)?; 383 - Ok(Cow::Owned(did)) 384 - } 385 - 386 - fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E> 387 - where 388 - E: serde::de::Error, 389 - { 390 - let did = v.try_into().map_err(serde::de::Error::custom)?; 391 - Ok(Cow::Borrowed(did)) 392 - } 393 - } 394 - 395 - impl<'de: 'a, 'a> serde::Deserialize<'de> for &'a Did { 396 - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 397 - where 398 - D: serde::Deserializer<'de>, 399 - { 400 - match deserializer.deserialize_str(DidVisitor::default())? { 401 - Cow::Borrowed(did) => Ok(did), 402 - Cow::Owned(_) => Err(serde::de::Error::custom("expected a borrowed DID")), 403 - } 404 - } 405 - } 406 - 407 - impl<'de> serde::Deserialize<'de> for Box<Did> { 408 - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 409 - where 410 - D: serde::Deserializer<'de>, 411 - { 412 - let did = deserializer.deserialize_string(DidVisitor::default())?; 413 - Ok(did.into_boxed()) 414 - } 415 - } 416 - 417 - impl<'de> serde::Deserialize<'de> for OwnedDid { 418 - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 419 - where 420 - D: serde::Deserializer<'de>, 421 - { 422 - let did = deserializer.deserialize_string(DidVisitor::default())?; 423 - Ok(did.into()) 424 - } 425 - } 426 - 427 - macro_rules! impl_serialize { 428 - ($type:ty) => { 429 - impl serde::Serialize for $type { 430 - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 431 - where 432 - S: serde::Serializer, 433 - { 434 - serializer.serialize_str(self.as_str()) 435 - } 436 - } 437 - }; 438 - } 439 - 440 - impl_serialize!(Did); 441 - impl_serialize!(OwnedDid); 442 - } 421 + impl_str_newtype_serde!(Did, OwnedDid); 443 422 444 423 #[cfg(feature = "sqlx")] 445 424 mod sqlx_impl { 446 - use sqlx::{Database, Decode, Encode, Type}; 447 - 448 - use super::{Did, OwnedDid, validate_did}; 449 - 450 - impl<DB: Database> Type<DB> for Did 451 - where 452 - for<'a> &'a str: Type<DB>, 453 - { 454 - fn type_info() -> DB::TypeInfo { 455 - <&str as Type<DB>>::type_info() 456 - } 457 - } 458 - 459 - impl<'q, DB: Database> Encode<'q, DB> for &'q Did 460 - where 461 - &'q str: Encode<'q, DB>, 462 - { 463 - fn encode_by_ref( 464 - &self, 465 - buf: &mut <DB as Database>::ArgumentBuffer<'q>, 466 - ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> { 467 - let s = self.as_str(); 468 - <&'q str as Encode<'q, DB>>::encode_by_ref(&s, buf) 469 - } 470 - } 471 - 472 - impl<'r, DB: Database> Decode<'r, DB> for &'r Did 473 - where 474 - &'r str: Decode<'r, DB>, 475 - { 476 - fn decode(value: <DB as Database>::ValueRef<'r>) -> Result<Self, sqlx::error::BoxDynError> { 477 - let value = <&str as Decode<'r, DB>>::decode(value)?; 478 - validate_did(value)?; 479 - Ok(Did::new(value)) 480 - } 481 - } 482 - 483 - impl<'r, DB: Database> Decode<'r, DB> for Box<Did> 484 - where 485 - &'r Did: Decode<'r, DB>, 486 - { 487 - fn decode(value: <DB as Database>::ValueRef<'r>) -> Result<Self, sqlx::error::BoxDynError> { 488 - let value = <&Did as Decode<'r, DB>>::decode(value)?; 489 - Ok(value.into_boxed()) 490 - } 491 - } 492 - 493 - impl<'r, DB: Database> Decode<'r, DB> for OwnedDid 494 - where 495 - &'r Did: Decode<'r, DB>, 496 - { 497 - fn decode(value: <DB as Database>::ValueRef<'r>) -> Result<Self, sqlx::error::BoxDynError> { 498 - let value = <&Did as Decode<'r, DB>>::decode(value)?; 499 - Ok(value.to_owned()) 500 - } 501 - } 425 + use super::{Did, OwnedDid}; 426 + impl_str_newtype_sqlx!(ref Did); 427 + impl_str_newtype_sqlx!(Box<Did>); 428 + impl_str_newtype_sqlx!(OwnedDid); 502 429 } 503 430 504 431 #[cfg(test)]
+47 -255
crates/atproto/src/handle.rs
··· 1 - use core::{fmt, mem, ops}; 2 - 3 - /// An `ATproto` handle. 1 + /// An Atmosphere handle. 2 + /// 3 + /// This is an _unsized_ type similiar to [`str`], so must always be used behind a pointer like 4 + /// `&` or [`Box`]. 5 + /// 6 + /// See: <https://atproto.com/specs/handle> 7 + /// 4 8 #[derive(Hash, PartialEq, Eq, PartialOrd, Ord)] 5 9 #[repr(transparent)] 6 10 pub struct Handle { ··· 12 8 } 13 9 14 10 impl Handle { 15 - fn new<S: AsRef<str> + ?Sized>(s: &S) -> &Self { 16 - unsafe { &*(std::ptr::from_ref::<str>(s.as_ref()) as *const Self) } 11 + /// Construct a `&'static Handle` from a `&'static str`. 12 + /// 13 + /// # Panics 14 + /// 15 + /// Panics if `handle` is not a valid DID. 16 + /// 17 + #[must_use] 18 + pub fn from_static(handle: &'static str) -> &'static Self { 19 + validate_handle(handle).expect("Hard-coded handle should be valid"); 20 + Self::new(handle) 17 21 } 18 22 19 - fn new_owned(handle: Box<str>) -> Box<Self> { 20 - unsafe { mem::transmute::<Box<str>, Box<Self>>(handle) } 21 - } 22 - 23 - /// Create a `Handle` from an `&'static str`. Panics if the handle is not 24 - /// valid. 25 - #[must_use] 26 - pub fn from_static(s: &'static str) -> &'static Self { 27 - validate_handle(s).expect("`s` should be a valid Handle"); 28 - Self::new(s) 29 - } 30 - 31 - pub fn parse<S: AsRef<str> + ?Sized>(s: &S) -> Result<&Self, Error> { 32 - validate_handle(s.as_ref())?; 33 - Ok(Self::new(s)) 34 - } 35 - 36 - #[inline] 37 - #[must_use] 38 - pub const fn as_str(&self) -> &str { 39 - &self.inner 40 - } 41 - 42 - #[inline] 43 - #[must_use] 44 - pub const fn len(&self) -> usize { 45 - self.inner.len() 46 - } 47 - 48 - #[inline] 49 - #[must_use] 50 - pub const fn is_empty(&self) -> bool { 51 - assert!(!self.inner.is_empty()); 52 - false 53 - } 54 - 55 - pub fn segments(&self) -> impl Iterator<Item = &str> { 56 - self.inner.split('.') 23 + /// Parse a `Handle` from a `str`. 24 + /// 25 + /// # Example 26 + /// 27 + /// ```rust 28 + /// use atproto::Handle; 29 + /// 30 + /// let handle = Handle::parse("tjh.dev").expect("'tjh.dev' is a valid handle"); 31 + /// assert_eq!(handle, "tjh.dev"); 32 + /// ``` 33 + /// 34 + /// # Errors 35 + /// 36 + /// Returns an error if `handle` is not a valid handle. 37 + /// 38 + pub fn parse<S: AsRef<str> + ?Sized>(handle: &S) -> Result<&Self, Error> { 39 + validate_handle(handle.as_ref())?; 40 + Ok(Self::new(handle)) 57 41 } 58 42 } 59 43 60 - impl fmt::Display for Handle { 61 - #[inline] 62 - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { 63 - fmt::Display::fmt(&self.inner, f) 64 - } 65 - } 44 + impl_str_newtype!(Handle, validate_handle); 66 45 67 - impl fmt::Debug for Handle { 68 - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { 69 - fmt::Debug::fmt(&self.inner, f) 70 - } 71 - } 46 + #[cfg(feature = "serde")] 47 + impl_str_newtype_serde!(Handle); 72 48 73 - impl AsRef<str> for &Handle { 74 - #[inline] 75 - fn as_ref(&self) -> &str { 76 - &self.inner 77 - } 78 - } 79 - 80 - impl ops::Deref for Handle { 81 - type Target = str; 82 - #[inline] 83 - fn deref(&self) -> &Self::Target { 84 - &self.inner 85 - } 49 + #[cfg(feature = "sqlx")] 50 + mod sqlx_impl { 51 + use super::Handle; 52 + impl_str_newtype_sqlx!(ref Handle); 53 + impl_str_newtype_sqlx!(Box<Handle>); 86 54 } 87 55 88 56 impl ToOwned for Handle { 89 57 type Owned = Box<Self>; 90 58 #[inline] 91 59 fn to_owned(&self) -> Self::Owned { 92 - Self::new_owned(self.inner.to_owned().into_boxed_str()) 93 - } 94 - } 95 - 96 - impl<'a> TryFrom<&'a str> for &'a Handle { 97 - type Error = Error; 98 - #[inline] 99 - fn try_from(value: &'a str) -> Result<Self, Self::Error> { 100 - validate_handle(value)?; 101 - Ok(Handle::new(value)) 102 - } 103 - } 104 - 105 - impl TryFrom<&str> for Box<Handle> { 106 - type Error = Error; 107 - #[inline] 108 - fn try_from(value: &str) -> Result<Self, Self::Error> { 109 - validate_handle(value)?; 110 - Ok(Handle::new_owned(value.to_owned().into_boxed_str())) 111 - } 112 - } 113 - 114 - impl TryFrom<Box<str>> for Box<Handle> { 115 - type Error = Error; 116 - #[inline] 117 - fn try_from(value: Box<str>) -> Result<Self, Self::Error> { 118 - validate_handle(&value)?; 119 - Ok(Handle::new_owned(value)) 120 - } 121 - } 122 - 123 - impl TryFrom<String> for Box<Handle> { 124 - type Error = Error; 125 - #[inline] 126 - fn try_from(value: String) -> Result<Self, Self::Error> { 127 - validate_handle(&value)?; 128 - Ok(Handle::new_owned(value.into_boxed_str())) 129 - } 130 - } 131 - 132 - impl From<&Handle> for Box<Handle> { 133 - #[inline] 134 - fn from(value: &Handle) -> Self { 135 - Handle::new_owned(value.inner.to_owned().into_boxed_str()) 136 - } 137 - } 138 - 139 - impl std::str::FromStr for Box<Handle> { 140 - type Err = Error; 141 - #[inline] 142 - fn from_str(s: &str) -> Result<Self, Self::Err> { 143 - s.try_into() 144 - } 145 - } 146 - 147 - impl Clone for Box<Handle> { 148 - #[inline] 149 - fn clone(&self) -> Self { 150 - (**self).to_owned() 151 - } 152 - } 153 - 154 - impl PartialEq<Box<Handle>> for &Handle { 155 - #[inline] 156 - fn eq(&self, other: &Box<Handle>) -> bool { 157 - self.as_str() == other.as_str() 158 - } 159 - } 160 - 161 - impl PartialEq<&Handle> for Box<Handle> { 162 - #[inline] 163 - fn eq(&self, other: &&Handle) -> bool { 164 - self.as_str() == other.as_str() 165 - } 166 - } 167 - 168 - impl PartialEq<Handle> for str { 169 - #[inline] 170 - fn eq(&self, other: &Handle) -> bool { 171 - self == other.as_str() 172 - } 173 - } 174 - 175 - impl PartialEq<str> for Handle { 176 - #[inline] 177 - fn eq(&self, other: &str) -> bool { 178 - self.as_str() == other 179 - } 180 - } 181 - 182 - impl PartialEq<Box<Handle>> for &str { 183 - #[inline] 184 - fn eq(&self, other: &Box<Handle>) -> bool { 185 - *self == other.as_str() 186 - } 187 - } 188 - 189 - impl PartialEq<&str> for Box<Handle> { 190 - #[inline] 191 - fn eq(&self, other: &&str) -> bool { 192 - self.as_str() == *other 193 - } 194 - } 195 - 196 - #[cfg(feature = "serde")] 197 - mod serde_impl { 198 - use super::Handle; 199 - 200 - impl<'de> serde::Deserialize<'de> for Box<Handle> { 201 - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 202 - where 203 - D: serde::Deserializer<'de>, 204 - { 205 - struct HandleVisitor; 206 - 207 - impl serde::de::Visitor<'_> for HandleVisitor { 208 - type Value = Box<Handle>; 209 - 210 - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 211 - formatter.write_str("ATproto Handle") 212 - } 213 - 214 - fn visit_string<E>(self, v: String) -> Result<Self::Value, E> 215 - where 216 - E: serde::de::Error, 217 - { 218 - v.try_into().map_err(serde::de::Error::custom) 219 - } 220 - 221 - fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 222 - where 223 - E: serde::de::Error, 224 - { 225 - v.try_into().map_err(serde::de::Error::custom) 226 - } 227 - } 228 - 229 - deserializer.deserialize_string(HandleVisitor) 230 - } 231 - } 232 - 233 - impl<'de: 'a, 'a> serde::Deserialize<'de> for &'a Handle { 234 - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 235 - where 236 - D: serde::Deserializer<'de>, 237 - { 238 - #[derive(Default)] 239 - struct HandleVisitor<'a>(std::marker::PhantomData<&'a Handle>); 240 - 241 - impl<'de: 'a, 'a> serde::de::Visitor<'de> for HandleVisitor<'a> { 242 - type Value = &'a Handle; 243 - 244 - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 245 - formatter.write_str("ATproto Handle") 246 - } 247 - 248 - fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E> 249 - where 250 - E: serde::de::Error, 251 - { 252 - v.try_into().map_err(serde::de::Error::custom) 253 - } 254 - } 255 - 256 - deserializer.deserialize_str(HandleVisitor::default()) 257 - } 258 - } 259 - 260 - impl serde::Serialize for Handle { 261 - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 262 - where 263 - S: serde::Serializer, 264 - { 265 - serializer.serialize_str(&self.inner) 266 - } 60 + Self::new_boxed(self.inner.to_owned().into_boxed_str()) 267 61 } 268 62 } 269 63 ··· 128 326 use super::Handle; 129 327 130 328 #[test] 131 - fn accept_valid() { 329 + fn can_parse_examples() { 132 330 for handle in [ 133 331 "jay.bsky.social", 134 332 "8.cn", ··· 145 343 } 146 344 147 345 #[test] 148 - fn reject_invalid_syntax() { 346 + fn rejects_invalid_examples() { 149 347 for handle in [ 150 348 "jo@hn.test", 151 349 "💩.test", ··· 162 360 } 163 361 164 362 #[test] 165 - fn reject_restricted() { 363 + fn rejects_restricted_tlds() { 166 364 for handle in [ 167 365 "2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion", 168 366 "laptop.local", ··· 213 411 assert_eq!(test.handle, "tjh.dev"); 214 412 assert_eq!("tjh.dev", test.handle); 215 413 } 216 - } 217 - 218 - #[test] 219 - fn can_compare() { 220 - let h1 = "example.com"; 221 - let h2 = "another.sh"; 222 - let handle = Handle::parse(h1).unwrap(); 223 - 224 - assert_eq!(handle, h1); 225 - assert_ne!(handle, h2); 226 414 } 227 415 }
+5 -1
crates/atproto/src/lib.rs
··· 1 1 //! 2 2 //! Primitive types in the atmosphere. 3 3 //! 4 + #[macro_use] 5 + mod macros; 6 + 4 7 pub mod aturi; 5 8 pub mod did; 6 9 pub mod handle; ··· 11 8 pub mod tid; 12 9 pub mod uri; 13 10 14 - pub use did::Did; 11 + pub use did::{Did, OwnedDid}; 15 12 pub use handle::Handle; 16 13 pub use nsid::Nsid; 14 + pub use tid::{Tid, TidClock}; 17 15 pub use uri::RecordUri; 18 16 19 17 #[cfg(feature = "serde")]
+336
crates/atproto/src/macros.rs
··· 1 + macro_rules! impl_partial_eq { 2 + ($type:ty, &str) => { 3 + impl PartialEq<$type> for str { 4 + #[inline] 5 + fn eq(&self, other: &$type) -> bool { 6 + self == other.as_str() 7 + } 8 + } 9 + 10 + impl PartialEq<$type> for &str { 11 + #[inline] 12 + fn eq(&self, other: &$type) -> bool { 13 + *self == other.as_str() 14 + } 15 + } 16 + 17 + impl PartialEq<str> for $type { 18 + #[inline] 19 + fn eq(&self, other: &str) -> bool { 20 + self.as_str() == other 21 + } 22 + } 23 + 24 + impl PartialEq<&str> for $type { 25 + #[inline] 26 + fn eq(&self, other: &&str) -> bool { 27 + self.as_str() == *other 28 + } 29 + } 30 + }; 31 + ($type:ty, $other:ty) => { 32 + impl PartialEq<$other> for $type { 33 + #[inline] 34 + fn eq(&self, other: &$other) -> bool { 35 + self.as_str() == other.as_str() 36 + } 37 + } 38 + 39 + impl PartialEq<$type> for $other { 40 + #[inline] 41 + fn eq(&self, other: &$type) -> bool { 42 + self.as_str() == other.as_str() 43 + } 44 + } 45 + }; 46 + } 47 + 48 + macro_rules! impl_str_newtype { 49 + ($type:ty, $validate:ident) => { 50 + impl $type { 51 + fn new<D: AsRef<str> + ?Sized>(did: &D) -> &Self { 52 + unsafe { &*(::core::ptr::from_ref::<str>(did.as_ref()) as *const Self) } 53 + } 54 + 55 + const fn new_boxed(did: Box<str>) -> Box<Self> { 56 + unsafe { ::core::mem::transmute::<Box<str>, Box<Self>>(did) } 57 + } 58 + 59 + #[doc = concat!("Get the `&", stringify!($type), "` as a `&str`")] 60 + #[inline] 61 + #[must_use] 62 + pub const fn as_str(&self) -> &str { 63 + &self.inner 64 + } 65 + 66 + #[doc = concat!("Convert the `&", stringify!($type), "` to a `Box<", stringify!($type), ">`")] 67 + #[must_use] 68 + pub fn into_boxed(&self) -> Box<Self> { 69 + Self::new_boxed(self.inner.to_owned().into_boxed_str()) 70 + } 71 + } 72 + 73 + impl ::core::fmt::Display for $type { 74 + #[inline] 75 + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { 76 + ::core::fmt::Display::fmt(&self.inner, f) 77 + } 78 + } 79 + 80 + impl ::core::fmt::Debug for $type { 81 + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { 82 + ::core::fmt::Debug::fmt(&self.inner, f) 83 + } 84 + } 85 + 86 + impl AsRef<str> for $type { 87 + #[inline] 88 + fn as_ref(&self) -> &str { 89 + <$type>::as_str(&self) 90 + } 91 + } 92 + 93 + impl ::core::ops::Deref for $type { 94 + type Target = str; 95 + #[inline] 96 + fn deref(&self) -> &Self::Target { 97 + &self.inner 98 + } 99 + } 100 + 101 + // Ensure boxed variants can be clone. 102 + impl Clone for Box<$type> { 103 + fn clone(&self) -> Self { 104 + (**self).into_boxed() 105 + } 106 + } 107 + 108 + // Standard conversions 109 + impl<'a> TryFrom<&'a str> for &'a $type { 110 + type Error = Error; 111 + fn try_from(value: &'a str) -> Result<Self, Self::Error> { 112 + $validate(value)?; 113 + Ok(<$type>::new(value)) 114 + } 115 + } 116 + 117 + impl TryFrom<&str> for Box<$type> { 118 + type Error = Error; 119 + fn try_from(value: &str) -> Result<Self, Self::Error> { 120 + $validate(value)?; 121 + Ok(<$type>::new_boxed(value.to_owned().into_boxed_str())) 122 + } 123 + } 124 + 125 + impl TryFrom<Box<str>> for Box<$type> { 126 + type Error = Error; 127 + #[inline] 128 + fn try_from(value: Box<str>) -> Result<Self, Self::Error> { 129 + $validate(&value)?; 130 + Ok(<$type>::new_boxed(value)) 131 + } 132 + } 133 + 134 + impl TryFrom<String> for Box<$type> { 135 + type Error = Error; 136 + #[inline] 137 + fn try_from(value: String) -> Result<Self, Self::Error> { 138 + $validate(&value)?; 139 + Ok(<$type>::new_boxed(value.into_boxed_str())) 140 + } 141 + } 142 + 143 + impl std::str::FromStr for Box<$type> { 144 + type Err = Error; 145 + #[inline] 146 + fn from_str(s: &str) -> Result<Self, Self::Err> { 147 + $validate(s)?; 148 + Ok(<$type>::new_boxed(s.into())) 149 + } 150 + } 151 + 152 + 153 + impl From<Box<$type>> for Box<str> { 154 + #[doc = concat!("Transmute a `Box<", stringify!($type), ">` to a `Box<str>`")] 155 + fn from(value: Box<$type>) -> Self { 156 + unsafe { ::core::mem::transmute::<Box<$type>, Self>(value) } 157 + } 158 + } 159 + 160 + impl_partial_eq!($type, &str); 161 + impl_partial_eq!(Box<$type>, &str); 162 + impl_partial_eq!(Box<$type>, $type); 163 + impl_partial_eq!(Box<$type>, &$type); 164 + }; 165 + } 166 + 167 + macro_rules! impl_str_newtype_serde { 168 + (__inner $type:ty) => { 169 + #[derive(Default)] 170 + pub struct Visitor<'a>(std::marker::PhantomData<&'a $type>); 171 + 172 + impl<'de: 'a, 'a> serde::de::Visitor<'de> for Visitor<'a> { 173 + type Value = std::borrow::Cow<'a, $type>; 174 + 175 + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 176 + formatter.write_str(concat!("Atmosphere ", stringify!($type))) 177 + } 178 + 179 + fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 180 + where 181 + E: serde::de::Error, 182 + { 183 + let value = v.try_into().map_err(serde::de::Error::custom)?; 184 + Ok(std::borrow::Cow::Owned(value)) 185 + } 186 + 187 + fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E> 188 + where 189 + E: serde::de::Error, 190 + { 191 + let value = v.try_into().map_err(serde::de::Error::custom)?; 192 + Ok(std::borrow::Cow::Borrowed(value)) 193 + } 194 + } 195 + 196 + impl<'de: 'a, 'a> serde::Deserialize<'de> for &'a $type { 197 + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 198 + where 199 + D: serde::Deserializer<'de>, 200 + { 201 + use std::borrow::Cow; 202 + match deserializer.deserialize_str(Visitor::default())? { 203 + Cow::Borrowed(value) => Ok(value), 204 + Cow::Owned(_) => Err(serde::de::Error::custom(concat!( 205 + "expected a borrowed ", stringify!($type) 206 + ))), 207 + } 208 + } 209 + } 210 + 211 + impl<'de> serde::Deserialize<'de> for Box<$type> { 212 + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 213 + where 214 + D: serde::Deserializer<'de>, 215 + { 216 + let value = deserializer.deserialize_string(Visitor::default())?; 217 + Ok(value.into_boxed()) 218 + } 219 + } 220 + 221 + impl serde::Serialize for $type { 222 + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 223 + where 224 + S: serde::Serializer, 225 + { 226 + serializer.serialize_str(self.as_str()) 227 + } 228 + } 229 + }; 230 + ($type:ty) => { 231 + mod __serde_impl { 232 + use super::{$type}; 233 + 234 + impl_str_newtype_serde!(__inner $type); 235 + } 236 + }; 237 + ($type:ty, $owned:ty) => { 238 + mod __serde_impl { 239 + use super::{$type, $owned}; 240 + 241 + impl_str_newtype_serde!(__inner $type); 242 + 243 + impl<'de> serde::Deserialize<'de> for $owned { 244 + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 245 + where 246 + D: serde::Deserializer<'de>, 247 + { 248 + let value = deserializer.deserialize_string(Visitor::default())?; 249 + Ok(value.into_owned()) 250 + } 251 + } 252 + 253 + impl serde::Serialize for $owned{ 254 + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 255 + where 256 + S: serde::Serializer, 257 + { 258 + serializer.serialize_str(self.as_str()) 259 + } 260 + } 261 + } 262 + }; 263 + } 264 + 265 + macro_rules! impl_str_newtype_sqlx { 266 + (ref $type:ty) => { 267 + impl<DB: ::sqlx::Database> ::sqlx::Type<DB> for $type 268 + where 269 + for<'a> &'a str: ::sqlx::Type<DB>, 270 + { 271 + fn type_info() -> DB::TypeInfo { 272 + <&str as ::sqlx::Type<DB>>::type_info() 273 + } 274 + } 275 + 276 + impl<'q, DB: ::sqlx::Database> ::sqlx::Encode<'q, DB> for &'q $type 277 + where 278 + &'q str: ::sqlx::Encode<'q, DB>, 279 + { 280 + fn encode_by_ref( 281 + &self, 282 + buf: &mut <DB as ::sqlx::Database>::ArgumentBuffer<'q>, 283 + ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> { 284 + let s = self.as_str(); 285 + <&'q str as ::sqlx::Encode<'q, DB>>::encode_by_ref(&s, buf) 286 + } 287 + } 288 + 289 + impl<'r, DB: ::sqlx::Database> ::sqlx::Decode<'r, DB> for &'r $type 290 + where 291 + &'r str: ::sqlx::Decode<'r, DB>, 292 + { 293 + fn decode( 294 + value: <DB as ::sqlx::Database>::ValueRef<'r>, 295 + ) -> Result<Self, sqlx::error::BoxDynError> { 296 + let value = <&str as ::sqlx::Decode<'r, DB>>::decode(value)?; 297 + Ok(<$type>::parse(value)?) 298 + } 299 + } 300 + }; 301 + ($type:ty) => { 302 + impl<DB: ::sqlx::Database> ::sqlx::Type<DB> for $type 303 + where 304 + for<'a> &'a str: ::sqlx::Type<DB>, 305 + { 306 + fn type_info() -> DB::TypeInfo { 307 + <&str as ::sqlx::Type<DB>>::type_info() 308 + } 309 + } 310 + 311 + impl<'q, DB: ::sqlx::Database> ::sqlx::Encode<'q, DB> for $type 312 + where 313 + String: ::sqlx::Encode<'q, DB>, 314 + { 315 + fn encode_by_ref( 316 + &self, 317 + buf: &mut <DB as ::sqlx::Database>::ArgumentBuffer<'q>, 318 + ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> { 319 + let s = self.to_string(); 320 + <String as ::sqlx::Encode<'q, DB>>::encode_by_ref(&s, buf) 321 + } 322 + } 323 + 324 + impl<'r, DB: ::sqlx::Database> ::sqlx::Decode<'r, DB> for $type 325 + where 326 + &'r str: ::sqlx::Decode<'r, DB>, 327 + { 328 + fn decode( 329 + value: <DB as ::sqlx::Database>::ValueRef<'r>, 330 + ) -> Result<Self, sqlx::error::BoxDynError> { 331 + let value = <&str as ::sqlx::Decode<'r, DB>>::decode(value)?; 332 + Ok(value.try_into()?) 333 + } 334 + } 335 + }; 336 + }
+60 -229
crates/atproto/src/nsid.rs
··· 1 - use core::{fmt, mem, ops}; 1 + use core::ptr; 2 2 3 + /// An Atmosphere Namespaced Identifer. 4 + /// 5 + /// This is an _unsized_ type similiar to [`str`], so must always be used behind a pointer like 6 + /// `&` or [`Box`]. 7 + /// 8 + /// See: <https://atproto.com/specs/nsid> 9 + /// 3 10 #[derive(Hash, PartialEq, Eq, PartialOrd, Ord)] 4 11 #[repr(transparent)] 5 12 pub struct Nsid { ··· 14 7 } 15 8 16 9 impl Nsid { 17 - fn new<N: AsRef<str> + ?Sized>(s: &N) -> &Self { 18 - unsafe { &*(std::ptr::from_ref::<str>(s.as_ref()) as *const Self) } 10 + /// Construct a `&'static Nsid` from a `&'static str`. 11 + /// 12 + /// # Panics 13 + /// 14 + /// Panics if `nsid` is not a valid NSID. 15 + /// 16 + #[must_use] 17 + pub fn from_static(nsid: &'static str) -> &'static Self { 18 + validate_nsid(nsid).expect("hard-coded NSID should be valid"); 19 + Self::new(nsid) 19 20 } 20 21 21 - fn new_owned(nsid: Box<str>) -> Box<Self> { 22 - unsafe { mem::transmute::<Box<str>, Box<Self>>(nsid) } 22 + /// Unsafely create an `&'static Nsid` from a `&'static str`. 23 + /// 24 + /// Allows constructing an [`Nsid`] in a const context. 25 + /// 26 + /// # Example 27 + /// 28 + /// ```rust 29 + /// use atproto::Nsid; 30 + /// 31 + /// const NSID: &Nsid = unsafe { Nsid::from_static_unchecked("com.atproto.sync.getRecord") }; 32 + /// ``` 33 + /// 34 + /// # Safety 35 + /// 36 + /// It is the callers responsibility to ensure the NSID is valid. 37 + /// 38 + #[must_use] 39 + pub const unsafe fn from_static_unchecked(nsid: &'static str) -> &'static Self { 40 + unsafe { &*(ptr::from_ref::<str>(nsid) as *const Self) } 23 41 } 24 42 25 - #[must_use] 26 - pub fn from_static(s: &'static str) -> &'static Self { 27 - validate_nsid(s).expect("`s` should be a valid NSID"); 28 - Self::new(s) 29 - } 30 - 43 + /// Parse a `Nsid` from a `str`. 44 + /// 45 + /// # Example 46 + /// 47 + /// ```rust 48 + /// use atproto::Nsid; 49 + /// 50 + /// let nsid = Nsid::parse("app.bsky.actor.profile").unwrap(); 51 + /// ``` 52 + /// 53 + /// # Errors 54 + /// 55 + /// Returns an error if `nsid` is not a valid NSID. 56 + /// 31 57 pub fn parse<S: AsRef<str> + ?Sized>(nsid: &S) -> Result<&Self, Error> { 32 58 validate_nsid(nsid.as_ref())?; 33 59 Ok(Self::new(nsid)) 34 60 } 35 - 36 - #[inline] 37 - #[must_use] 38 - pub const fn as_str(&self) -> &str { 39 - &self.inner 40 - } 41 - 42 - #[inline] 43 - #[must_use] 44 - pub const fn len(&self) -> usize { 45 - self.inner.len() 46 - } 47 - 48 - #[inline] 49 - #[must_use] 50 - pub const fn is_empty(&self) -> bool { 51 - assert!(!self.inner.is_empty()); 52 - false 53 - } 54 61 } 55 62 56 - impl fmt::Display for Nsid { 57 - #[inline] 58 - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { 59 - fmt::Display::fmt(&self.inner, f) 60 - } 61 - } 63 + impl_str_newtype!(Nsid, validate_nsid); 62 64 63 - impl fmt::Debug for Nsid { 64 - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { 65 - fmt::Debug::fmt(&self.inner, f) 66 - } 67 - } 65 + #[cfg(feature = "serde")] 66 + impl_str_newtype_serde!(Nsid); 68 67 69 - impl AsRef<str> for &Nsid { 70 - #[inline] 71 - fn as_ref(&self) -> &str { 72 - &self.inner 73 - } 74 - } 75 - 76 - impl ops::Deref for Nsid { 77 - type Target = str; 78 - #[inline] 79 - fn deref(&self) -> &Self::Target { 80 - &self.inner 81 - } 68 + #[cfg(feature = "sqlx")] 69 + mod sqlx_impl { 70 + use super::Nsid; 71 + impl_str_newtype_sqlx!(ref Nsid); 72 + impl_str_newtype_sqlx!(Box<Nsid>); 82 73 } 83 74 84 75 impl ToOwned for Nsid { 85 76 type Owned = Box<Self>; 86 77 #[inline] 87 78 fn to_owned(&self) -> Self::Owned { 88 - Self::new_owned(self.inner.to_owned().into_boxed_str()) 89 - } 90 - } 91 - 92 - impl<'a> TryFrom<&'a str> for &'a Nsid { 93 - type Error = Error; 94 - #[inline] 95 - fn try_from(value: &'a str) -> Result<Self, Self::Error> { 96 - validate_nsid(value)?; 97 - Ok(Nsid::new(value)) 98 - } 99 - } 100 - 101 - impl TryFrom<&str> for Box<Nsid> { 102 - type Error = Error; 103 - #[inline] 104 - fn try_from(value: &str) -> Result<Self, Self::Error> { 105 - validate_nsid(value)?; 106 - Ok(Nsid::new_owned(value.to_owned().into_boxed_str())) 107 - } 108 - } 109 - 110 - impl TryFrom<Box<str>> for Box<Nsid> { 111 - type Error = Error; 112 - #[inline] 113 - fn try_from(value: Box<str>) -> Result<Self, Self::Error> { 114 - validate_nsid(&value)?; 115 - Ok(Nsid::new_owned(value)) 116 - } 117 - } 118 - 119 - impl TryFrom<String> for Box<Nsid> { 120 - type Error = Error; 121 - #[inline] 122 - fn try_from(value: String) -> Result<Self, Self::Error> { 123 - validate_nsid(&value)?; 124 - Ok(Nsid::new_owned(value.into_boxed_str())) 125 - } 126 - } 127 - 128 - impl From<&Nsid> for Box<Nsid> { 129 - #[inline] 130 - fn from(value: &Nsid) -> Self { 131 - Nsid::new_owned(value.inner.to_owned().into_boxed_str()) 132 - } 133 - } 134 - 135 - impl std::str::FromStr for Box<Nsid> { 136 - type Err = Error; 137 - #[inline] 138 - fn from_str(s: &str) -> Result<Self, Self::Err> { 139 - s.try_into() 140 - } 141 - } 142 - 143 - impl Clone for Box<Nsid> { 144 - #[inline] 145 - fn clone(&self) -> Self { 146 - (**self).to_owned() 147 - } 148 - } 149 - 150 - impl PartialEq<Box<Nsid>> for &Nsid { 151 - #[inline] 152 - fn eq(&self, other: &Box<Nsid>) -> bool { 153 - self.as_str() == other.as_str() 154 - } 155 - } 156 - 157 - impl PartialEq<&Nsid> for Box<Nsid> { 158 - #[inline] 159 - fn eq(&self, other: &&Nsid) -> bool { 160 - self.as_str() == other.as_str() 161 - } 162 - } 163 - 164 - impl PartialEq<Nsid> for str { 165 - #[inline] 166 - fn eq(&self, other: &Nsid) -> bool { 167 - self == other.as_str() 168 - } 169 - } 170 - 171 - impl PartialEq<str> for Nsid { 172 - #[inline] 173 - fn eq(&self, other: &str) -> bool { 174 - self.as_str() == other 175 - } 176 - } 177 - 178 - impl PartialEq<Box<Nsid>> for &str { 179 - #[inline] 180 - fn eq(&self, other: &Box<Nsid>) -> bool { 181 - *self == other.as_str() 182 - } 183 - } 184 - 185 - impl PartialEq<&str> for Box<Nsid> { 186 - #[inline] 187 - fn eq(&self, other: &&str) -> bool { 188 - self.as_str() == *other 189 - } 190 - } 191 - 192 - #[cfg(feature = "serde")] 193 - mod serde_impl { 194 - use super::Nsid; 195 - 196 - impl<'de> serde::Deserialize<'de> for Box<Nsid> { 197 - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 198 - where 199 - D: serde::Deserializer<'de>, 200 - { 201 - struct NsidVisitor; 202 - 203 - impl serde::de::Visitor<'_> for NsidVisitor { 204 - type Value = Box<Nsid>; 205 - 206 - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 207 - formatter.write_str("ATproto NSID") 208 - } 209 - 210 - fn visit_string<E>(self, v: String) -> Result<Self::Value, E> 211 - where 212 - E: serde::de::Error, 213 - { 214 - v.try_into().map_err(serde::de::Error::custom) 215 - } 216 - 217 - fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 218 - where 219 - E: serde::de::Error, 220 - { 221 - v.try_into().map_err(serde::de::Error::custom) 222 - } 223 - } 224 - 225 - deserializer.deserialize_string(NsidVisitor) 226 - } 227 - } 228 - 229 - impl<'de: 'a, 'a> serde::Deserialize<'de> for &'a Nsid { 230 - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 231 - where 232 - D: serde::Deserializer<'de>, 233 - { 234 - #[derive(Default)] 235 - struct NsidVisitor<'a>(std::marker::PhantomData<&'a Nsid>); 236 - 237 - impl<'de: 'a, 'a> serde::de::Visitor<'de> for NsidVisitor<'a> { 238 - type Value = &'a Nsid; 239 - 240 - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 241 - formatter.write_str("ATproto NSID") 242 - } 243 - 244 - fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E> 245 - where 246 - E: serde::de::Error, 247 - { 248 - v.try_into().map_err(serde::de::Error::custom) 249 - } 250 - } 251 - 252 - deserializer.deserialize_str(NsidVisitor::default()) 253 - } 254 - } 255 - 256 - impl serde::Serialize for Nsid { 257 - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 258 - where 259 - S: serde::Serializer, 260 - { 261 - serializer.serialize_str(&self.inner) 262 - } 79 + Self::new_boxed(self.inner.to_owned().into_boxed_str()) 263 80 } 264 81 } 265 82
+4 -2
crates/atproto/src/serde.rs
··· 19 19 20 20 use crate::{Did, did::DidVisitor}; 21 21 22 + #[allow(clippy::missing_errors_doc)] 22 23 pub fn deserialize<'a, 'de: 'a, D>(deserializer: D) -> Result<Cow<'a, Did>, D::Error> 23 24 where 24 25 D: Deserializer<'de>, 25 26 { 26 - Ok(deserializer.deserialize_str(DidVisitor::default())?) 27 + deserializer.deserialize_str(DidVisitor::default()) 27 28 } 28 29 29 - pub fn serialize<S>(did: &Cow<'_, Did>, serializer: S) -> Result<S::Ok, S::Error> 30 + #[allow(clippy::missing_errors_doc)] 31 + pub fn serialize<S>(did: &Did, serializer: S) -> Result<S::Ok, S::Error> 30 32 where 31 33 S: Serializer, 32 34 {
+78 -25
crates/atproto/src/tid.rs
··· 10 10 const MASK_CLOCK_ID: u64 = 0x03ff; 11 11 const BITS_CLOCK_ID: usize = 10; 12 12 13 - /// Timestamp Identifier 13 + /// [Atmosphere](https://atproto.com/) Timestamp Identifier 14 14 /// 15 15 /// See: <https://atproto.com/specs/tid> 16 16 #[derive(Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] 17 17 pub struct Tid(u64); 18 18 19 19 impl Tid { 20 - const MAX_CLOCK_ID: u16 = MASK_CLOCK_ID as u16; 21 - const MIN_CLOCK_ID: u16 = u16::MIN; 20 + pub const MAX_CLOCK_ID: u16 = 0x3ff; 21 + pub const MIN_CLOCK_ID: u16 = 0; 22 22 23 - const MAX_TIMESTAMP: u64 = MASK_TIMESTAMP; 24 - const MIN_TIMESTAMP: u64 = u64::MIN; 23 + pub const MAX_TIMESTAMP: u64 = 0x1f_ffff_ffff_ffff; 24 + pub const MIN_TIMESTAMP: u64 = 0; 25 25 26 26 /// Maximum TID value. 27 27 /// ··· 56 56 /// Panics if `micros` is greater than [`Self::MAX_TIMESTAMP`]. 57 57 /// 58 58 pub const fn set_micros(&mut self, micros: u64) { 59 - assert!(micros <= Self::MAX_TIMESTAMP, "TID timestamp must be in the range 0..=9007199254740991 microseconds"); 59 + assert!( 60 + micros <= Self::MAX_TIMESTAMP, 61 + "TID timestamp must be in the range 0..=9007199254740991 microseconds" 62 + ); 60 63 self.0 |= (micros & MASK_TIMESTAMP) << BITS_CLOCK_ID; 61 64 } 62 65 ··· 70 67 /// Panics if `clock_id` is greater than [`Self::MAX_CLOCK_ID`]. 71 68 /// 72 69 pub const fn set_clock_id(&mut self, clock_id: u16) { 73 - assert!(clock_id <= Self::MAX_CLOCK_ID, "TID clock ID must be in the range 0..=1023"); 70 + assert!( 71 + clock_id <= Self::MAX_CLOCK_ID, 72 + "TID clock ID must be in the range 0..=1023" 73 + ); 74 74 self.0 |= (clock_id as u64) & MASK_CLOCK_ID; 75 75 } 76 76 ··· 86 80 /// * `micros` exceeds [`Self::MAX_TIMESTAMP`]. 87 81 /// * `clock_id` exceeds [`Self::MAX_CLOCK_ID`] 88 82 /// 89 - #[must_use] 83 + #[must_use] 90 84 pub const fn new(micros: u64, clock_id: u16) -> Self { 91 85 let mut new = Self(0); 92 86 new.set_micros(micros); ··· 112 106 /// * `seconds` exceeds [`Self::MAX_TIMESTAMP`] when converted to microseconds. 113 107 /// * `clock_id` exceeds [`Self::MAX_CLOCK_ID`] 114 108 /// 115 - #[must_use] 109 + #[must_use] 116 110 pub const fn from_secs(seconds: u64, clock_id: u16) -> Self { 117 111 Self::new(seconds * 1_000_000, clock_id) 118 112 } ··· 136 130 /// assert_eq!(t.clock_id(), 1023); 137 131 /// ``` 138 132 /// 139 - pub fn parse(s: &str) -> Result<Self, Error> { 140 - parse(s) 133 + /// # Errors 134 + /// 135 + /// Returns an error if `tid` is not a valid TID. 136 + /// 137 + pub fn parse(tid: &str) -> Result<Self, Error> { 138 + parse(tid) 141 139 } 142 140 143 141 /// Get the microsecond offset from UNIX epoch. ··· 152 142 /// assert_eq!(Tid::MAX.micros(), 9007199254740991); 153 143 /// ``` 154 144 /// 155 - #[must_use] 145 + #[must_use] 156 146 pub const fn micros(&self) -> u64 { 157 147 (self.0 >> BITS_CLOCK_ID) & Self::MAX_TIMESTAMP 158 148 } ··· 165 155 /// assert_eq!(Tid::MAX.clock_id(), 1023); 166 156 /// ``` 167 157 /// 168 - #[must_use] 158 + #[must_use] 169 159 pub const fn clock_id(&self) -> u16 { 170 160 // CONVERSION: Clock ID mask ensures this will always produce an equivalent u16. 171 161 (self.0 & MASK_CLOCK_ID) as _ ··· 209 199 /// 210 200 /// Panics if `dt` is later than 2255-06-05 23:47:34.740991 +00:00:00. 211 201 /// 212 - #[must_use] 202 + #[must_use] 213 203 pub fn from_datetime(dt: time::OffsetDateTime, clock_id: u16) -> Self { 214 204 let micros = dt.unix_timestamp_nanos() / 1000; 215 205 Self::new(micros.try_into().unwrap(), clock_id) 216 206 } 217 207 208 + /// Create a [`Tid`] from the current UTC datetime and a clock ID of `0`. 209 + /// 210 + /// # Panics 211 + /// 212 + /// *Will* panic if called later than 2255-06-05 23:47:34.740991 +00:00:00. 213 + /// 214 + #[must_use] 215 + pub fn now_utc() -> Self { 216 + use time::OffsetDateTime; 217 + 218 + Self::from_datetime(OffsetDateTime::now_utc(), Self::MIN_CLOCK_ID) 219 + } 220 + 218 221 /// Convert the TID to a [`time::OffsetDateTime`] 219 - #[must_use] 222 + /// 223 + #[must_use] 224 + #[allow(clippy::missing_panics_doc)] 220 225 pub fn as_datetime(&self) -> time::OffsetDateTime { 221 226 time::OffsetDateTime::from_unix_timestamp_nanos(self.nanos()) 222 227 .expect("2^53 microseconds is less than MAX for OffsetDateTime") 223 228 } 224 229 225 - fn nanos(&self) -> i128 { 230 + fn nanos(self) -> i128 { 226 231 i128::from(self.micros()) * 1000 227 232 } 228 233 } ··· 296 271 HighBitSet, 297 272 } 298 273 299 - pub fn parse(s: &str) -> Result<Tid, Error> { 300 - let bytes = s.as_bytes(); 274 + /// Parse a [`Tid`] from a BASE32-sortable representation. 275 + /// 276 + /// # Example 277 + /// 278 + /// ```rust 279 + /// use atproto::Tid; 280 + /// 281 + /// let tid = atproto::tid::parse("3m24udbjajf22").unwrap(); 282 + /// assert_eq!(tid.micros(), 1759315551033835); 283 + /// assert_eq!(tid.clock_id(), 0); 284 + /// ``` 285 + /// 286 + /// # Errors 287 + /// 288 + /// Returns an error if `tid` is not a valid TID. 289 + /// 290 + pub fn parse(tid: &str) -> Result<Tid, Error> { 291 + let bytes = tid.as_bytes(); 301 292 if bytes.len() != 13 { 302 293 return Err(Error::Length); 303 294 } ··· 352 311 353 312 impl TidClock { 354 313 /// Create a new TID clock with the specified ID. 355 - #[must_use] 314 + /// 315 + /// # Panics 316 + /// 317 + /// Panics if `clock_id` is greater than [`Tid::MAX_CLOCK_ID`]. 318 + /// 319 + #[must_use] 356 320 pub const fn with_id(clock_id: u16) -> Self { 357 - assert!(clock_id <= Tid::MAX_CLOCK_ID, "TID clock ID must be in the range 0..=1023"); 321 + assert!( 322 + clock_id <= Tid::MAX_CLOCK_ID, 323 + "TID clock ID must be in the range 0..=1023" 324 + ); 358 325 359 326 Self { 360 327 id: clock_id, ··· 376 327 /// 377 328 /// Panics if the current date is later than 2255-06-05 23:47:34.740991 +00:00:00. 378 329 /// 330 + /// [`SystemTime::now()`]: std::time::SystemTime 331 + /// 379 332 pub fn next(&self) -> Tid { 380 333 use std::time::SystemTime; 381 334 ··· 390 339 .try_into() 391 340 .unwrap(); 392 341 393 - let mut last = self.ts.lock().unwrap(); 394 - if *last >= new_ts { 395 - new_ts = *last + 1; 396 - } 342 + { 343 + let mut last = self.ts.lock().unwrap(); 344 + if *last >= new_ts { 345 + new_ts = *last + 1; 346 + } 397 347 398 - *last = new_ts; 348 + *last = new_ts; 349 + } 399 350 Tid::new(new_ts, self.id) 400 351 } 401 352 }
+3 -3
crates/atproto/src/uri.rs
··· 28 28 } 29 29 } 30 30 31 - #[derive(Debug, PartialEq, thiserror::Error)] 31 + #[derive(Debug, PartialEq, Eq, thiserror::Error)] 32 32 pub enum Error { 33 33 #[error("Invalid authority: {0}")] 34 34 InvalidAuthority(#[from] crate::did::Error), ··· 64 64 #[derive(Default)] 65 65 pub struct RecordUriVisitor; 66 66 67 - impl<'de> serde::de::Visitor<'de> for RecordUriVisitor { 67 + impl serde::de::Visitor<'_> for RecordUriVisitor { 68 68 type Value = RecordUri; 69 69 70 70 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { ··· 85 85 where 86 86 D: serde::Deserializer<'de>, 87 87 { 88 - deserializer.deserialize_str(RecordUriVisitor::default()) 88 + deserializer.deserialize_str(RecordUriVisitor) 89 89 } 90 90 } 91 91 }
crates/atproto/src/util.rs
+10 -10
crates/jetstream/src/subscriber_options.rs
··· 80 80 } 81 81 82 82 /// Get the normalized maximum message size. 83 - #[must_use] 83 + #[must_use] 84 84 pub fn max_message_size(&self) -> i64 { 85 85 normalize_max_message_size(self.max_message_size_bytes) 86 86 } 87 87 88 88 /// Construct the Jetstream subscribe URL, returning a tuple of the URL and a boolean 89 89 /// indicating whether the client should send an options update message on connect. 90 - #[must_use] 90 + #[must_use] 91 91 pub fn subscribe_url(&self, url: &url::Url) -> (url::Url, bool) { 92 92 let mut url = url.to_owned(); 93 93 url.set_path("/subscribe"); ··· 124 124 } 125 125 126 126 /// Present the `SubscriberOptions` as a [`SubscriberSourcedMessage`] for serialization. 127 - #[must_use] 127 + #[must_use] 128 128 pub fn as_subscriber_sourced_message(&self) -> SubscriberSourcedMessage<'_> { 129 129 SubscriberSourcedMessage::OptionsUpdate(self.into()) 130 130 } ··· 190 190 191 191 impl SubscriberSourcedMessage<'_> { 192 192 /// Serialize the [`SubscriberSourcedMessage`] to JSON. 193 - #[must_use] 193 + #[must_use] 194 194 pub fn to_json(&self) -> String { 195 195 serde_json::to_string(self).expect("SubscriberSourcedMessage should be serializable") 196 196 } ··· 245 245 let base = "wss://jetstream1.us-east.bsky.network".parse().unwrap(); 246 246 let mut options = SubscriberOptions::default(); 247 247 options 248 - .add_collection(Nsid::from_static("app.bsky.feed.like").into()) 248 + .add_collection(Nsid::from_static("app.bsky.feed.like").into_boxed()) 249 249 .unwrap(); 250 250 let (url, _) = options.subscribe_url(&base); 251 251 assert_eq!( ··· 264 264 ); 265 265 266 266 options 267 - .add_collection(Nsid::from_static("sh.tangled.*").into()) 267 + .add_collection(Nsid::from_static("sh.tangled.*").into_boxed()) 268 268 .unwrap(); 269 269 270 270 assert_eq!( ··· 273 273 ); 274 274 275 275 options 276 - .add_collection(Nsid::from_static("app.bsky.*").into()) 276 + .add_collection(Nsid::from_static("app.bsky.*").into_boxed()) 277 277 .unwrap(); 278 278 assert_eq!( 279 279 options.subscribe_url_len(&url), ··· 300 300 #[test] 301 301 fn serialize_example_options() { 302 302 let options = SubscriberOptions { 303 - wanted_collections: HashSet::from_iter( 304 - [Nsid::from_static("app.bsky.feed.post").into()], 305 - ), 303 + wanted_collections: HashSet::from_iter([ 304 + Nsid::from_static("app.bsky.feed.post").into_boxed() 305 + ]), 306 306 wanted_dids: HashSet::from_iter([ 307 307 Did::from_static("did:plc:q6gjnaw2blty4crticxkmujt").to_owned() 308 308 ]),
+1 -1
crates/knot/src/cli.rs
··· 105 105 106 106 // @TODO Validate? 107 107 108 - let instance = format!("did:web:{name}").try_into()?; 108 + let instance = format!("did:web:{name}").parse()?; 109 109 110 110 Ok(KnotConfiguration { 111 111 owner,
+36 -4
crates/knot/src/lib.rs
··· 1 1 use std::io; 2 2 3 + use atproto::Nsid; 3 4 use axum::Router; 4 5 use tokio::{net::TcpListener, task::JoinSet}; 5 6 use tokio_util::sync::CancellationToken; ··· 16 15 17 16 #[cfg(test)] 18 17 pub(crate) mod mock; 18 + 19 + pub mod nsid { 20 + use atproto::Nsid; 21 + 22 + macro_rules! nsid { 23 + ($nsid:literal) => { 24 + unsafe { Nsid::from_static_unchecked($nsid) } 25 + }; 26 + } 27 + 28 + pub const SH_TANGLED_KNOT_MEMBER: &Nsid = nsid!("sh.tangled.knot.member"); 29 + pub const SH_TANGLED_PUBLICKEY: &Nsid = nsid!("sh.tangled.publicKey"); 30 + pub const SH_TANGLED_REPO: &Nsid = nsid!("sh.tangled.repo"); 31 + pub const SH_TANGLED_REPO_COLLABORATOR: &Nsid = nsid!("sh.tangled.repo.collaborator"); 32 + pub const SH_TANGLED_REPO_CREATE: &Nsid = nsid!("sh.tangled.repo.create"); 33 + pub const SH_TANGLED_REPO_DELETE: &Nsid = nsid!("sh.tangled.repo.delete"); 34 + pub const SH_TANGLED_REPO_GITRECEIVEPACK: &Nsid = nsid!("sh.tangled.repo.gitReceivePack"); 35 + pub const SH_TANGLED_REPO_SETDEFAULTBRANCH: &Nsid = nsid!("sh.tangled.repo.setDefaultBranch"); 36 + } 37 + 38 + /// NSIDs of interest to a knot server. 39 + pub const NSIDS: &[&Nsid] = { 40 + &[ 41 + nsid::SH_TANGLED_KNOT_MEMBER, 42 + nsid::SH_TANGLED_PUBLICKEY, 43 + nsid::SH_TANGLED_REPO, 44 + nsid::SH_TANGLED_REPO_COLLABORATOR, 45 + ] 46 + }; 19 47 20 48 pub async fn serve_all( 21 49 router: Router, ··· 180 150 } 181 151 182 152 mod sh_tangled_repo_create { 153 + use crate::nsid::{SH_TANGLED_REPO_CREATE, SH_TANGLED_REPO_DELETE}; 154 + 183 155 use super::super::public; 184 156 use super::*; 185 157 use axum::http::{HeaderValue, Method, Response, header}; ··· 268 236 }; 269 237 270 238 let auth = service_auth_with(&pds, &did, &knot.instance, |claims| { 271 - claims.lxm = Some("sh.tangled.repo.create".try_into().unwrap()); 239 + claims.lxm = Some(SH_TANGLED_REPO_CREATE.into_boxed()); 272 240 modify_claims(claims); 273 241 }) 274 242 .await; ··· 597 565 gix::open(base.path().join(did.as_str()).join(&rkey)).expect("repository should exist"); 598 566 assert!(repo_exists_in_db(&knot, &did, &rkey).await); 599 567 600 - // Or with the wrong auth. 568 + // Or with the wrong lxm. 601 569 let auth = service_auth_with(&pds, &did, &knot.instance(), |claims| { 602 - claims.lxm = Some("sh.tangled.repo.create".try_into().unwrap()); 570 + claims.lxm = Some(SH_TANGLED_REPO_CREATE.into_boxed()); 603 571 }) 604 572 .await; 605 573 ··· 628 596 // Valid auth, empty request body. 629 597 // Or with the wrong auth. 630 598 let auth = service_auth_with(&pds, &did, &knot.instance(), |claims| { 631 - claims.lxm = Some("sh.tangled.repo.delete".try_into().unwrap()); 599 + claims.lxm = Some(SH_TANGLED_REPO_DELETE.into_boxed()); 632 600 }) 633 601 .await; 634 602 assert_eq!(
+5 -1
crates/knot/src/main.rs
··· 73 73 assert!(git_config_global_set("core.hooksPath", &arguments.hooks)?); 74 74 assert!(git_config_global_set( 75 75 "receive.advertisePushOptions", 76 - if arguments.require_signed_push { "true" } else { "false" } 76 + if arguments.require_signed_push { 77 + "true" 78 + } else { 79 + "false" 80 + } 77 81 )?); 78 82 79 83 let database = {
+3 -1
crates/knot/src/public/git/authorization.rs
··· 1 + use atproto::Nsid; 1 2 use auth::{ 2 3 IntoVerificationKey, OpenSshKey, 3 4 jwt::{Claims, Token, decode}, ··· 12 11 13 12 use crate::{ 14 13 model::Knot, 14 + nsid::SH_TANGLED_REPO_GITRECEIVEPACK, 15 15 services::authorization::{ 16 16 AuthorizationClaimsStore as _, Verification, VerificationError, extract_token, 17 17 }, ··· 24 22 struct GitVerification; 25 23 26 24 impl Verification for GitVerification { 27 - const LEXICON_METHOD: &'static str = "sh.tangled.repo.gitReceivePack"; 25 + const LEXICON_METHOD: &'static Nsid = SH_TANGLED_REPO_GITRECEIVEPACK; 28 26 } 29 27 30 28 #[derive(Clone, Debug)]
+3 -1
crates/knot/src/public/xrpc/sh_tangled/repo/impl_create.rs
··· 1 + use atproto::Nsid; 1 2 use axum::{Json, extract::State, http::StatusCode}; 2 3 use lexicon::{ 3 4 com::atproto::repo::list_records::Record, ··· 7 6 8 7 use crate::{ 9 8 model::{Knot, errors}, 9 + nsid::SH_TANGLED_REPO_CREATE, 10 10 public::xrpc::{XrpcError, XrpcResult}, 11 11 services::{ 12 12 atrepo, ··· 22 20 pub struct CreateVerification; 23 21 24 22 impl Verification for CreateVerification { 25 - const LEXICON_METHOD: &'static str = "sh.tangled.repo.create"; 23 + const LEXICON_METHOD: &'static Nsid = SH_TANGLED_REPO_CREATE; 26 24 } 27 25 28 26 #[tracing::instrument(target = "sh_tangled::repo::create", skip(knot, authorization), err)]
+3 -2
crates/knot/src/public/xrpc/sh_tangled/repo/impl_delete.rs
··· 1 - use atproto::tid::Tid; 1 + use atproto::{Nsid, tid::Tid}; 2 2 use axum::{Json, extract::State}; 3 3 use lexicon::sh_tangled::repo::delete::Input; 4 4 5 5 use crate::{ 6 6 model::{Knot, errors}, 7 + nsid::SH_TANGLED_REPO_DELETE, 7 8 public::xrpc::XrpcResult, 8 9 services::authorization::{Authorization, Verification}, 9 10 types::RecordKey, ··· 16 15 pub struct DeleteVerification; 17 16 18 17 impl Verification for DeleteVerification { 19 - const LEXICON_METHOD: &'static str = "sh.tangled.repo.delete"; 18 + const LEXICON_METHOD: &'static Nsid = SH_TANGLED_REPO_DELETE; 20 19 } 21 20 22 21 #[tracing::instrument(target = "sh_tangled::repo::delete", skip(knot, authorization), err)]
+3 -1
crates/knot/src/public/xrpc/sh_tangled/repo/impl_set_default_branch.rs
··· 1 + use atproto::Nsid; 1 2 use axum::{Json, extract::State}; 2 3 use gix::{ 3 4 lock::acquire::Fail, ··· 11 10 12 11 use crate::{ 13 12 model::{Knot, errors}, 13 + nsid::SH_TANGLED_REPO_SETDEFAULTBRANCH, 14 14 public::xrpc::XrpcError, 15 15 services::{ 16 16 authorization::{Authorization, Verification}, ··· 26 24 pub struct SetDefaultBranchVerification; 27 25 28 26 impl Verification for SetDefaultBranchVerification { 29 - const LEXICON_METHOD: &'static str = "sh.tangled.repo.setDefaultBranch"; 27 + const LEXICON_METHOD: &'static Nsid = SH_TANGLED_REPO_SETDEFAULTBRANCH; 30 28 } 31 29 32 30 #[tracing::instrument(target = "sh_tangled::repo::setDefaultBranch", skip(knot), err)]
+2 -1
crates/knot/src/services/authorization.rs
··· 1 1 use core::fmt; 2 2 3 + use atproto::Nsid; 3 4 use auth::{ 4 5 IntoVerificationKey as _, 5 6 jwt::{Claims, Token, decode}, ··· 56 55 } 57 56 58 57 pub trait Verification: fmt::Debug + Send { 59 - const LEXICON_METHOD: &'static str; 58 + const LEXICON_METHOD: &'static Nsid; 60 59 61 60 fn verify_iat(now: i64, claims: &Claims) -> Result<i64, VerificationError> { 62 61 match claims.iat {