use std::{fmt, sync::LazyLock}; use log::{debug, trace}; use regex::Regex; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Uri(String); impl fmt::Display for Uri { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.0) } } impl AsRef<[u8]> for Uri { fn as_ref(&self) -> &[u8] { self.0.as_bytes() } } impl From for Uri { fn from(value: String) -> Self { fn parse_url(value: &str) -> Option<(String, String)> { /// Pattern breakdown: /// - `^(?P[^:/?#]+)://` - capture scheme (anything but `:/?#`) + `"://"` /// - `(?P[^/?#]+)` - capture authority/host (anything but `/?#`) /// - `(/.*)?` - maybe trailing slash and some path /// - `$` - end of string static SHARRY_URI_RE: LazyLock = LazyLock::new(|| { trace!("compiling SHARRY_URI_RE"); Regex::new(r"^(?P[^:/?#]+)://(?P[^/?#]+)(/.*)?$") .expect("Regex compilation failed") }); SHARRY_URI_RE.captures(value).map(|caps| { let captured = |name| { caps.name(name) .unwrap_or_else(|| panic!("{name} not captured")) .as_str() .to_string() }; (captured("scheme"), captured("host")) }) } trace!("TryFrom {value:?}"); if let Some((scheme, host)) = parse_url(&value) { let result = Self(format!("{scheme}://{host}")); debug!("{result:?}"); result } else { Self(value) } } } impl Uri { fn endpoint(&self, path: fmt::Arguments) -> String { let uri = format!("{}/api/v2/{path}", self.0); trace!("endpoint: {uri:?}"); uri } pub fn share_create(&self) -> String { self.endpoint(format_args!("alias/upload/new")) } pub fn share_notify(&self, share_id: &super::ShareID) -> String { self.endpoint(format_args!("alias/mail/notify/{share_id}")) } pub fn file_create(&self, share_id: &super::ShareID) -> String { self.endpoint(format_args!("alias/upload/{share_id}/files/tus")) } pub fn file_patch(&self, share_id: &super::ShareID, file_id: &super::FileID) -> String { self.endpoint(format_args!("alias/upload/{share_id}/files/tus/{file_id}")) } }