From 4d4a9dfc64a4a21be49a7f76d48be363b29b8dba Mon Sep 17 00:00:00 2001 From: Orual Date: Sun, 22 Mar 2026 16:38:33 -0400 Subject: [PATCH] Data and all its serialization is migrated now. Added a float variant to it. --- crates/jacquard-common/src/bos.rs | 5 +- crates/jacquard-common/src/types/aturi.rs | 22 +- crates/jacquard-common/src/types/blob.rs | 48 +- crates/jacquard-common/src/types/cid.rs | 58 ++- .../jacquard-common/src/types/collection.rs | 15 +- crates/jacquard-common/src/types/did.rs | 13 +- crates/jacquard-common/src/types/did_doc.rs | 35 +- crates/jacquard-common/src/types/handle.rs | 7 + crates/jacquard-common/src/types/ident.rs | 14 +- crates/jacquard-common/src/types/nsid.rs | 7 + crates/jacquard-common/src/types/recordkey.rs | 14 + crates/jacquard-common/src/types/string.rs | 39 +- crates/jacquard-common/src/types/uri.rs | 16 +- crates/jacquard-common/src/types/value.rs | 432 +++++++----------- .../src/types/value/convert.rs | 175 +++++-- .../src/types/value/parsing.rs | 225 +++++---- .../src/types/value/serde_impl.rs | 424 +++++++++++------ .../jacquard-common/src/types/value/tests.rs | 111 ++--- crates/jacquard-common/src/xrpc/atproto.rs | 112 +++-- 19 files changed, 1052 insertions(+), 720 deletions(-) diff --git a/crates/jacquard-common/src/bos.rs b/crates/jacquard-common/src/bos.rs index 2dd8efa0..b0d0ed96 100644 --- a/crates/jacquard-common/src/bos.rs +++ b/crates/jacquard-common/src/bos.rs @@ -32,8 +32,6 @@ //! } //! ``` -use core::{fmt, marker::PhantomData, ops::Deref}; - use alloc::{ borrow::{Cow, ToOwned}, boxed::Box, @@ -41,10 +39,9 @@ use alloc::{ vec::Vec, }; -use serde::{Deserialize, Serialize}; use smol_str::SmolStr; -use crate::{CowStr, IntoStatic}; +use crate::CowStr; mod internal { pub trait Ref { diff --git a/crates/jacquard-common/src/types/aturi.rs b/crates/jacquard-common/src/types/aturi.rs index d1073850..449c6b0c 100644 --- a/crates/jacquard-common/src/types/aturi.rs +++ b/crates/jacquard-common/src/types/aturi.rs @@ -20,7 +20,7 @@ use regex_automata::meta::Regex; use regex_lite::Regex; use serde::Serializer; use serde::{Deserialize, Deserializer, Serialize, de::Error}; -use smol_str::{SmolStr, ToSmolStr}; +use smol_str::SmolStr; use super::Lazy; @@ -172,6 +172,16 @@ where } } +impl + AsRef> RepoPath { + /// Convert to a `RepoPath` with a different backing type. + pub fn convert + AsRef + From>(self) -> RepoPath { + RepoPath { + collection: self.collection.convert(), + rkey: self.rkey.map(|rkey| RecordKey(rkey.0.convert())), + } + } +} + /// Owned (static lifetime) version of `RepoPath`. pub type UriPathBuf = RepoPath; @@ -451,6 +461,16 @@ where } } +impl + AsRef> AtUri { + /// Convert to an `AtUri` with a different backing type. + pub fn convert + AsRef + From>(self) -> AtUri { + AtUri { + uri: B::from(self.uri), + indices: self.indices, + } + } +} + // --------------------------------------------------------------------------- // Serde // --------------------------------------------------------------------------- diff --git a/crates/jacquard-common/src/types/blob.rs b/crates/jacquard-common/src/types/blob.rs index 9fff3c22..5c28425d 100644 --- a/crates/jacquard-common/src/types/blob.rs +++ b/crates/jacquard-common/src/types/blob.rs @@ -4,8 +4,7 @@ use crate::{CowStr, IntoStatic, types::cid::CidLink}; use alloc::string::{String, ToString}; use core::convert::Infallible; use core::{fmt, hash::Hash, ops::Deref, str::FromStr}; -use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; -use smol_str::{SmolStr, ToSmolStr}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// Blob reference for binary data in AT Protocol. /// @@ -78,6 +77,17 @@ where } } +impl + AsRef> Blob { + /// Convert to a `Blob` with a different backing type. + pub fn convert + AsRef + From>(self) -> Blob { + Blob { + r#ref: self.r#ref.convert(), + mime_type: self.mime_type.convert(), + size: self.size, + } + } +} + /// Tagged blob reference with `$type` field for serde. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(tag = "$type", rename_all = "lowercase")] @@ -126,6 +136,15 @@ where } } +impl + AsRef> BlobRef { + /// Convert to a `BlobRef` with a different backing type. + pub fn convert + AsRef + From>(self) -> BlobRef { + match self { + BlobRef::Blob(blob) => BlobRef::Blob(blob.convert()), + } + } +} + /// MIME type identifier for blob data. /// /// Used to specify the content type of blobs. Supports patterns like "image/*" and "*/*". @@ -142,34 +161,27 @@ impl + AsRef> MimeType { } } -impl<'m> MimeType<&'m str> { - /// Infallible constructor, borrows from input. - pub fn new(mime_type: &'m str) -> Self { +impl> MimeType { + /// Infallible constructor, wraps the input directly. + pub fn new(mime_type: S) -> Self { Self(mime_type) } - /// Infallible constructor for trusted MIME type strings. - pub fn raw(mime_type: &'m str) -> Self { - Self(mime_type) + /// Convert to a `MimeType` with a different backing type. + pub fn convert + From>(self) -> MimeType { + MimeType(B::from(self.0)) } } -impl + From> MimeType { +impl + FromStr> MimeType { /// Infallible constructor, takes ownership. pub fn new_owned(mime_type: impl AsRef) -> Self { - Self(S::from(mime_type.as_ref().to_smolstr())) + Self(S::from_str(mime_type.as_ref()).unwrap_or_else(|_| unreachable!())) } /// Infallible constructor for static strings. pub fn new_static(mime_type: &'static str) -> Self { - Self(S::from(SmolStr::new_static(mime_type))) - } -} - -impl<'m> MimeType> { - /// Infallible constructor, borrows if possible. - pub fn new_cow(mime_type: CowStr<'m>) -> Self { - Self(mime_type) + Self(S::from_str(mime_type).unwrap_or_else(|_| unreachable!())) } } diff --git a/crates/jacquard-common/src/types/cid.rs b/crates/jacquard-common/src/types/cid.rs index 34f091e1..758a2330 100644 --- a/crates/jacquard-common/src/types/cid.rs +++ b/crates/jacquard-common/src/types/cid.rs @@ -1,5 +1,5 @@ use crate::bos::{Bos, DefaultStr}; -use crate::{CowStr, IntoStatic, cowstr::ToCowStr}; +use crate::{CowStr, IntoStatic}; use alloc::string::{String, ToString}; pub use cid::Cid as IpldCid; use core::{convert::Infallible, fmt, ops::Deref, str::FromStr}; @@ -53,6 +53,10 @@ pub enum Error { /// Invalid UTF-8 in CID string. #[error("{:?}", 0)] Utf8(#[from] core::str::Utf8Error), + /// Wraps another error with additional context + #[error("converting from a string slice")] + #[cfg_attr(feature = "std", diagnostic(code(jacquard::cid::str_conversion)))] + Conversion, } // --------------------------------------------------------------------------- @@ -117,26 +121,33 @@ impl<'c> Cid<&'c str> { pub fn str(cid: &'c str) -> Self { Self::Str(cid) } +} +impl> Cid { /// Parse a CID from bytes (tries IPLD first, falls back to UTF-8 string). - pub fn new(cid: &'c [u8]) -> Result { + pub fn new<'c>(cid: &'c [u8]) -> Result, Error> + where + S: From<&'c str>, + { if let Ok(cid) = IpldCid::try_from(cid.as_ref()) { - Ok(Self::ipld(cid)) + Ok(Cid::ipld(cid)) } else { let cid_str = core::str::from_utf8(cid)?; - Ok(Self::Str(cid_str)) + Ok(Cid::Str(cid_str.into())) } } } -impl + From> Cid { +impl + FromStr> Cid { /// Parse a CID from bytes into an owned value. pub fn new_owned(cid: &[u8]) -> Result { if let Ok(cid) = IpldCid::try_from(cid.as_ref()) { Ok(Self::ipld(cid)) } else { let cid_str = core::str::from_utf8(cid)?; - Ok(Cid::Str(S::from(cid_str.to_smolstr()))) + Ok(Cid::Str( + S::from_str(cid_str).map_err(|_| Error::Conversion)?, + )) } } } @@ -223,6 +234,16 @@ where } } +impl> Cid { + /// Convert to a `Cid` with a different backing type. + pub fn convert + From>(self) -> Cid { + match self { + Cid::Ipld { cid, s } => Cid::Ipld { cid, s }, + Cid::Str(s) => Cid::Str(B::from(s)), + } + } +} + impl + AsRef> From> for String { fn from(value: Cid) -> Self { value.as_str().to_string() @@ -296,14 +317,17 @@ impl> CidLink { pub fn ipld(cid: IpldCid) -> Self { CidLink(Cid::ipld(cid)) } -} -impl<'c> CidLink<&'c str> { /// Parse a CID link from bytes. - pub fn new(cid: &'c [u8]) -> Result { - Ok(Self(Cid::new(cid)?)) + pub fn new<'c>(cid: &'c [u8]) -> Result, Error> + where + S: Bos + From<&'c str>, + { + Ok(CidLink(Cid::new(cid)?)) } +} +impl<'c> CidLink<&'c str> { /// Construct a CID link from a string slice. pub fn str(cid: &'c str) -> Self { Self(Cid::str(cid)) @@ -315,7 +339,7 @@ impl<'c> CidLink<&'c str> { } } -impl + From> CidLink { +impl + FromStr> CidLink { /// Parse a CID link from bytes into an owned value. pub fn new_owned(cid: &[u8]) -> Result { Ok(CidLink(Cid::new_owned(cid)?)) @@ -405,10 +429,7 @@ where self.visit_bytes(&v) } - fn visit_newtype_struct( - self, - deserializer: D, - ) -> Result + fn visit_newtype_struct(self, deserializer: D) -> Result where D: serde::de::Deserializer<'de>, { @@ -503,6 +524,13 @@ where } } +impl> CidLink { + /// Convert to a `CidLink` with a different backing type. + pub fn convert + From>(self) -> CidLink { + CidLink(self.0.convert()) + } +} + impl + AsRef> From> for String { fn from(value: CidLink) -> Self { value.0.into() diff --git a/crates/jacquard-common/src/types/collection.rs b/crates/jacquard-common/src/types/collection.rs index c4658aff..f2840b7f 100644 --- a/crates/jacquard-common/src/types/collection.rs +++ b/crates/jacquard-common/src/types/collection.rs @@ -1,6 +1,6 @@ use alloc::string::String; use core::fmt; -use core::str::FromStr; +use smol_str::SmolStr; use serde::{Deserialize, Serialize}; @@ -8,10 +8,10 @@ use crate::types::value::Data; use crate::types::{ aturi::RepoPath, nsid::Nsid, - recordkey::{RecordKey, RecordKeyType, Rkey}, + recordkey::{RecordKey, RecordKeyType}, }; use crate::xrpc::XrpcResp; -use crate::{BorrowOrShare, Bos, CowStr, IntoStatic}; +use crate::{BorrowOrShare, Bos, IntoStatic}; /// Trait for a collection of records that can be stored in a repository. /// @@ -72,7 +72,7 @@ pub trait Collection: fmt::Debug + Serialize { )] #[serde(tag = "error", content = "message")] #[non_exhaustive] -pub enum RecordError<'a> { +pub enum RecordError { /// The requested record was not found #[error("RecordNotFound")] #[serde(rename = "RecordNotFound")] @@ -80,12 +80,11 @@ pub enum RecordError<'a> { /// An unknown error occurred #[error("Unknown")] #[serde(rename = "Unknown")] - #[serde(borrow)] - Unknown(Data<'a>), + Unknown(Data), } -impl IntoStatic for RecordError<'_> { - type Output = RecordError<'static>; +impl IntoStatic for RecordError { + type Output = RecordError; fn into_static(self) -> Self::Output { match self { diff --git a/crates/jacquard-common/src/types/did.rs b/crates/jacquard-common/src/types/did.rs index 6a883ed0..91ca05ab 100644 --- a/crates/jacquard-common/src/types/did.rs +++ b/crates/jacquard-common/src/types/did.rs @@ -12,7 +12,7 @@ use regex_automata::meta::Regex; #[cfg(target_arch = "wasm32")] use regex_lite::Regex; use serde::{Deserialize, Deserializer, Serialize}; -use smol_str::{SmolStr, ToSmolStr}; +use smol_str::SmolStr; use super::Lazy; @@ -159,6 +159,13 @@ where } } +impl> Did { + /// Convert to a `Did` with a different backing type. + pub fn convert + From>(self) -> Did { + Did(B::from(self.0)) + } +} + impl FromStr for Did { type Err = AtStrError; @@ -258,7 +265,9 @@ mod tests { // new() does not strip — use new_owned() for that. assert!(Did::<&str>::new("at://did:plc:foo").is_err()); assert_eq!( - Did::::new_owned("at://did:plc:foo").unwrap().as_str(), + Did::::new_owned("at://did:plc:foo") + .unwrap() + .as_str(), "did:plc:foo" ); assert_eq!( diff --git a/crates/jacquard-common/src/types/did_doc.rs b/crates/jacquard-common/src/types/did_doc.rs index 8f57a487..517531fd 100644 --- a/crates/jacquard-common/src/types/did_doc.rs +++ b/crates/jacquard-common/src/types/did_doc.rs @@ -1,10 +1,9 @@ use crate::deps::fluent_uri::Uri; use crate::types::crypto::{CryptoError, PublicKey}; -use crate::types::string::{Did, Handle}; +use crate::types::string::{AtprotoStr, Did, Handle}; use crate::types::value::Data; -use crate::{Bos, CowStr, DefaultStr, IntoStatic}; +use crate::{Bos, DefaultStr, IntoStatic}; use alloc::collections::BTreeMap; -use alloc::string::String; use alloc::vec::Vec; use bon::Builder; use serde::{Deserialize, Serialize}; @@ -69,9 +68,8 @@ pub struct DidDocument + AsRef = DefaultStr> { #[serde(skip_serializing_if = "Option::is_none")] pub service: Option>>, // Forward‑compatible capture of unmodeled fields - // TODO: re-enable extra data fields - // #[serde(flatten)] - // pub extra_data: BTreeMap>, + #[serde(flatten)] + pub extra_data: BTreeMap>, } /// Default context fields for DID documents @@ -98,7 +96,7 @@ where verification_method: self.verification_method.into_static(), service: self.service.into_static(), // TODO: re-enable extra data fields - // extra_data: self.extra_data.into_static(), + extra_data: self.extra_data.into_static(), } } } @@ -135,15 +133,14 @@ where }) } - /// Extract the AtprotoPersonalDataServer service endpoint as a `fluent_uri::Uri`. + /// Extract the AtprotoPersonalDataServer service endpoint as a `fluent_uri::Uri<&str>`. /// Accepts endpoint as string or object (string preferred). pub fn pds_endpoint(&self) -> Option> { self.service.as_ref().and_then(|services| { services.iter().find_map(|s| { if s.r#type.as_ref() == "AtprotoPersonalDataServer" { match &s.service_endpoint { - Some(strv) => Uri::parse(strv.as_ref()).ok(), - + Some(Data::String(AtprotoStr::Uri(u))) => Uri::parse(u.as_ref()).ok(), _ => None, } } else { @@ -185,9 +182,8 @@ pub struct VerificationMethod + AsRef> { #[serde(skip_serializing_if = "Option::is_none")] pub public_key_multibase: Option, // Forward‑compatible capture of unmodeled fields - // TODO: re-enable extra data fields - // #[serde(flatten)] - // pub extra_data: BTreeMap>, + #[serde(flatten)] + pub extra_data: BTreeMap>, } impl crate::IntoStatic for VerificationMethod @@ -204,7 +200,7 @@ where controller: self.controller.into_static(), public_key_multibase: self.public_key_multibase.into_static(), // TODO: re-enable extra data fields - // extra_data: self.extra_data.into_static(), + extra_data: self.extra_data.into_static(), } } } @@ -224,14 +220,11 @@ pub struct Service + AsRef> { #[serde(rename = "type")] pub r#type: S, /// currently atproto expects this to be a url - /// - /// TODO: add back in map/set support once Data<'_> is migrated #[serde(skip_serializing_if = "Option::is_none")] - pub service_endpoint: Option, + pub service_endpoint: Option>, // Forward‑compatible capture of unmodeled fields - // TODO: re-enable extra data fields - // #[serde(flatten)] - // pub extra_data: BTreeMap>, + #[serde(flatten)] + pub extra_data: BTreeMap>, } impl crate::IntoStatic for Service @@ -247,7 +240,7 @@ where r#type: self.r#type.into_static(), service_endpoint: self.service_endpoint.into_static(), // TODO: re-enable extra data fields - // extra_data: self.extra_data.into_static(), + extra_data: self.extra_data.into_static(), } } } diff --git a/crates/jacquard-common/src/types/handle.rs b/crates/jacquard-common/src/types/handle.rs index 0aed5141..2e7a7f60 100644 --- a/crates/jacquard-common/src/types/handle.rs +++ b/crates/jacquard-common/src/types/handle.rs @@ -232,6 +232,13 @@ where } } +impl> Handle { + /// Convert to a `Handle` with a different backing type. + pub fn convert + From>(self) -> Handle { + Handle(B::from(self.0)) + } +} + impl FromStr for Handle { type Err = AtStrError; diff --git a/crates/jacquard-common/src/types/ident.rs b/crates/jacquard-common/src/types/ident.rs index a728eb05..d78953d2 100644 --- a/crates/jacquard-common/src/types/ident.rs +++ b/crates/jacquard-common/src/types/ident.rs @@ -12,8 +12,6 @@ use core::str::FromStr; use serde::{Deserialize, Serialize}; -use smol_str::SmolStr; - /// AT Protocol identifier (either a DID or handle). /// /// Represents the union of DIDs and handles, which can both be used to identify @@ -131,6 +129,16 @@ where } } +impl + AsRef> AtIdentifier { + /// Convert to an `AtIdentifier` with a different backing type. + pub fn convert + AsRef + From>(self) -> AtIdentifier { + match self { + AtIdentifier::Did(did) => AtIdentifier::Did(did.convert()), + AtIdentifier::Handle(handle) => AtIdentifier::Handle(handle.convert()), + } + } +} + impl + AsRef> From> for AtIdentifier { fn from(did: Did) -> Self { AtIdentifier::Did(did) @@ -202,6 +210,8 @@ impl + AsRef> AsRef for AtIdentifier { #[cfg(test)] mod tests { + use smol_str::SmolStr; + use super::*; use crate::cowstr::ToCowStr; diff --git a/crates/jacquard-common/src/types/nsid.rs b/crates/jacquard-common/src/types/nsid.rs index 924bdc47..6daf5163 100644 --- a/crates/jacquard-common/src/types/nsid.rs +++ b/crates/jacquard-common/src/types/nsid.rs @@ -131,6 +131,13 @@ where } } +impl> Nsid { + /// Convert to an `Nsid` with a different backing type. + pub fn convert + From>(self) -> Nsid { + Nsid(B::from(self.0)) + } +} + impl FromStr for Nsid { type Err = AtStrError; diff --git a/crates/jacquard-common/src/types/recordkey.rs b/crates/jacquard-common/src/types/recordkey.rs index ad663542..6ab998c0 100644 --- a/crates/jacquard-common/src/types/recordkey.rs +++ b/crates/jacquard-common/src/types/recordkey.rs @@ -98,6 +98,13 @@ where } } +impl RecordKey { + /// Convert the inner key to a different type. + pub fn convert>(self) -> RecordKey { + RecordKey(U::from(self.0)) + } +} + /// AT Protocol record key (generic "any" type) /// /// Record keys uniquely identify records within a collection. This is the catch-all @@ -232,6 +239,13 @@ where } } +impl> Rkey { + /// Convert to an `Rkey` with a different backing type. + pub fn convert + From>(self) -> Rkey { + Rkey(B::from(self.0)) + } +} + impl FromStr for Rkey { type Err = AtStrError; diff --git a/crates/jacquard-common/src/types/string.rs b/crates/jacquard-common/src/types/string.rs index 83759e61..6f3bc70c 100644 --- a/crates/jacquard-common/src/types/string.rs +++ b/crates/jacquard-common/src/types/string.rs @@ -1,10 +1,11 @@ +use crate::bos::{Bos, DefaultStr}; use alloc::string::{String, ToString}; use alloc::sync::Arc; use core::str::FromStr; #[cfg(feature = "std")] use miette::{Diagnostic, SourceSpan}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use smol_str::{SmolStr, ToSmolStr}; +use smol_str::SmolStr; /// Source span for error reporting (offset, length) /// With `std` feature, this is `miette::SourceSpan`. Without, a simple tuple struct. @@ -26,8 +27,6 @@ impl From<(usize, usize)> for SourceSpan { } } -use crate::bos::{Bos, DefaultStr}; -use crate::cowstr::ToCowStr; pub use crate::{ CowStr, types::{ @@ -58,7 +57,7 @@ use crate::{ /// record keys are intentionally NOT parsed from bare strings as the validation /// is too permissive and would catch too many values. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum AtprotoStr + AsRef + Clone + Serialize = DefaultStr> { +pub enum AtprotoStr + AsRef = DefaultStr> { /// ISO 8601 datetime Datetime(Datetime), /// BCP 47 language tag @@ -90,7 +89,7 @@ use crate::types::did::validate_did; use crate::types::handle::validate_handle; use crate::types::nsid::validate_nsid; -impl + AsRef + Clone + Serialize> AtprotoStr { +impl + AsRef> AtprotoStr { /// Classify and wrap a string value into the appropriate variant. /// /// This is fairly exhaustive and potentially **slow**, prefer using anything @@ -191,13 +190,13 @@ impl + AsRef + Clone + Serialize> AtprotoStr { } } -impl + AsRef + Clone + Serialize> AsRef for AtprotoStr { +impl + AsRef> AsRef for AtprotoStr { fn as_ref(&self) -> &str { self.as_str() } } -impl + AsRef + Clone + Serialize> Serialize for AtprotoStr { +impl + AsRef + Serialize> Serialize for AtprotoStr { fn serialize(&self, serializer: Ser) -> Result where Ser: Serializer, @@ -208,7 +207,7 @@ impl + AsRef + Clone + Serialize> Serialize for AtprotoStr { impl<'de, S> Deserialize<'de> for AtprotoStr where - S: Bos + AsRef + Clone + Serialize + Deserialize<'de>, + S: Bos + AsRef + Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where @@ -219,9 +218,29 @@ where } } -impl + AsRef + Clone + Serialize + IntoStatic> IntoStatic for AtprotoStr +impl + AsRef> AtprotoStr { + /// Convert to an `AtprotoStr` with a different backing type. + pub fn convert + AsRef + From>(self) -> AtprotoStr { + match self { + AtprotoStr::Datetime(dt) => AtprotoStr::Datetime(dt), + AtprotoStr::Language(lang) => AtprotoStr::Language(lang), + AtprotoStr::Tid(tid) => AtprotoStr::Tid(tid), + AtprotoStr::Nsid(nsid) => AtprotoStr::Nsid(nsid.convert()), + AtprotoStr::Did(did) => AtprotoStr::Did(did.convert()), + AtprotoStr::Handle(handle) => AtprotoStr::Handle(handle.convert()), + AtprotoStr::AtIdentifier(ident) => AtprotoStr::AtIdentifier(ident.convert()), + AtprotoStr::AtUri(at_uri) => AtprotoStr::AtUri(at_uri.convert()), + AtprotoStr::Uri(uri) => AtprotoStr::Uri(uri.convert()), + AtprotoStr::Cid(cid) => AtprotoStr::Cid(cid.convert()), + AtprotoStr::RecordKey(rkey) => AtprotoStr::RecordKey(RecordKey(rkey.0.convert())), + AtprotoStr::String(s) => AtprotoStr::String(B::from(s)), + } + } +} + +impl + AsRef + IntoStatic> IntoStatic for AtprotoStr where - S::Output: Bos + AsRef + Clone + Serialize, + S::Output: Bos + AsRef, { type Output = AtprotoStr; diff --git a/crates/jacquard-common/src/types/uri.rs b/crates/jacquard-common/src/types/uri.rs index 82121f5c..af1f7f92 100644 --- a/crates/jacquard-common/src/types/uri.rs +++ b/crates/jacquard-common/src/types/uri.rs @@ -14,7 +14,7 @@ use crate::{ use alloc::string::{String, ToString}; use core::{fmt::Display, marker::PhantomData, ops::Deref, str::FromStr}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use smol_str::{SmolStr, ToSmolStr}; +use smol_str::SmolStr; /// Generic URI with type-specific parsing. /// @@ -215,6 +215,20 @@ where } } +impl + AsRef> UriValue { + /// Convert to a `UriValue` with a different backing type. + pub fn convert + AsRef + From>(self) -> UriValue { + match self { + UriValue::Did(did) => UriValue::Did(did.convert()), + UriValue::At(at_uri) => UriValue::At(at_uri.convert()), + UriValue::Https(url) => UriValue::Https(url), + UriValue::Wss(url) => UriValue::Wss(url), + UriValue::Cid(cid) => UriValue::Cid(cid.convert()), + UriValue::Any(s) => UriValue::Any(B::from(s)), + } + } +} + // --------------------------------------------------------------------------- // RecordUri // --------------------------------------------------------------------------- diff --git a/crates/jacquard-common/src/types/value.rs b/crates/jacquard-common/src/types/value.rs index 755712e8..ab299d57 100644 --- a/crates/jacquard-common/src/types/value.rs +++ b/crates/jacquard-common/src/types/value.rs @@ -1,14 +1,15 @@ use crate::{ - IntoStatic, + Bos, DefaultStr, IntoStatic, types::{DataModelType, LexiconStringType, UriType, blob::Blob, string::*}, }; use alloc::boxed::Box; use alloc::collections::BTreeMap; -use alloc::string::{String, ToString}; +use alloc::string::ToString; use alloc::vec::Vec; use bytes::Bytes; use core::convert::Infallible; use ipld_core::ipld::Ipld; +use serde::Serialize; use smol_str::{SmolStr, ToSmolStr}; /// Conversion utilities for Data types @@ -31,7 +32,7 @@ mod tests; /// This is the generic "unknown data" type used for lexicon values, extra fields captured /// by `#[lexicon]`, and IPLD data structures. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum Data<'s> { +pub enum Data + AsRef = DefaultStr> { /// Null value Null, /// Boolean value @@ -39,17 +40,19 @@ pub enum Data<'s> { /// Integer value (no floats in AT Protocol) Integer(i64), /// String value (parsed into specific AT Protocol types when possible) - String(AtprotoStr>), + String(AtprotoStr), /// Raw bytes Bytes(Bytes), /// CID link reference - CidLink(Cid>), + CidLink(Cid), /// Array of values - Array(Array<'s>), + Array(Array), /// Object/map of values - Object(Object<'s>), + Object(Object), /// Blob reference with metadata - Blob(Blob>), + Blob(Blob), + /// Invalid number (floating point) + InvalidNumber(S), } /// Errors that can occur when working with AT Protocol data @@ -61,7 +64,10 @@ pub enum AtDataError { FloatNotAllowed, } -impl<'s> Data<'s> { +impl Data +where + S: Bos + AsRef, +{ /// Get the data model type of this value pub fn data_type(&self) -> DataModelType { match self { @@ -98,34 +104,12 @@ impl<'s> Data<'s> { Data::Array(_) => DataModelType::Array, Data::Object(_) => DataModelType::Object, Data::Blob(_) => DataModelType::Blob, + Data::InvalidNumber(_) => DataModelType::Bytes, } } - /// Parse a Data value from a JSON value - pub fn from_json(json: &'s serde_json::Value) -> Result { - Ok(if let Some(value) = json.as_bool() { - Self::Boolean(value) - } else if let Some(value) = json.as_i64() { - Self::Integer(value) - } else if let Some(value) = json.as_str() { - Self::String(parsing::parse_string(value)) - } else if let Some(value) = json.as_array() { - Self::Array(Array::from_json(value)?) - } else if let Some(value) = json.as_object() { - Object::from_json(value)? - } else if json.is_f64() { - return Err(AtDataError::FloatNotAllowed); - } else { - Self::Null - }) - } - - /// Parse a Data value from a JSON value (owned) - pub fn from_json_owned(json: serde_json::Value) -> Result, AtDataError> { - Data::from_json(&json).map(|data| data.into_static()) - } /// Get as object if this is an Object variant - pub fn as_object(&self) -> Option<&Object<'s>> { + pub fn as_object(&self) -> Option<&Object> { if let Data::Object(obj) = self { Some(obj) } else { @@ -134,7 +118,7 @@ impl<'s> Data<'s> { } /// Get as array if this is an Array variant - pub fn as_array(&self) -> Option<&Array<'s>> { + pub fn as_array(&self) -> Option<&Array> { if let Data::Array(arr) = self { Some(arr) } else { @@ -152,7 +136,7 @@ impl<'s> Data<'s> { } /// Get as object if this is an Object variant - pub fn as_object_mut<'a>(&'a mut self) -> Option<&'a mut Object<'s>> { + pub fn as_object_mut<'a>(&'a mut self) -> Option<&'a mut Object> { if let Data::Object(obj) = self { Some(obj) } else { @@ -161,7 +145,7 @@ impl<'s> Data<'s> { } /// Get as array if this is an Array variant - pub fn as_array_mut<'a>(&'a mut self) -> Option<&'a mut Array<'s>> { + pub fn as_array_mut<'a>(&'a mut self) -> Option<&'a mut Array> { if let Data::Array(arr) = self { Some(arr) } else { @@ -170,7 +154,7 @@ impl<'s> Data<'s> { } /// Get as string if this is a String variant - pub fn as_str_mut(&'s mut self) -> Option<&'s mut AtprotoStr>> { + pub fn as_str_mut<'s>(&'s mut self) -> Option<&'s mut AtprotoStr> { if let Data::String(s) = self { Some(s) } else { @@ -232,7 +216,10 @@ impl<'s> Data<'s> { /// This produces the deterministic CBOR encoding used for content-addressing. pub fn to_dag_cbor( &self, - ) -> Result, serde_ipld_dagcbor::EncodeError> { + ) -> Result, serde_ipld_dagcbor::EncodeError> + where + S: Serialize, + { serde_ipld_dagcbor::to_vec(self) } @@ -250,23 +237,23 @@ impl<'s> Data<'s> { /// println!("Alt text: {}", alt_text.as_str().unwrap()); /// } /// ``` - pub fn get_at_path(&'s self, path: &str) -> Option<&'s Data<'s>> { + pub fn get_at_path<'s>(&'s self, path: &str) -> Option<&'s Data> { parse_and_traverse_path(self, path) } /// Get a mutable reference to a field at the given path /// /// Uses the same path syntax as [`get_at_path`](Self::get_at_path). - pub fn get_at_path_mut(&mut self, path: &str) -> Option<&mut Data<'s>> { + pub fn get_at_path_mut(&mut self, path: &str) -> Option<&mut Data> { parse_and_traverse_path_mut(self, path) } /// Set the value at the given path, returning true if successful /// /// Uses the same path syntax as [`get_at_path`](Self::get_at_path). - pub fn set_at_path(&mut self, path: &str, new_data: Data<'_>) -> bool { + pub fn set_at_path(&mut self, path: &str, new_data: Data) -> bool { if let Some(data) = parse_and_traverse_path_mut(self, path) { - *data = new_data.into_static(); + *data = new_data; true } else { false @@ -292,31 +279,36 @@ impl<'s> Data<'s> { /// // Global recursion /// let all_cids = data.query("...cid"); // all CIDs anywhere /// ``` - pub fn query(&'s self, pattern: &str) -> QueryResult<'s> { + pub fn query<'s>(&'s self, pattern: &str) -> QueryResult<'s, S> { query_data(self, pattern) } +} - /// Parse a Data value from an IPLD value (CBOR) - pub fn from_cbor(cbor: &'s Ipld) -> Result { - Ok(match cbor { - Ipld::Null => Data::Null, - Ipld::Bool(bool) => Data::Boolean(*bool), - Ipld::Integer(int) => Data::Integer(*int as i64), - Ipld::Float(_) => { - return Err(AtDataError::FloatNotAllowed); - } - Ipld::String(string) => Self::String(parsing::parse_string(string)), - Ipld::Bytes(items) => Self::Bytes(Bytes::copy_from_slice(items.as_slice())), - Ipld::List(iplds) => Self::Array(Array::from_cbor(iplds)?), - Ipld::Map(btree_map) => Object::from_cbor(btree_map)?, - Ipld::Link(cid) => Self::CidLink(Cid::ipld(*cid)), - }) +impl + AsRef> Data { + /// Convert to a `Data` with a different backing type. + pub fn convert + AsRef + From>(self) -> Data { + match self { + Data::Null => Data::Null, + Data::Boolean(b) => Data::Boolean(b), + Data::Integer(i) => Data::Integer(i), + Data::String(s) => Data::String(s.convert()), + Data::Bytes(b) => Data::Bytes(b), + Data::CidLink(cid) => Data::CidLink(cid.convert()), + Data::Array(arr) => Data::Array(arr.convert()), + Data::Object(obj) => Data::Object(obj.convert()), + Data::Blob(blob) => Data::Blob(blob.convert()), + Data::InvalidNumber(float) => Data::InvalidNumber(float.into()), + } } } -impl IntoStatic for Data<'_> { - type Output = Data<'static>; - fn into_static(self) -> Data<'static> { +impl IntoStatic for Data +where + S: Bos + AsRef + IntoStatic, + ::Output: AsRef + Bos, +{ + type Output = Data<::Output>; + fn into_static(self) -> Data<::Output> { match self { Data::Null => Data::Null, Data::Boolean(bool) => Data::Boolean(bool), @@ -327,22 +319,39 @@ impl IntoStatic for Data<'_> { Data::Object(object) => Data::Object(object.into_static()), Data::CidLink(cid) => Data::CidLink(cid.into_static()), Data::Blob(blob) => Data::Blob(blob.into_static()), + Data::InvalidNumber(float) => Data::InvalidNumber(float.into_static()), } } } /// Array of AT Protocol data values #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Array<'s>(pub Vec>); +pub struct Array(pub Vec>) +where + S: Bos + AsRef; -impl IntoStatic for Array<'_> { - type Output = Array<'static>; - fn into_static(self) -> Array<'static> { +impl + AsRef> Array { + /// Convert to an `Array` with a different backing type. + pub fn convert + AsRef + From>(self) -> Array { + Array(self.0.into_iter().map(|d| d.convert()).collect()) + } +} + +impl IntoStatic for Array +where + S: Bos + AsRef + IntoStatic, + ::Output: AsRef + Bos, +{ + type Output = Array<::Output>; + fn into_static(self) -> Array<::Output> { Array(self.0.into_static()) } } -impl<'s> Array<'s> { +impl Array +where + S: Bos + AsRef, +{ /// Get the number of elements in the array pub fn len(&self) -> usize { self.0.len() @@ -354,40 +363,26 @@ impl<'s> Array<'s> { } /// Get an element by index - pub fn get(&self, index: usize) -> Option<&Data<'s>> { + pub fn get(&self, index: usize) -> Option<&Data> { self.0.get(index) } /// Get a mutable reference to an element by index - pub fn get_mut(&mut self, index: usize) -> Option<&mut Data<'s>> { + pub fn get_mut(&mut self, index: usize) -> Option<&mut Data> { self.0.get_mut(index) } /// Get an iterator over the array elements - pub fn iter(&self) -> core::slice::Iter<'_, Data<'s>> { + pub fn iter(&self) -> core::slice::Iter<'_, Data> { self.0.iter() } - - /// Parse an array from JSON values - pub fn from_json(json: &'s Vec) -> Result { - let mut array = Vec::with_capacity(json.len()); - for item in json { - array.push(Data::from_json(item)?); - } - Ok(Self(array)) - } - /// Parse an array from IPLD values (CBOR) - pub fn from_cbor(cbor: &'s Vec) -> Result { - let mut array = Vec::with_capacity(cbor.len()); - for item in cbor { - array.push(Data::from_cbor(item)?); - } - Ok(Self(array)) - } } -impl<'s> core::ops::Index for Array<'s> { - type Output = Data<'s>; +impl core::ops::Index for Array +where + S: AsRef + Bos, +{ + type Output = Data; fn index(&self, index: usize) -> &Self::Output { &self.0[index] @@ -396,23 +391,39 @@ impl<'s> core::ops::Index for Array<'s> { /// Object/map of AT Protocol data values #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Object<'s>(pub BTreeMap>); +pub struct Object(pub BTreeMap>) +where + S: Bos + AsRef; + +impl + AsRef> Object { + /// Convert to an `Object` with a different backing type. + pub fn convert + AsRef + From>(self) -> Object { + Object(self.0.into_iter().map(|(k, v)| (k, v.convert())).collect()) + } +} -impl IntoStatic for Object<'_> { - type Output = Object<'static>; - fn into_static(self) -> Object<'static> { +impl IntoStatic for Object +where + S: Bos + AsRef + IntoStatic, + ::Output: AsRef + Bos, +{ + type Output = Object<::Output>; + fn into_static(self) -> Object<::Output> { Object(self.0.into_static()) } } -impl<'s> Object<'s> { +impl Object +where + S: AsRef + Bos, +{ /// Get a value by key - pub fn get(&self, key: &str) -> Option<&Data<'s>> { + pub fn get(&self, key: &str) -> Option<&Data> { self.0.get(key) } /// Get a mutable reference to a value by key - pub fn get_mut(&mut self, key: &str) -> Option<&mut Data<'s>> { + pub fn get_mut(&mut self, key: &str) -> Option<&mut Data> { self.0.get_mut(key) } @@ -432,12 +443,12 @@ impl<'s> Object<'s> { } /// Get an iterator over the key-value pairs - pub fn iter(&self) -> alloc::collections::btree_map::Iter<'_, SmolStr, Data<'s>> { + pub fn iter(&self) -> alloc::collections::btree_map::Iter<'_, SmolStr, Data> { self.0.iter() } /// Get an iterator over the keys - pub fn keys(&self) -> alloc::collections::btree_map::Keys<'_, SmolStr, Data<'s>> { + pub fn keys(&self) -> alloc::collections::btree_map::Keys<'_, SmolStr, Data> { self.0.keys() } @@ -449,140 +460,16 @@ impl<'s> Object<'s> { } /// Get an iterator over the values - pub fn values(&self) -> alloc::collections::btree_map::Values<'_, SmolStr, Data<'s>> { + pub fn values(&self) -> alloc::collections::btree_map::Values<'_, SmolStr, Data> { self.0.values() } - - /// Parse an object from a JSON map with type inference - /// - /// Uses key names to infer the appropriate AT Protocol types for values. - pub fn from_json( - json: &'s serde_json::Map, - ) -> Result, AtDataError> { - if let Some(type_field) = json.get("$type").and_then(|v| v.as_str()) { - if parsing::infer_from_type(type_field) == DataModelType::Blob { - if let Some(blob) = parsing::json_to_blob(json) { - return Ok(Data::Blob(blob)); - } - } - } - let mut map = BTreeMap::new(); - - for (key, value) in json { - if key == "$type" { - map.insert(key.to_smolstr(), Data::from_json(value)?); - } - match parsing::string_key_type_guess(key) { - DataModelType::Null if value.is_null() => { - map.insert(key.to_smolstr(), Data::Null); - } - DataModelType::Boolean if value.is_boolean() => { - map.insert(key.to_smolstr(), Data::Boolean(value.as_bool().unwrap())); - } - DataModelType::Integer if value.is_i64() => { - map.insert(key.to_smolstr(), Data::Integer(value.as_i64().unwrap())); - } - DataModelType::Bytes if value.is_string() => { - map.insert( - key.to_smolstr(), - parsing::decode_bytes(value.as_str().unwrap()), - ); - } - DataModelType::CidLink => { - if let Some(value) = value.as_object() { - if let Some(value) = value.get("$link").and_then(|v| v.as_str()) { - map.insert(key.to_smolstr(), Data::CidLink(Cid::Str(value.into()))); - } else { - map.insert(key.to_smolstr(), Object::from_json(value)?); - } - } else { - map.insert(key.to_smolstr(), Data::from_json(value)?); - } - } - DataModelType::Blob if value.is_object() => { - map.insert( - key.to_smolstr(), - Object::from_json(value.as_object().unwrap())?, - ); - } - DataModelType::Array if value.is_array() => { - map.insert( - key.to_smolstr(), - Data::Array(Array::from_json(value.as_array().unwrap())?), - ); - } - DataModelType::Object if value.is_object() => { - map.insert( - key.to_smolstr(), - Object::from_json(value.as_object().unwrap())?, - ); - } - DataModelType::String(string_type) if value.is_string() => { - parsing::insert_string(&mut map, key, value.as_str().unwrap(), string_type)?; - } - _ => { - map.insert(key.to_smolstr(), Data::from_json(value)?); - } - } - } - - Ok(Data::Object(Object(map))) - } - - /// Parse an object from IPLD (CBOR) with type inference - /// - /// Uses key names to infer the appropriate AT Protocol types for values. - pub fn from_cbor(cbor: &'s BTreeMap) -> Result, AtDataError> { - if let Some(Ipld::String(type_field)) = cbor.get("$type") { - if parsing::infer_from_type(type_field) == DataModelType::Blob { - if let Some(blob) = parsing::cbor_to_blob(cbor) { - return Ok(Data::Blob(blob)); - } - } - } - let mut map = BTreeMap::new(); - - for (key, value) in cbor { - if key == "$type" { - map.insert(key.to_smolstr(), Data::from_cbor(value)?); - } - match (parsing::string_key_type_guess(key), value) { - (DataModelType::Null, Ipld::Null) => { - map.insert(key.to_smolstr(), Data::Null); - } - (DataModelType::Boolean, Ipld::Bool(value)) => { - map.insert(key.to_smolstr(), Data::Boolean(*value)); - } - (DataModelType::Integer, Ipld::Integer(int)) => { - map.insert(key.to_smolstr(), Data::Integer(*int as i64)); - } - (DataModelType::Bytes, Ipld::Bytes(value)) => { - map.insert(key.to_smolstr(), Data::Bytes(Bytes::copy_from_slice(value))); - } - (DataModelType::Blob, Ipld::Map(value)) => { - map.insert(key.to_smolstr(), Object::from_cbor(value)?); - } - (DataModelType::Array, Ipld::List(value)) => { - map.insert(key.to_smolstr(), Data::Array(Array::from_cbor(value)?)); - } - (DataModelType::Object, Ipld::Map(value)) => { - map.insert(key.to_smolstr(), Object::from_cbor(value)?); - } - (DataModelType::String(string_type), Ipld::String(value)) => { - parsing::insert_string(&mut map, key, value, string_type)?; - } - _ => { - map.insert(key.to_smolstr(), Data::from_cbor(value)?); - } - } - } - - Ok(Data::Object(Object(map))) - } } -impl<'s> core::ops::Index<&str> for Object<'s> { - type Output = Data<'s>; +impl core::ops::Index<&str> for Object +where + S: AsRef + Bos, +{ + type Output = Data; fn index(&self, key: &str) -> &Self::Output { &self.0[key] @@ -834,9 +721,10 @@ impl IntoStatic for RawData<'_> { /// # Ok(()) /// # } /// ``` -pub fn from_data<'de, T>(data: &'de Data<'de>) -> Result +pub fn from_data<'de, T, S>(data: &'de Data) -> Result where T: serde::Deserialize<'de>, + S: Bos + AsRef + serde::Deserialize<'de> + core::convert::From<&'de str>, { T::deserialize(data) } @@ -844,11 +732,15 @@ where /// Deserialize a typed value from a `Data` value /// /// Takes ownership rather than borrows. Will allocate. -pub fn from_data_owned<'de, T>(data: Data<'_>) -> Result +pub fn from_data_owned<'de, T, S>( + data: Data, +) -> Result<::Output, DataDeserializerError> where - T: serde::Deserialize<'de>, + T: serde::Deserialize<'de> + IntoStatic, + S: Bos + AsRef + serde::Deserialize<'de> + IntoStatic + core::convert::From<&'de str>, + ::Output: Bos + AsRef, { - T::deserialize(data.into_static()) + T::deserialize(data).map(|d| d.into_static()) } /// Deserialize a typed value from a `serde_json::Value` @@ -976,9 +868,10 @@ where /// # Ok(()) /// # } /// ``` -pub fn to_data(value: &T) -> Result, convert::ConversionError> +pub fn to_data<'s, T, S>(value: &T) -> Result, convert::ConversionError> where T: serde::Serialize, + S: Bos + AsRef + serde::Serialize + From>, { let raw = to_raw_data(value).map_err(|e| convert::ConversionError::InvalidRawData { message: e.to_string(), @@ -987,7 +880,10 @@ where } /// Parse and traverse a path through nested Data structures -fn parse_and_traverse_path<'s>(data: &'s Data<'s>, path: &str) -> Option<&'s Data<'s>> { +fn parse_and_traverse_path<'s, S>(data: &'s Data, path: &str) -> Option<&'s Data> +where + S: AsRef + Bos, +{ let mut current = data; let mut path = path.trim_start_matches('.'); @@ -1049,10 +945,13 @@ fn parse_and_traverse_raw_path<'d>(data: &'d RawData<'d>, path: &str) -> Option< } /// Parse and traverse a path through nested Data structures -fn parse_and_traverse_path_mut<'d, 's>( - data: &'s mut Data<'d>, +fn parse_and_traverse_path_mut<'d, 's, S>( + data: &'s mut Data, path: &str, -) -> Option<&'s mut Data<'d>> { +) -> Option<&'s mut Data> +where + S: AsRef + Bos, +{ let mut current = data; let mut path = path.trim_start_matches('.'); @@ -1118,20 +1017,26 @@ fn parse_and_traverse_raw_path_mut<'a, 'd>( /// Result of a data query operation #[derive(Debug, Clone, PartialEq)] -pub enum QueryResult<'s> { +pub enum QueryResult<'s, S> +where + S: AsRef + Bos, +{ /// Single value expected and found - Single(&'s Data<'s>), + Single(&'s Data), /// Multiple values from wildcard or global recursion - Multiple(Vec>), + Multiple(Vec>), /// No matches found None, } -impl<'s> QueryResult<'s> { +impl<'s, S> QueryResult<'s, S> +where + S: AsRef + Bos, +{ /// Get single value if available - pub fn single(&self) -> Option<&'s Data<'s>> { + pub fn single(&self) -> Option<&'s Data> { match self { QueryResult::Single(data) => Some(data), _ => None, @@ -1139,7 +1044,7 @@ impl<'s> QueryResult<'s> { } /// Get multiple matches if available - pub fn multiple(&self) -> Option<&[QueryMatch<'s>]> { + pub fn multiple(&self) -> Option<&[QueryMatch<'s, S>]> { match self { QueryResult::Multiple(matches) => Some(matches), _ => None, @@ -1147,7 +1052,7 @@ impl<'s> QueryResult<'s> { } /// Get first value regardless of result type - pub fn first(&self) -> Option<&'s Data<'s>> { + pub fn first(&self) -> Option<&'s Data> { match self { QueryResult::Single(data) => Some(data), QueryResult::Multiple(matches) => matches.first().and_then(|m| m.value), @@ -1161,7 +1066,7 @@ impl<'s> QueryResult<'s> { } /// Get all values as an iterator (flattens single/multiple) - pub fn values(&self) -> impl Iterator> { + pub fn values(&self) -> impl Iterator> { match self { QueryResult::Single(data) => vec![*data].into_iter(), QueryResult::Multiple(matches) => matches @@ -1176,11 +1081,14 @@ impl<'s> QueryResult<'s> { /// A single match from a query operation #[derive(Debug, Clone, PartialEq)] -pub struct QueryMatch<'s> { +pub struct QueryMatch<'s, S> +where + S: AsRef + Bos, +{ /// Path where this value was found (e.g., "actors\[0\].handle") pub path: SmolStr, /// The value (None if field was missing during wildcard iteration) - pub value: Option<&'s Data<'s>>, + pub value: Option<&'s Data>, } /// Query pattern segment @@ -1256,7 +1164,10 @@ fn parse_query_pattern(pattern: &str) -> Vec { } /// Execute a query on data -fn query_data<'s>(data: &'s Data<'s>, pattern: &str) -> QueryResult<'s> { +fn query_data<'s, S>(data: &'s Data, pattern: &str) -> QueryResult<'s, S> +where + S: AsRef + Bos, +{ let segments = parse_query_pattern(pattern); if segments.is_empty() { return QueryResult::None; @@ -1294,7 +1205,13 @@ fn query_data<'s>(data: &'s Data<'s>, pattern: &str) -> QueryResult<'s> { } /// Execute a single segment on current results -fn execute_segment<'s>(current: &[QueryMatch<'s>], segment: &QuerySegment) -> Vec> { +fn execute_segment<'s, S>( + current: &[QueryMatch<'s, S>], + segment: &QuerySegment, +) -> Vec> +where + S: AsRef + Bos, +{ let mut next = Vec::new(); for qm in current { @@ -1351,11 +1268,14 @@ fn execute_segment<'s>(current: &[QueryMatch<'s>], segment: &QuerySegment) -> Ve } /// Recursively find first occurrence of a field (scoped recursion) -fn find_field_recursive<'s>( - data: &'s Data<'s>, +fn find_field_recursive<'s, S>( + data: &'s Data, field: &str, base_path: &SmolStr, -) -> Option> { +) -> Option> +where + S: AsRef + Bos, +{ match data { Data::Object(obj) => { // Check direct children first @@ -1390,12 +1310,14 @@ fn find_field_recursive<'s>( } /// Recursively find all occurrences of a field (global recursion) -fn find_all_fields_recursive<'s>( - data: &'s Data<'s>, +fn find_all_fields_recursive<'s, S>( + data: &'s Data, field: &str, base_path: &SmolStr, - results: &mut Vec>, -) { + results: &mut Vec>, +) where + S: AsRef + Bos, +{ match data { Data::Object(obj) => { // Check direct children diff --git a/crates/jacquard-common/src/types/value/convert.rs b/crates/jacquard-common/src/types/value/convert.rs index b49578b8..bcde5afe 100644 --- a/crates/jacquard-common/src/types/value/convert.rs +++ b/crates/jacquard-common/src/types/value/convert.rs @@ -5,8 +5,7 @@ use crate::types::{ string::AtprotoStr, value::{Array, Data, Object, RawData, parsing}, }; -use crate::{CowStr, IntoStatic}; -use alloc::borrow::ToOwned; +use crate::{Bos, CowStr}; use alloc::boxed::Box; use alloc::collections::BTreeMap; use alloc::string::String; @@ -14,6 +13,9 @@ use alloc::string::ToString; use alloc::vec::Vec; use bytes::Bytes; use core::any::TypeId; +use core::convert::Infallible; +use core::str::FromStr; +use serde::Serialize; use smol_str::SmolStr; use std::borrow::Cow; @@ -45,10 +47,13 @@ pub enum ConversionError { }, } -impl TryFrom> for () { +impl TryFrom> for () +where + S: AsRef + Bos, +{ type Error = ConversionError; - fn try_from(ipld: Data) -> Result { + fn try_from(ipld: Data) -> Result { match ipld { Data::Null => Ok(()), _ => Err(ConversionError::WrongAtprotoType { @@ -61,10 +66,13 @@ impl TryFrom> for () { macro_rules! derive_try_from_atproto_option { ($enum:ident, $ty:ty) => { - impl TryFrom> for Option<$ty> { + impl TryFrom> for Option<$ty> + where + S: AsRef + Bos, + { type Error = ConversionError; - fn try_from(ipld: Data<'static>) -> Result { + fn try_from(ipld: Data) -> Result { match ipld { Data::Null => Ok(None), Data::$enum(value) => Ok(Some(value.try_into().map_err(|_| { @@ -85,10 +93,13 @@ macro_rules! derive_try_from_atproto_option { macro_rules! derive_try_from_atproto { ($enum:ident, $ty:ty) => { - impl TryFrom> for $ty { + impl TryFrom> for $ty + where + S: AsRef + Bos, + { type Error = ConversionError; - fn try_from(ipld: Data<'static>) -> Result { + fn try_from(ipld: Data) -> Result { match ipld { Data::$enum(value) => { Ok(value @@ -111,7 +122,10 @@ macro_rules! derive_try_from_atproto { macro_rules! derive_into_atproto_prim { ($enum:ident, $ty:ty, $fn:ident) => { - impl<'s> From<$ty> for Data<'s> { + impl From<$ty> for Data + where + S: AsRef + Bos, + { fn from(t: $ty) -> Self { Data::$enum(t.$fn() as _) } @@ -121,7 +135,9 @@ macro_rules! derive_into_atproto_prim { macro_rules! derive_into_atproto { ($enum:ident, $ty:ty, $($fn:ident),*) => { - impl<'s> From<$ty> for Data<'s> { + impl From<$ty> for Data + where + S: AsRef + Bos, { fn from(t: $ty) -> Self { Data::$enum(t$(.$fn())*) } @@ -129,46 +145,71 @@ macro_rules! derive_into_atproto { }; } -impl From for Data<'_> { +impl From for Data +where + S: AsRef + Bos + From, +{ fn from(t: String) -> Self { - Data::String(AtprotoStr::new(CowStr::from(t))) + Data::String(AtprotoStr::new(t.into())) } } -impl<'a> From<&'a str> for Data<'a> { +impl<'a, S> From<&'a str> for Data +where + S: AsRef + Bos + From<&'a str>, +{ fn from(t: &'a str) -> Self { - Data::String(AtprotoStr::new(CowStr::Borrowed(t))) + Data::String(AtprotoStr::new(S::from(t))) } } -impl From<&[u8]> for Data<'_> { +impl From<&[u8]> for Data +where + S: AsRef + Bos, +{ fn from(t: &[u8]) -> Self { Data::Bytes(Bytes::copy_from_slice(t)) } } -impl<'s> From> for Data<'s> { +impl<'s, S> From> for Data +where + S: AsRef + Bos + FromStr, +{ fn from(t: CowStr<'s>) -> Self { - Data::String(AtprotoStr::new(t)) + Data::String(AtprotoStr::new( + S::from_str(t.as_ref()).unwrap_or_else(|_| unreachable!()), + )) } } -impl From for Data<'_> { +impl From for Data +where + S: AsRef + Bos + From, +{ fn from(t: SmolStr) -> Self { - Data::String(AtprotoStr::new(CowStr::Owned(t))) + Data::String(AtprotoStr::new(S::from(t))) } } -impl<'s> From> for Data<'s> { +impl<'s, S> From> for Data +where + S: AsRef + Bos + FromStr, +{ fn from(t: Cow<'s, str>) -> Self { - Data::String(AtprotoStr::new(CowStr::from(t))) + Data::String(AtprotoStr::new( + S::from_str(t.as_ref()).unwrap_or_else(|_| unreachable!()), + )) } } -impl<'s> TryFrom> for Option { +impl TryFrom> for Option +where + S: AsRef + Bos + Clone + Serialize, +{ type Error = ConversionError; - fn try_from(ipld: Data<'s>) -> Result { + fn try_from(ipld: Data) -> Result { match ipld { Data::Null => Ok(None), Data::String(value) => Ok(Some(value.try_into().map_err(|_| { @@ -185,10 +226,13 @@ impl<'s> TryFrom> for Option { } } -impl<'s> TryFrom> for String { +impl TryFrom> for String +where + S: AsRef + Bos + TryFrom + Clone + Serialize, +{ type Error = ConversionError; - fn try_from(ipld: Data<'s>) -> Result { + fn try_from(ipld: Data) -> Result { match ipld { Data::String(value) => { Ok(value @@ -207,14 +251,20 @@ impl<'s> TryFrom> for String { } } -impl<'s> From>> for Array<'s> { - fn from(value: Vec>) -> Self { +impl From>> for Array +where + S: AsRef + Bos, +{ + fn from(value: Vec>) -> Self { Array(value) } } -impl<'s> From>> for Object<'s> { - fn from(value: BTreeMap>) -> Self { +impl From>> for Object +where + S: AsRef + Bos, +{ + fn from(value: BTreeMap>) -> Self { Object(value) } } @@ -233,11 +283,39 @@ derive_into_atproto_prim!(Integer, u64, clone); derive_into_atproto_prim!(Integer, usize, clone); derive_into_atproto!(Bytes, Box<[u8]>, into); derive_into_atproto!(Bytes, Vec, into); -derive_into_atproto!(Array, Array<'s>, into); -derive_into_atproto!(Object, Object<'s>, to_owned); +derive_into_atproto!(Array, Array,); +derive_into_atproto!(Object, Object,); -derive_into_atproto!(CidLink, Cid>, clone); -derive_into_atproto!(CidLink, &Cid>, to_owned); +derive_into_atproto!(CidLink, Cid,); +derive_into_atproto!(Blob, crate::types::blob::Blob,); +derive_into_atproto!(String, AtprotoStr,); + +impl From> for Data +where + S: AsRef + Bos, +{ + fn from(t: CidLink) -> Self { + Data::CidLink(t.0) + } +} + +impl From> for Data +where + S: AsRef + Bos, +{ + fn from(t: crate::types::blob::BlobRef) -> Self { + Data::Blob(t.into()) + } +} + +impl From<&Cid> for Data +where + S: AsRef + Bos + Clone, +{ + fn from(t: &Cid) -> Self { + Data::CidLink(t.clone()) + } +} derive_try_from_atproto!(Boolean, bool); derive_try_from_atproto!(Integer, i8); @@ -253,8 +331,10 @@ derive_try_from_atproto!(Integer, u64); derive_try_from_atproto!(Integer, u128); derive_try_from_atproto!(Integer, usize); derive_try_from_atproto!(Bytes, Vec); -derive_try_from_atproto!(Object, Object<'static>); -derive_try_from_atproto!(CidLink, Cid>); +derive_try_from_atproto!(Array, Array); +derive_try_from_atproto!(Object, Object); +derive_try_from_atproto!(CidLink, Cid); +derive_try_from_atproto!(Blob, crate::types::blob::Blob); derive_try_from_atproto_option!(Boolean, bool); derive_try_from_atproto_option!(Integer, i8); @@ -271,12 +351,16 @@ derive_try_from_atproto_option!(Integer, u128); derive_try_from_atproto_option!(Integer, usize); derive_try_from_atproto_option!(Bytes, Vec); -derive_try_from_atproto_option!(Array, Array<'static>); -derive_try_from_atproto_option!(Object, Object<'static>); -derive_try_from_atproto_option!(CidLink, Cid>); +derive_try_from_atproto_option!(Array, Array); +derive_try_from_atproto_option!(Object, Object); +derive_try_from_atproto_option!(CidLink, Cid); +derive_try_from_atproto_option!(Blob, crate::types::blob::Blob); /// Convert RawData to validated Data with type inference -impl<'s> TryFrom> for Data<'s> { +impl<'s, S> TryFrom> for Data +where + S: Bos + AsRef + From>, +{ type Error = ConversionError; fn try_from(raw: RawData<'s>) -> Result { @@ -290,11 +374,10 @@ impl<'s> TryFrom> for Data<'s> { } RawData::String(s) => { // Apply string type inference - // Need to convert to owned because parse_string borrows from its input - Ok(Data::String(parsing::parse_string(&s).into_static())) + Ok(Data::String(parsing::parse_string(S::from(s)))) } RawData::Bytes(b) => Ok(Data::Bytes(b)), - RawData::CidLink(cid) => Ok(Data::CidLink(cid)), + RawData::CidLink(cid) => Ok(Data::CidLink(cid.convert())), RawData::Array(arr) => { let mut validated = Vec::with_capacity(arr.len()); for item in arr { @@ -323,8 +406,8 @@ impl<'s> TryFrom> for Data<'s> { } }; return Ok(Data::Blob(crate::types::blob::Blob { - r#ref: CidLink(cid.clone()), - mime_type: crate::types::blob::MimeType::from(mime.clone()), + r#ref: CidLink(cid.clone().convert()), + mime_type: crate::types::blob::MimeType::new(S::from(mime.clone())), size: size_val, })); } @@ -334,12 +417,12 @@ impl<'s> TryFrom> for Data<'s> { // Regular object - convert recursively with type inference based on keys let mut validated = BTreeMap::new(); for (key, value) in map { - let data_value: Data = value.try_into()?; + let data_value: Data = value.try_into()?; validated.insert(key, data_value); } Ok(Data::Object(Object(validated))) } - RawData::Blob(blob) => Ok(Data::Blob(blob)), + RawData::Blob(blob) => Ok(Data::Blob(blob.convert())), RawData::InvalidBlob(_) => Err(ConversionError::InvalidRawData { message: "invalid blob structure".to_string(), }), diff --git a/crates/jacquard-common/src/types/value/parsing.rs b/crates/jacquard-common/src/types/value/parsing.rs index 6b9b2f0a..6d007c9e 100644 --- a/crates/jacquard-common/src/types/value/parsing.rs +++ b/crates/jacquard-common/src/types/value/parsing.rs @@ -1,5 +1,10 @@ -use crate::cowstr::ToCowStr; +use crate::Bos; use crate::deps::fluent_uri::Uri; +use crate::types::aturi::validate_and_index; +use crate::types::cid::IpldCid; +use crate::types::did::validate_did; +use crate::types::handle::validate_handle; +use crate::types::nsid::validate_nsid; use crate::{ IntoStatic, types::{ @@ -21,65 +26,64 @@ use ipld_core::ipld::Ipld; use smol_str::{SmolStr, ToSmolStr}; /// Insert a string into an at:// `Data<'_>` map, inferring its type. -pub fn insert_string<'s>( - map: &mut BTreeMap>, +pub fn insert_string<'s, S>( + map: &mut BTreeMap>, key: &'s str, - value: &'s str, + value: S, string_type: LexiconStringType, -) -> Result<(), AtDataError> { +) -> Result<(), AtDataError> +where + S: AsRef + Bos, +{ match string_type { LexiconStringType::Datetime => { - if let Ok(datetime) = Datetime::from_str(value) { + if let Ok(datetime) = Datetime::from_str(value.as_ref()) { map.insert( key.to_smolstr(), Data::String(AtprotoStr::Datetime(datetime)), ); } else { - map.insert( - key.to_smolstr(), - Data::String(AtprotoStr::String(value.into())), - ); + map.insert(key.to_smolstr(), Data::String(AtprotoStr::String(value))); } } LexiconStringType::AtUri => { - if let Ok(value) = AtUri::new(value.to_cowstr()) { + if validate_and_index(value.as_ref()).is_ok() { // AtprotoStr::AtUri stores AtUri<'static>; convert to owned. map.insert( key.to_smolstr(), - Data::String(AtprotoStr::AtUri(value.into_static())), + Data::String(AtprotoStr::AtUri(unsafe { AtUri::unchecked(value) })), ); } else { - map.insert( - key.to_smolstr(), - Data::String(AtprotoStr::String(value.into())), - ); + map.insert(key.to_smolstr(), Data::String(AtprotoStr::String(value))); } } LexiconStringType::Did => { - if let Ok(value) = Did::new(value.to_cowstr()) { - map.insert(key.to_smolstr(), Data::String(AtprotoStr::Did(value))); - } else { + if validate_did(value.as_ref()).is_ok() { map.insert( key.to_smolstr(), - Data::String(AtprotoStr::String(value.into())), + Data::String(AtprotoStr::Did(unsafe { Did::unchecked(value) })), ); + } else { + map.insert(key.to_smolstr(), Data::String(AtprotoStr::String(value))); } } LexiconStringType::Handle => { - if let Ok(value) = Handle::new(value.to_cowstr()) { - map.insert(key.to_smolstr(), Data::String(AtprotoStr::Handle(value))); - } else { + if validate_handle(value.as_ref()).is_ok() { map.insert( key.to_smolstr(), - Data::String(AtprotoStr::String(value.into())), + Data::String(AtprotoStr::Handle(unsafe { Handle::unchecked(value) })), ); + } else { + map.insert(key.to_smolstr(), Data::String(AtprotoStr::String(value))); } } LexiconStringType::AtIdentifier => { - if let Ok(value) = AtIdentifier::new(value.to_cowstr()) { + if validate_handle(value.as_ref()).is_ok() || validate_did(value.as_ref()).is_ok() { map.insert( key.to_smolstr(), - Data::String(AtprotoStr::AtIdentifier(value)), + Data::String(AtprotoStr::AtIdentifier(unsafe { + AtIdentifier::unchecked(value) + })), ); } else { map.insert( @@ -89,18 +93,23 @@ pub fn insert_string<'s>( } } LexiconStringType::Nsid => { - if let Ok(value) = Nsid::new(value.to_cowstr()) { - map.insert(key.to_smolstr(), Data::String(AtprotoStr::Nsid(value))); - } else { + if validate_nsid(value.as_ref()).is_ok() { map.insert( key.to_smolstr(), - Data::String(AtprotoStr::String(value.into())), + Data::String(AtprotoStr::Nsid(unsafe { Nsid::unchecked(value) })), ); + } else { + map.insert(key.to_smolstr(), Data::String(AtprotoStr::String(value))); } } LexiconStringType::Cid => { - if let Ok(value) = Cid::>::new_owned(value.as_bytes()) { - map.insert(key.to_smolstr(), Data::String(AtprotoStr::Cid(value))); + let s: &str = value.as_ref(); + // CID: try to parse as IPLD first, otherwise wrap as string CID. + if IpldCid::try_from(s).is_ok() || s.starts_with("bafy") { + map.insert( + key.to_smolstr(), + Data::String(AtprotoStr::Cid(unsafe { Cid::unchecked_str(value) })), + ); } else { map.insert( key.to_smolstr(), @@ -109,33 +118,27 @@ pub fn insert_string<'s>( } } LexiconStringType::Language => { - if let Ok(value) = Language::new(value) { + if let Ok(value) = Language::new(value.as_ref()) { map.insert(key.to_smolstr(), Data::String(AtprotoStr::Language(value))); } else { - map.insert( - key.to_smolstr(), - Data::String(AtprotoStr::String(value.into())), - ); + map.insert(key.to_smolstr(), Data::String(AtprotoStr::String(value))); } } LexiconStringType::Tid => { - if let Ok(value) = Tid::new(value) { + if let Ok(value) = Tid::new(value.as_ref()) { map.insert(key.to_smolstr(), Data::String(AtprotoStr::Tid(value))); } else { - map.insert( - key.to_smolstr(), - Data::String(AtprotoStr::String(value.into())), - ); + map.insert(key.to_smolstr(), Data::String(AtprotoStr::String(value))); } } LexiconStringType::RecordKey => { // Validate the rkey without shadowing the original `value: &'s str`. - if Rkey::new(value).is_ok() { + if Rkey::new(value.as_ref()).is_ok() { map.insert( key.to_smolstr(), // Rkey already validated above; borrow the original &'s str directly. Data::String(AtprotoStr::RecordKey( - RecordKey::any(CowStr::Borrowed(value)).expect("Rkey validation passed"), + RecordKey::any(value).expect("Rkey validation passed"), )), ); } else { @@ -147,8 +150,23 @@ pub fn insert_string<'s>( } LexiconStringType::Uri(_) => { // AtprotoStr::Uri stores UriValue<'static>, so we must produce an owned value. - if let Ok(uri) = UriValue::new_owned(value) { - map.insert(key.to_smolstr(), Data::String(AtprotoStr::Uri(uri))); + if let Ok(uri) = UriValue::new(value.as_ref()) { + match uri { + UriValue::Any(_) => { + map.insert( + key.to_smolstr(), + Data::String(AtprotoStr::String(value.into())), + ); + } + _ => { + map.insert( + key.to_smolstr(), + Data::String(AtprotoStr::Uri( + UriValue::new(value).expect("already verified"), + )), + ); + } + } } else { map.insert( key.to_smolstr(), @@ -164,43 +182,46 @@ pub fn insert_string<'s>( } /// smarter parsing to avoid trying as many posibilities. -pub fn parse_string<'s>(string: &'s str) -> AtprotoStr> { - if string.len() < 2048 && string.starts_with("did:") { - if let Ok(did) = Did::new(string.to_cowstr()) { - return AtprotoStr::Did(did); +pub fn parse_string(string: S) -> AtprotoStr +where + S: Bos + AsRef, +{ + let s = string.as_ref(); + if s.len() < 2048 && s.starts_with("did:") { + if validate_did(s).is_ok() { + return AtprotoStr::Did(unsafe { Did::unchecked(string) }); } - } else if string.starts_with("20") && string.ends_with("Z") { + } else if s.starts_with("20") && s.ends_with("Z") { // probably a date (for the next 75 years) - if let Ok(datetime) = Datetime::from_str(string) { + if let Ok(datetime) = Datetime::from_str(s) { return AtprotoStr::Datetime(datetime); } - } else if string.starts_with("at://") { - // AtprotoStr::AtUri stores AtUri<'static>; convert to owned. - if let Ok(uri) = AtUri::new(string.to_cowstr()) { - return AtprotoStr::AtUri(uri); + } else if s.starts_with("at://") { + if crate::types::aturi::validate_and_index(s).is_ok() { + return AtprotoStr::AtUri(unsafe { AtUri::unchecked(string) }); } - } else if string.starts_with("https://") { - if let Ok(uri) = Uri::parse(string) { + } else if s.starts_with("https://") { + if let Ok(uri) = Uri::parse(s) { return AtprotoStr::Uri(UriValue::Https(uri.to_owned())); } - } else if string.starts_with("wss://") { - if let Ok(uri) = Uri::parse(string) { + } else if s.starts_with("wss://") { + if let Ok(uri) = Uri::parse(s) { return AtprotoStr::Uri(UriValue::Wss(uri.to_owned())); } - } else if string.starts_with("ipfs://") { - // URI variant must be 'static; convert to an owned CID. - return AtprotoStr::Uri(UriValue::Cid( - Cid::>::new_owned(string.as_bytes()) - .unwrap_or_else(|_| Cid::cow_str(CowStr::Owned(string.to_smolstr()))), - )); - } else if string.contains('.') && !string.contains([' ', '\n']) { + } else if s.starts_with("ipfs://") { + let s: &str = string.as_ref(); + // CID: try to parse as IPLD first, otherwise wrap as string CID. + if IpldCid::try_from(s).is_ok() || s.starts_with("bafy") { + return AtprotoStr::Uri(UriValue::Cid(unsafe { Cid::unchecked_str(string) })); + } + } else if s.contains('.') && !s.contains([' ', '\n']) { // Dotted strings without a scheme could be handles, NSIDs, or URIs. // Use TLD lookup and camelCase heuristic to disambiguate. // - Handles: domain order with TLD at the end (e.g., "example.com") // - NSIDs: reverse domain order with TLD at the start (e.g., "com.example.service") // - Tiebreaker: camelCase in the last segment indicates NSID (e.g., "getRecord") - let first_segment = string.split('.').next().unwrap_or(""); - let last_segment = string.rsplit('.').next().unwrap_or(""); + let first_segment = s.split('.').next().unwrap_or(""); + let last_segment = s.rsplit('.').next().unwrap_or(""); let first_is_tld = crate::tld::is_tld(first_segment); let last_is_tld = crate::tld::is_tld(last_segment); @@ -208,47 +229,50 @@ pub fn parse_string<'s>(string: &'s str) -> AtprotoStr> { // First segment is a known TLD → reverse domain order → try NSID first. if first_is_tld { - if let Ok(nsid) = Nsid::new(string.to_cowstr()) { - return AtprotoStr::Nsid(nsid); + if validate_nsid(s).is_ok() { + return AtprotoStr::Nsid(unsafe { Nsid::unchecked(string) }); } } // Last segment is a known TLD and first is not → normal domain order → handle. if last_is_tld && !first_is_tld { - if let Ok(handle) = AtIdentifier::new(string.to_cowstr()) { - return AtprotoStr::AtIdentifier(handle); + if validate_handle(s.as_ref()).is_ok() || validate_did(s.as_ref()).is_ok() { + return AtprotoStr::AtIdentifier(unsafe { AtIdentifier::unchecked(string) }); } } // camelCase in last segment → NSID (e.g., "com.atproto.repo.getRecord"). if has_upper_last_segment { - if let Ok(nsid) = Nsid::new(string.to_cowstr()) { - return AtprotoStr::Nsid(nsid); + if validate_nsid(s).is_ok() { + return AtprotoStr::Nsid(unsafe { Nsid::unchecked(string) }); } } // Fallback: try both, preferring handle. - if let Ok(handle) = AtIdentifier::new(string.to_cowstr()) { - return AtprotoStr::AtIdentifier(handle); - } else if let Ok(nsid) = Nsid::new(string.to_cowstr()) { - return AtprotoStr::Nsid(nsid); - } else if string.contains("://") && Uri::<&str>::parse(string).is_ok() { + if validate_handle(s.as_ref()).is_ok() { + return AtprotoStr::AtIdentifier(AtIdentifier::Handle(unsafe { + Handle::unchecked(string) + })); + } else if validate_nsid(s).is_ok() { + return AtprotoStr::Nsid(unsafe { Nsid::unchecked(string) }); + } else if s.contains("://") && Uri::<&str>::parse(s).is_ok() { // AtprotoStr::Uri stores UriValue<'static>; convert to owned. - return AtprotoStr::Uri(UriValue::Any(CowStr::Owned(string.to_smolstr()))); + return AtprotoStr::Uri(UriValue::Any(string)); } - } else if string.len() == 13 { - if let Ok(tid) = Tid::new(string) { + } else if s.len() == 13 { + drop(s); + if let Ok(tid) = Tid::new(string.as_ref()) { return AtprotoStr::Tid(tid); } - } else if !string.contains([' ', '\n']) && string.len() > 20 { - // CID: must be longer than typical short strings to avoid false positives - // Most CIDs are 46+ chars (base32 encoded), minimum realistic is around 30 - if let Ok(cid) = Cid::>::new_owned(string.as_bytes()) { - return AtprotoStr::Cid(cid); + } else if !s.contains([' ', '\n']) && s.len() > 20 { + // CID: try to parse as IPLD first, otherwise wrap as string CID. + if IpldCid::try_from(s).is_ok() || s.starts_with("bafy") { + return AtprotoStr::Cid(unsafe { Cid::unchecked_str(string) }); } } + drop(s); - AtprotoStr::String(string.into()) + AtprotoStr::String(string) } /// First-level guess at what we should parse the corresponding value as @@ -301,7 +325,7 @@ pub fn cbor_to_blob<'b>(blob: &'b BTreeMap) -> Option>::ipld(*value), - mime_type: MimeType::new_cow(CowStr::Borrowed(mime_type)), + mime_type: MimeType::new(CowStr::Borrowed(mime_type)), size: size as usize, }); } @@ -309,7 +333,7 @@ pub fn cbor_to_blob<'b>(blob: &'b BTreeMap) -> Option( if let (Some(mime_type), Some(size)) = (mime_type, size) { return Some(Blob { r#ref: CidLink::cow_str(CowStr::Borrowed(value)), - mime_type: MimeType::new_cow(CowStr::Borrowed(mime_type)), + mime_type: MimeType::new(CowStr::Borrowed(mime_type)), size: size as usize, }); } @@ -342,7 +366,7 @@ pub fn json_to_blob<'b>( if let Some(mime_type) = mime_type { return Some(Blob { r#ref: CidLink::cow_str(CowStr::Borrowed(value)), - mime_type: MimeType::new_cow(CowStr::Borrowed(mime_type)), + mime_type: MimeType::new(CowStr::Borrowed(mime_type)), size: 0, }); } @@ -360,18 +384,21 @@ pub fn infer_from_type(type_field: &str) -> DataModelType { } /// decode a base64 byte string into atproto data -pub fn decode_bytes<'s>(bytes: &str) -> Data<'s> { +pub fn decode_bytes(bytes: S) -> Data +where + S: Bos + AsRef, +{ // First one should just work. rest are insurance. - if let Ok(bytes) = BASE64_STANDARD.decode(bytes) { + if let Ok(bytes) = BASE64_STANDARD.decode(bytes.as_ref().as_bytes()) { Data::Bytes(Bytes::from_owner(bytes)) - } else if let Ok(bytes) = BASE64_STANDARD_NO_PAD.decode(bytes) { + } else if let Ok(bytes) = BASE64_STANDARD_NO_PAD.decode(bytes.as_ref().as_bytes()) { Data::Bytes(Bytes::from_owner(bytes)) - } else if let Ok(bytes) = BASE64_URL_SAFE.decode(bytes) { + } else if let Ok(bytes) = BASE64_URL_SAFE.decode(bytes.as_ref().as_bytes()) { Data::Bytes(Bytes::from_owner(bytes)) - } else if let Ok(bytes) = BASE64_URL_SAFE_NO_PAD.decode(bytes) { + } else if let Ok(bytes) = BASE64_URL_SAFE_NO_PAD.decode(bytes.as_ref().as_bytes()) { Data::Bytes(Bytes::from_owner(bytes)) } else { - Data::String(AtprotoStr::String(CowStr::Borrowed(bytes).into_static())) + Data::String(AtprotoStr::String(S::from(bytes))) } } diff --git a/crates/jacquard-common/src/types/value/serde_impl.rs b/crates/jacquard-common/src/types/value/serde_impl.rs index 1ac05e3d..747220f9 100644 --- a/crates/jacquard-common/src/types/value/serde_impl.rs +++ b/crates/jacquard-common/src/types/value/serde_impl.rs @@ -1,5 +1,5 @@ +use crate::Bos; use crate::types::cid::IpldCid; -use alloc::borrow::ToOwned; use alloc::boxed::Box; use alloc::collections::BTreeMap; use alloc::string::String; @@ -8,7 +8,9 @@ use alloc::vec::Vec; use base64::{Engine, prelude::BASE64_STANDARD}; use bytes::Bytes; use core::fmt; +use core::marker::PhantomData; use core::str::FromStr; +use serde::de::value::StrDeserializer; use serde::{Deserialize, Deserializer, Serialize, Serializer, de::VariantAccess}; use smol_str::{SmolStr, ToSmolStr}; @@ -28,7 +30,10 @@ use crate::{ }, }; -impl Serialize for Data<'_> { +impl Serialize for Data +where + D: Bos + AsRef + Serialize, +{ fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -65,13 +70,20 @@ impl Serialize for Data<'_> { Data::Array(arr) => arr.serialize(serializer), Data::Object(obj) => obj.serialize(serializer), Data::Blob(blob) => blob.serialize(serializer), + Data::InvalidNumber(float) => { + if let Ok(f) = float.as_ref().parse::() { + f.serialize(serializer) + } else { + float.serialize(serializer) + } + } } } } -impl<'de, 'a> Deserialize<'de> for Data<'a> +impl<'de, S> Deserialize<'de> for Data where - 'de: 'a, + S: Bos + AsRef + Deserialize<'de>, { /// Currently only works for self-describing formats /// Thankfully the supported atproto data formats are both self-describing (json and dag-cbor). @@ -80,14 +92,17 @@ where where D: Deserializer<'de>, { - deserializer.deserialize_any(DataVisitor) + deserializer.deserialize_any(DataVisitor(PhantomData)) } } -struct DataVisitor; +struct DataVisitor(PhantomData); -impl<'de: 'v, 'v> serde::de::Visitor<'v> for DataVisitor { - type Value = Data<'v>; +impl<'de: 'v, 'v, S> serde::de::Visitor<'v> for DataVisitor +where + S: Bos + AsRef + Deserialize<'v>, +{ + type Value = Data; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("any valid AT Protocol data value") @@ -135,13 +150,13 @@ impl<'de: 'v, 'v> serde::de::Visitor<'v> for DataVisitor { Ok(Data::Integer((v % (i64::MAX as u64)) as i64)) } - fn visit_f64(self, _v: f64) -> Result + fn visit_f64(self, v: f64) -> Result where E: serde::de::Error, { - Ok(Data::String(AtprotoStr::String( - CowStr::Owned(_v.to_smolstr()).into_static(), - ))) + let v = v.to_smolstr(); + let s = StrDeserializer::new(v.as_str()); + Ok(Data::InvalidNumber(S::deserialize(s)?)) // Err(E::custom( // "floating point numbers not allowed in AT protocol data", // )) @@ -151,25 +166,24 @@ impl<'de: 'v, 'v> serde::de::Visitor<'v> for DataVisitor { where E: serde::de::Error, { - Ok(Data::String(AtprotoStr::String( - CowStr::Borrowed(v).into_static(), - ))) + let s = StrDeserializer::new(v); + Ok(Data::String(AtprotoStr::String(S::deserialize(s)?))) } fn visit_borrowed_str(self, v: &'v str) -> Result where E: serde::de::Error, { - // Don't infer type here - just store as plain string - // Type inference happens in apply_type_inference based on field names - Ok(Data::String(AtprotoStr::String(v.into()))) + let s = StrDeserializer::new(v); + Ok(Data::String(AtprotoStr::String(S::deserialize(s)?))) } fn visit_string(self, v: String) -> Result where E: serde::de::Error, { - Ok(Data::String(AtprotoStr::String(v.into()))) + let s = StrDeserializer::new(&v); + Ok(Data::String(AtprotoStr::String(S::deserialize(s)?))) } fn visit_bytes(self, v: &[u8]) -> Result @@ -200,7 +214,7 @@ impl<'de: 'v, 'v> serde::de::Visitor<'v> for DataVisitor { match data.variant::() { Ok((key, value)) => { let mut map = BTreeMap::new(); - if let Ok(variant) = value.newtype_variant::() { + if let Ok(variant) = value.newtype_variant::>() { map.insert(key, variant); } Ok(Data::Object(Object(map))) @@ -224,7 +238,7 @@ impl<'de: 'v, 'v> serde::de::Visitor<'v> for DataVisitor { where D: Deserializer<'v>, { - deserializer.deserialize_any(CidAwareVisitor) + deserializer.deserialize_any(CidAwareVisitor(PhantomData)) } fn visit_map(self, mut map: A) -> Result @@ -234,28 +248,30 @@ impl<'de: 'v, 'v> serde::de::Visitor<'v> for DataVisitor { use serde::de::Error; // Peek at first key to check for special single-key patterns - let mut temp_map: BTreeMap> = BTreeMap::new(); + let mut temp_map: BTreeMap> = BTreeMap::new(); while let Some(key) = map.next_key::()? { // Check for special patterns on single-key maps if temp_map.is_empty() { if key.as_str() == "$link" { // {"$link": "cid_string"} pattern - let cid_str: String = map.next_value()?; + let cid_str: S = map.next_value()?; // Check if there are more keys if let Some(next_key) = map.next_key::()? { // More keys, treat as regular object temp_map.insert(key, Data::String(AtprotoStr::String(cid_str.into()))); - let next_value: Data = map.next_value()?; + let next_value: Data = map.next_value()?; temp_map.insert(next_key, next_value); continue; } else { // Only key, return CidLink - return Ok(Data::CidLink(Cid::cow_str(CowStr::from(cid_str)))); + return Ok(Data::CidLink(unsafe { + Cid::unchecked_str(S::from(cid_str)) + })); } } else if key.as_str() == "$bytes" { // {"$bytes": "base64_string"} pattern - let bytes_str: String = map.next_value()?; + let bytes_str: S = map.next_value()?; // Check if there are more keys if map.next_key::()?.is_some() { // More keys, treat as regular object - shouldn't happen but handle it @@ -263,12 +279,12 @@ impl<'de: 'v, 'v> serde::de::Visitor<'v> for DataVisitor { continue; } else { // Only key, decode and return bytes - return Ok(decode_bytes(&bytes_str)); + return Ok(decode_bytes(bytes_str)); } } } - let value: Data = map.next_value()?; + let value: Data = map.next_value()?; temp_map.insert(key, value); } @@ -277,20 +293,21 @@ impl<'de: 'v, 'v> serde::de::Visitor<'v> for DataVisitor { } } -fn apply_type_inference<'s>(mut map: BTreeMap>) -> Result, AtDataError> { +fn apply_type_inference<'s, S>(mut map: BTreeMap>) -> Result, AtDataError> +where + S: AsRef + Bos, +{ // Check for CID link pattern first: {"$link": "cid_string"} if map.len() == 1 { - if let Some(Data::String(AtprotoStr::String(link))) = map.get("$link") { - // Need to extract ownership, can't borrow from map we're about to consume - let link_owned = link.clone(); - return Ok(Data::CidLink(Cid::cow_str(link_owned))); + if let Some(Data::String(AtprotoStr::String(link))) = map.remove("$link") { + return Ok(Data::CidLink(unsafe { Cid::unchecked_str(link) })); } } // Check for $type field to detect special structures - let type_field = map.get("$type").and_then(|v| { + let type_field = map.remove("$type").and_then(|v| { if let Data::String(AtprotoStr::String(s)) = v { - Some(s.as_ref()) + Some(s) } else { None } @@ -298,27 +315,27 @@ fn apply_type_inference<'s>(mut map: BTreeMap>) -> Result(mut map: BTreeMap>) -> Result>; wrap directly. r#ref: CidLink(ref_cid), - mime_type: MimeType::from(mime_cowstr), + mime_type: MimeType::new(mime_cowstr), size, })); } } } - // Apply type inference for string fields based on key names (mutate in place) - for (key, value) in map.iter_mut() { - if let Data::String(AtprotoStr::String(s)) = value.to_owned() { - let type_hint = string_key_type_guess(key.as_str()); - let refined = match type_hint { - DataModelType::String(string_type) => refine_string_by_type(s, string_type), - DataModelType::Bytes => { - // Decode base64 - decode_bytes(&s) + // Apply type inference for string fields based on key names. + // Drain and rebuild to avoid cloning S values. + let map = map + .into_iter() + .map(|(key, value)| { + let refined = if let Data::String(AtprotoStr::String(s)) = value { + let type_hint = string_key_type_guess(key.as_str()); + match type_hint { + DataModelType::String(string_type) => refine_string_by_type(s, string_type), + DataModelType::Bytes => decode_bytes(s), + DataModelType::CidLink if key.as_str() == "$link" => { + Data::CidLink(unsafe { Cid::unchecked_str(s) }) + } + _ => Data::String(AtprotoStr::String(s)), } - DataModelType::CidLink if key.as_str() == "$link" => Data::CidLink(Cid::cow_str(s)), - _ => continue, // no refinement needed + } else { + value }; - *value = refined; - } - } + (key, refined) + }) + .collect(); Ok(Data::Object(Object(map))) } -fn refine_string_by_type<'s>(s: CowStr<'s>, string_type: LexiconStringType) -> Data<'s> { +fn refine_string_by_type(s: S, string_type: LexiconStringType) -> Data +where + S: Bos + AsRef, +{ + use crate::types::aturi::validate_and_index; + use crate::types::cid::IpldCid; + use crate::types::did::validate_did; + use crate::types::handle::validate_handle; + use crate::types::nsid::validate_nsid; + use crate::types::recordkey::validate_rkey; + match string_type { - LexiconStringType::Datetime => Datetime::from_str(&s) - .map(|dt| Data::String(AtprotoStr::Datetime(dt))) - .unwrap_or_else(|_| Data::String(AtprotoStr::String(s.clone()))), - LexiconStringType::AtUri => AtUri::new_owned(s.clone()) - .map(|uri| Data::String(AtprotoStr::AtUri(uri))) - .unwrap_or_else(|_| Data::String(AtprotoStr::String(s.clone()))), - LexiconStringType::Did => Did::new_owned(s.clone()) - .map(|did| Data::String(AtprotoStr::Did(did))) - .unwrap_or_else(|_| Data::String(AtprotoStr::String(s.clone()))), - LexiconStringType::Handle => Handle::new_owned(s.clone()) - .map(|handle| Data::String(AtprotoStr::Handle(handle))) - .unwrap_or_else(|_| Data::String(AtprotoStr::String(s.clone()))), - LexiconStringType::AtIdentifier => AtIdentifier::new_owned(s.clone()) - .map(|ident| Data::String(AtprotoStr::AtIdentifier(ident))) - .unwrap_or_else(|_| Data::String(AtprotoStr::String(s.clone()))), - LexiconStringType::Nsid => Nsid::new_owned(s.clone()) - .map(|nsid| Data::String(AtprotoStr::Nsid(nsid))) - .unwrap_or_else(|_| Data::String(AtprotoStr::String(s.clone()))), - LexiconStringType::Cid => Cid::>::new_owned(s.as_bytes()) - .map(|cid| Data::String(AtprotoStr::Cid(cid))) - .unwrap_or_else(|_| Data::String(AtprotoStr::String(s.into()))), - LexiconStringType::Language => Language::new(&s) - .map(|lang| Data::String(AtprotoStr::Language(lang))) - .unwrap_or_else(|_| Data::String(AtprotoStr::String(s.clone()))), - LexiconStringType::Tid => Tid::new(s.clone()) - .map(|tid| Data::String(AtprotoStr::Tid(tid))) - .unwrap_or_else(|_| Data::String(AtprotoStr::String(s.clone()))), - LexiconStringType::RecordKey => Rkey::new(s.clone()) - .map(|rkey| Data::String(AtprotoStr::RecordKey(RecordKey(rkey)))) - .unwrap_or_else(|_| Data::String(AtprotoStr::String(s.clone()))), - LexiconStringType::Uri(_) => UriValue::new_owned(s.clone()) - .map(|uri| Data::String(AtprotoStr::Uri(uri))) - .unwrap_or_else(|_| Data::String(AtprotoStr::String(s.clone()))), - LexiconStringType::String => Data::String(parse_string(&s).into_static()), - } -} - -impl Serialize for Array<'_> { + LexiconStringType::Datetime => { + if let Ok(dt) = Datetime::from_str(s.as_ref()) { + return Data::String(AtprotoStr::Datetime(dt)); + } + } + LexiconStringType::AtUri => { + if validate_and_index(s.as_ref()).is_ok() { + return Data::String(AtprotoStr::AtUri(unsafe { AtUri::unchecked(s) })); + } + } + LexiconStringType::Did => { + if validate_did(s.as_ref()).is_ok() { + return Data::String(AtprotoStr::Did(unsafe { Did::unchecked(s) })); + } + } + LexiconStringType::Handle => { + if validate_handle(s.as_ref()).is_ok() + && !s.as_ref().contains(|c: char| c.is_ascii_uppercase()) + { + return Data::String(AtprotoStr::Handle(unsafe { Handle::unchecked(s) })); + } + } + LexiconStringType::AtIdentifier => { + if validate_did(s.as_ref()).is_ok() + || (validate_handle(s.as_ref()).is_ok() + && !s.as_ref().contains(|c: char| c.is_ascii_uppercase())) + { + return Data::String(AtprotoStr::AtIdentifier(unsafe { + AtIdentifier::unchecked(s) + })); + } + } + LexiconStringType::Nsid => { + if validate_nsid(s.as_ref()).is_ok() { + return Data::String(AtprotoStr::Nsid(unsafe { Nsid::unchecked(s) })); + } + } + LexiconStringType::Cid => { + if IpldCid::try_from(s.as_ref().as_bytes()).is_ok() || s.as_ref().starts_with("bafy") { + return Data::String(AtprotoStr::Cid(unsafe { Cid::unchecked_str(s) })); + } + } + LexiconStringType::Language => { + if let Ok(lang) = Language::new(s.as_ref()) { + return Data::String(AtprotoStr::Language(lang)); + } + } + LexiconStringType::Tid => { + if let Ok(tid) = Tid::new(s.as_ref()) { + return Data::String(AtprotoStr::Tid(tid)); + } + } + LexiconStringType::RecordKey => { + if validate_rkey(s.as_ref()).is_ok() { + return Data::String(AtprotoStr::RecordKey(RecordKey(unsafe { + Rkey::unchecked(s) + }))); + } + } + LexiconStringType::Uri(_) => { + // UriValue::new is infallible but may fall through to Any. + // Prefer AtprotoStr::String over wrapping as Uri(Any). + match UriValue::new(s) { + Ok(UriValue::Any(s)) => return Data::String(AtprotoStr::String(s)), + Ok(uri) => return Data::String(AtprotoStr::Uri(uri)), + Err(_) => unreachable!(), + } + } + LexiconStringType::String => { + return Data::String(parse_string(s)); + } + } + // Fallback for failed validation. + Data::String(AtprotoStr::String(s)) +} + +impl Serialize for Array +where + D: Bos + AsRef + Serialize, +{ fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -408,21 +482,25 @@ impl Serialize for Array<'_> { } } -impl<'de, 'a> Deserialize<'de> for Array<'a> +impl<'de, 'a, S> Deserialize<'de> for Array where 'de: 'a, + S: AsRef + Bos + Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { // Just deserialize as Vec directly - the Data visitor handles everything - let vec: Vec> = Deserialize::deserialize(deserializer)?; + let vec: Vec> = Deserialize::deserialize(deserializer)?; Ok(Array(vec)) } } -impl Serialize for Object<'_> { +impl Serialize for Object +where + D: AsRef + Bos + Serialize, +{ fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -436,9 +514,10 @@ impl Serialize for Object<'_> { } } -impl<'de, 'a> Deserialize<'de> for Object<'a> +impl<'de, 'a, S> Deserialize<'de> for Object where 'de: 'a, + S: AsRef + Bos + Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where @@ -448,7 +527,7 @@ where // Deserialize via Data, then extract the Object // The Data visitor handles all the type inference and special cases - let data: Data<'a> = Data::deserialize(deserializer)?; + let data: Data = Data::deserialize(deserializer)?; match data { Data::Object(obj) => Ok(obj), _ => Err(D::Error::custom("expected object, got something else")), @@ -703,10 +782,13 @@ impl<'de: 'v, 'v> serde::de::Visitor<'v> for RawDataVisitor { // In DAG-CBOR, newtype_struct wraps tag 42 (CID) // The CidDeserializer will call visit_bytes with the CID bytes -struct CidAwareVisitor; +struct CidAwareVisitor(PhantomData); -impl<'de: 'v, 'v> serde::de::Visitor<'v> for CidAwareVisitor { - type Value = Data<'v>; +impl<'de: 'v, 'v, S> serde::de::Visitor<'v> for CidAwareVisitor +where + S: AsRef + Bos, +{ + type Value = Data; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("CID bytes or other newtype content") @@ -827,7 +909,10 @@ fn apply_raw_type_inference<'s>( } // Deserializer implementation for &Data<'de> - allows deserializing typed data from Data values -impl<'de> serde::Deserializer<'de> for &'de Data<'de> { +impl<'de, S> serde::Deserializer<'de> for &'de Data +where + S: AsRef + Bos, +{ type Error = DataDeserializerError; fn deserialize_any(self, visitor: V) -> Result @@ -852,8 +937,9 @@ impl<'de> serde::Deserializer<'de> for &'de Data<'de> { Data::Object(obj) => visitor.visit_map(ObjectDeserializer::new(&obj.0)), Data::Blob(blob) => { // Blob is a root type - deserialize as the Blob itself via map representation - visitor.visit_map(BlobDeserializer::new(blob)) + visitor.visit_map(BlobDeserializer::new(&blob)) } + Data::InvalidNumber(float) => visitor.visit_borrowed_str(float.as_ref()), } } @@ -875,7 +961,10 @@ impl<'de> serde::Deserializer<'de> for &'de Data<'de> { } // Deserializer implementation for &Data<'de> - allows deserializing typed data from Data values -impl<'de> serde::Deserializer<'de> for Data<'static> { +impl<'de, S> serde::Deserializer<'de> for Data +where + S: AsRef + Bos, +{ type Error = DataDeserializerError; fn deserialize_any(self, visitor: V) -> Result @@ -895,6 +984,7 @@ impl<'de> serde::Deserializer<'de> for Data<'static> { // Blob is a root type - deserialize as the Blob itself via map representation visitor.visit_map(OwnedBlobDeserializer::new(blob)) } + Data::InvalidNumber(float) => visitor.visit_str(float.as_ref()), } } @@ -1035,13 +1125,19 @@ impl serde::de::Error for DataDeserializerError { } // MapAccess implementation for Blob - allows borrowing from blob fields -struct BlobDeserializer<'de> { - blob: &'de Blob>, +struct BlobDeserializer<'de, S> +where + S: AsRef + Bos, +{ + blob: &'de Blob, field_index: usize, } -impl<'de> BlobDeserializer<'de> { - fn new(blob: &'de Blob>) -> Self { +impl<'de, S> BlobDeserializer<'de, S> +where + S: AsRef + Bos, +{ + fn new(blob: &'de Blob) -> Self { Self { blob, field_index: 0, @@ -1049,7 +1145,10 @@ impl<'de> BlobDeserializer<'de> { } } -impl<'de> serde::de::MapAccess<'de> for BlobDeserializer<'de> { +impl<'de, S> serde::de::MapAccess<'de> for BlobDeserializer<'de, S> +where + S: AsRef + Bos, +{ type Error = DataDeserializerError; fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> @@ -1083,21 +1182,30 @@ impl<'de> serde::de::MapAccess<'de> for BlobDeserializer<'de> { } } -struct OwnedBlobDeserializer { - blob: Blob>, +struct OwnedBlobDeserializer +where + S: AsRef + Bos, +{ + blob: Blob, field_index: usize, } -impl OwnedBlobDeserializer { - fn new(blob: Blob>) -> Self { +impl OwnedBlobDeserializer +where + S: AsRef + Bos, +{ + fn new(blob: Blob) -> Self { Self { - blob: blob.into_static(), + blob: blob, field_index: 0, } } } -impl<'de> serde::de::MapAccess<'de> for OwnedBlobDeserializer { +impl<'de, S> serde::de::MapAccess<'de> for OwnedBlobDeserializer +where + S: AsRef + Bos, +{ type Error = DataDeserializerError; fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> @@ -1192,17 +1300,26 @@ impl<'de> serde::Deserializer<'de> for I64Deserializer { } // SeqAccess implementation for Data::Array -struct ArrayDeserializer<'de> { - iter: core::slice::Iter<'de, Data<'de>>, +struct ArrayDeserializer<'de, S> +where + S: Bos + AsRef, +{ + iter: core::slice::Iter<'de, Data>, } -impl<'de> ArrayDeserializer<'de> { - fn new(slice: &'de [Data<'de>]) -> Self { +impl<'de, S> ArrayDeserializer<'de, S> +where + S: Bos + AsRef, +{ + fn new(slice: &'de [Data]) -> Self { Self { iter: slice.iter() } } } -impl<'de> serde::de::SeqAccess<'de> for ArrayDeserializer<'de> { +impl<'de, S> serde::de::SeqAccess<'de> for ArrayDeserializer<'de, S> +where + S: Bos + AsRef, +{ type Error = DataDeserializerError; fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> @@ -1217,19 +1334,28 @@ impl<'de> serde::de::SeqAccess<'de> for ArrayDeserializer<'de> { } // SeqAccess implementation for Data::Array -struct OwnedArrayDeserializer { - iter: alloc::vec::IntoIter>, +struct OwnedArrayDeserializer +where + S: Bos + AsRef, +{ + iter: alloc::vec::IntoIter>, } -impl OwnedArrayDeserializer { - fn new(slice: Vec>) -> Self { +impl OwnedArrayDeserializer +where + S: Bos + AsRef, +{ + fn new(slice: Vec>) -> Self { Self { iter: slice.into_iter(), } } } -impl<'de> serde::de::SeqAccess<'de> for OwnedArrayDeserializer { +impl<'de, S> serde::de::SeqAccess<'de> for OwnedArrayDeserializer +where + S: Bos + AsRef, +{ type Error = DataDeserializerError; fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> @@ -1244,13 +1370,19 @@ impl<'de> serde::de::SeqAccess<'de> for OwnedArrayDeserializer { } // MapAccess implementation for Data::Object -struct ObjectDeserializer<'de> { - iter: alloc::collections::btree_map::Iter<'de, SmolStr, Data<'de>>, - value: Option<&'de Data<'de>>, +struct ObjectDeserializer<'de, S> +where + S: Bos + AsRef, +{ + iter: alloc::collections::btree_map::Iter<'de, SmolStr, Data>, + value: Option<&'de Data>, } -impl<'de> ObjectDeserializer<'de> { - fn new(map: &'de BTreeMap>) -> Self { +impl<'de, S> ObjectDeserializer<'de, S> +where + S: Bos + AsRef, +{ + fn new(map: &'de BTreeMap>) -> Self { Self { iter: map.iter(), value: None, @@ -1258,7 +1390,10 @@ impl<'de> ObjectDeserializer<'de> { } } -impl<'de> serde::de::MapAccess<'de> for ObjectDeserializer<'de> { +impl<'de, S> serde::de::MapAccess<'de> for ObjectDeserializer<'de, S> +where + S: Bos + AsRef, +{ type Error = DataDeserializerError; fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> @@ -1289,13 +1424,19 @@ impl<'de> serde::de::MapAccess<'de> for ObjectDeserializer<'de> { } // MapAccess implementation for Data::Object -struct OwnedObjectDeserializer { - iter: alloc::collections::btree_map::IntoIter>, - value: Option>, +struct OwnedObjectDeserializer +where + S: Bos + AsRef, +{ + iter: alloc::collections::btree_map::IntoIter>, + value: Option>, } -impl OwnedObjectDeserializer { - fn new(map: BTreeMap>) -> Self { +impl OwnedObjectDeserializer +where + S: Bos + AsRef, +{ + fn new(map: BTreeMap>) -> Self { Self { iter: map.into_iter(), value: None, @@ -1303,7 +1444,10 @@ impl OwnedObjectDeserializer { } } -impl<'de> serde::de::MapAccess<'de> for OwnedObjectDeserializer { +impl<'de, S> serde::de::MapAccess<'de> for OwnedObjectDeserializer +where + S: Bos + AsRef, +{ type Error = DataDeserializerError; fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> diff --git a/crates/jacquard-common/src/types/value/tests.rs b/crates/jacquard-common/src/types/value/tests.rs index 3fcef20c..ee413166 100644 --- a/crates/jacquard-common/src/types/value/tests.rs +++ b/crates/jacquard-common/src/types/value/tests.rs @@ -2,6 +2,7 @@ use crate::cowstr::ToCowStr; use super::*; use core::str::FromStr; +use std::string::String; /// Canonicalize JSON by sorting object keys recursively fn canonicalize_json(value: &serde_json::Value) -> serde_json::Value { @@ -118,7 +119,7 @@ fn serialize_deserialize_array() { assert!(matches!(items[1], Data::Boolean(true))); assert!(matches!(items[2], Data::Integer(42))); if let Data::String(AtprotoStr::String(s)) = &items[3] { - assert_eq!(s.as_ref(), "test"); + assert_eq!(s, "test"); } else { panic!("expected plain string"); } @@ -333,7 +334,7 @@ fn test_from_data_struct() { let mut map = BTreeMap::new(); map.insert( SmolStr::new_static("name"), - Data::String(AtprotoStr::String("Alice".into())), + Data::String(AtprotoStr::String("Alice")), ); map.insert(SmolStr::new_static("age"), Data::Integer(30)); let data = Data::Object(Object(map)); @@ -345,7 +346,7 @@ fn test_from_data_struct() { #[test] fn test_from_data_vec() { - let data = Data::Array(Array(vec![ + let data: Data = Data::Array(Array(vec![ Data::Integer(1), Data::Integer(2), Data::Integer(3), @@ -375,7 +376,7 @@ fn test_from_data_nested() { let mut nested_map = BTreeMap::new(); nested_map.insert( SmolStr::new_static("value"), - Data::String(AtprotoStr::String("test".into())), + Data::String(AtprotoStr::String("test")), ); let mut parent_map = BTreeMap::new(); @@ -427,7 +428,7 @@ fn test_from_data_option() { let mut map = BTreeMap::new(); map.insert( SmolStr::new_static("required"), - Data::String(AtprotoStr::String("value".into())), + Data::String(AtprotoStr::String("value")), ); // optional field not present let data = Data::Object(Object(map)); @@ -522,7 +523,7 @@ fn test_datetime_and_nsid_deserialization() { map.insert( SmolStr::new_static("nsid"), Data::String(AtprotoStr::Nsid( - Nsid::new_static("app.bsky.feed.post").unwrap(), + Nsid::>::new_static("app.bsky.feed.post").unwrap(), )), ); map.insert( @@ -626,13 +627,13 @@ fn test_atidentifier_deserialization() { map.insert( SmolStr::new_static("ident_did"), Data::String(AtprotoStr::AtIdentifier(AtIdentifier::Did( - Did::new_owned("did:plc:abc").unwrap(), + Did::new("did:plc:abc").unwrap(), ))), ); map.insert( SmolStr::new_static("ident_handle"), Data::String(AtprotoStr::AtIdentifier(AtIdentifier::Handle( - Handle::new_static("bob.test").unwrap(), + Handle::new("bob.test").unwrap(), ))), ); let data = Data::Object(Object(map)); @@ -730,13 +731,13 @@ fn test_to_data_with_inference() { created_at: "2024-01-15T12:30:45.123Z".to_string(), }; - let data = to_data(&post).unwrap(); + let data: Data = to_data(&post).unwrap(); match data { Data::Object(obj) => { // Check text is plain string match obj.0.get("text").unwrap() { - Data::String(AtprotoStr::String(s)) => assert_eq!(s.as_ref(), "hello world"), + Data::String(AtprotoStr::String(s)) => assert_eq!(s, "hello world"), _ => panic!("expected plain string for text"), } // Check DID was inferred @@ -774,7 +775,7 @@ fn test_option_vec_deserialization() { let mut map_with_langs = BTreeMap::new(); map_with_langs.insert( SmolStr::new_static("text"), - Data::String(AtprotoStr::String("hello".into())), + Data::String(AtprotoStr::String("hello")), ); map_with_langs.insert( SmolStr::new_static("langs"), @@ -794,7 +795,7 @@ fn test_option_vec_deserialization() { let mut map_without_langs = BTreeMap::new(); map_without_langs.insert( SmolStr::new_static("text"), - Data::String(AtprotoStr::String("world".into())), + Data::String(AtprotoStr::String("world")), ); let data_without_langs = Data::Object(Object(map_without_langs)); @@ -807,7 +808,7 @@ fn test_option_vec_deserialization() { let mut map_with_null = BTreeMap::new(); map_with_null.insert( SmolStr::new_static("text"), - Data::String(AtprotoStr::String("null test".into())), + Data::String(AtprotoStr::String("null test")), ); map_with_null.insert(SmolStr::new_static("langs"), Data::Null); let data_with_null = Data::Object(Object(map_with_null)); @@ -821,36 +822,36 @@ fn test_option_vec_deserialization() { fn test_data_accessors() { // Test as_object let mut map = BTreeMap::new(); - map.insert(SmolStr::new_static("key"), Data::Integer(42)); + map.insert(SmolStr::new_static("key"), Data::<&str>::Integer(42)); let obj_data = Data::Object(Object(map.clone())); assert!(obj_data.as_object().is_some()); assert_eq!(obj_data.as_object().unwrap().0.len(), 1); - assert!(Data::Null.as_object().is_none()); + assert!(Data::<&str>::Null.as_object().is_none()); // Test as_array - let arr_data = Data::Array(Array(vec![Data::Integer(1), Data::Integer(2)])); + let arr_data = Data::<&str>::Array(Array(vec![Data::Integer(1), Data::Integer(2)])); assert!(arr_data.as_array().is_some()); assert_eq!(arr_data.as_array().unwrap().0.len(), 2); - assert!(Data::Null.as_array().is_none()); + assert!(Data::<&str>::Null.as_array().is_none()); // Test as_str - let str_data = Data::String(AtprotoStr::String("hello".into())); + let str_data = Data::<&str>::String(AtprotoStr::String("hello".into())); assert_eq!(str_data.as_str(), Some("hello")); - assert!(Data::Null.as_str().is_none()); + assert!(Data::<&str>::Null.as_str().is_none()); // Test as_integer - let int_data = Data::Integer(42); + let int_data: Data = Data::Integer(42); assert_eq!(int_data.as_integer(), Some(42)); - assert!(Data::Null.as_integer().is_none()); + assert!(Data::<&str>::Null.as_integer().is_none()); // Test as_boolean - let bool_data = Data::Boolean(true); + let bool_data: Data = Data::Boolean(true); assert_eq!(bool_data.as_boolean(), Some(true)); - assert!(Data::Null.as_boolean().is_none()); + assert!(Data::<&str>::Null.as_boolean().is_none()); // Test is_null - assert!(Data::Null.is_null()); - assert!(!Data::Integer(0).is_null()); + assert!(Data::<&str>::Null.is_null()); + assert!(!Data::<&str>::Integer(0).is_null()); } #[test] @@ -887,18 +888,18 @@ fn test_rawdata_accessors() { #[test] fn test_data_to_dag_cbor() { // Test simple types - let null_data = Data::Null; + let null_data: Data = Data::Null; assert!(null_data.to_dag_cbor().is_ok()); - let int_data = Data::Integer(42); + let int_data: Data = Data::Integer(42); assert!(int_data.to_dag_cbor().is_ok()); - let str_data = Data::String(AtprotoStr::String("hello".into())); + let str_data: Data = Data::String(AtprotoStr::String("hello".into())); assert!(str_data.to_dag_cbor().is_ok()); // Test complex types let mut map = BTreeMap::new(); - map.insert(SmolStr::new_static("num"), Data::Integer(42)); + map.insert(SmolStr::new_static("num"), Data::::Integer(42)); map.insert( SmolStr::new_static("text"), Data::String(AtprotoStr::String("test".into())), @@ -909,7 +910,7 @@ fn test_data_to_dag_cbor() { assert!(!cbor_result.unwrap().is_empty()); // Test array - let arr_data = Data::Array(Array(vec![ + let arr_data: Data = Data::Array(Array(vec![ Data::Integer(1), Data::Integer(2), Data::Integer(3), @@ -944,7 +945,7 @@ fn test_rawdata_to_dag_cbor() { #[test] fn test_object_methods() { let mut map = BTreeMap::new(); - map.insert(SmolStr::new_static("num"), Data::Integer(42)); + map.insert(SmolStr::new_static("num"), Data::::Integer(42)); map.insert( SmolStr::new_static("text"), Data::String(AtprotoStr::String("hello".into())), @@ -964,7 +965,7 @@ fn test_object_methods() { assert_eq!(obj.len(), 2); assert!(!obj.is_empty()); - let empty_obj = Object(BTreeMap::new()); + let empty_obj: Object = Object(BTreeMap::new()); assert_eq!(empty_obj.len(), 0); assert!(empty_obj.is_empty()); @@ -979,7 +980,7 @@ fn test_object_methods() { #[test] fn test_array_methods() { - let arr = Array(vec![Data::Integer(1), Data::Integer(2), Data::Integer(3)]); + let arr = Array::<&str>(vec![Data::Integer(1), Data::Integer(2), Data::Integer(3)]); // Test get assert_eq!(arr.get(0), Some(&Data::Integer(1))); @@ -990,7 +991,7 @@ fn test_array_methods() { assert_eq!(arr.len(), 3); assert!(!arr.is_empty()); - let empty_arr = Array(vec![]); + let empty_arr = Array::<&str>(vec![]); assert_eq!(empty_arr.len(), 0); assert!(empty_arr.is_empty()); @@ -1007,7 +1008,7 @@ fn test_get_at_path_simple() { let mut inner = BTreeMap::new(); inner.insert( SmolStr::new_static("alt"), - Data::String(AtprotoStr::String("test".into())), + Data::String(AtprotoStr::String("test")), ); let mut outer = BTreeMap::new(); @@ -1036,13 +1037,13 @@ fn test_get_at_path_arrays() { let mut item1 = BTreeMap::new(); item1.insert( SmolStr::new_static("name"), - Data::String(AtprotoStr::String("first".into())), + Data::String(AtprotoStr::String("first")), ); let mut item2 = BTreeMap::new(); item2.insert( SmolStr::new_static("name"), - Data::String(AtprotoStr::String("second".into())), + Data::String(AtprotoStr::String("second")), ); let items = Data::Array(Array(vec![ @@ -1073,13 +1074,13 @@ fn test_get_at_path_complex() { let mut img1 = BTreeMap::new(); img1.insert( SmolStr::new_static("alt"), - Data::String(AtprotoStr::String("img1".into())), + Data::String(AtprotoStr::String("img1")), ); let mut img2 = BTreeMap::new(); img2.insert( SmolStr::new_static("alt"), - Data::String(AtprotoStr::String("img2".into())), + Data::String(AtprotoStr::String("img2")), ); let images = Data::Array(Array(vec![ @@ -1133,7 +1134,7 @@ fn test_query_exact_path() { let mut inner = BTreeMap::new(); inner.insert( SmolStr::new_static("handle"), - Data::String(AtprotoStr::String("alice.bsky.social".into())), + Data::String(AtprotoStr::String("alice.bsky.social")), ); let mut outer = BTreeMap::new(); @@ -1153,19 +1154,19 @@ fn test_query_wildcard_array() { let mut actor1 = BTreeMap::new(); actor1.insert( SmolStr::new_static("handle"), - Data::String(AtprotoStr::String("alice".into())), + Data::String(AtprotoStr::String("alice")), ); let mut actor2 = BTreeMap::new(); actor2.insert( SmolStr::new_static("handle"), - Data::String(AtprotoStr::String("bob".into())), + Data::String(AtprotoStr::String("bob")), ); let mut actor3 = BTreeMap::new(); actor3.insert( SmolStr::new_static("name"), - Data::String(AtprotoStr::String("carol".into())), + Data::String(AtprotoStr::String("carol")), ); let actors = Data::Array(Array(vec![ @@ -1194,13 +1195,13 @@ fn test_query_wildcard_object() { let mut images = BTreeMap::new(); images.insert( SmolStr::new_static("alt"), - Data::String(AtprotoStr::String("img".into())), + Data::String(AtprotoStr::String("img")), ); let mut video = BTreeMap::new(); video.insert( SmolStr::new_static("alt"), - Data::String(AtprotoStr::String("vid".into())), + Data::String(AtprotoStr::String("vid")), ); let mut embed = BTreeMap::new(); @@ -1224,7 +1225,7 @@ fn test_query_scoped_recursion() { let mut handle_map = BTreeMap::new(); handle_map.insert( SmolStr::new_static("handle"), - Data::String(AtprotoStr::String("alice".into())), + Data::String(AtprotoStr::String("alice")), ); let mut profile_map = BTreeMap::new(); @@ -1260,13 +1261,13 @@ fn test_query_global_recursion() { let mut inner1 = BTreeMap::new(); inner1.insert( SmolStr::new_static("cid"), - Data::String(AtprotoStr::String("cid1".into())), + Data::String(AtprotoStr::String("cid1")), ); let mut inner2 = BTreeMap::new(); inner2.insert( SmolStr::new_static("cid"), - Data::String(AtprotoStr::String("cid2".into())), + Data::String(AtprotoStr::String("cid2")), ); let mut middle = BTreeMap::new(); @@ -1277,7 +1278,7 @@ fn test_query_global_recursion() { root.insert(SmolStr::new_static("thread"), Data::Object(Object(middle))); root.insert( SmolStr::new_static("cid"), - Data::String(AtprotoStr::String("cid3".into())), + Data::String(AtprotoStr::String("cid3")), ); let data = Data::Object(Object(root)); @@ -1301,13 +1302,13 @@ fn test_query_combined_wildcard_field() { let mut actor1 = BTreeMap::new(); actor1.insert( SmolStr::new_static("handle"), - Data::String(AtprotoStr::String("alice".into())), + Data::String(AtprotoStr::String("alice")), ); let mut actor2 = BTreeMap::new(); actor2.insert( SmolStr::new_static("handle"), - Data::String(AtprotoStr::String("bob".into())), + Data::String(AtprotoStr::String("bob")), ); let actors = Data::Array(Array(vec![ @@ -1331,7 +1332,7 @@ fn test_query_combined_wildcard_field() { #[test] fn test_query_no_match() { let mut map = BTreeMap::new(); - map.insert(SmolStr::new_static("foo"), Data::Integer(42)); + map.insert(SmolStr::new_static("foo"), Data::<&str>::Integer(42)); let data = Data::Object(Object(map)); // Field doesn't exist @@ -1345,7 +1346,7 @@ fn test_query_no_match() { #[test] fn test_query_result_helpers() { let mut map = BTreeMap::new(); - map.insert(SmolStr::new_static("value"), Data::Integer(42)); + map.insert(SmolStr::new_static("value"), Data::<&str>::Integer(42)); let data = Data::Object(Object(map)); let result = data.query("value"); @@ -1380,7 +1381,7 @@ fn test_type_discriminator() { // Object without $type field let mut map2 = BTreeMap::new(); - map2.insert(SmolStr::new_static("foo"), Data::Integer(42)); + map2.insert(SmolStr::new_static("foo"), Data::<&str>::Integer(42)); let obj2 = Object(map2); assert_eq!(obj2.type_discriminator(), None); @@ -1389,7 +1390,7 @@ fn test_type_discriminator() { assert_eq!(data2.type_discriminator(), None); // Non-object data - let data3 = Data::Integer(42); + let data3: Data = Data::Integer(42); assert_eq!(data3.type_discriminator(), None); // RawData with $type diff --git a/crates/jacquard-common/src/xrpc/atproto.rs b/crates/jacquard-common/src/xrpc/atproto.rs index d1fc1c88..2755e9d9 100644 --- a/crates/jacquard-common/src/xrpc/atproto.rs +++ b/crates/jacquard-common/src/xrpc/atproto.rs @@ -29,22 +29,26 @@ use smol_str::SmolStr; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[allow(missing_docs)] -pub struct ListRecords<'a> { - #[serde(borrow)] - pub collection: Nsid>, +pub struct ListRecords +where + S: Bos + AsRef, +{ + pub collection: Nsid, #[serde(skip_serializing_if = "Option::is_none")] - #[serde(borrow)] - pub cursor: Option>, + pub cursor: Option, #[serde(skip_serializing_if = "Option::is_none")] pub limit: Option, - #[serde(borrow)] - pub repo: AtIdentifier>, + pub repo: AtIdentifier, #[serde(skip_serializing_if = "Option::is_none")] pub reverse: Option, } -impl IntoStatic for ListRecords<'_> { - type Output = ListRecords<'static>; +impl IntoStatic for ListRecords +where + S: Bos + AsRef + IntoStatic, + S::Output: Bos + AsRef, +{ + type Output = ListRecords; fn into_static(self) -> Self::Output { ListRecords { @@ -61,16 +65,21 @@ impl IntoStatic for ListRecords<'_> { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[allow(missing_docs)] -pub struct ListRecordsOutput<'a> { +pub struct ListRecordsOutput +where + S: Bos + AsRef, +{ #[serde(skip_serializing_if = "Option::is_none")] - #[serde(borrow)] - pub cursor: Option>, - #[serde(borrow)] - pub records: Vec>, + pub cursor: Option, + pub records: Vec>, } -impl IntoStatic for ListRecordsOutput<'_> { - type Output = ListRecordsOutput<'static>; +impl IntoStatic for ListRecordsOutput +where + S: Bos + AsRef + IntoStatic, + S::Output: Bos + AsRef, +{ + type Output = ListRecordsOutput; fn into_static(self) -> Self::Output { ListRecordsOutput { @@ -84,18 +93,22 @@ impl IntoStatic for ListRecordsOutput<'_> { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[allow(missing_docs)] -pub struct ListRecordsRecord<'a> { +pub struct ListRecordsRecord +where + S: Bos + AsRef, +{ #[serde(skip_serializing_if = "Option::is_none")] - #[serde(borrow)] - pub cid: Option>>, - #[serde(borrow)] - pub uri: AtUri>, - #[serde(borrow)] - pub value: Data<'a>, + pub cid: Option>, + pub uri: AtUri, + pub value: Data, } -impl IntoStatic for ListRecordsRecord<'_> { - type Output = ListRecordsRecord<'static>; +impl IntoStatic for ListRecordsRecord +where + S: Bos + AsRef + IntoStatic, + S::Output: Bos + AsRef, +{ + type Output = ListRecordsRecord<::Output>; fn into_static(self) -> Self::Output { ListRecordsRecord { @@ -112,11 +125,14 @@ pub struct ListRecordsResponse; impl XrpcResp for ListRecordsResponse { const NSID: &'static str = "com.atproto.repo.listRecords"; const ENCODING: &'static str = "application/json"; - type Output<'de> = ListRecordsOutput<'de>; + type Output<'de> = ListRecordsOutput; type Err<'de> = GenericError<'de>; } -impl<'a> XrpcRequest for ListRecords<'a> { +impl<'a, S> XrpcRequest for ListRecords +where + S: Bos + AsRef + Serialize, +{ const NSID: &'static str = "com.atproto.repo.listRecords"; const METHOD: XrpcMethod = XrpcMethod::Query; type Response = ListRecordsResponse; @@ -159,18 +175,22 @@ impl IntoStatic for GetRecord<'_> { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[allow(missing_docs)] #[serde(rename_all = "camelCase")] -pub struct GetRecordOutput<'a> { +pub struct GetRecordOutput +where + S: Bos + AsRef, +{ #[serde(skip_serializing_if = "Option::is_none")] - #[serde(borrow)] - pub cid: Option>>, - #[serde(borrow)] - pub uri: AtUri>, - #[serde(borrow)] - pub value: Data<'a>, + pub cid: Option>, + pub uri: AtUri, + pub value: Data, } -impl IntoStatic for GetRecordOutput<'_> { - type Output = GetRecordOutput<'static>; +impl IntoStatic for GetRecordOutput +where + S: Bos + AsRef + IntoStatic, + S::Output: Bos + AsRef, +{ + type Output = GetRecordOutput; fn into_static(self) -> Self::Output { GetRecordOutput { @@ -223,7 +243,7 @@ pub struct GetRecordResponse; impl XrpcResp for GetRecordResponse { const NSID: &'static str = "com.atproto.repo.getRecord"; const ENCODING: &'static str = "application/json"; - type Output<'de> = GetRecordOutput<'de>; + type Output<'de> = GetRecordOutput; type Err<'de> = GetRecordError<'de>; } @@ -366,13 +386,19 @@ where #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[allow(missing_docs)] -pub struct ResolveDidOutput<'a> { - #[serde(borrow)] - pub did_doc: Data<'a>, +pub struct ResolveDidOutput +where + S: Bos + AsRef, +{ + pub did_doc: Data, } -impl IntoStatic for ResolveDidOutput<'_> { - type Output = ResolveDidOutput<'static>; +impl IntoStatic for ResolveDidOutput +where + S: Bos + AsRef + IntoStatic, + S::Output: Bos + AsRef, +{ + type Output = ResolveDidOutput; fn into_static(self) -> Self::Output { ResolveDidOutput { @@ -433,7 +459,7 @@ pub struct ResolveDidResponse; impl XrpcResp for ResolveDidResponse { const NSID: &'static str = "com.atproto.identity.resolveDid"; const ENCODING: &'static str = "application/json"; - type Output<'de> = ResolveDidOutput<'de>; + type Output<'de> = ResolveDidOutput; type Err<'de> = ResolveDidError<'de>; } -- 2.51.2