diff --git a/Cargo.lock b/Cargo.lock index 1cb726e5..889ffcc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -275,7 +281,7 @@ version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.106", @@ -502,6 +508,12 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -718,6 +730,7 @@ dependencies = [ "miette", "multibase", "multihash", + "ouroboros", "regex", "serde", "serde_html_form", @@ -864,6 +877,30 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +[[package]] +name = "ouroboros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.106", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -918,6 +955,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "version_check", + "yansi", +] + [[package]] name = "quote" version = "1.0.40" @@ -1180,6 +1230,12 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.11.1" @@ -1551,6 +1607,12 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yoke" version = "0.8.0" diff --git a/crates/jacquard-common/Cargo.toml b/crates/jacquard-common/Cargo.toml index f794d00b..ac371874 100644 --- a/crates/jacquard-common/Cargo.toml +++ b/crates/jacquard-common/Cargo.toml @@ -20,6 +20,7 @@ langtag = { version = "0.4.0", features = ["serde"] } miette = "7.6.0" multibase = "0.9.1" multihash = "0.19.3" +ouroboros = "0.18.5" regex = "1.11.3" serde = { version = "1.0.227", features = ["derive"] } serde_html_form = "0.2.8" diff --git a/crates/jacquard-common/src/types/aturi.rs b/crates/jacquard-common/src/types/aturi.rs index 2aa71145..fdae93aa 100644 --- a/crates/jacquard-common/src/types/aturi.rs +++ b/crates/jacquard-common/src/types/aturi.rs @@ -1,13 +1,14 @@ -use crate::CowStr; use crate::types::ident::AtIdentifier; use crate::types::nsid::Nsid; use crate::types::recordkey::{RecordKey, Rkey}; use crate::types::string::AtStrError; +use crate::{CowStr, IntoStatic}; use regex::Regex; use serde::Serializer; use serde::{Deserialize, Deserializer, Serialize, de::Error}; use smol_str::{SmolStr, ToSmolStr}; use std::fmt; +use std::hash::{Hash, Hasher}; use std::sync::LazyLock; use std::{ops::Deref, str::FromStr}; @@ -15,16 +16,69 @@ use std::{ops::Deref, str::FromStr}; /// /// based on the regex here: https://github.com/bluesky-social/atproto/blob/main/packages/syntax/src/aturi_validation.ts /// -/// Doesn't support the query segment, but then neither does the Typescript SDK -/// -/// TODO: support IntoStatic on string types. For composites like this where all borrow from (present) input, -/// perhaps use some careful unsafe to launder the lifetimes. -#[derive(Clone, PartialEq, Eq, Hash, Debug)] +/// Doesn't support the query segment, but then neither does the Typescript SDK. +#[derive(PartialEq, Eq, Debug)] pub struct AtUri<'u> { + inner: Inner<'u>, +} + +#[ouroboros::self_referencing] +#[derive(PartialEq, Eq, Debug)] +struct Inner<'u> { uri: CowStr<'u>, - pub authority: AtIdentifier<'u>, - pub path: Option>, - pub fragment: Option>, + #[borrows(uri)] + #[covariant] + pub authority: AtIdentifier<'this>, + #[borrows(uri)] + #[covariant] + pub path: Option>, + #[borrows(uri)] + #[covariant] + pub fragment: Option>, +} + +impl Clone for AtUri<'_> { + fn clone(&self) -> Self { + let uri = self.inner.borrow_uri(); + + Self { + inner: Inner::new( + CowStr::Owned(uri.as_ref().to_smolstr()), + |uri| { + let parts = ATURI_REGEX.captures(uri).unwrap(); + unsafe { AtIdentifier::unchecked(parts.name("authority").unwrap().as_str()) } + }, + |uri| { + let parts = ATURI_REGEX.captures(uri).unwrap(); + if let Some(collection) = parts.name("collection") { + let collection = unsafe { Nsid::unchecked(collection.as_str()) }; + let rkey = if let Some(rkey) = parts.name("rkey") { + let rkey = unsafe { RecordKey::from(Rkey::unchecked(rkey.as_str())) }; + Some(rkey) + } else { + None + }; + Some(UriPath { collection, rkey }) + } else { + None + } + }, + |uri| { + let parts = ATURI_REGEX.captures(uri).unwrap(); + parts.name("fragment").map(|fragment| { + let fragment = CowStr::Borrowed(fragment.as_str()); + fragment + }) + }, + ), + } + } +} + +impl Hash for AtUri<'_> { + fn hash(&self, state: &mut H) { + self.inner.borrow_uri().hash(state); + } } /// at:// URI path component (current subset) @@ -34,6 +88,17 @@ pub struct UriPath<'u> { pub rkey: Option>>, } +impl IntoStatic for UriPath<'_> { + type Output = UriPath<'static>; + + fn into_static(self) -> Self::Output { + UriPath { + collection: self.collection.into_static(), + rkey: self.rkey.map(|rkey| rkey.into_static()), + } + } +} + pub type UriPathBuf = UriPath<'static>; pub static ATURI_REGEX: LazyLock = LazyLock::new(|| { @@ -68,10 +133,13 @@ impl<'u> AtUri<'u> { fragment }); Ok(AtUri { - uri: CowStr::Borrowed(uri), - authority, - path, - fragment, + inner: InnerBuilder { + uri: CowStr::Borrowed(uri), + authority_builder: |_| authority, + path_builder: |_| path, + fragment_builder: |_| fragment, + } + .build(), }) } else { Err(AtStrError::missing("at-uri-scheme", uri, "authority")) @@ -106,10 +174,13 @@ impl<'u> AtUri<'u> { fragment }); AtUri { - uri: CowStr::Borrowed(uri), - authority, - path, - fragment, + inner: InnerBuilder { + uri: CowStr::Borrowed(uri), + authority_builder: |_| authority, + path_builder: |_| path, + fragment_builder: |_| fragment, + } + .build(), } } else { panic!("at:// URI missing authority") @@ -119,20 +190,18 @@ impl<'u> AtUri<'u> { } } - pub fn new_owned(uri: impl AsRef) -> Result { - let uri = uri.as_ref(); + /// Unchecked borrowing constructor. This one does do some validation but if that fails will just + /// dump everything in the authority field. + /// + /// TODO: do some fallback splitting, but really, if you use this on something invalid, you deserve it. + pub unsafe fn unchecked(uri: &'u str) -> Self { if let Some(parts) = ATURI_REGEX.captures(uri) { if let Some(authority) = parts.name("authority") { - let authority = AtIdentifier::new_owned(authority.as_str()) - .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?; + let authority = unsafe { AtIdentifier::unchecked(authority.as_str()) }; let path = if let Some(collection) = parts.name("collection") { - let collection = Nsid::new_owned(collection.as_str()) - .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?; + let collection = unsafe { Nsid::unchecked(collection.as_str()) }; let rkey = if let Some(rkey) = parts.name("rkey") { - let rkey = - RecordKey::from(Rkey::new_owned(rkey.as_str()).map_err(|e| { - AtStrError::wrap("at-uri-scheme", uri.to_string(), e) - })?); + let rkey = RecordKey::from(unsafe { Rkey::unchecked(rkey.as_str()) }); Some(rkey) } else { None @@ -142,28 +211,195 @@ impl<'u> AtUri<'u> { None }; let fragment = parts.name("fragment").map(|fragment| { - let fragment = CowStr::Owned(fragment.as_str().to_smolstr()); + let fragment = CowStr::Borrowed(fragment.as_str()); fragment }); + AtUri { + inner: InnerBuilder { + uri: CowStr::Borrowed(uri), + authority_builder: |_| authority, + path_builder: |_| path, + fragment_builder: |_| fragment, + } + .build(), + } + } else { + // let mut uriParts = uri.split('#'); + // let mut parts = uriParts.next().unwrap_or(uri).split('/'); + // let auth = parts.next().unwrap_or(uri); + Self { + inner: InnerBuilder { + uri: CowStr::Borrowed(uri), + authority_builder: |_| unsafe { AtIdentifier::unchecked(uri) }, + path_builder: |_| None, + fragment_builder: |_| None, + } + .build(), + } + } + } else { + Self { + inner: InnerBuilder { + uri: CowStr::Borrowed(uri), + authority_builder: |_| unsafe { AtIdentifier::unchecked(uri) }, + path_builder: |_| None, + fragment_builder: |_| None, + } + .build(), + } + } + } + + /// Clone method that should be O(1) in terms of time + /// + /// Calling on a borrowed variant will turn it into an owned variant, taking a little + /// more time and allocating memory for each part. Calling it on an owned variant will + /// increment all the internal reference counters (or, if constructed from a `&'static str`, + /// essentially do nothing). + pub fn fast_clone(&self) -> AtUri<'static> { + self.inner.with(move |u| { + let uri = u.uri.clone().into_static(); + let authority = u.authority.clone().into_static(); + let path = u.path.clone().into_static(); + let fragment = u.fragment.clone().into_static(); + AtUri { + inner: InnerBuilder { + uri, + authority_builder: |_| authority, + path_builder: |_| path, + fragment_builder: |_| fragment, + } + .build(), + } + }) + } + + pub fn as_str(&self) -> &str { + { + let this = &self.inner.borrow_uri(); + this + } + } + + pub fn authority(&self) -> &AtIdentifier<'_> { + self.inner.borrow_authority() + } + + pub fn path(&self) -> &Option> { + self.inner.borrow_path() + } + + pub fn fragment(&self) -> &Option> { + self.inner.borrow_fragment() + } + + pub fn collection(&self) -> Option<&Nsid<'_>> { + self.inner.borrow_path().as_ref().map(|p| &p.collection) + } + + pub fn rkey(&self) -> Option<&RecordKey>> { + self.inner + .borrow_path() + .as_ref() + .and_then(|p| p.rkey.as_ref()) + } +} + +impl AtUri<'static> { + /// Owned constructor + /// + /// Uses ouroboros self-referential tricks internally to make sure everything + /// borrows efficiently from the uri `CowStr<'static>`. + /// + /// Performs validation up-front, but is slower than the borrowing constructor + /// due to currently having to re-run the main regex, in addition to allocating. + /// + /// `.into_static()` and Clone implementations have similar limitations. + /// + /// O(1) clone mathod is AtUri::fast_clone(). + /// + /// Future optimization involves working out the indices borrowed and either using those + /// to avoid re-computing in some places, or, for a likely fully optimal version, only storing + /// the indices and constructing the borrowed components unsafely when asked. + pub fn new_owned(uri: impl AsRef) -> Result { + if let Some(parts) = ATURI_REGEX.captures(uri.as_ref()) { + if let Some(authority) = parts.name("authority") { + let _authority = AtIdentifier::new(authority.as_str()) + .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.as_ref().to_string(), e))?; + let path = if let Some(collection) = parts.name("collection") { + let collection = Nsid::new(collection.as_str()).map_err(|e| { + AtStrError::wrap("at-uri-scheme", uri.as_ref().to_string(), e) + })?; + let rkey = if let Some(rkey) = parts.name("rkey") { + let rkey = RecordKey::from(Rkey::new(rkey.as_str()).map_err(|e| { + AtStrError::wrap("at-uri-scheme", uri.as_ref().to_string(), e) + })?); + Some(rkey) + } else { + None + }; + Some(UriPath { collection, rkey }) + } else { + None + }; + Ok(AtUri { - uri: CowStr::Owned(uri.to_smolstr()), - authority, - path, - fragment, + inner: Inner::new( + CowStr::Owned(uri.as_ref().to_smolstr()), + |uri| { + let parts = ATURI_REGEX.captures(uri).unwrap(); + unsafe { + AtIdentifier::unchecked(parts.name("authority").unwrap().as_str()) + } + }, + |uri| { + if path.is_some() { + let parts = ATURI_REGEX.captures(uri).unwrap(); + if let Some(collection) = parts.name("collection") { + let collection = + unsafe { Nsid::unchecked(collection.as_str()) }; + let rkey = if let Some(rkey) = parts.name("rkey") { + let rkey = unsafe { + RecordKey::from(Rkey::unchecked(rkey.as_str())) + }; + Some(rkey) + } else { + None + }; + Some(UriPath { collection, rkey }) + } else { + None + } + } else { + None + } + }, + |uri| { + let parts = ATURI_REGEX.captures(uri).unwrap(); + parts.name("fragment").map(|fragment| { + let fragment = CowStr::Borrowed(fragment.as_str()); + fragment + }) + }, + ), }) } else { - Err(AtStrError::missing("at-uri-scheme", uri, "authority")) + Err(AtStrError::missing( + "at-uri-scheme", + &uri.as_ref(), + "authority", + )) } } else { Err(AtStrError::regex( "at-uri-scheme", - uri, + &uri.as_ref(), SmolStr::new_static("doesn't match schema"), )) } } - pub fn new_static(uri: &'static str) -> Result, AtStrError> { + pub fn new_static(uri: &'static str) -> Result { let uri = uri.as_ref(); if let Some(parts) = ATURI_REGEX.captures(uri) { if let Some(authority) = parts.name("authority") { @@ -190,10 +426,13 @@ impl<'u> AtUri<'u> { fragment }); Ok(AtUri { - uri: CowStr::new_static(uri), - authority, - path, - fragment, + inner: InnerBuilder { + uri: CowStr::new_static(uri), + authority_builder: |_| authority, + path_builder: |_| path, + fragment_builder: |_| fragment, + } + .build(), }) } else { Err(AtStrError::missing("at-uri-scheme", uri, "authority")) @@ -206,15 +445,26 @@ impl<'u> AtUri<'u> { )) } } +} - pub unsafe fn unchecked(uri: &'u str) -> Self { - if let Some(parts) = ATURI_REGEX.captures(uri) { +impl FromStr for AtUri<'_> { + type Err = AtStrError; + + /// Has to take ownership due to the lifetime constraints of the FromStr trait. + /// Prefer `AtUri::new()` or `AtUri::raw()` if you want to borrow. + fn from_str(uri: &str) -> Result { + if let Some(parts) = ATURI_REGEX.captures(uri.as_ref()) { if let Some(authority) = parts.name("authority") { - let authority = unsafe { AtIdentifier::unchecked(authority.as_str()) }; + let _authority = AtIdentifier::new(authority.as_str()) + .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?; let path = if let Some(collection) = parts.name("collection") { - let collection = unsafe { Nsid::unchecked(collection.as_str()) }; + let collection = Nsid::new(collection.as_str()) + .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?; let rkey = if let Some(rkey) = parts.name("rkey") { - let rkey = RecordKey::from(unsafe { Rkey::unchecked(rkey.as_str()) }); + let rkey = + RecordKey::from(Rkey::new(rkey.as_str()).map_err(|e| { + AtStrError::wrap("at-uri-scheme", uri.to_string(), e) + })?); Some(rkey) } else { None @@ -223,49 +473,108 @@ impl<'u> AtUri<'u> { } else { None }; - let fragment = parts.name("fragment").map(|fragment| { - let fragment = CowStr::Borrowed(fragment.as_str()); - fragment - }); - AtUri { - uri: CowStr::Borrowed(uri), - authority, - path, - fragment, - } + + Ok(AtUri { + inner: Inner::new( + CowStr::Owned(uri.to_smolstr()), + |uri| { + let parts = ATURI_REGEX.captures(uri).unwrap(); + unsafe { + AtIdentifier::unchecked(parts.name("authority").unwrap().as_str()) + } + }, + |uri| { + if path.is_some() { + let parts = ATURI_REGEX.captures(uri).unwrap(); + if let Some(collection) = parts.name("collection") { + let collection = + unsafe { Nsid::unchecked(collection.as_str()) }; + let rkey = if let Some(rkey) = parts.name("rkey") { + let rkey = unsafe { + RecordKey::from(Rkey::unchecked(rkey.as_str())) + }; + Some(rkey) + } else { + None + }; + Some(UriPath { collection, rkey }) + } else { + None + } + } else { + None + } + }, + |uri| { + let parts = ATURI_REGEX.captures(uri).unwrap(); + parts.name("fragment").map(|fragment| { + let fragment = CowStr::Borrowed(fragment.as_str()); + fragment + }) + }, + ), + }) } else { - Self { - uri: CowStr::Borrowed(uri), - authority: unsafe { AtIdentifier::unchecked(uri) }, - path: None, - fragment: None, - } + Err(AtStrError::missing( + "at-uri-scheme", + &uri.as_ref(), + "authority", + )) } } else { - Self { - uri: CowStr::Borrowed(uri), - authority: unsafe { AtIdentifier::unchecked(uri) }, - path: None, - fragment: None, - } - } - } - - pub fn as_str(&self) -> &str { - { - let this = &self.uri; - this + Err(AtStrError::regex( + "at-uri-scheme", + &uri.as_ref(), + SmolStr::new_static("doesn't match schema"), + )) } } } -impl FromStr for AtUri<'_> { - type Err = AtStrError; +impl IntoStatic for AtUri<'_> { + type Output = AtUri<'static>; - /// Has to take ownership due to the lifetime constraints of the FromStr trait. - /// Prefer `AtUri::new()` or `AtUri::raw()` if you want to borrow. - fn from_str(s: &str) -> Result { - Self::new_owned(s) + fn into_static(self) -> AtUri<'static> { + AtUri { + inner: Inner::new( + self.inner.borrow_uri().clone().into_static(), + |uri| { + let parts = ATURI_REGEX.captures(uri).unwrap(); + unsafe { AtIdentifier::unchecked(parts.name("authority").unwrap().as_str()) } + }, + |uri| { + if self.inner.borrow_path().is_some() { + let parts = ATURI_REGEX.captures(uri).unwrap(); + if let Some(collection) = parts.name("collection") { + let collection = unsafe { Nsid::unchecked(collection.as_str()) }; + let rkey = if let Some(rkey) = parts.name("rkey") { + let rkey = + unsafe { RecordKey::from(Rkey::unchecked(rkey.as_str())) }; + Some(rkey) + } else { + None + }; + Some(UriPath { collection, rkey }) + } else { + None + } + } else { + None + } + }, + |uri| { + if self.inner.borrow_fragment().is_some() { + let parts = ATURI_REGEX.captures(uri).unwrap(); + parts.name("fragment").map(|fragment| { + let fragment = CowStr::Borrowed(fragment.as_str()); + fragment + }) + } else { + None + } + }, + ), + } } } @@ -284,25 +593,25 @@ impl Serialize for AtUri<'_> { where S: Serializer, { - serializer.serialize_str(&self.uri) + serializer.serialize_str(&self.inner.borrow_uri()) } } impl fmt::Display for AtUri<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.uri) + f.write_str(&self.inner.borrow_uri()) } } impl<'d> From> for String { fn from(value: AtUri<'d>) -> Self { - value.uri.to_string() + value.inner.borrow_uri().to_string() } } impl<'d> From> for CowStr<'d> { fn from(value: AtUri<'d>) -> Self { - value.uri + value.inner.borrow_uri().clone() } } @@ -316,15 +625,83 @@ impl TryFrom for AtUri<'static> { impl<'d> TryFrom> for AtUri<'d> { type Error = AtStrError; - /// TODO: rewrite to avoid taking ownership/cloning - fn try_from(value: CowStr<'d>) -> Result { - Self::new_owned(value) + fn try_from(uri: CowStr<'d>) -> Result { + if let Some(parts) = ATURI_REGEX.captures(uri.as_ref()) { + if let Some(authority) = parts.name("authority") { + let _authority = AtIdentifier::new(authority.as_str()) + .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?; + let _path = if let Some(collection) = parts.name("collection") { + let collection = Nsid::new(collection.as_str()) + .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?; + let rkey = if let Some(rkey) = parts.name("rkey") { + let rkey = + RecordKey::from(Rkey::new(rkey.as_str()).map_err(|e| { + AtStrError::wrap("at-uri-scheme", uri.to_string(), e) + })?); + Some(rkey) + } else { + None + }; + Some(UriPath { collection, rkey }) + } else { + None + }; + drop(parts); + + Ok(AtUri { + inner: Inner::new( + uri, + |uri| { + let parts = ATURI_REGEX.captures(uri).unwrap(); + unsafe { + AtIdentifier::unchecked(parts.name("authority").unwrap().as_str()) + } + }, + |uri| { + let parts = ATURI_REGEX.captures(uri).unwrap(); + if let Some(collection) = parts.name("collection") { + let collection = unsafe { Nsid::unchecked(collection.as_str()) }; + let rkey = if let Some(rkey) = parts.name("rkey") { + let rkey = + unsafe { RecordKey::from(Rkey::unchecked(rkey.as_str())) }; + Some(rkey) + } else { + None + }; + Some(UriPath { collection, rkey }) + } else { + None + } + }, + |uri| { + let parts = ATURI_REGEX.captures(uri).unwrap(); + parts.name("fragment").map(|fragment| { + let fragment = CowStr::Borrowed(fragment.as_str()); + fragment + }) + }, + ), + }) + } else { + Err(AtStrError::missing( + "at-uri-scheme", + &uri.as_ref(), + "authority", + )) + } + } else { + Err(AtStrError::regex( + "at-uri-scheme", + &uri.as_ref(), + SmolStr::new_static("doesn't match schema"), + )) + } } } impl AsRef for AtUri<'_> { fn as_ref(&self) -> &str { - &self.uri.as_ref() + &self.inner.borrow_uri().as_ref() } } @@ -332,6 +709,6 @@ impl Deref for AtUri<'_> { type Target = str; fn deref(&self) -> &Self::Target { - self.uri.as_ref() + self.inner.borrow_uri().as_ref() } } diff --git a/crates/jacquard-common/src/types/blob.rs b/crates/jacquard-common/src/types/blob.rs index 3d28b7cf..75ce5aad 100644 --- a/crates/jacquard-common/src/types/blob.rs +++ b/crates/jacquard-common/src/types/blob.rs @@ -21,15 +21,22 @@ pub struct Blob<'b> { pub size: usize, } -impl<'r> BlobRef<'r> { - pub fn blob(&self) -> &Blob<'r> { - match self { - BlobRef::Blob(blob) => blob, +impl IntoStatic for Blob<'_> { + type Output = Blob<'static>; + + fn into_static(self) -> Self::Output { + Blob { + r#ref: self.r#ref.into_static(), + mime_type: self.mime_type.into_static(), + size: self.size, } } } /// Current, typed blob reference. +/// Quite dislike this nesting, but it serves the same purpose as it did in Atrium +/// Couple of helper methods and conversions to make it less annoying. +/// TODO: revisit nesting and maybe hand-roll a serde impl that supports this sans nesting #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(tag = "$type", rename_all = "lowercase")] pub enum BlobRef<'r> { @@ -37,6 +44,38 @@ pub enum BlobRef<'r> { Blob(Blob<'r>), } +impl<'r> BlobRef<'r> { + pub fn blob(&self) -> &Blob<'r> { + match self { + BlobRef::Blob(blob) => blob, + } + } +} + +impl<'b> From> for Blob<'b> { + fn from(blob_ref: BlobRef<'b>) -> Self { + match blob_ref { + BlobRef::Blob(blob) => blob, + } + } +} + +impl<'b> From> for BlobRef<'b> { + fn from(blob: Blob<'b>) -> Self { + BlobRef::Blob(blob) + } +} + +impl IntoStatic for BlobRef<'_> { + type Output = BlobRef<'static>; + + fn into_static(self) -> Self::Output { + match self { + BlobRef::Blob(blob) => BlobRef::Blob(blob.into_static()), + } + } +} + /// Wrapper for file type #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)] #[serde(transparent)] diff --git a/crates/jacquard-common/src/types/string.rs b/crates/jacquard-common/src/types/string.rs index 5eb8a934..7a1f8394 100644 --- a/crates/jacquard-common/src/types/string.rs +++ b/crates/jacquard-common/src/types/string.rs @@ -1,8 +1,7 @@ -use bytes::Bytes; use miette::SourceSpan; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use smol_str::{SmolStr, ToSmolStr}; -use std::{collections::BTreeMap, str::FromStr, sync::Arc}; +use std::{str::FromStr, sync::Arc}; use crate::IntoStatic; pub use crate::{ @@ -187,9 +186,7 @@ impl IntoStatic for AtprotoStr<'_> { AtprotoStr::Did(did) => AtprotoStr::Did(did.into_static()), AtprotoStr::Handle(handle) => AtprotoStr::Handle(handle.into_static()), AtprotoStr::AtIdentifier(ident) => AtprotoStr::AtIdentifier(ident.into_static()), - AtprotoStr::AtUri(at_uri) => { - AtprotoStr::AtUri(AtUri::new_owned(at_uri.as_str()).unwrap()) - } + AtprotoStr::AtUri(at_uri) => AtprotoStr::AtUri(at_uri.into_static()), AtprotoStr::Uri(uri) => AtprotoStr::Uri(uri.into_static()), AtprotoStr::Cid(cid) => AtprotoStr::Cid(cid.into_static()), AtprotoStr::RecordKey(record_key) => AtprotoStr::RecordKey(record_key.into_static()), @@ -214,7 +211,7 @@ impl IntoStatic for AtprotoStr<'_> { pub struct AtStrError { pub spec: SmolStr, #[source_code] - source: String, + pub source: String, #[source] #[diagnostic_source] pub kind: StrParseKind,