diff --git a/Cargo.lock b/Cargo.lock index 3283355..796c756 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1305,6 +1305,29 @@ dependencies = [ "serde", ] +[[package]] +name = "lazy-regex" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60c7310b93682b36b98fa7ea4de998d3463ccbebd94d935d6b48ba5b6ffa7126" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba01db5ef81e17eb10a5e0f2109d1b3a3e29bac3070fdbd7d156bf7dbd206a1" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1805,6 +1828,15 @@ dependencies = [ "regex-syntax 0.8.5", ] +[[package]] +name = "regex-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d306632607af6ec61c0b117971d57a96381b6317cf18ae419b5558048fe016e" +dependencies = [ + "regex", +] + [[package]] name = "regex-syntax" version = "0.6.29" @@ -1886,7 +1918,8 @@ dependencies = [ "axum-macros", "esquema-codegen", "http 1.3.1", - "regex", + "lazy-regex", + "regex-macro", "rocketman", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 4e7eb84..0dfa374 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,8 @@ atrium-api = { version = "0.25.2", default-features = false } axum = { version = "0.8.3", features = ["json"] } axum-macros = "0.5.0" http = "1.3.1" -regex = "1.11.1" +lazy-regex = "3.4.1" +regex-macro = "0.3.0" rocketman = "0.2.0" serde = "1.0.219" serde_json = "1.0.140" diff --git a/src/atproto.rs b/src/atproto.rs index 10bf5d4..a58f8b6 100644 --- a/src/atproto.rs +++ b/src/atproto.rs @@ -1,67 +1,55 @@ -use atrium_api::types::string::RecordKey; -// use regex::Regex; +use lazy_regex::regex_captures; +use core::str::FromStr; -pub use atrium_api::types::string::{ - Nsid, - Did, - Handle, +pub use atrium_api::types::{ + Collection, + string::{ + Nsid, + RecordKey, + AtIdentifier as Authority, + } }; -enum Authority { - Did(Did), - Handle(Handle), -} - -// impl Authority { -// pub fn new(authority: String) -> Result { -// } -// } - -// This implementation does not support Query or Fragments, and thus follows -// the following schema: "at://" AUTHORITY [ "/" COLLECTION [ "/" RKEY ] ] pub struct Uri { - authority: Authority, - collection: Option, - rkey: Option, + whole: String, + // These fields could be useful in the future, + // so I'm leaving the code for them. + // authority: Authority, + // collection: Option, + // rkey: Option, +} + +impl Uri { + pub fn as_str(&self) -> &str { + self.whole.as_str() + } + + pub fn from_str(uri: String) -> Result { + if uri.len() > 8000 { + return Err("Uri too long") + } + + let Some(( + whole, unchecked_authority, unchecked_collection, unchecked_rkey + )) = regex_captures!( + r"/^at:\/\/([\w\.\-_~:]+)(?:\/([\w\.\-_~:]+)(?:)\/([\w\.\-_~:]+))?$/i", + &uri, + ) else { + return Err("Invalid Uri"); + }; + + // This parsing is required, but the values don't need to be used yet. + // No compute cost to use them, just storage cost + let _authority = Authority::from_str(unchecked_authority)?; + + let _collection = if unchecked_collection.is_empty() { None } + else { Some(Nsid::new(unchecked_collection.to_string())?) }; + + let _rkey = if unchecked_rkey.is_empty() { None } + else { Some(RecordKey::new(unchecked_rkey.to_string())?) }; + + // Ok(Uri{ whole: whole.to_string(), authority, collection, rkey }) + Ok(Uri { whole: whole.to_string() }) + } } -// TODO: Replace super basic URI regex with real uri parsing -// const URI_REGEX: Regex = Regex::new( -// r"/^at:\/\/([\w\.\-_~:]+)(?:\/([\w\.\-_~:]+)(?:)\/([\w\.\-_~:]+))?$/i" -// ).expect("valid regex"); -// -// impl Uri { -// pub fn new(uri: String) -> Result { -// let Some(captures) = URI_REGEX.captures(&uri) else { -// return Err("Invalid Uri"); -// }; -// // TODO: Convert authority if its a did or a handle -// let Some(Ok(authority)) = captures.get(1).map(|mtch| { -// Authority::new(mtch.as_str().to_string()) -// }) else { -// return Err("Invalid Authority") -// }; -// let collection = captures.get(2).map(|mtch| { -// Nsid::new(mtch.as_str().to_string()) -// }); -// let rkey = captures.get(3).map(|mtch| { -// RecordKey::new(mtch.as_str().to_string()) -// }); -// Ok(Uri { authority, collection, rkey }) -// } -// -// pub fn as_string(&self) -> String { -// let mut uri = String::from("at://"); -// uri.push_str(match &self.authority { -// Authority::Handle(h) => &*h, -// Authority::Did(d) => &*d, -// }); -// if let Some(nsid) = &self.collection { -// uri.push_str(&*nsid); -// } -// if let Some(rkey) = &self.rkey { -// uri.push_str(&*rkey); -// } -// uri -// } -// }