diff --git a/Cargo.lock b/Cargo.lock index 6c97f48..13fd0a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,8 +70,8 @@ name = "api" version = "0.1.0" dependencies = [ "atproto", - "axum", "http 1.3.1", + "router", "serde", "serde_json", "tokio", @@ -123,6 +123,7 @@ dependencies = [ "time", "tracing", "tracing-subscriber", + "unicode-segmentation", ] [[package]] @@ -293,9 +294,9 @@ dependencies = [ [[package]] name = "bon" -version = "3.6.3" +version = "3.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced38439e7a86a4761f7f7d5ded5ff009135939ecb464a24452eaa4c1696af7d" +checksum = "f61138465baf186c63e8d9b6b613b508cd832cba4ce93cf37ce5f096f91ac1a6" dependencies = [ "bon-macros", "rustversion", @@ -303,9 +304,9 @@ dependencies = [ [[package]] name = "bon-macros" -version = "3.6.3" +version = "3.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce61d2d3844c6b8d31b2353d9f66cf5e632b3e9549583fe3cac2f4f6136725e" +checksum = "40d1dad34aa19bf02295382f08d9bc40651585bd497266831d40ee6296fb49ca" dependencies = [ "darling", "ident_case", @@ -662,6 +663,20 @@ dependencies = [ "serde", ] +[[package]] +name = "entryway" +version = "0.1.0" +dependencies = [ + "atproto", + "http 1.3.1", + "router", + "serde", + "serde_json", + "tokio", + "tracing", + "tracing-subscriber", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1183,6 +1198,8 @@ dependencies = [ "anyhow", "async-trait", "atproto", + "atrium-api", + "db", "rocketman", "serde", "serde_json", @@ -1813,6 +1830,20 @@ dependencies = [ "zstd", ] +[[package]] +name = "router" +version = "0.1.0" +dependencies = [ + "atproto", + "axum", + "http 1.3.1", + "serde", + "serde_json", + "tokio", + "tracing", + "tracing-subscriber", +] + [[package]] name = "rsa" version = "0.9.8" @@ -2708,6 +2739,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unsigned-varint" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index abb5327..55a82e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ async-trait = "0.1.88" atproto = { path = "./atproto" } db = { path = "./db" } router = { path = "./router" } -serde = "1.0.219" +serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio"] } thiserror = "2.0.12" diff --git a/atproto/src/error.rs b/atproto/src/error.rs index 1db7106..5b52dfb 100644 --- a/atproto/src/error.rs +++ b/atproto/src/error.rs @@ -20,6 +20,8 @@ pub enum FormatError { pub enum ParseError { #[error("Time Parse Error: {0}")] Datetime(#[from] time::error::Parse), + #[error("Json Parse Error: {0}")] + Serde(#[from] serde_json::error::Error), #[error("Length of parsed object too long, max: {max:?}, got: {got:?}.")] Length { max: usize, got: usize }, #[error("Currently Did is enforced, cannot use handle, {handle:?}")] diff --git a/atproto/src/types.rs b/atproto/src/types.rs index 26ee60d..5baa02d 100644 --- a/atproto/src/types.rs +++ b/atproto/src/types.rs @@ -1,5 +1,20 @@ use crate::error::{Error, ParseError}; +#[macro_export] +macro_rules! basic_deserializer { + ($name:ident) => { + impl<'de> serde::de::Deserialize<'de> for $name { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + let value: String = serde::de::Deserialize::deserialize(deserializer)?; + value.parse::<$name>().map_err(::custom) + } + } + } +} + macro_rules! basic_string_type { ($name:ident, $regex:literal, $max_len:literal) => { pub struct $name { value: String, } @@ -35,20 +50,24 @@ macro_rules! basic_string_type { }) } } + + basic_deserializer!($name); } } -mod did; -pub use did::Did; -mod cid; -pub use cid::Cid; mod authority; -pub use authority::Authority; +mod cid; mod datetime; -pub use datetime::Datetime; +mod did; mod record_key; -pub use record_key::RecordKey; +mod strong_ref; mod uri; +pub use authority::Authority; +pub use cid::Cid; +pub use datetime::Datetime; +pub use did::Did; +pub use record_key::RecordKey; +pub use strong_ref::StrongRef; pub use uri::Uri; basic_string_type!(Handle, @@ -63,22 +82,3 @@ basic_string_type!(Tid, r"^[234567abcdefghij][234567abcdefghijklmnopqrstuvwxyz]{12}$", 13 ); - -pub struct StrongRef { - content: T, - cid: Cid, -} - -impl StrongRef { - pub fn get_content(&self) -> &T { - &self.content - } - - pub fn extract_content(self) -> (T, Cid) { - (self.content, self.cid) - } - - pub fn get_cid(&self) -> &Cid { - &self.cid - } -} diff --git a/atproto/src/types/authority.rs b/atproto/src/types/authority.rs index 1c76750..ad3d68c 100644 --- a/atproto/src/types/authority.rs +++ b/atproto/src/types/authority.rs @@ -1,12 +1,16 @@ use crate::{ - types::{Did, Handle}, + types::{ + Did, Handle + }, error::{Error, ParseError}, }; +use serde::Deserialize; use std::{ fmt::{Display, Formatter, Result as FmtResult}, str::FromStr, }; +#[derive(Deserialize)] pub enum Authority { Did(Did), Handle(Handle), diff --git a/atproto/src/types/cid.rs b/atproto/src/types/cid.rs index 3581ad2..a1d3ff2 100644 --- a/atproto/src/types/cid.rs +++ b/atproto/src/types/cid.rs @@ -1,4 +1,7 @@ -use crate::error::Error; +use crate::{ + basic_deserializer, + error::Error +}; pub struct Cid { value: String, } @@ -14,3 +17,5 @@ impl std::str::FromStr for Cid { Ok(Self { value: s.to_string() }) } } + +basic_deserializer!(Cid); diff --git a/atproto/src/types/datetime.rs b/atproto/src/types/datetime.rs index 6dcf234..bd5583b 100644 --- a/atproto/src/types/datetime.rs +++ b/atproto/src/types/datetime.rs @@ -1,4 +1,7 @@ -use crate::error::{Error, ParseError, FormatError}; +use crate::{ + basic_deserializer, + error::{Error, ParseError, FormatError}, +}; use time::{ UtcDateTime, format_description::well_known::{Rfc3339, Iso8601}, @@ -61,3 +64,5 @@ impl FromStr for Datetime { Ok(Datetime { time: datetime, derived_string: s.to_string() }) } } + +basic_deserializer!(Datetime); diff --git a/atproto/src/types/did.rs b/atproto/src/types/did.rs index 6c91db2..fa9b739 100644 --- a/atproto/src/types/did.rs +++ b/atproto/src/types/did.rs @@ -1,4 +1,7 @@ -use crate::error::{Error, ParseError}; +use crate::{ + basic_deserializer, + error::{Error, ParseError}, +}; use std::{ fmt::{Display, Formatter, Result as FmtResult}, str::FromStr, @@ -31,6 +34,8 @@ impl FromStr for DidMethod { } } +basic_deserializer!(DidMethod); + pub struct Did { method: DidMethod, identifier: String, @@ -70,3 +75,5 @@ impl FromStr for Did { }) } } + +basic_deserializer!(Did); diff --git a/atproto/src/types/record_key.rs b/atproto/src/types/record_key.rs index 7368b53..8542029 100644 --- a/atproto/src/types/record_key.rs +++ b/atproto/src/types/record_key.rs @@ -61,3 +61,5 @@ impl FromStr for RecordKey { Ok(RecordKey::Any(s.to_string())) } } + +basic_deserializer!(RecordKey); diff --git a/atproto/src/types/strong_ref.rs b/atproto/src/types/strong_ref.rs new file mode 100644 index 0000000..9e23ff7 --- /dev/null +++ b/atproto/src/types/strong_ref.rs @@ -0,0 +1,46 @@ +use crate::{ + basic_deserializer, + types::{Cid, Uri}, + error::{Error, ParseError}, +}; + +pub struct StrongRef { + content: T, + cid: Cid, +} + +impl StrongRef { + pub fn from_atrium_api(strong_ref: atrium_api::com::atproto::repo::strong_ref::MainData) -> Result { + let str_cid = serde_json::to_string(&strong_ref.cid).map_err(|e| { + Error::Parse { err: ParseError::Serde(e), object: "Uri".to_string() } + })?; + Ok(Self { + content: strong_ref.uri.parse::()?, + cid: str_cid.parse::()?, + }) + } +} + +impl StrongRef { + pub fn map_content(self, f: F) -> StrongRef + where + F: FnOnce(T) -> U, + { + StrongRef { + content: f(self.content), + cid: self.cid, + } + } + + pub fn get_content(&self) -> &T { + &self.content + } + + pub fn extract_content(self) -> (T, Cid) { + (self.content, self.cid) + } + + pub fn get_cid(&self) -> &Cid { + &self.cid + } +} diff --git a/atproto/src/types/uri.rs b/atproto/src/types/uri.rs index 843ee2b..1fbf423 100644 --- a/atproto/src/types/uri.rs +++ b/atproto/src/types/uri.rs @@ -1,4 +1,5 @@ use crate::{ + basic_deserializer, types::{Did, Authority, Nsid, RecordKey}, error::{Error, ParseError}, }; @@ -33,47 +34,78 @@ impl Display for Uri { impl FromStr for Uri { type Err = Error; fn from_str(s: &str) -> Result { - if s.len() > 8000 { - return Err(Error::Parse { - err: ParseError::Length { max: 8000, got: s.len() }, - object: "Did".to_string(), - }); - } + Self::check_length(s)?; let Some(( _whole, unchecked_authority, unchecked_collection, unchecked_rkey - )) = regex_captures!( + )): Option<(&str, &str, &str, &str)> = regex_captures!( r"/^at:\/\/([\w\.\-_~:]+)(?:\/([\w\.\-_~:]+)(?:)\/([\w\.\-_~:]+))?$/i", s, - ) else { + ) else { return Err(Error::Parse { err: ParseError::Format, object: "Uri".to_string(), }); }; - let did = match Authority::from_str(unchecked_authority)? { - Authority::Handle(h) => - return Err(Error::Parse { - err: ParseError::ForceDid { handle: h.to_string() }, - object: "Uri".to_string(), - }), - Authority::Did(d) => d, - }; + let did = Self::check_authority(unchecked_authority.to_string())?; let collection = if unchecked_collection.is_empty() { None } - else { Some(unchecked_collection.parse::()?) }; + else { Some(Self::check_collection(unchecked_collection.to_string())?) }; let rkey = if unchecked_rkey.is_empty() { None } - else { Some(unchecked_rkey.parse::()?) }; + else { Some(Self::check_rkey(unchecked_rkey.to_string())?) }; Ok(Uri { authority: did, collection, rkey }) } } impl Uri { + pub fn from_components( + authority_str: String, collection_str: Option, + rkey_str: Option + ) -> Result { + let authority = Self::check_authority(authority_str)?; + let collection = collection_str.map(Self::check_collection).transpose()?; + let rkey = rkey_str.map(Self::check_rkey).transpose()?; + let uri = Uri { authority, collection, rkey }; + Self::check_length(&uri.to_string())?; + + Ok(uri) + } + + fn check_length(s: &str) -> Result<(), Error> { + if s.len() > 8000 { + return Err(Error::Parse { + err: ParseError::Length { max: 8000, got: s.len() }, + object: "Did".to_string(), + }); + } + Ok(()) + } + + fn check_authority(authority: String) -> Result { + Ok(match Authority::from_str(&authority)? { + Authority::Handle(h) => + return Err(Error::Parse { + err: ParseError::ForceDid { handle: h.to_string() }, + object: "Uri".to_string(), + }), + Authority::Did(d) => d, + }) + } + + fn check_collection(collection: String) -> Result { + Ok(collection.parse::()?) + } + + fn check_rkey(rkey: String) -> Result { + Ok(rkey.parse::()?) + } + pub fn authority_as_did(&self) -> &Did { &self.authority } } +basic_deserializer!(Uri);