diff --git a/crates/jacquard-api/src/app_bsky/actor.rs b/crates/jacquard-api/src/app_bsky/actor.rs index 95382548..fe23b636 100644 --- a/crates/jacquard-api/src/app_bsky/actor.rs +++ b/crates/jacquard-api/src/app_bsky/actor.rs @@ -15,7 +15,6 @@ pub mod search_actors; pub mod search_actors_typeahead; pub mod status; - #[allow(unused_imports)] use alloc::collections::BTreeMap; @@ -25,15 +24,13 @@ use jacquard_common::CowStr; #[allow(unused_imports)] use jacquard_common::deps::codegen::unicode_segmentation::UnicodeSegmentation; -use jacquard_common::types::string::{Did, Handle, AtUri, Cid, Datetime, UriValue}; +use jacquard_common::types::string::{AtUri, Cid, Datetime, Did, Handle, UriValue}; use jacquard_common::types::value::Data; use jacquard_derive::{IntoStatic, lexicon, open_union}; use jacquard_lexicon::lexicon::LexiconDoc; use jacquard_lexicon::schema::LexiconSchema; -#[allow(unused_imports)] -use jacquard_lexicon::validation::{ConstraintError, ValidationPath}; -use serde::{Serialize, Deserialize}; +use crate::app_bsky::actor; use crate::app_bsky::embed::external::View; use crate::app_bsky::feed::postgate::DisableRule; use crate::app_bsky::feed::threadgate::FollowerRule; @@ -45,7 +42,9 @@ use crate::app_bsky::graph::StarterPackViewBasic; use crate::app_bsky::notification::ActivitySubscription; use crate::com_atproto::label::Label; use crate::com_atproto::repo::strong_ref::StrongRef; -use crate::app_bsky::actor; +#[allow(unused_imports)] +use jacquard_lexicon::validation::{ConstraintError, ValidationPath}; +use serde::{Deserialize, Serialize}; #[lexicon] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] @@ -85,7 +84,6 @@ pub struct BskyAppStatePref<'a> { pub queued_nudges: Option>>, } - #[lexicon] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic, Default)] #[serde(rename_all = "camelCase")] @@ -100,7 +98,6 @@ pub struct ContentLabelPref<'a> { pub visibility: ContentLabelPrefVisibility<'a>, } - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ContentLabelPrefVisibility<'a> { Ignore, @@ -218,7 +215,6 @@ pub struct DeclaredAgePref<'a> { pub is_over_age18: Option, } - #[lexicon] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic, Default)] #[serde(rename_all = "camelCase")] @@ -244,7 +240,6 @@ pub struct FeedViewPref<'a> { pub hide_reposts: Option, } - #[lexicon] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] #[serde(rename_all = "camelCase")] @@ -254,7 +249,6 @@ pub struct HiddenPostsPref<'a> { pub items: Vec>, } - #[lexicon] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] #[serde(rename_all = "camelCase")] @@ -275,7 +269,6 @@ pub struct KnownFollowers<'a> { pub followers: Vec>, } - #[lexicon] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] #[serde(rename_all = "camelCase")] @@ -284,7 +277,6 @@ pub struct LabelerPrefItem<'a> { pub did: Did<'a>, } - #[lexicon] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] #[serde(rename_all = "camelCase")] @@ -417,17 +409,12 @@ impl jacquard_common::IntoStatic for MutedWordActorTarget<'_> { fn into_static(self) -> Self::Output { match self { MutedWordActorTarget::All => MutedWordActorTarget::All, - MutedWordActorTarget::ExcludeFollowing => { - MutedWordActorTarget::ExcludeFollowing - } - MutedWordActorTarget::Other(v) => { - MutedWordActorTarget::Other(v.into_static()) - } + MutedWordActorTarget::ExcludeFollowing => MutedWordActorTarget::ExcludeFollowing, + MutedWordActorTarget::Other(v) => MutedWordActorTarget::Other(v.into_static()), } } } - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum MutedWordTarget<'a> { Content, @@ -510,7 +497,6 @@ impl jacquard_common::IntoStatic for MutedWordTarget<'_> { } } - #[lexicon] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] #[serde(rename_all = "camelCase")] @@ -540,7 +526,6 @@ pub struct Nux<'a> { pub id: CowStr<'a>, } - #[lexicon] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic, Default)] #[serde(rename_all = "camelCase")] @@ -563,12 +548,10 @@ pub struct PostInteractionSettingsPref<'a> { ///Matches threadgate record. List of rules defining who can reply to this users posts. If value is an empty array, no one can reply. If value is undefined, anyone can reply. #[serde(skip_serializing_if = "Option::is_none")] #[serde(borrow)] - pub threadgate_allow_rules: Option< - Vec>, - >, + pub threadgate_allow_rules: + Option>>, } - #[open_union] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] #[serde(tag = "$type", bound(deserialize = "'de: 'a"))] @@ -583,7 +566,6 @@ pub enum PostInteractionSettingsPrefThreadgateAllowRulesItem<'a> { ThreadgateListRule(Box>), } - #[open_union] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] #[serde(tag = "$type", bound(deserialize = "'de: 'a"))] @@ -647,7 +629,6 @@ pub struct ProfileAssociated<'a> { pub starter_packs: Option, } - #[lexicon] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic, Default)] #[serde(rename_all = "camelCase")] @@ -656,7 +637,6 @@ pub struct ProfileAssociatedActivitySubscription<'a> { pub allow_subscriptions: ProfileAssociatedActivitySubscriptionAllowSubscriptions<'a>, } - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ProfileAssociatedActivitySubscriptionAllowSubscriptions<'a> { Followers, @@ -698,8 +678,7 @@ impl<'a> From for ProfileAssociatedActivitySubscriptionAllowSubscription } } -impl<'a> core::fmt::Display -for ProfileAssociatedActivitySubscriptionAllowSubscriptions<'a> { +impl<'a> core::fmt::Display for ProfileAssociatedActivitySubscriptionAllowSubscriptions<'a> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{}", self.as_str()) } @@ -711,8 +690,7 @@ impl<'a> AsRef for ProfileAssociatedActivitySubscriptionAllowSubscriptions< } } -impl<'a> serde::Serialize -for ProfileAssociatedActivitySubscriptionAllowSubscriptions<'a> { +impl<'a> serde::Serialize for ProfileAssociatedActivitySubscriptionAllowSubscriptions<'a> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -722,7 +700,7 @@ for ProfileAssociatedActivitySubscriptionAllowSubscriptions<'a> { } impl<'de, 'a> serde::Deserialize<'de> -for ProfileAssociatedActivitySubscriptionAllowSubscriptions<'a> + for ProfileAssociatedActivitySubscriptionAllowSubscriptions<'a> where 'de: 'a, { @@ -741,8 +719,7 @@ impl<'a> Default for ProfileAssociatedActivitySubscriptionAllowSubscriptions<'a> } } -impl jacquard_common::IntoStatic -for ProfileAssociatedActivitySubscriptionAllowSubscriptions<'_> { +impl jacquard_common::IntoStatic for ProfileAssociatedActivitySubscriptionAllowSubscriptions<'_> { type Output = ProfileAssociatedActivitySubscriptionAllowSubscriptions<'static>; fn into_static(self) -> Self::Output { match self { @@ -756,15 +733,12 @@ for ProfileAssociatedActivitySubscriptionAllowSubscriptions<'_> { ProfileAssociatedActivitySubscriptionAllowSubscriptions::None } ProfileAssociatedActivitySubscriptionAllowSubscriptions::Other(v) => { - ProfileAssociatedActivitySubscriptionAllowSubscriptions::Other( - v.into_static(), - ) + ProfileAssociatedActivitySubscriptionAllowSubscriptions::Other(v.into_static()) } } } } - #[lexicon] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic, Default)] #[serde(rename_all = "camelCase")] @@ -773,7 +747,6 @@ pub struct ProfileAssociatedChat<'a> { pub allow_incoming: ProfileAssociatedChatAllowIncoming<'a>, } - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ProfileAssociatedChatAllowIncoming<'a> { All, @@ -859,12 +832,8 @@ impl jacquard_common::IntoStatic for ProfileAssociatedChatAllowIncoming<'_> { type Output = ProfileAssociatedChatAllowIncoming<'static>; fn into_static(self) -> Self::Output { match self { - ProfileAssociatedChatAllowIncoming::All => { - ProfileAssociatedChatAllowIncoming::All - } - ProfileAssociatedChatAllowIncoming::None => { - ProfileAssociatedChatAllowIncoming::None - } + ProfileAssociatedChatAllowIncoming::All => ProfileAssociatedChatAllowIncoming::All, + ProfileAssociatedChatAllowIncoming::None => ProfileAssociatedChatAllowIncoming::None, ProfileAssociatedChatAllowIncoming::Following => { ProfileAssociatedChatAllowIncoming::Following } @@ -875,7 +844,6 @@ impl jacquard_common::IntoStatic for ProfileAssociatedChatAllowIncoming<'_> { } } - #[lexicon] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] #[serde(rename_all = "camelCase")] @@ -886,7 +854,6 @@ pub struct ProfileAssociatedGerm<'a> { pub show_button_to: ProfileAssociatedGermShowButtonTo<'a>, } - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ProfileAssociatedGermShowButtonTo<'a> { UsersIFollow, @@ -981,7 +948,6 @@ impl jacquard_common::IntoStatic for ProfileAssociatedGermShowButtonTo<'_> { } } - #[lexicon] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] #[serde(rename_all = "camelCase")] @@ -1027,7 +993,6 @@ pub struct ProfileView<'a> { pub viewer: Option>, } - #[lexicon] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] #[serde(rename_all = "camelCase")] @@ -1068,7 +1033,6 @@ pub struct ProfileViewBasic<'a> { pub viewer: Option>, } - #[lexicon] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] #[serde(rename_all = "camelCase")] @@ -1132,7 +1096,6 @@ pub struct ProfileViewDetailed<'a> { pub website: Option>, } - #[lexicon] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] #[serde(rename_all = "camelCase")] @@ -1146,7 +1109,6 @@ pub struct SavedFeed<'a> { pub value: CowStr<'a>, } - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum SavedFeedType<'a> { Feed, @@ -1240,7 +1202,6 @@ impl jacquard_common::IntoStatic for SavedFeedType<'_> { } } - #[lexicon] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] #[serde(rename_all = "camelCase")] @@ -1253,7 +1214,6 @@ pub struct SavedFeedsPref<'a> { pub timeline_index: Option, } - #[lexicon] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] #[serde(rename_all = "camelCase")] @@ -1262,7 +1222,6 @@ pub struct SavedFeedsPrefV2<'a> { pub items: Vec>, } - #[lexicon] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] #[serde(rename_all = "camelCase")] @@ -1378,7 +1337,6 @@ impl jacquard_common::IntoStatic for StatusViewStatus<'_> { } } - #[lexicon] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic, Default)] #[serde(rename_all = "camelCase")] @@ -1713,15 +1671,9 @@ impl jacquard_common::IntoStatic for VerificationStateVerifiedStatus<'_> { type Output = VerificationStateVerifiedStatus<'static>; fn into_static(self) -> Self::Output { match self { - VerificationStateVerifiedStatus::Valid => { - VerificationStateVerifiedStatus::Valid - } - VerificationStateVerifiedStatus::Invalid => { - VerificationStateVerifiedStatus::Invalid - } - VerificationStateVerifiedStatus::None => { - VerificationStateVerifiedStatus::None - } + VerificationStateVerifiedStatus::Valid => VerificationStateVerifiedStatus::Valid, + VerificationStateVerifiedStatus::Invalid => VerificationStateVerifiedStatus::Invalid, + VerificationStateVerifiedStatus::None => VerificationStateVerifiedStatus::None, VerificationStateVerifiedStatus::Other(v) => { VerificationStateVerifiedStatus::Other(v.into_static()) } @@ -2543,7 +2495,7 @@ impl Default for AdultContentPref<'_> { pub mod adult_content_pref_state { - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; #[allow(unused)] use ::core::marker::PhantomData; mod sealed { @@ -2642,10 +2594,10 @@ where } fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { + use alloc::collections::BTreeMap; #[allow(unused_imports)] use jacquard_common::{CowStr, deps::smol_str::SmolStr, types::blob::MimeType}; use jacquard_lexicon::lexicon::*; - use alloc::collections::BTreeMap; LexiconDoc { lexicon: Lexicon::Lexicon1, id: CowStr::new_static("app.bsky.actor.defs"), @@ -2882,11 +2834,9 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { map.insert( SmolStr::new_static("items"), LexObjectProperty::Array(LexArray { - description: Some( - CowStr::new_static( - "A list of URIs of posts the account owner has hidden.", - ), - ), + description: Some(CowStr::new_static( + "A list of URIs of posts the account owner has hidden.", + )), items: LexArrayItem::String(LexString { format: Some(LexStringFormat::AtUri), ..Default::default() @@ -2931,17 +2881,13 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { map.insert( SmolStr::new_static("knownFollowers"), LexUserType::Object(LexObject { - description: Some( - CowStr::new_static( - "The subject's followers whom you also follow", - ), - ), - required: Some( - vec![ - SmolStr::new_static("count"), - SmolStr::new_static("followers") - ], - ), + description: Some(CowStr::new_static( + "The subject's followers whom you also follow", + )), + required: Some(vec![ + SmolStr::new_static("count"), + SmolStr::new_static("followers"), + ]), properties: { #[allow(unused_mut)] let mut map = BTreeMap::new(); @@ -3012,20 +2958,16 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { map.insert( SmolStr::new_static("liveEventPreferences"), LexUserType::Object(LexObject { - description: Some( - CowStr::new_static("Preferences for live events."), - ), + description: Some(CowStr::new_static("Preferences for live events.")), properties: { #[allow(unused_mut)] let mut map = BTreeMap::new(); map.insert( SmolStr::new_static("hiddenFeedIds"), LexObjectProperty::Array(LexArray { - description: Some( - CowStr::new_static( - "A list of feed IDs that the user has hidden from live events.", - ), - ), + description: Some(CowStr::new_static( + "A list of feed IDs that the user has hidden from live events.", + )), items: LexArrayItem::String(LexString { ..Default::default() }), @@ -3135,11 +3077,9 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { map.insert( SmolStr::new_static("items"), LexObjectProperty::Array(LexArray { - description: Some( - CowStr::new_static( - "A list of words the account owner has muted.", - ), - ), + description: Some(CowStr::new_static( + "A list of words the account owner has muted.", + )), items: LexArrayItem::Ref(LexRef { r#ref: CowStr::new_static("app.bsky.actor.defs#mutedWord"), ..Default::default() @@ -3216,9 +3156,9 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { map.insert( SmolStr::new_static("birthDate"), LexObjectProperty::String(LexString { - description: Some( - CowStr::new_static("The birth date of account owner."), - ), + description: Some(CowStr::new_static( + "The birth date of account owner.", + )), format: Some(LexStringFormat::Datetime), ..Default::default() }), @@ -3304,7 +3244,7 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { CowStr::new_static("#labelersPref"), CowStr::new_static("#postInteractionSettingsPref"), CowStr::new_static("#verificationPrefs"), - CowStr::new_static("#liveEventPreferences") + CowStr::new_static("#liveEventPreferences"), ], ..Default::default() }), @@ -3320,9 +3260,7 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { map.insert( SmolStr::new_static("activitySubscription"), LexObjectProperty::Ref(LexRef { - r#ref: CowStr::new_static( - "#profileAssociatedActivitySubscription", - ), + r#ref: CowStr::new_static("#profileAssociatedActivitySubscription"), ..Default::default() }), ); @@ -3378,7 +3316,9 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { let mut map = BTreeMap::new(); map.insert( SmolStr::new_static("allowSubscriptions"), - LexObjectProperty::String(LexString { ..Default::default() }), + LexObjectProperty::String(LexString { + ..Default::default() + }), ); map }, @@ -3394,7 +3334,9 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { let mut map = BTreeMap::new(); map.insert( SmolStr::new_static("allowIncoming"), - LexObjectProperty::String(LexString { ..Default::default() }), + LexObjectProperty::String(LexString { + ..Default::default() + }), ); map }, @@ -3404,12 +3346,10 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { map.insert( SmolStr::new_static("profileAssociatedGerm"), LexUserType::Object(LexObject { - required: Some( - vec![ - SmolStr::new_static("showButtonTo"), - SmolStr::new_static("messageMeUrl") - ], - ), + required: Some(vec![ + SmolStr::new_static("showButtonTo"), + SmolStr::new_static("messageMeUrl"), + ]), properties: { #[allow(unused_mut)] let mut map = BTreeMap::new(); @@ -3422,7 +3362,9 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { ); map.insert( SmolStr::new_static("showButtonTo"), - LexObjectProperty::String(LexString { ..Default::default() }), + LexObjectProperty::String(LexString { + ..Default::default() + }), ); map }, @@ -3432,9 +3374,10 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { map.insert( SmolStr::new_static("profileView"), LexUserType::Object(LexObject { - required: Some( - vec![SmolStr::new_static("did"), SmolStr::new_static("handle")], - ), + required: Some(vec![ + SmolStr::new_static("did"), + SmolStr::new_static("handle"), + ]), properties: { #[allow(unused_mut)] let mut map = BTreeMap::new(); @@ -3514,7 +3457,9 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { ); map.insert( SmolStr::new_static("pronouns"), - LexObjectProperty::String(LexString { ..Default::default() }), + LexObjectProperty::String(LexString { + ..Default::default() + }), ); map.insert( SmolStr::new_static("status"), @@ -3545,9 +3490,10 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { map.insert( SmolStr::new_static("profileViewBasic"), LexUserType::Object(LexObject { - required: Some( - vec![SmolStr::new_static("did"), SmolStr::new_static("handle")], - ), + required: Some(vec![ + SmolStr::new_static("did"), + SmolStr::new_static("handle"), + ]), properties: { #[allow(unused_mut)] let mut map = BTreeMap::new(); @@ -3612,7 +3558,9 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { ); map.insert( SmolStr::new_static("pronouns"), - LexObjectProperty::String(LexString { ..Default::default() }), + LexObjectProperty::String(LexString { + ..Default::default() + }), ); map.insert( SmolStr::new_static("status"), @@ -3643,9 +3591,10 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { map.insert( SmolStr::new_static("profileViewDetailed"), LexUserType::Object(LexObject { - required: Some( - vec![SmolStr::new_static("did"), SmolStr::new_static("handle")], - ), + required: Some(vec![ + SmolStr::new_static("did"), + SmolStr::new_static("handle"), + ]), properties: { #[allow(unused_mut)] let mut map = BTreeMap::new(); @@ -3766,7 +3715,9 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { ); map.insert( SmolStr::new_static("pronouns"), - LexObjectProperty::String(LexString { ..Default::default() }), + LexObjectProperty::String(LexString { + ..Default::default() + }), ); map.insert( SmolStr::new_static("status"), @@ -3804,18 +3755,20 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { map.insert( SmolStr::new_static("savedFeed"), LexUserType::Object(LexObject { - required: Some( - vec![ - SmolStr::new_static("id"), SmolStr::new_static("type"), - SmolStr::new_static("value"), SmolStr::new_static("pinned") - ], - ), + required: Some(vec![ + SmolStr::new_static("id"), + SmolStr::new_static("type"), + SmolStr::new_static("value"), + SmolStr::new_static("pinned"), + ]), properties: { #[allow(unused_mut)] let mut map = BTreeMap::new(); map.insert( SmolStr::new_static("id"), - LexObjectProperty::String(LexString { ..Default::default() }), + LexObjectProperty::String(LexString { + ..Default::default() + }), ); map.insert( SmolStr::new_static("pinned"), @@ -3825,11 +3778,15 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { ); map.insert( SmolStr::new_static("type"), - LexObjectProperty::String(LexString { ..Default::default() }), + LexObjectProperty::String(LexString { + ..Default::default() + }), ); map.insert( SmolStr::new_static("value"), - LexObjectProperty::String(LexString { ..Default::default() }), + LexObjectProperty::String(LexString { + ..Default::default() + }), ); map }, @@ -3839,9 +3796,10 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { map.insert( SmolStr::new_static("savedFeedsPref"), LexUserType::Object(LexObject { - required: Some( - vec![SmolStr::new_static("pinned"), SmolStr::new_static("saved")], - ), + required: Some(vec![ + SmolStr::new_static("pinned"), + SmolStr::new_static("saved"), + ]), properties: { #[allow(unused_mut)] let mut map = BTreeMap::new(); @@ -3990,9 +3948,7 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { map.insert( SmolStr::new_static("sort"), LexObjectProperty::String(LexString { - description: Some( - CowStr::new_static("Sorting mode for threads."), - ), + description: Some(CowStr::new_static("Sorting mode for threads.")), ..Default::default() }), ); @@ -4004,11 +3960,9 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { map.insert( SmolStr::new_static("verificationPrefs"), LexUserType::Object(LexObject { - description: Some( - CowStr::new_static( - "Preferences for how verified accounts appear in the app.", - ), - ), + description: Some(CowStr::new_static( + "Preferences for how verified accounts appear in the app.", + )), required: Some(vec![]), properties: { #[allow(unused_mut)] @@ -4087,29 +4041,24 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { map.insert( SmolStr::new_static("verificationView"), LexUserType::Object(LexObject { - description: Some( - CowStr::new_static( - "An individual verification for an associated subject.", - ), - ), - required: Some( - vec![ - SmolStr::new_static("issuer"), SmolStr::new_static("uri"), - SmolStr::new_static("isValid"), - SmolStr::new_static("createdAt") - ], - ), + description: Some(CowStr::new_static( + "An individual verification for an associated subject.", + )), + required: Some(vec![ + SmolStr::new_static("issuer"), + SmolStr::new_static("uri"), + SmolStr::new_static("isValid"), + SmolStr::new_static("createdAt"), + ]), properties: { #[allow(unused_mut)] let mut map = BTreeMap::new(); map.insert( SmolStr::new_static("createdAt"), LexObjectProperty::String(LexString { - description: Some( - CowStr::new_static( - "Timestamp when the verification was created.", - ), - ), + description: Some(CowStr::new_static( + "Timestamp when the verification was created.", + )), format: Some(LexStringFormat::Datetime), ..Default::default() }), @@ -4123,9 +4072,9 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { map.insert( SmolStr::new_static("issuer"), LexObjectProperty::String(LexString { - description: Some( - CowStr::new_static("The user who issued this verification."), - ), + description: Some(CowStr::new_static( + "The user who issued this verification.", + )), format: Some(LexStringFormat::Did), ..Default::default() }), @@ -4133,9 +4082,9 @@ fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { map.insert( SmolStr::new_static("uri"), LexObjectProperty::String(LexString { - description: Some( - CowStr::new_static("The AT-URI of the verification record."), - ), + description: Some(CowStr::new_static( + "The AT-URI of the verification record.", + )), format: Some(LexStringFormat::AtUri), ..Default::default() }), @@ -4240,7 +4189,7 @@ fn _default_feed_view_pref_hide_replies_by_unfollowed() -> Option { pub mod hidden_posts_pref_state { - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; #[allow(unused)] use ::core::marker::PhantomData; mod sealed { @@ -4340,7 +4289,7 @@ where pub mod interests_pref_state { - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; #[allow(unused)] use ::core::marker::PhantomData; mod sealed { @@ -4440,7 +4389,7 @@ where pub mod known_followers_state { - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; #[allow(unused)] use ::core::marker::PhantomData; mod sealed { @@ -4574,7 +4523,7 @@ where pub mod labeler_pref_item_state { - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; #[allow(unused)] use ::core::marker::PhantomData; mod sealed { @@ -4674,7 +4623,7 @@ where pub mod labelers_pref_state { - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; #[allow(unused)] use ::core::marker::PhantomData; mod sealed { @@ -4788,7 +4737,7 @@ impl Default for LiveEventPreferences<'_> { pub mod muted_word_state { - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; #[allow(unused)] use ::core::marker::PhantomData; mod sealed { @@ -4863,18 +4812,12 @@ impl<'a> MutedWordBuilder<'a, muted_word_state::Empty> { impl<'a, S: muted_word_state::State> MutedWordBuilder<'a, S> { /// Set the `actorTarget` field (optional) - pub fn actor_target( - mut self, - value: impl Into>>, - ) -> Self { + pub fn actor_target(mut self, value: impl Into>>) -> Self { self._fields.0 = value.into(); self } /// Set the `actorTarget` field to an Option value (optional) - pub fn maybe_actor_target( - mut self, - value: Option>, - ) -> Self { + pub fn maybe_actor_target(mut self, value: Option>) -> Self { self._fields.0 = value; self } @@ -4979,7 +4922,7 @@ where pub mod muted_words_pref_state { - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; #[allow(unused)] use ::core::marker::PhantomData; mod sealed { @@ -5083,7 +5026,7 @@ fn _default_nux_completed() -> bool { pub mod nux_state { - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; #[allow(unused)] use ::core::marker::PhantomData; mod sealed { @@ -5128,7 +5071,12 @@ pub mod nux_state { /// Builder for constructing an instance of this type pub struct NuxBuilder<'a, S: nux_state::State> { _state: PhantomData S>, - _fields: (Option, Option>, Option, Option>), + _fields: ( + Option, + Option>, + Option, + Option>, + ), _lifetime: PhantomData<&'a ()>, } @@ -5201,10 +5149,7 @@ where S::Id: nux_state::IsUnset, { /// Set the `id` field (required) - pub fn id( - mut self, - value: impl Into>, - ) -> NuxBuilder<'a, nux_state::SetId> { + pub fn id(mut self, value: impl Into>) -> NuxBuilder<'a, nux_state::SetId> { self._fields.3 = Option::Some(value.into()); NuxBuilder { _state: PhantomData, @@ -5247,7 +5192,7 @@ where pub mod profile_associated_germ_state { - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; #[allow(unused)] use ::core::marker::PhantomData; mod sealed { @@ -5292,16 +5237,16 @@ pub mod profile_associated_germ_state { /// Builder for constructing an instance of this type pub struct ProfileAssociatedGermBuilder<'a, S: profile_associated_germ_state::State> { _state: PhantomData S>, - _fields: (Option>, Option>), + _fields: ( + Option>, + Option>, + ), _lifetime: PhantomData<&'a ()>, } impl<'a> ProfileAssociatedGerm<'a> { /// Create a new builder for this type - pub fn new() -> ProfileAssociatedGermBuilder< - 'a, - profile_associated_germ_state::Empty, - > { + pub fn new() -> ProfileAssociatedGermBuilder<'a, profile_associated_germ_state::Empty> { ProfileAssociatedGermBuilder::new() } } @@ -5326,10 +5271,7 @@ where pub fn message_me_url( mut self, value: impl Into>, - ) -> ProfileAssociatedGermBuilder< - 'a, - profile_associated_germ_state::SetMessageMeUrl, - > { + ) -> ProfileAssociatedGermBuilder<'a, profile_associated_germ_state::SetMessageMeUrl> { self._fields.0 = Option::Some(value.into()); ProfileAssociatedGermBuilder { _state: PhantomData, @@ -5348,10 +5290,7 @@ where pub fn show_button_to( mut self, value: impl Into>, - ) -> ProfileAssociatedGermBuilder< - 'a, - profile_associated_germ_state::SetShowButtonTo, - > { + ) -> ProfileAssociatedGermBuilder<'a, profile_associated_germ_state::SetShowButtonTo> { self._fields.1 = Option::Some(value.into()); ProfileAssociatedGermBuilder { _state: PhantomData, @@ -5390,7 +5329,7 @@ where pub mod profile_view_state { - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; #[allow(unused)] use ::core::marker::PhantomData; mod sealed { @@ -5467,20 +5406,7 @@ impl<'a> ProfileViewBuilder<'a, profile_view_state::Empty> { ProfileViewBuilder { _state: PhantomData, _fields: ( - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, ), _lifetime: PhantomData, } @@ -5489,18 +5415,12 @@ impl<'a> ProfileViewBuilder<'a, profile_view_state::Empty> { impl<'a, S: profile_view_state::State> ProfileViewBuilder<'a, S> { /// Set the `associated` field (optional) - pub fn associated( - mut self, - value: impl Into>>, - ) -> Self { + pub fn associated(mut self, value: impl Into>>) -> Self { self._fields.0 = value.into(); self } /// Set the `associated` field to an Option value (optional) - pub fn maybe_associated( - mut self, - value: Option>, - ) -> Self { + pub fn maybe_associated(mut self, value: Option>) -> Self { self._fields.0 = value; self } @@ -5663,18 +5583,12 @@ impl<'a, S: profile_view_state::State> ProfileViewBuilder<'a, S> { impl<'a, S: profile_view_state::State> ProfileViewBuilder<'a, S> { /// Set the `verification` field (optional) - pub fn verification( - mut self, - value: impl Into>>, - ) -> Self { + pub fn verification(mut self, value: impl Into>>) -> Self { self._fields.12 = value.into(); self } /// Set the `verification` field to an Option value (optional) - pub fn maybe_verification( - mut self, - value: Option>, - ) -> Self { + pub fn maybe_verification(mut self, value: Option>) -> Self { self._fields.12 = value; self } @@ -5746,7 +5660,7 @@ where pub mod profile_view_basic_state { - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; #[allow(unused)] use ::core::marker::PhantomData; mod sealed { @@ -5821,18 +5735,7 @@ impl<'a> ProfileViewBasicBuilder<'a, profile_view_basic_state::Empty> { ProfileViewBasicBuilder { _state: PhantomData, _fields: ( - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, + None, None, None, None, None, None, None, None, None, None, None, None, ), _lifetime: PhantomData, } @@ -5841,18 +5744,12 @@ impl<'a> ProfileViewBasicBuilder<'a, profile_view_basic_state::Empty> { impl<'a, S: profile_view_basic_state::State> ProfileViewBasicBuilder<'a, S> { /// Set the `associated` field (optional) - pub fn associated( - mut self, - value: impl Into>>, - ) -> Self { + pub fn associated(mut self, value: impl Into>>) -> Self { self._fields.0 = value.into(); self } /// Set the `associated` field to an Option value (optional) - pub fn maybe_associated( - mut self, - value: Option>, - ) -> Self { + pub fn maybe_associated(mut self, value: Option>) -> Self { self._fields.0 = value; self } @@ -5989,18 +5886,12 @@ impl<'a, S: profile_view_basic_state::State> ProfileViewBasicBuilder<'a, S> { impl<'a, S: profile_view_basic_state::State> ProfileViewBasicBuilder<'a, S> { /// Set the `verification` field (optional) - pub fn verification( - mut self, - value: impl Into>>, - ) -> Self { + pub fn verification(mut self, value: impl Into>>) -> Self { self._fields.10 = value.into(); self } /// Set the `verification` field to an Option value (optional) - pub fn maybe_verification( - mut self, - value: Option>, - ) -> Self { + pub fn maybe_verification(mut self, value: Option>) -> Self { self._fields.10 = value; self } @@ -6068,7 +5959,7 @@ where pub mod profile_view_detailed_state { - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; #[allow(unused)] use ::core::marker::PhantomData; mod sealed { @@ -6152,27 +6043,8 @@ impl<'a> ProfileViewDetailedBuilder<'a, profile_view_detailed_state::Empty> { ProfileViewDetailedBuilder { _state: PhantomData, _fields: ( - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, ), _lifetime: PhantomData, } @@ -6181,18 +6053,12 @@ impl<'a> ProfileViewDetailedBuilder<'a, profile_view_detailed_state::Empty> { impl<'a, S: profile_view_detailed_state::State> ProfileViewDetailedBuilder<'a, S> { /// Set the `associated` field (optional) - pub fn associated( - mut self, - value: impl Into>>, - ) -> Self { + pub fn associated(mut self, value: impl Into>>) -> Self { self._fields.0 = value.into(); self } /// Set the `associated` field to an Option value (optional) - pub fn maybe_associated( - mut self, - value: Option>, - ) -> Self { + pub fn maybe_associated(mut self, value: Option>) -> Self { self._fields.0 = value; self } @@ -6439,18 +6305,12 @@ impl<'a, S: profile_view_detailed_state::State> ProfileViewDetailedBuilder<'a, S impl<'a, S: profile_view_detailed_state::State> ProfileViewDetailedBuilder<'a, S> { /// Set the `verification` field (optional) - pub fn verification( - mut self, - value: impl Into>>, - ) -> Self { + pub fn verification(mut self, value: impl Into>>) -> Self { self._fields.18 = value.into(); self } /// Set the `verification` field to an Option value (optional) - pub fn maybe_verification( - mut self, - value: Option>, - ) -> Self { + pub fn maybe_verification(mut self, value: Option>) -> Self { self._fields.18 = value; self } @@ -6549,7 +6409,7 @@ where pub mod saved_feed_state { - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; #[allow(unused)] use ::core::marker::PhantomData; mod sealed { @@ -6762,7 +6622,7 @@ where pub mod saved_feeds_pref_state { - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; #[allow(unused)] use ::core::marker::PhantomData; mod sealed { @@ -6911,7 +6771,7 @@ where pub mod saved_feeds_pref_v2_state { - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; #[allow(unused)] use ::core::marker::PhantomData; mod sealed { @@ -7011,7 +6871,7 @@ where pub mod status_view_state { - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; #[allow(unused)] use ::core::marker::PhantomData; mod sealed { @@ -7257,7 +7117,7 @@ impl Default for VerificationPrefs<'_> { pub mod verification_state_state { - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; #[allow(unused)] use ::core::marker::PhantomData; mod sealed { @@ -7351,10 +7211,7 @@ where pub fn trusted_verifier_status( mut self, value: impl Into>, - ) -> VerificationStateBuilder< - 'a, - verification_state_state::SetTrustedVerifierStatus, - > { + ) -> VerificationStateBuilder<'a, verification_state_state::SetTrustedVerifierStatus> { self._fields.0 = Option::Some(value.into()); VerificationStateBuilder { _state: PhantomData, @@ -7434,7 +7291,7 @@ where pub mod verification_view_state { - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; #[allow(unused)] use ::core::marker::PhantomData; mod sealed { @@ -7509,7 +7366,12 @@ pub mod verification_view_state { /// Builder for constructing an instance of this type pub struct VerificationViewBuilder<'a, S: verification_view_state::State> { _state: PhantomData S>, - _fields: (Option, Option, Option>, Option>), + _fields: ( + Option, + Option, + Option>, + Option>, + ), _lifetime: PhantomData<&'a ()>, } @@ -7638,4 +7500,4 @@ where extra_data: Some(extra_data), } } -} \ No newline at end of file +} diff --git a/crates/jacquard-common/src/jetstream.rs b/crates/jacquard-common/src/jetstream.rs index 5ec38a86..7d774c80 100644 --- a/crates/jacquard-common/src/jetstream.rs +++ b/crates/jacquard-common/src/jetstream.rs @@ -95,7 +95,7 @@ pub struct JetstreamCommit<'a> { pub collection: Nsid>, /// Record key #[serde(borrow)] - pub rkey: Rkey<'a>, + pub rkey: Rkey>, /// Record data (present for create/update) #[serde(skip_serializing_if = "Option::is_none")] #[serde(borrow)] @@ -103,7 +103,7 @@ pub struct JetstreamCommit<'a> { /// Content identifier #[serde(skip_serializing_if = "Option::is_none")] #[serde(borrow)] - pub cid: Option>, + pub cid: Option>>, } /// Identity event details diff --git a/crates/jacquard-common/src/types/aturi.rs b/crates/jacquard-common/src/types/aturi.rs index 8e58f2a9..d806de12 100644 --- a/crates/jacquard-common/src/types/aturi.rs +++ b/crates/jacquard-common/src/types/aturi.rs @@ -76,7 +76,7 @@ impl Clone for AtUri<'_> { let collection = unsafe { Nsid::unchecked(CowStr::Borrowed(collection.as_str())) }; let rkey = if let Some(rkey) = parts.name("rkey") { - let rkey = unsafe { RecordKey::from(Rkey::unchecked(rkey.as_str())) }; + let rkey = unsafe { RecordKey(Rkey::unchecked_cow(CowStr::Borrowed(rkey.as_str()))) }; Some(rkey) } else { None @@ -112,7 +112,7 @@ pub struct RepoPath<'u> { /// Collection NSID (e.g., `app.bsky.feed.post`) pub collection: Nsid>, /// Optional record key identifying a specific record - pub rkey: Option>>, + pub rkey: Option>>>, } impl fmt::Display for RepoPath<'_> { @@ -157,7 +157,7 @@ impl<'u> AtUri<'u> { .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?; let rkey = if let Some(rkey) = parts.name("rkey") { let rkey = - RecordKey::from(Rkey::new(rkey.as_str()).map_err(|e| { + RecordKey(Rkey::new_cow(CowStr::Borrowed(rkey.as_str())).map_err(|e| { AtStrError::wrap("at-uri-scheme", uri.to_string(), e) })?); Some(rkey) @@ -203,7 +203,7 @@ impl<'u> AtUri<'u> { let path = if let Some(collection) = parts.name("collection") { let collection = Nsid::new_cow(CowStr::Borrowed(collection.as_str())).unwrap(); let rkey = if let Some(rkey) = parts.name("rkey") { - let rkey = RecordKey::from(Rkey::raw(rkey.as_str())); + let rkey = RecordKey(Rkey::new_cow(CowStr::Borrowed(rkey.as_str())).unwrap()); Some(rkey) } else { None @@ -246,7 +246,7 @@ impl<'u> AtUri<'u> { let collection = unsafe { Nsid::unchecked(CowStr::Borrowed(collection.as_str())) }; let rkey = if let Some(rkey) = parts.name("rkey") { - let rkey = RecordKey::from(unsafe { Rkey::unchecked(rkey.as_str()) }); + let rkey = unsafe { RecordKey(Rkey::unchecked_cow(CowStr::Borrowed(rkey.as_str()))) }; Some(rkey) } else { None @@ -350,7 +350,7 @@ impl<'u> AtUri<'u> { } /// Get the record key from the path, if present - pub fn rkey(&self) -> Option<&RecordKey>> { + pub fn rkey(&self) -> Option<&RecordKey>>> { self.inner .borrow_path() .as_ref() @@ -414,7 +414,7 @@ impl AtUri<'static> { AtStrError::wrap("at-uri-scheme", uri.as_ref().to_string(), e) })?; let rkey = if let Some(rkey) = parts.name("rkey") { - let rkey = RecordKey::from(Rkey::new(rkey.as_str()).map_err(|e| { + let rkey = RecordKey(Rkey::new_cow(CowStr::Borrowed(rkey.as_str())).map_err(|e| { AtStrError::wrap("at-uri-scheme", uri.as_ref().to_string(), e) })?); Some(rkey) @@ -446,7 +446,7 @@ impl AtUri<'static> { }; let rkey = if let Some(rkey) = parts.name("rkey") { let rkey = unsafe { - RecordKey::from(Rkey::unchecked(rkey.as_str())) + RecordKey(Rkey::unchecked_cow(CowStr::Borrowed(rkey.as_str()))) }; Some(rkey) } else { @@ -497,7 +497,7 @@ impl AtUri<'static> { .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?; let rkey = if let Some(rkey) = parts.name("rkey") { let rkey = - RecordKey::from(Rkey::new_static(rkey.as_str()).map_err(|e| { + RecordKey(Rkey::new_cow(CowStr::Borrowed(rkey.as_str())).map_err(|e| { AtStrError::wrap("at-uri-scheme", uri.to_string(), e) })?); Some(rkey) @@ -549,7 +549,7 @@ impl FromStr for AtUri<'_> { .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?; let rkey = if let Some(rkey) = parts.name("rkey") { let rkey = - RecordKey::from(Rkey::new(rkey.as_str()).map_err(|e| { + RecordKey(Rkey::new_cow(CowStr::Borrowed(rkey.as_str())).map_err(|e| { AtStrError::wrap("at-uri-scheme", uri.to_string(), e) })?); Some(rkey) @@ -581,7 +581,7 @@ impl FromStr for AtUri<'_> { }; let rkey = if let Some(rkey) = parts.name("rkey") { let rkey = unsafe { - RecordKey::from(Rkey::unchecked(rkey.as_str())) + RecordKey(Rkey::unchecked_cow(CowStr::Borrowed(rkey.as_str()))) }; Some(rkey) } else { @@ -644,7 +644,7 @@ impl IntoStatic for AtUri<'_> { unsafe { Nsid::unchecked(CowStr::Borrowed(collection.as_str())) }; let rkey = if let Some(rkey) = parts.name("rkey") { let rkey = - unsafe { RecordKey::from(Rkey::unchecked(rkey.as_str())) }; + unsafe { RecordKey(Rkey::unchecked_cow(CowStr::Borrowed(rkey.as_str()))) }; Some(rkey) } else { None @@ -733,7 +733,7 @@ impl<'d> TryFrom> for AtUri<'d> { .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?; let rkey = if let Some(rkey) = parts.name("rkey") { let rkey = - RecordKey::from(Rkey::new(rkey.as_str()).map_err(|e| { + RecordKey(Rkey::new_cow(CowStr::Borrowed(rkey.as_str())).map_err(|e| { AtStrError::wrap("at-uri-scheme", uri.to_string(), e) })?); Some(rkey) @@ -765,7 +765,7 @@ impl<'d> TryFrom> for AtUri<'d> { }; let rkey = if let Some(rkey) = parts.name("rkey") { let rkey = - unsafe { RecordKey::from(Rkey::unchecked(rkey.as_str())) }; + unsafe { RecordKey(Rkey::unchecked_cow(CowStr::Borrowed(rkey.as_str()))) }; Some(rkey) } else { None diff --git a/crates/jacquard-common/src/types/blob.rs b/crates/jacquard-common/src/types/blob.rs index e6886191..9fff3c22 100644 --- a/crates/jacquard-common/src/types/blob.rs +++ b/crates/jacquard-common/src/types/blob.rs @@ -1,51 +1,49 @@ +use crate::bos::{Bos, DefaultStr}; use crate::types::cid::Cid; 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::ToSmolStr; +use smol_str::{SmolStr, ToSmolStr}; -/// Blob reference for binary data in AT Protocol -/// -/// Blobs represent uploaded binary data (images, videos, etc.) stored separately from records. -/// They include a CID reference, MIME type, and size information. +/// Blob reference for binary data in AT Protocol. /// /// Serialization differs between formats: /// - JSON: `ref` is serialized as `{"$link": "cid_string"}` /// - CBOR: `ref` is the raw CID #[derive(Deserialize, Debug, Clone, PartialEq, Eq, Hash)] #[serde(rename_all = "camelCase")] -pub struct Blob<'b> { - /// CID (Content Identifier) reference to the blob data - pub r#ref: CidLink<'b>, - /// MIME type of the blob (e.g., "image/png", "video/mp4") - #[serde(borrow)] - pub mime_type: MimeType<'b>, - /// Size of the blob in bytes +#[serde(bound(deserialize = "S: Bos + AsRef + Deserialize<'de>"))] +pub struct Blob + AsRef = DefaultStr> { + /// CID (Content Identifier) reference to the blob data. + pub r#ref: CidLink, + /// MIME type of the blob (e.g., "image/png", "video/mp4"). + pub mime_type: MimeType, + /// Size of the blob in bytes. pub size: usize, } -impl<'b> Blob<'b> { - /// Create a new Blob reference with the given CID, MIME type, and size. - pub fn cid(&self) -> &Cid<'b> { +impl + AsRef> Blob { + /// Get the inner CID reference. + pub fn cid(&self) -> &Cid { &self.r#ref.0 } } -impl Serialize for Blob<'_> { - fn serialize(&self, serializer: S) -> Result +impl + AsRef + Serialize> Serialize for Blob { + fn serialize(&self, serializer: Ser) -> Result where - S: Serializer, + Ser: Serializer, { use serde::ser::SerializeMap; if serializer.is_human_readable() { - // JSON: ref needs to be {"$link": "cid"} + // JSON: ref needs to be {"$link": "cid"}. let mut map = serializer.serialize_map(Some(4))?; map.serialize_entry("$type", "blob")?; - // Serialize ref as {"$link": "cid_string"} + // Serialize ref as {"$link": "cid_string"}. let mut ref_map = alloc::collections::BTreeMap::new(); ref_map.insert("$link", self.r#ref.as_str()); map.serialize_entry("ref", &ref_map)?; @@ -54,7 +52,7 @@ impl Serialize for Blob<'_> { map.serialize_entry("size", &self.size)?; map.end() } else { - // CBOR: ref is just the CID directly + // CBOR: ref is just the CID directly. let mut map = serializer.serialize_map(Some(4))?; map.serialize_entry("$type", "blob")?; map.serialize_entry("ref", &self.r#ref)?; @@ -65,8 +63,11 @@ impl Serialize for Blob<'_> { } } -impl IntoStatic for Blob<'_> { - type Output = Blob<'static>; +impl + AsRef + IntoStatic> IntoStatic for Blob +where + S::Output: Bos + AsRef, +{ + type Output = Blob; fn into_static(self) -> Self::Output { Blob { @@ -77,43 +78,46 @@ impl IntoStatic for Blob<'_> { } } -/// Tagged blob reference with `$type` field for serde -/// -/// This enum provides the `{"$type": "blob"}` wrapper expected by AT Protocol's JSON format. -/// Currently only contains the `Blob` variant, but the enum structure supports future extensions. +/// Tagged blob reference with `$type` field for serde. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(tag = "$type", rename_all = "lowercase")] -pub enum BlobRef<'r> { - /// Blob variant with embedded blob data - #[serde(borrow)] - Blob(Blob<'r>), -} - -impl<'r> BlobRef<'r> { - /// Get the inner blob reference - pub fn blob(&self) -> &Blob<'r> { +#[serde(bound( + serialize = "S: Bos + AsRef + Serialize", + deserialize = "S: Bos + AsRef + Deserialize<'de>" +))] +pub enum BlobRef + AsRef = DefaultStr> { + /// Blob variant with embedded blob data. + Blob(Blob), +} + +impl + AsRef> BlobRef { + /// Get the inner blob reference. + pub fn blob(&self) -> &Blob { match self { BlobRef::Blob(blob) => blob, } } } -impl<'b> From> for Blob<'b> { - fn from(blob_ref: BlobRef<'b>) -> Self { +impl + AsRef> From> for Blob { + fn from(blob_ref: BlobRef) -> Self { match blob_ref { BlobRef::Blob(blob) => blob, } } } -impl<'b> From> for BlobRef<'b> { - fn from(blob: Blob<'b>) -> Self { +impl + AsRef> From> for BlobRef { + fn from(blob: Blob) -> Self { BlobRef::Blob(blob) } } -impl IntoStatic for BlobRef<'_> { - type Output = BlobRef<'static>; +impl + AsRef + IntoStatic> IntoStatic for BlobRef +where + S::Output: Bos + AsRef, +{ + type Output = BlobRef; fn into_static(self) -> Self::Output { match self { @@ -122,121 +126,115 @@ impl IntoStatic for BlobRef<'_> { } } -/// MIME type identifier for blob data +/// MIME type identifier for blob data. /// /// Used to specify the content type of blobs. Supports patterns like "image/*" and "*/*". +/// No validation is performed — any string is accepted. #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)] #[serde(transparent)] #[repr(transparent)] -pub struct MimeType<'m>(pub CowStr<'m>); +pub struct MimeType = DefaultStr>(pub S); -impl<'m> MimeType<'m> { - /// Fallible constructor, validates, borrows from input - pub fn new(mime_type: &'m str) -> Result, &'static str> { - Ok(Self(CowStr::Borrowed(mime_type))) +impl + AsRef> MimeType { + /// Get the MIME type as a string slice. + pub fn as_str(&self) -> &str { + self.0.as_ref() } +} - /// Fallible constructor, validates, takes ownership - pub fn new_owned(mime_type: impl AsRef) -> Self { - Self(CowStr::Owned(mime_type.as_ref().to_smolstr())) +impl<'m> MimeType<&'m str> { + /// Infallible constructor, borrows from input. + pub fn new(mime_type: &'m str) -> Self { + Self(mime_type) } - /// Fallible constructor, validates, doesn't allocate - pub fn new_static(mime_type: &'static str) -> Self { - Self(CowStr::new_static(mime_type)) + /// Infallible constructor for trusted MIME type strings. + pub fn raw(mime_type: &'m str) -> Self { + Self(mime_type) } +} - /// Fallible constructor from an existing CowStr - pub fn from_cowstr(mime_type: CowStr<'m>) -> Result, &'static str> { - Ok(Self(mime_type)) +impl + From> MimeType { + /// Infallible constructor, takes ownership. + pub fn new_owned(mime_type: impl AsRef) -> Self { + Self(S::from(mime_type.as_ref().to_smolstr())) } - /// Fallible constructor, validates, borrows from input if possible - pub fn new_cow(mime_type: CowStr<'m>) -> Result, &'static str> { - Self::from_cowstr(mime_type) + /// Infallible constructor for static strings. + pub fn new_static(mime_type: &'static str) -> Self { + Self(S::from(SmolStr::new_static(mime_type))) } +} - /// Infallible constructor for trusted MIME type strings - pub fn raw(mime_type: &'m str) -> Self { - Self(CowStr::Borrowed(mime_type)) +impl<'m> MimeType> { + /// Infallible constructor, borrows if possible. + pub fn new_cow(mime_type: CowStr<'m>) -> Self { + Self(mime_type) } +} - /// Get the MIME type as a string slice - pub fn as_str(&self) -> &str { - { - let this = &self.0; - this - } +impl<'de, S> Deserialize<'de> for MimeType +where + S: Bos + Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(MimeType(S::deserialize(deserializer)?)) } } -impl FromStr for MimeType<'_> { +impl FromStr for MimeType { type Err = Infallible; - /// Has to take ownership due to the lifetime constraints of the FromStr trait. fn from_str(s: &str) -> Result { Ok(Self::new_owned(s)) } } -impl IntoStatic for MimeType<'_> { - type Output = MimeType<'static>; +impl + IntoStatic> IntoStatic for MimeType +where + S::Output: Bos, +{ + type Output = MimeType; fn into_static(self) -> Self::Output { MimeType(self.0.into_static()) } } -impl<'de, 'b> Deserialize<'de> for MimeType<'b> -where - 'de: 'b, -{ - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let value = Deserialize::deserialize(deserializer)?; - Self::new_cow(value).map_err(D::Error::custom) - } -} - -impl fmt::Display for MimeType<'_> { +impl + AsRef> fmt::Display for MimeType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.0) - } -} - -impl<'m> From> for String { - fn from(value: MimeType<'m>) -> Self { - value.0.to_string() + f.write_str(self.as_str()) } } -impl<'m> From> for CowStr<'m> { - fn from(value: MimeType<'m>) -> Self { - value.0 +impl + AsRef> From> for String { + fn from(value: MimeType) -> Self { + value.as_str().to_string() } } -impl From for MimeType<'static> { +impl From for MimeType { fn from(value: String) -> Self { - Self(CowStr::Owned(value.to_smolstr())) + Self::new_owned(value) } } -impl<'m> From> for MimeType<'m> { +impl<'m> From> for MimeType> { fn from(value: CowStr<'m>) -> Self { Self(value) } } -impl AsRef for MimeType<'_> { +impl + AsRef> AsRef for MimeType { fn as_ref(&self) -> &str { self.as_str() } } -impl Deref for MimeType<'_> { +impl + AsRef> Deref for MimeType { type Target = str; fn deref(&self) -> &Self::Target { diff --git a/crates/jacquard-common/src/types/cid.rs b/crates/jacquard-common/src/types/cid.rs index ede5bda0..34f091e1 100644 --- a/crates/jacquard-common/src/types/cid.rs +++ b/crates/jacquard-common/src/types/cid.rs @@ -1,24 +1,25 @@ +use crate::bos::{Bos, DefaultStr}; use crate::{CowStr, IntoStatic, cowstr::ToCowStr}; use alloc::string::{String, ToString}; pub use cid::Cid as IpldCid; use core::{convert::Infallible, fmt, ops::Deref, str::FromStr}; use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor}; -use smol_str::ToSmolStr; +use smol_str::{SmolStr, ToSmolStr}; -/// CID codec for AT Protocol (raw) +/// CID codec for AT Protocol (raw). pub const ATP_CID_CODEC: u64 = 0x55; -/// CID hash function for AT Protocol (SHA-256) +/// CID hash function for AT Protocol (SHA-256). pub const ATP_CID_HASH: u64 = 0x12; -/// CID encoding base for AT Protocol (base32 lowercase) +/// CID encoding base for AT Protocol (base32 lowercase). pub const ATP_CID_BASE: multibase::Base = multibase::Base::Base32Lower; -/// Content Identifier (CID) for IPLD data in AT Protocol +/// Content Identifier (CID) for IPLD data in AT Protocol. /// /// CIDs are self-describing content addresses used to reference IPLD data. /// This type supports both string and parsed IPLD forms, with string caching -/// for the parsed form to optimize serialization. +/// for the parsed form to optimise serialization. /// /// # Validation /// @@ -29,88 +30,53 @@ pub const ATP_CID_BASE: multibase::Base = multibase::Base::Base32Lower; /// /// Byte deserialization (CBOR) parses immediately since the data is already in binary form. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Cid<'c> { - /// Parsed IPLD CID with cached string representation +pub enum Cid = DefaultStr> { + /// Parsed IPLD CID with cached string representation. + /// The cached string is always SmolStr regardless of `S`. Ipld { - /// Parsed CID structure + /// Parsed CID structure. cid: IpldCid, - /// Cached base32 string form - s: CowStr<'c>, + /// Cached base32 string form. + s: SmolStr, }, - /// String-only form (not yet parsed) - Str(CowStr<'c>), + /// String-only form (not yet parsed). + Str(S), } -/// Errors that can occur when working with CIDs +/// Errors that can occur when working with CIDs. #[derive(Debug, thiserror::Error, miette::Diagnostic)] #[non_exhaustive] pub enum Error { - /// Invalid IPLD CID structure + /// Invalid IPLD CID structure. #[error("Invalid IPLD CID {:?}", 0)] Ipld(#[from] cid::Error), - /// Invalid UTF-8 in CID string + /// Invalid UTF-8 in CID string. #[error("{:?}", 0)] Utf8(#[from] core::str::Utf8Error), } -impl<'c> Cid<'c> { - /// Parse a CID from bytes (tries IPLD first, falls back to UTF-8 string) - pub fn new(cid: &'c [u8]) -> Result { - if let Ok(cid) = IpldCid::try_from(cid.as_ref()) { - Ok(Self::ipld(cid)) - } else { - let cid_str = CowStr::from_utf8(cid)?; - Ok(Self::Str(cid_str)) - } - } +// --------------------------------------------------------------------------- +// Core methods +// --------------------------------------------------------------------------- - /// Parse a CID from bytes into an owned (static lifetime) value - pub fn new_owned(cid: &[u8]) -> Result, Error> { - if let Ok(cid) = IpldCid::try_from(cid.as_ref()) { - Ok(Self::ipld(cid)) - } else { - let cid_str = CowStr::from_utf8(cid)?; - Ok(Cid::Str(cid_str.into_static())) +impl + AsRef> Cid { + /// Get the CID as a string slice. + pub fn as_str(&self) -> &str { + match self { + Cid::Ipld { cid: _, s } => s.as_ref(), + Cid::Str(s) => s.as_ref(), } } - /// Construct a CID from a parsed IPLD CID - pub fn ipld(cid: IpldCid) -> Cid<'static> { - let s = CowStr::Owned( - cid.to_string_of_base(ATP_CID_BASE) - .unwrap_or_default() - .to_smolstr(), - ); - Cid::Ipld { cid, s } - } - - /// Construct a CID from a string slice (borrows) - pub fn str(cid: &'c str) -> Self { - Self::Str(CowStr::Borrowed(cid)) - } - - /// Construct a CID from a CowStr - pub fn cow_str(cid: CowStr<'c>) -> Self { - Self::Str(cid) - } - - /// Convert to a parsed IPLD CID (parses if needed) + /// Convert to a parsed IPLD CID (parses if needed). pub fn to_ipld(&self) -> Result { match self { Cid::Ipld { cid, s: _ } => Ok(cid.clone()), - Cid::Str(cow_str) => IpldCid::try_from(cow_str.as_ref()), + Cid::Str(s) => IpldCid::try_from(s.as_ref()), } } - /// Get the CID as a string slice - pub fn as_str(&self) -> &str { - match self { - Cid::Ipld { cid: _, s } => s.as_ref(), - Cid::Str(cow_str) => cow_str.as_ref(), - } - } - - /// Check if the CID string is valid without parsing + /// Check if the CID string is valid without parsing. /// /// Returns `true` if the CID is already parsed (`Ipld` variant) or if /// the string can be successfully parsed as an IPLD CID. @@ -122,137 +88,172 @@ impl<'c> Cid<'c> { } } -impl core::fmt::Display for Cid<'_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Cid::Ipld { cid: _, s } => f.write_str(&s), - Cid::Str(cow_str) => f.write_str(&cow_str), - } +// --------------------------------------------------------------------------- +// Constructors +// --------------------------------------------------------------------------- + +impl> Cid { + /// Wrap a string directly as a CID without validation. + /// + /// # Safety + /// + /// The caller must ensure the string is a valid CID. + pub unsafe fn unchecked_str(s: S) -> Self { + Cid::Str(s) + } + + /// Construct a CID from a parsed IPLD CID. + pub fn ipld(cid: IpldCid) -> Self { + let s = cid + .to_string_of_base(ATP_CID_BASE) + .unwrap_or_default() + .to_smolstr(); + Cid::Ipld { cid, s } } } -impl FromStr for Cid<'_> { - type Err = Infallible; +impl<'c> Cid<&'c str> { + /// Construct a CID from a string slice (borrows). + pub fn str(cid: &'c str) -> Self { + Self::Str(cid) + } - /// Has to take ownership due to the lifetime constraints of the FromStr trait. - fn from_str(s: &str) -> Result { - Ok(Cid::Str(CowStr::Owned(s.to_smolstr()))) + /// Parse a CID from bytes (tries IPLD first, falls back to UTF-8 string). + pub fn new(cid: &'c [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(Self::Str(cid_str)) + } } } -impl IntoStatic for Cid<'_> { - type Output = Cid<'static>; - - fn into_static(self) -> Self::Output { - match self { - Cid::Ipld { cid, s } => Cid::Ipld { - cid, - s: s.into_static(), - }, - Cid::Str(cow_str) => Cid::Str(cow_str.into_static()), +impl + From> 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()))) } } } -impl Serialize for Cid<'_> { - fn serialize(&self, serializer: S) -> Result +impl<'c> Cid> { + /// Construct a CID from a CowStr. + pub fn cow_str(cid: CowStr<'c>) -> Self { + Self::Str(cid) + } +} + +// --------------------------------------------------------------------------- +// Serialization — preserving existing logic exactly +// --------------------------------------------------------------------------- + +impl + AsRef> Serialize for Cid { + fn serialize(&self, serializer: Ser) -> Result where - S: Serializer, + Ser: Serializer, { match self { Cid::Ipld { cid, s: _ } => cid.serialize(serializer), - Cid::Str(cow_str) => cow_str.serialize(serializer), + Cid::Str(s) => s.as_ref().serialize(serializer), } } } -impl<'de, 'a> Deserialize<'de> for Cid<'a> +// --------------------------------------------------------------------------- +// Deserialization — preserving existing logic exactly +// --------------------------------------------------------------------------- + +impl<'de, S> Deserialize<'de> for Cid where - 'de: 'a, + S: Bos + AsRef + Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { if deserializer.is_human_readable() { - // JSON: always a string - struct StrVisitor; - - impl<'de> Visitor<'de> for StrVisitor { - type Value = Cid<'de>; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a CID string") - } - - fn visit_borrowed_str(self, v: &'de str) -> Result - where - E: serde::de::Error, - { - Ok(Cid::str(v)) - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - Ok(FromStr::from_str(v).unwrap()) - } - } - - deserializer.deserialize_str(StrVisitor) + // JSON: deserialize S (string), wrap in Str variant. + let s = S::deserialize(deserializer)?; + Ok(Cid::Str(s)) } else { - // CBOR: use IpldCid's deserializer which handles CBOR tag 42 + // CBOR: use IpldCid's deserializer which handles CBOR tag 42. let cid = IpldCid::deserialize(deserializer)?; Ok(Cid::ipld(cid)) } } } -impl From> for String { - fn from(value: Cid) -> Self { - let cow_str = match value { - Cid::Ipld { cid: _, s } => s, - Cid::Str(cow_str) => cow_str, - }; - cow_str.to_string() +// --------------------------------------------------------------------------- +// Trait impls +// --------------------------------------------------------------------------- + +impl + AsRef> fmt::Display for Cid { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Cid::Ipld { cid: _, s } => f.write_str(s), + Cid::Str(s) => f.write_str(s.as_ref()), + } + } +} + +impl FromStr for Cid { + type Err = Infallible; + + fn from_str(s: &str) -> Result { + Ok(Cid::Str(s.to_smolstr())) } } -impl<'d> From> for CowStr<'d> { - fn from(value: Cid<'d>) -> Self { - match value { - Cid::Ipld { cid: _, s } => s, - Cid::Str(cow_str) => cow_str, +impl + IntoStatic> IntoStatic for Cid +where + S::Output: Bos, +{ + type Output = Cid; + + fn into_static(self) -> Self::Output { + match self { + Cid::Ipld { cid, s } => Cid::Ipld { cid, s }, + Cid::Str(s) => Cid::Str(s.into_static()), } } } -impl From for Cid<'_> { +impl + AsRef> From> for String { + fn from(value: Cid) -> Self { + value.as_str().to_string() + } +} + +impl From for Cid { fn from(value: String) -> Self { - Cid::Str(CowStr::Owned(value.to_smolstr())) + Cid::Str(value.to_smolstr()) } } -impl<'d> From> for Cid<'d> { +impl<'d> From> for Cid> { fn from(value: CowStr<'d>) -> Self { Cid::Str(value) } } -impl From for Cid<'_> { +impl> From for Cid { fn from(value: IpldCid) -> Self { Cid::ipld(value) } } -impl AsRef for Cid<'_> { +impl + AsRef> AsRef for Cid { fn as_ref(&self) -> &str { self.as_str() } } -impl Deref for Cid<'_> { +impl + AsRef> Deref for Cid { type Target = str; fn deref(&self) -> &Self::Target { @@ -260,90 +261,82 @@ impl Deref for Cid<'_> { } } -/// CID link wrapper for JSON `{"$link": "cid"}` serialization +// =========================================================================== +// CidLink +// =========================================================================== + +/// CID link wrapper for JSON `{"$link": "cid"}` serialization. /// /// Wraps a `Cid` and handles format-specific serialization: /// - JSON: `{"$link": "cid_string"}` /// - CBOR: raw CID bytes -/// -/// Used in the AT Protocol data model to represent IPLD links in JSON. #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[repr(transparent)] -pub struct CidLink<'c>(pub Cid<'c>); - -impl<'c> CidLink<'c> { - /// Parse a CID link from bytes - pub fn new(cid: &'c [u8]) -> Result { - Ok(Self(Cid::new(cid)?)) - } - - /// Parse a CID link from bytes into an owned value - pub fn new_owned(cid: &[u8]) -> Result, Error> { - Ok(CidLink(Cid::new_owned(cid)?)) - } - - /// Construct a CID link from a static string - pub fn new_static(cid: &'static str) -> Self { - Self(Cid::str(cid)) - } +pub struct CidLink = DefaultStr>(pub Cid); - /// Construct a CID link from a parsed IPLD CID - pub fn ipld(cid: IpldCid) -> CidLink<'static> { - CidLink(Cid::ipld(cid)) - } - - /// Construct a CID link from a string slice - pub fn str(cid: &'c str) -> Self { - Self(Cid::str(cid)) - } - - /// Construct a CID link from a CowStr - pub fn cow_str(cid: CowStr<'c>) -> Self { - Self(Cid::cow_str(cid)) - } - - /// Get the CID as a string slice +impl + AsRef> CidLink { + /// Get the CID as a string slice. pub fn as_str(&self) -> &str { self.0.as_str() } - /// Convert to a parsed IPLD CID + /// Convert to a parsed IPLD CID. pub fn to_ipld(&self) -> Result { self.0.to_ipld() } - /// Unwrap into the inner Cid - pub fn into_inner(self) -> Cid<'c> { + /// Unwrap into the inner Cid. + pub fn into_inner(self) -> Cid { self.0 } } -impl fmt::Display for CidLink<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) +impl> CidLink { + /// Construct a CID link from a parsed IPLD CID. + pub fn ipld(cid: IpldCid) -> Self { + CidLink(Cid::ipld(cid)) } } -impl FromStr for CidLink<'_> { - type Err = Infallible; +impl<'c> CidLink<&'c str> { + /// Parse a CID link from bytes. + pub fn new(cid: &'c [u8]) -> Result { + Ok(Self(Cid::new(cid)?)) + } - fn from_str(s: &str) -> Result { - Ok(CidLink(Cid::from_str(s)?)) + /// Construct a CID link from a string slice. + pub fn str(cid: &'c str) -> Self { + Self(Cid::str(cid)) + } + + /// Construct a CID link from a static string. + pub fn new_static(cid: &'static str) -> Self { + Self(Cid::str(cid)) } } -impl IntoStatic for CidLink<'_> { - type Output = CidLink<'static>; +impl + From> CidLink { + /// Parse a CID link from bytes into an owned value. + pub fn new_owned(cid: &[u8]) -> Result { + Ok(CidLink(Cid::new_owned(cid)?)) + } +} - fn into_static(self) -> Self::Output { - CidLink(self.0.into_static()) +impl<'c> CidLink> { + /// Construct a CID link from a CowStr. + pub fn cow_str(cid: CowStr<'c>) -> Self { + Self(Cid::cow_str(cid)) } } -impl Serialize for CidLink<'_> { - fn serialize(&self, serializer: S) -> Result +// --------------------------------------------------------------------------- +// CidLink serialization — preserving existing logic exactly +// --------------------------------------------------------------------------- + +impl + AsRef> Serialize for CidLink { + fn serialize(&self, serializer: Ser) -> Result where - S: Serializer, + Ser: Serializer, { if serializer.is_human_readable() { // JSON: {"$link": "cid_string"} @@ -358,20 +351,29 @@ impl Serialize for CidLink<'_> { } } -impl<'de, 'a> Deserialize<'de> for CidLink<'a> +// --------------------------------------------------------------------------- +// CidLink deserialization — preserving existing logic exactly +// --------------------------------------------------------------------------- + +impl<'de, S> Deserialize<'de> for CidLink where - 'de: 'a, + S: Bos + AsRef + Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { + use core::marker::PhantomData; + if deserializer.is_human_readable() { // JSON: expect {"$link": "cid_string"} - struct LinkVisitor; + struct LinkVisitor(PhantomData S>); - impl<'de> Visitor<'de> for LinkVisitor { - type Value = CidLink<'static>; + impl<'de, S> Visitor<'de> for LinkVisitor + where + S: Bos + AsRef + Deserialize<'de>, + { + type Value = CidLink; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a CID link object with $link field") @@ -381,15 +383,19 @@ where where E: serde::de::Error, { - // TODO: currently overly permissive, should fix - Ok(CidLink::cow_str(v.to_cowstr()).into_static()) + // TODO: currently overly permissive, should fix. + // Delegate to S's Deserialize via a StrDeserializer. + let s = S::deserialize(serde::de::value::StrDeserializer::::new(v))?; + Ok(CidLink(Cid::Str(s))) } fn visit_bytes(self, v: &[u8]) -> Result where E: serde::de::Error, { - Cid::new_owned(v).map(CidLink).map_err(E::custom) + // Binary CID data — parse as IpldCid (produces Ipld variant with SmolStr). + let cid = IpldCid::try_from(v).map_err(E::custom)?; + Ok(CidLink(Cid::ipld(cid))) } fn visit_byte_buf(self, v: alloc::vec::Vec) -> Result @@ -399,7 +405,10 @@ 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>, { @@ -436,26 +445,28 @@ where { use serde::de::Error; - let mut link: Option = None; + // Deserialize the $link value as Cid, which delegates + // to S::deserialize for the string content. + let mut link: Option> = None; - while let Some(key) = map.next_key::()? { + while let Some(key) = map.next_key::<&str>()? { if key == "$link" { link = Some(map.next_value()?); } else { - // Skip unknown fields + // Skip unknown fields. let _: serde::de::IgnoredAny = map.next_value()?; } } - if let Some(cid_str) = link { - Ok(CidLink(Cid::from(cid_str))) + if let Some(cid) = link { + Ok(CidLink(cid)) } else { Err(A::Error::missing_field("$link")) } } } - deserializer.deserialize_any(LinkVisitor) + deserializer.deserialize_any(LinkVisitor(PhantomData)) } else { // CBOR: raw CID Ok(CidLink(Cid::deserialize(deserializer)?)) @@ -463,55 +474,78 @@ where } } -impl From> for String { - fn from(value: CidLink) -> Self { - value.0.into() +// --------------------------------------------------------------------------- +// CidLink trait impls +// --------------------------------------------------------------------------- + +impl + AsRef> fmt::Display for CidLink { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl FromStr for CidLink { + type Err = Infallible; + + fn from_str(s: &str) -> Result { + Ok(CidLink(Cid::from_str(s)?)) } } -impl<'c> From> for CowStr<'c> { - fn from(value: CidLink<'c>) -> Self { +impl + IntoStatic> IntoStatic for CidLink +where + S::Output: Bos, +{ + type Output = CidLink; + + fn into_static(self) -> Self::Output { + CidLink(self.0.into_static()) + } +} + +impl + AsRef> From> for String { + fn from(value: CidLink) -> Self { value.0.into() } } -impl From for CidLink<'_> { +impl From for CidLink { fn from(value: String) -> Self { CidLink(Cid::from(value)) } } -impl<'c> From> for CidLink<'c> { +impl<'c> From> for CidLink> { fn from(value: CowStr<'c>) -> Self { CidLink(Cid::from(value)) } } -impl From for CidLink<'_> { +impl> From for CidLink { fn from(value: IpldCid) -> Self { CidLink(Cid::from(value)) } } -impl<'c> From> for CidLink<'c> { - fn from(value: Cid<'c>) -> Self { +impl> From> for CidLink { + fn from(value: Cid) -> Self { CidLink(value) } } -impl<'c> From> for Cid<'c> { - fn from(value: CidLink<'c>) -> Self { +impl> From> for Cid { + fn from(value: CidLink) -> Self { value.0 } } -impl AsRef for CidLink<'_> { +impl + AsRef> AsRef for CidLink { fn as_ref(&self) -> &str { self.0.as_ref() } } -impl Deref for CidLink<'_> { +impl + AsRef> Deref for CidLink { type Target = str; fn deref(&self) -> &Self::Target { @@ -547,7 +581,7 @@ mod tests { let link = CidLink::str(TEST_CID); let json = serde_json::to_string(&link).unwrap(); let parsed: CidLink = serde_json::from_str(&json).unwrap(); - assert_eq!(link, parsed); + assert_eq!(link.as_str(), parsed.as_str()); assert_eq!(link.as_str(), TEST_CID); } @@ -566,23 +600,19 @@ mod tests { #[test] fn cidlink_conversions() { - let link = CidLink::str(TEST_CID); + let link = CidLink::::from(TEST_CID.to_string()); // CidLink -> Cid - let cid: Cid = link.clone().into(); + let cid: Cid = link.clone().into(); assert_eq!(cid.as_str(), TEST_CID); // Cid -> CidLink - let link2: CidLink = cid.into(); + let link2: CidLink = cid.into(); assert_eq!(link2.as_str(), TEST_CID); // CidLink -> String let s: String = link.clone().into(); assert_eq!(s, TEST_CID); - - // CidLink -> CowStr - let cow: CowStr = link.into(); - assert_eq!(cow.as_ref(), TEST_CID); } #[test] diff --git a/crates/jacquard-common/src/types/collection.rs b/crates/jacquard-common/src/types/collection.rs index 8a212ce2..bc91379c 100644 --- a/crates/jacquard-common/src/types/collection.rs +++ b/crates/jacquard-common/src/types/collection.rs @@ -53,7 +53,11 @@ pub trait Collection: fmt::Debug + Serialize { ) -> RepoPath<'u> { RepoPath { collection: Self::nsid(), - rkey: Some(RecordKey::from(Rkey::raw(rkey.as_ref()))), + // Borrow the record key string with the caller's lifetime via CowStr. + rkey: Some( + RecordKey::any_cow(CowStr::Borrowed(rkey.as_ref())) + .expect("RecordKey implements RecordKeyType, which guarantees a valid rkey"), + ), } } } diff --git a/crates/jacquard-common/src/types/did.rs b/crates/jacquard-common/src/types/did.rs index 46623355..8d4cf3c4 100644 --- a/crates/jacquard-common/src/types/did.rs +++ b/crates/jacquard-common/src/types/did.rs @@ -43,7 +43,7 @@ fn strip_did_prefix(did: &str) -> &str { did.strip_prefix("at://").unwrap_or(did) } -fn validate_did(did: &str) -> Result<(), AtStrError> { +pub(crate) fn validate_did(did: &str) -> Result<(), AtStrError> { if did.len() > 2048 { Err(AtStrError::too_long("did", did, 2048, did.len())) } else if !DID_REGEX.is_match(did) { diff --git a/crates/jacquard-common/src/types/handle.rs b/crates/jacquard-common/src/types/handle.rs index 5533b279..a3d7c3aa 100644 --- a/crates/jacquard-common/src/types/handle.rs +++ b/crates/jacquard-common/src/types/handle.rs @@ -47,7 +47,7 @@ fn strip_handle_prefix(handle: &str) -> &str { .unwrap_or(handle) } -fn validate_handle(handle: &str) -> Result<(), AtStrError> { +pub(crate) fn validate_handle(handle: &str) -> Result<(), AtStrError> { if handle.len() > 253 { Err(AtStrError::too_long("handle", handle, 253, handle.len())) } else if !HANDLE_REGEX.is_match(handle) { diff --git a/crates/jacquard-common/src/types/nsid.rs b/crates/jacquard-common/src/types/nsid.rs index 318cd3b7..42e3c1cb 100644 --- a/crates/jacquard-common/src/types/nsid.rs +++ b/crates/jacquard-common/src/types/nsid.rs @@ -30,7 +30,7 @@ pub static NSID_REGEX: Lazy = Lazy::new(|| { Regex::new(r"^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\.[a-zA-Z][a-zA-Z0-9]{0,62})$").unwrap() }); -fn validate_nsid(nsid: &str) -> Result<(), AtStrError> { +pub(crate) fn validate_nsid(nsid: &str) -> Result<(), AtStrError> { if nsid.len() > 317 { Err(AtStrError::too_long("nsid", nsid, 317, nsid.len())) } else if !NSID_REGEX.is_match(nsid) { diff --git a/crates/jacquard-common/src/types/recordkey.rs b/crates/jacquard-common/src/types/recordkey.rs index 5ca5541a..c9bc3534 100644 --- a/crates/jacquard-common/src/types/recordkey.rs +++ b/crates/jacquard-common/src/types/recordkey.rs @@ -1,3 +1,4 @@ +use crate::bos::{Bos, DefaultStr}; use crate::types::Literal; use crate::types::string::AtStrError; use crate::{CowStr, IntoStatic}; @@ -40,33 +41,42 @@ pub unsafe trait RecordKeyType: Clone + Serialize { #[repr(transparent)] pub struct RecordKey(pub T); -impl<'a> RecordKey> { - /// Create a new `RecordKey` from a string slice +impl<'a> RecordKey> { + /// Create a new `RecordKey` from a string slice. pub fn any(str: &'a str) -> Result { Ok(RecordKey(Rkey::new(str)?)) } +} - /// Create a new `RecordKey` from a CowStr - pub fn any_cow(str: CowStr<'a>) -> Result { - Ok(RecordKey(Rkey::new_cow(str)?)) - } - - /// Create a new `RecordKey` from a static string slice +impl + AsRef + Clone + Serialize + From> RecordKey> { + /// Create a new `RecordKey` from a static string slice. pub fn any_static(str: &'static str) -> Result { Ok(RecordKey(Rkey::new_static(str)?)) } + + /// Create a new `RecordKey` from an owned string. + pub fn any_owned(str: impl AsRef) -> Result { + Ok(RecordKey(Rkey::new_owned(str)?)) + } } -impl From for RecordKey> +impl<'a> RecordKey>> { + /// Create a new `RecordKey` from a CowStr. + pub fn any_cow(str: CowStr<'a>) -> Result { + Ok(RecordKey(Rkey::new_cow(str)?)) + } +} + +impl From for RecordKey where T: RecordKeyType, { fn from(value: T) -> Self { - RecordKey(Rkey::from_str(value.as_str()).expect("Invalid rkey")) + RecordKey(Rkey::new_owned(value.as_str()).expect("Invalid rkey")) } } -impl FromStr for RecordKey> { +impl FromStr for RecordKey { type Err = AtStrError; fn from_str(s: &str) -> Result { @@ -108,216 +118,191 @@ where /// - Any: flexible strings matching the validation rules /// /// See: -#[derive(Clone, PartialEq, Eq, Serialize, Hash)] +/// AT Protocol record key (generic "any" type). +/// +/// See: +#[derive(Clone, PartialEq, Eq, Hash, Serialize)] #[serde(transparent)] #[repr(transparent)] -pub struct Rkey<'r>(pub(crate) CowStr<'r>); +pub struct Rkey = DefaultStr>(pub(crate) S); -unsafe impl<'r> RecordKeyType for Rkey<'r> { +unsafe impl + AsRef + Clone + Serialize> RecordKeyType for Rkey { fn as_str(&self) -> &str { self.0.as_ref() } } -/// Regex for record key validation per AT Protocol spec +/// Regex for record key validation per AT Protocol spec. pub static RKEY_REGEX: Lazy = Lazy::new(|| Regex::new(r"^[a-zA-Z0-9.\-_:~]{1,512}$").unwrap()); -impl<'r> Rkey<'r> { - /// Fallible constructor, validates, borrows from input +pub(crate) fn validate_rkey(rkey: &str) -> Result<(), AtStrError> { + if [".", ".."].contains(&rkey) { + Err(AtStrError::disallowed("record-key", rkey, &[".", ".."])) + } else if !RKEY_REGEX.is_match(rkey) { + Err(AtStrError::regex( + "record-key", + rkey, + SmolStr::new_static("doesn't match 'any' schema"), + )) + } else { + Ok(()) + } +} + +impl + AsRef> Rkey { + /// Get the record key as a string slice. + pub fn as_str(&self) -> &str { + self.0.as_ref() + } +} + +impl> Rkey { + /// # Safety + /// + /// The caller must ensure the rkey is valid. + pub unsafe fn unchecked(rkey: S) -> Self { + Rkey(rkey) + } +} + +impl<'r> Rkey<&'r str> { + /// Fallible constructor, validates, borrows from input. pub fn new(rkey: &'r str) -> Result { - if [".", ".."].contains(&rkey) { - Err(AtStrError::disallowed("record-key", rkey, &[".", ".."])) - } else if !RKEY_REGEX.is_match(rkey) { - Err(AtStrError::regex( - "record-key", - rkey, - SmolStr::new_static("doesn't match 'any' schema"), - )) - } else { - Ok(Self(CowStr::Borrowed(rkey))) - } + validate_rkey(rkey)?; + Ok(Self(rkey)) } - /// Fallible constructor, validates, takes ownership + /// Infallible constructor. Panics on invalid rkeys. + pub fn raw(rkey: &'r str) -> Self { + Self::new(rkey).expect("invalid rkey") + } +} + +impl + From> Rkey { + /// Fallible constructor, validates, takes ownership. pub fn new_owned(rkey: impl AsRef) -> Result { let rkey = rkey.as_ref(); - if [".", ".."].contains(&rkey) { - Err(AtStrError::disallowed("record-key", rkey, &[".", ".."])) - } else if !RKEY_REGEX.is_match(rkey) { - Err(AtStrError::regex( - "record-key", - rkey, - SmolStr::new_static("doesn't match 'any' schema"), - )) - } else { - Ok(Self(CowStr::Owned(rkey.to_smolstr()))) - } + validate_rkey(rkey)?; + Ok(Self(S::from(rkey.to_smolstr()))) } - /// Fallible constructor, validates, doesn't allocate + /// Fallible constructor for static strings. pub fn new_static(rkey: &'static str) -> Result { - if [".", ".."].contains(&rkey) { - Err(AtStrError::disallowed("record-key", rkey, &[".", ".."])) - } else if !RKEY_REGEX.is_match(rkey) { - Err(AtStrError::regex( - "record-key", - rkey, - SmolStr::new_static("doesn't match 'any' schema"), - )) - } else { - Ok(Self(CowStr::new_static(rkey))) - } + validate_rkey(rkey)?; + Ok(Self(S::from(SmolStr::new_static(rkey)))) } +} - /// Fallible constructor, validates, borrows from input if possible +impl<'r> Rkey> { + /// Fallible constructor, borrows if possible. pub fn new_cow(rkey: CowStr<'r>) -> Result { - if [".", ".."].contains(&rkey.as_ref()) { - Err(AtStrError::disallowed("record-key", &rkey, &[".", ".."])) - } else if !RKEY_REGEX.is_match(&rkey) { - Err(AtStrError::regex( - "record-key", - &rkey, - SmolStr::new_static("doesn't match 'any' schema"), - )) - } else { - Ok(Self(rkey)) - } + validate_rkey(&rkey)?; + Ok(Self(rkey)) } - /// Infallible constructor for when you *know* the string is a valid rkey. - /// Will panic on invalid rkeys. If you're manually decoding atproto records - /// or API values you know are valid (rather than using serde), this is the one to use. - /// The From impls use the same logic. - pub fn raw(rkey: &'r str) -> Self { - if [".", ".."].contains(&rkey) { - panic!("Disallowed rkey") - } else if !RKEY_REGEX.is_match(rkey) { - panic!("Invalid rkey") - } else { - Self(CowStr::Borrowed(rkey)) - } + /// Infallible unchecked constructor for CowStr. + pub unsafe fn unchecked_cow(rkey: CowStr<'r>) -> Self { + Self(rkey) } +} - /// Infallible constructor for when you *know* the string is a valid rkey. - /// Marked unsafe because responsibility for upholding the invariant is on the developer. - pub unsafe fn unchecked(rkey: &'r str) -> Self { - Self(CowStr::Borrowed(rkey)) +impl<'de, S> Deserialize<'de> for Rkey +where + S: Bos + AsRef + Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = S::deserialize(deserializer)?; + validate_rkey(s.as_ref()).map_err(D::Error::custom)?; + Ok(Rkey(s)) } +} - /// Get the record key as a string slice - pub fn as_str(&self) -> &str { - { - let this = &self.0; - this - } +impl + IntoStatic> IntoStatic for Rkey +where + S::Output: Bos, +{ + type Output = Rkey; + + fn into_static(self) -> Self::Output { + Rkey(self.0.into_static()) } } -impl<'r> FromStr for Rkey<'r> { +impl FromStr for Rkey { type Err = AtStrError; fn from_str(s: &str) -> Result { - if [".", ".."].contains(&s) { - Err(AtStrError::disallowed("record-key", s, &[".", ".."])) - } else if !RKEY_REGEX.is_match(s) { - Err(AtStrError::regex( - "record-key", - s, - SmolStr::new_static("doesn't match 'any' schema"), - )) - } else { - Ok(Self(CowStr::Owned(s.to_smolstr()))) - } + Self::new_owned(s) } } -impl IntoStatic for Rkey<'_> { - type Output = Rkey<'static>; +impl FromStr for Rkey> { + type Err = AtStrError; - fn into_static(self) -> Self::Output { - Rkey(self.0.into_static()) + fn from_str(s: &str) -> Result { + Self::new_owned(s) } } -impl<'de, 'a> Deserialize<'de> for Rkey<'a> -where - 'de: 'a, -{ - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let value = Deserialize::deserialize(deserializer)?; - Self::new_cow(value).map_err(D::Error::custom) - } -} +impl FromStr for Rkey { + type Err = AtStrError; -impl fmt::Display for Rkey<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.0) + fn from_str(s: &str) -> Result { + Self::new_owned(s) } } -impl fmt::Debug for Rkey<'_> { +impl + AsRef> fmt::Display for Rkey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "record-key:{}", self.0) + f.write_str(self.as_str()) } } -impl From> for String { - fn from(value: Rkey<'_>) -> Self { - value.0.to_string() +impl + AsRef> fmt::Debug for Rkey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "record-key:{}", self.as_str()) } } -impl<'r> From> for CowStr<'r> { - fn from(value: Rkey<'r>) -> Self { - value.0 +impl + AsRef> From> for String { + fn from(value: Rkey) -> Self { + value.as_str().to_string() } } -impl<'r> From> for SmolStr { - fn from(value: Rkey) -> Self { - value.0.to_smolstr() +impl + AsRef> From> for SmolStr { + fn from(value: Rkey) -> Self { + value.as_str().to_smolstr() } } -impl<'r> From for Rkey<'r> { +impl From for Rkey { fn from(value: String) -> Self { - if [".", ".."].contains(&value.as_str()) { - panic!("Disallowed rkey") - } else if !RKEY_REGEX.is_match(&value) { - panic!("Invalid rkey") - } else { - Self(CowStr::Owned(value.to_smolstr())) - } + Self::new_owned(value).unwrap() } } -impl<'r> From> for Rkey<'r> { +impl<'r> From> for Rkey> { fn from(value: CowStr<'r>) -> Self { - if [".", ".."].contains(&value.as_ref()) { - panic!("Disallowed rkey") - } else if !RKEY_REGEX.is_match(&value) { - panic!("Invalid rkey") - } else { - Self(value) - } + Self::new_cow(value).unwrap() } } -impl AsRef for Rkey<'_> { +impl + AsRef> AsRef for Rkey { fn as_ref(&self) -> &str { self.as_str() } } -impl Deref for Rkey<'_> { +impl + AsRef> Deref for Rkey { type Target = str; fn deref(&self) -> &Self::Target { - self.0.as_ref() + self.as_str() } } diff --git a/crates/jacquard-common/src/types/string.rs b/crates/jacquard-common/src/types/string.rs index 91ec0e1b..a2617094 100644 --- a/crates/jacquard-common/src/types/string.rs +++ b/crates/jacquard-common/src/types/string.rs @@ -26,6 +26,7 @@ impl From<(usize, usize)> for SourceSpan { } } +use crate::bos::{Bos, DefaultStr}; use crate::cowstr::ToCowStr; pub use crate::{ CowStr, @@ -57,7 +58,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<'s> { +pub enum AtprotoStr + AsRef + Clone + Serialize = DefaultStr> { /// ISO 8601 datetime Datetime(Datetime), /// BCP 47 language tag @@ -65,65 +66,80 @@ pub enum AtprotoStr<'s> { /// Timestamp identifier Tid(Tid), /// Namespaced identifier - Nsid(Nsid>), + Nsid(Nsid), /// Decentralized identifier - Did(Did>), + Did(Did), /// Account handle - Handle(Handle>), + Handle(Handle), /// Identifier (DID or handle) - AtIdentifier(AtIdentifier>), + AtIdentifier(AtIdentifier), + // TODO(bos-migration): parameterise on S once AtUri is migrated. /// AT URI - AtUri(AtUri<'s>), + AtUri(AtUri<'static>), + // TODO(bos-migration): parameterise on S once UriValue is migrated. /// Generic URI - Uri(UriValue<'s>), + Uri(UriValue<'static>), /// Content identifier - Cid(Cid<'s>), + Cid(Cid), /// Record key - RecordKey(RecordKey>), + RecordKey(RecordKey>), /// Plain string (fallback) - String(CowStr<'s>), + String(S), } -impl<'s> AtprotoStr<'s> { - /// Borrowing constructor for bare atproto string values +use crate::types::cid::IpldCid; +use crate::types::did::validate_did; +use crate::types::handle::validate_handle; +use crate::types::nsid::validate_nsid; + +impl + AsRef + Clone + Serialize> AtprotoStr { + /// Classify and wrap a string value into the appropriate variant. + /// /// This is fairly exhaustive and potentially **slow**, prefer using anything /// that narrows down the search field quicker. /// - /// Note: We don't construct record keys from bare strings in this because - /// the type is too permissive and too many things would be classified as rkeys. - /// - /// Value object deserialization checks against the field names for common - /// names (uri, cid, did, handle, createdAt, indexedAt, etc.) to improve - /// performance of the happy path. - pub fn new(string: &'s str) -> Self { - // TODO: do some quick prefix checks like in Uri to drop through faster - if let Ok(datetime) = Datetime::from_str(string) { - Self::Datetime(datetime) - } else if let Ok(lang) = Language::new(string) { - Self::Language(lang) - } else if let Ok(tid) = Tid::from_str(string) { - Self::Tid(tid) - } else if let Ok(did) = Did::new_cow(string.to_cowstr()) { - Self::Did(did) - } else if let Ok(handle) = Handle::new_cow(string.to_cowstr()) { - Self::Handle(handle) - } else if let Ok(atid) = AtIdentifier::new_cow(string.to_cowstr()) { - Self::AtIdentifier(atid) - } else if let Ok(nsid) = Nsid::new_cow(string.to_cowstr()) { - Self::Nsid(nsid) - } else if let Ok(aturi) = AtUri::new(string) { - Self::AtUri(aturi) - } else if let Ok(uri) = UriValue::new(string) { - Self::Uri(uri) - } else if let Ok(cid) = Cid::new(string.as_bytes()) { - Self::Cid(cid) - } else { - // We don't construct record keys from bare strings because the type is too permissive - Self::String(CowStr::Borrowed(string)) + /// Inspects the string content, validates against known AT Protocol types, + /// and moves `string` into the matching variant via unchecked constructors + /// (safe because we validate first). + pub fn new(string: S) -> Self { + let s: &str = string.as_ref(); + // Non-string-backed types first (they don't consume S). + if let Ok(datetime) = Datetime::from_str(s) { + return Self::Datetime(datetime); + } + if let Ok(lang) = Language::new(s) { + return Self::Language(lang); + } + if let Ok(tid) = Tid::from_str(s) { + return Self::Tid(tid); + } + // String-backed types: validate then wrap S directly. + if validate_did(s).is_ok() { + return Self::Did(unsafe { Did::unchecked(string) }); + } + if validate_handle(s).is_ok() { + return Self::Handle(unsafe { Handle::unchecked(string) }); } + if validate_nsid(s).is_ok() { + return Self::Nsid(unsafe { Nsid::unchecked(string) }); + } + // TODO(bos-migration): AtUri and UriValue still use lifetimes. + // For now, construct owned versions for those variants. + if let Ok(aturi) = AtUri::new_owned(s) { + return Self::AtUri(aturi); + } + if let Ok(uri) = UriValue::new_owned(s) { + return Self::Uri(uri); + } + // CID: try to parse as IPLD first, otherwise wrap as string CID. + if IpldCid::try_from(s).is_ok() || s.starts_with("bafy") { + return Self::Cid(unsafe { Cid::unchecked_str(string) }); + } + // Fallback: plain string. + Self::String(string) } - /// Get the string value regardless of variant + /// Get the string value regardless of variant. pub fn as_str(&self) -> &str { match self { Self::Datetime(datetime) => datetime.as_str(), @@ -141,7 +157,7 @@ impl<'s> AtprotoStr<'s> { } } - /// detailed string type + /// Detailed string type classification. pub fn string_type(&self) -> LexiconStringType { match self { Self::Datetime(_) => LexiconStringType::Datetime, @@ -167,90 +183,39 @@ impl<'s> AtprotoStr<'s> { } } -impl AtprotoStr<'static> { - /// Owned constructor for bare atproto string values - /// This is fairly exhaustive and potentially **slow**, prefer using anything - /// that narrows down the search field quicker. - /// - /// Note: We don't construct record keys from bare strings in this because - /// the type is too permissive and too many things would be classified as rkeys. - /// - /// Value object deserialization checks against the field names for common - /// names (uri, cid, did, handle, createdAt, indexedAt, etc.) to improve - /// performance of the happy path. - pub fn new_owned(string: impl AsRef) -> AtprotoStr<'static> { - let string = string.as_ref(); - // TODO: do some quick prefix checks like in Uri to drop through faster - if let Ok(datetime) = Datetime::from_str(string) { - Self::Datetime(datetime) - } else if let Ok(lang) = Language::new(string) { - Self::Language(lang) - } else if let Ok(tid) = Tid::from_str(string) { - Self::Tid(tid) - } else if let Ok(did) = Did::new_owned(string) { - Self::Did(did) - } else if let Ok(handle) = Handle::new_owned(string) { - Self::Handle(handle) - } else if let Ok(atid) = AtIdentifier::new_owned(string) { - Self::AtIdentifier(atid) - } else if let Ok(nsid) = Nsid::new_owned(string) { - Self::Nsid(nsid) - } else if let Ok(aturi) = AtUri::new_owned(string) { - Self::AtUri(aturi) - } else if let Ok(uri) = UriValue::new_owned(string) { - Self::Uri(uri) - } else if let Ok(cid) = Cid::new_owned(string.as_bytes()) { - Self::Cid(cid) - } else { - // We don't construct record keys from bare strings because the type is too permissive - Self::String(CowStr::Owned(string.to_smolstr())) - } - } -} - -impl<'s> AsRef for AtprotoStr<'s> { +impl + AsRef + Clone + Serialize> AsRef for AtprotoStr { fn as_ref(&self) -> &str { - match self { - Self::Datetime(datetime) => datetime.as_str(), - Self::Language(lang) => lang.as_ref(), - Self::Tid(tid) => tid.as_ref(), - Self::Did(did) => did.as_ref(), - Self::Handle(handle) => handle.as_ref(), - Self::AtIdentifier(atid) => atid.as_ref(), - Self::Nsid(nsid) => nsid.as_ref(), - Self::AtUri(aturi) => aturi.as_ref(), - Self::Uri(uri) => uri.as_str(), - Self::Cid(cid) => cid.as_ref(), - Self::RecordKey(rkey) => rkey.as_ref(), - Self::String(string) => string.as_ref(), - } + self.as_str() } } -impl Serialize for AtprotoStr<'_> { - fn serialize(&self, serializer: S) -> Result +impl + AsRef + Clone + Serialize> Serialize for AtprotoStr { + fn serialize(&self, serializer: Ser) -> Result where - S: Serializer, + Ser: Serializer, { - serializer.serialize_str(self.as_ref()) + serializer.serialize_str(self.as_str()) } } -impl<'de, 'a> Deserialize<'de> for AtprotoStr<'a> +impl<'de, S> Deserialize<'de> for AtprotoStr where - 'de: 'a, + S: Bos + AsRef + Clone + Serialize + Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { - let value = Deserialize::deserialize(deserializer)?; + let value = S::deserialize(deserializer)?; Ok(Self::new(value)) } } -impl IntoStatic for AtprotoStr<'_> { - type Output = AtprotoStr<'static>; +impl + AsRef + Clone + Serialize + IntoStatic> IntoStatic for AtprotoStr +where + S::Output: Bos + AsRef + Clone + Serialize, +{ + type Output = AtprotoStr; fn into_static(self) -> Self::Output { match self { @@ -261,38 +226,19 @@ impl IntoStatic for AtprotoStr<'_> { AtprotoStr::Did(did) => AtprotoStr::Did(did.into_static()), AtprotoStr::Handle(handle) => AtprotoStr::Handle(handle.into_static()), AtprotoStr::AtIdentifier(ident) => AtprotoStr::AtIdentifier(ident.into_static()), - AtprotoStr::AtUri(at_uri) => AtprotoStr::AtUri(at_uri.into_static()), - AtprotoStr::Uri(uri) => AtprotoStr::Uri(uri.into_static()), + // AtUri and UriValue are already 'static in this enum. + AtprotoStr::AtUri(at_uri) => AtprotoStr::AtUri(at_uri), + AtprotoStr::Uri(uri) => AtprotoStr::Uri(uri), AtprotoStr::Cid(cid) => AtprotoStr::Cid(cid.into_static()), AtprotoStr::RecordKey(record_key) => AtprotoStr::RecordKey(record_key.into_static()), - AtprotoStr::String(cow_str) => AtprotoStr::String(cow_str.into_static()), + AtprotoStr::String(s) => AtprotoStr::String(s.into_static()), } } } -impl From> for String { - fn from(value: AtprotoStr<'_>) -> Self { - match value { - AtprotoStr::AtIdentifier(ident) => ident.to_string(), - AtprotoStr::AtUri(at_uri) => at_uri.to_string(), - AtprotoStr::Uri(uri) => match uri { - UriValue::At(at_uri) => at_uri.to_string(), - UriValue::Cid(cid) => cid.to_string(), - UriValue::Did(did) => did.to_string(), - UriValue::Https(url) => url.to_string(), - UriValue::Wss(url) => url.to_string(), - UriValue::Any(cow_str) => cow_str.to_string(), - }, - AtprotoStr::Cid(cid) => cid.to_string(), - AtprotoStr::RecordKey(record_key) => record_key.as_ref().to_string(), - AtprotoStr::String(cow_str) => cow_str.to_string(), - AtprotoStr::Datetime(datetime) => datetime.to_string(), - AtprotoStr::Language(language) => language.to_string(), - AtprotoStr::Tid(tid) => tid.to_string(), - AtprotoStr::Nsid(nsid) => nsid.to_string(), - AtprotoStr::Did(did) => did.to_string(), - AtprotoStr::Handle(handle) => handle.to_string(), - } +impl + AsRef + Clone + Serialize> From> for String { + fn from(value: AtprotoStr) -> Self { + value.as_str().to_string() } } diff --git a/crates/jacquard-common/src/types/uri.rs b/crates/jacquard-common/src/types/uri.rs index 263a4e59..f2c1e0cf 100644 --- a/crates/jacquard-common/src/types/uri.rs +++ b/crates/jacquard-common/src/types/uri.rs @@ -28,7 +28,7 @@ pub enum UriValue<'u> { /// WebSocket Secure URL Wss(Uri), /// IPLD CID URI - Cid(Cid<'u>), + Cid(Cid>), /// Unrecognized URI scheme (catch-all) Any(CowStr<'u>), } @@ -60,9 +60,12 @@ impl<'u> UriValue<'u> { } else if uri.starts_with("wss://") { Ok(UriValue::Wss(Uri::parse(uri)?.to_owned())) } else if uri.starts_with("ipld://") { - match Cid::from_str(&uri[7..]) { - Ok(cid) => Ok(UriValue::Cid(cid)), - Err(_) => Ok(UriValue::Any(CowStr::Borrowed(uri))), + // Borrow the slice after "ipld://" prefix (7 bytes) from the input &'u str. + let cid_part = &uri[7..]; + if cid_part.is_empty() { + Ok(UriValue::Any(CowStr::Borrowed(uri))) + } else { + Ok(UriValue::Cid(Cid::cow_str(CowStr::Borrowed(cid_part)))) } } else { Ok(UriValue::Any(CowStr::Borrowed(uri))) @@ -81,9 +84,12 @@ impl<'u> UriValue<'u> { } else if uri.starts_with("wss://") { Ok(UriValue::Wss(Uri::parse(uri)?.to_owned())) } else if uri.starts_with("ipld://") { - match Cid::from_str(&uri[7..]) { - Ok(cid) => Ok(UriValue::Cid(cid)), - Err(_) => Ok(UriValue::Any(CowStr::Owned(uri.to_smolstr()))), + // Owned context: use SmolStr via CowStr::Owned. + let cid_part = &uri[7..]; + if cid_part.is_empty() { + Ok(UriValue::Any(CowStr::Owned(uri.to_smolstr()))) + } else { + Ok(UriValue::Cid(Cid::cow_str(CowStr::Owned(cid_part.to_smolstr())))) } } else { Ok(UriValue::Any(CowStr::Owned(uri.to_smolstr()))) @@ -101,9 +107,16 @@ impl<'u> UriValue<'u> { } else if uri.starts_with("wss://") { Ok(UriValue::Wss(Uri::parse(uri.as_ref())?.to_owned())) } else if uri.starts_with("ipld://") { - match Cid::from_str(&uri.as_str()[7..]) { - Ok(cid) => Ok(UriValue::Cid(cid)), - Err(_) => Ok(UriValue::Any(uri)), + // Determine whether the CID part (after "ipld://") is non-empty before consuming uri. + if uri.as_ref()[7..].is_empty() { + Ok(UriValue::Any(uri)) + } else { + // Build a CowStr for the CID part, preserving the ownership variant. + let cid_cow: CowStr<'u> = match uri { + CowStr::Borrowed(s) => CowStr::Borrowed(&s[7..]), + CowStr::Owned(ref s) => CowStr::Owned(s[7..].to_smolstr()), + }; + Ok(UriValue::Cid(Cid::cow_str(cid_cow))) } } else { Ok(UriValue::Any(uri)) diff --git a/crates/jacquard-common/src/types/value.rs b/crates/jacquard-common/src/types/value.rs index fdbca5e7..755712e8 100644 --- a/crates/jacquard-common/src/types/value.rs +++ b/crates/jacquard-common/src/types/value.rs @@ -39,17 +39,17 @@ 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<'s>), + String(AtprotoStr>), /// Raw bytes Bytes(Bytes), /// CID link reference - CidLink(Cid<'s>), + CidLink(Cid>), /// Array of values Array(Array<'s>), /// Object/map of values Object(Object<'s>), /// Blob reference with metadata - Blob(Blob<'s>), + Blob(Blob>), } /// Errors that can occur when working with AT Protocol data @@ -170,7 +170,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<'s>> { + pub fn as_str_mut(&'s mut self) -> Option<&'s mut AtprotoStr>> { if let Data::String(s) = self { Some(s) } else { @@ -609,13 +609,13 @@ pub enum RawData<'s> { /// Raw bytes Bytes(Bytes), /// CID link reference - CidLink(Cid<'s>), + CidLink(Cid>), /// Array of raw values Array(Vec>), /// Object/map of raw values Object(BTreeMap>), /// Valid blob reference - Blob(Blob<'s>), + Blob(Blob>), /// Invalid blob structure (captured for debugging) InvalidBlob(Box>), /// Invalid number format, generally a floating point number (captured as bytes) diff --git a/crates/jacquard-common/src/types/value/convert.rs b/crates/jacquard-common/src/types/value/convert.rs index 1c339cab..b49578b8 100644 --- a/crates/jacquard-common/src/types/value/convert.rs +++ b/crates/jacquard-common/src/types/value/convert.rs @@ -131,13 +131,13 @@ macro_rules! derive_into_atproto { impl From for Data<'_> { fn from(t: String) -> Self { - Data::String(AtprotoStr::new_owned(t)) + Data::String(AtprotoStr::new(CowStr::from(t))) } } impl<'a> From<&'a str> for Data<'a> { fn from(t: &'a str) -> Self { - Data::String(AtprotoStr::new(t)) + Data::String(AtprotoStr::new(CowStr::Borrowed(t))) } } @@ -149,25 +149,19 @@ impl From<&[u8]> for Data<'_> { impl<'s> From> for Data<'s> { fn from(t: CowStr<'s>) -> Self { - match t { - CowStr::Borrowed(s) => Data::String(AtprotoStr::new(s)), - CowStr::Owned(s) => Data::String(AtprotoStr::new_owned(s)), - } + Data::String(AtprotoStr::new(t)) } } impl From for Data<'_> { fn from(t: SmolStr) -> Self { - Data::String(AtprotoStr::new_owned(t)) + Data::String(AtprotoStr::new(CowStr::Owned(t))) } } impl<'s> From> for Data<'s> { fn from(t: Cow<'s, str>) -> Self { - match t { - Cow::Borrowed(s) => Data::String(AtprotoStr::new(s)), - Cow::Owned(s) => Data::String(AtprotoStr::new_owned(s)), - } + Data::String(AtprotoStr::new(CowStr::from(t))) } } @@ -242,8 +236,8 @@ derive_into_atproto!(Bytes, Vec, into); derive_into_atproto!(Array, Array<'s>, into); derive_into_atproto!(Object, Object<'s>, to_owned); -derive_into_atproto!(CidLink, Cid<'s>, clone); -derive_into_atproto!(CidLink, &Cid<'s>, to_owned); +derive_into_atproto!(CidLink, Cid>, clone); +derive_into_atproto!(CidLink, &Cid>, to_owned); derive_try_from_atproto!(Boolean, bool); derive_try_from_atproto!(Integer, i8); @@ -260,7 +254,7 @@ 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<'static>); +derive_try_from_atproto!(CidLink, Cid>); derive_try_from_atproto_option!(Boolean, bool); derive_try_from_atproto_option!(Integer, i8); @@ -279,7 +273,7 @@ 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<'static>); +derive_try_from_atproto_option!(CidLink, Cid>); /// Convert RawData to validated Data with type inference impl<'s> TryFrom> for Data<'s> { @@ -329,7 +323,7 @@ impl<'s> TryFrom> for Data<'s> { } }; return Ok(Data::Blob(crate::types::blob::Blob { - r#ref: CidLink::str(cid).into_static(), + r#ref: CidLink(cid.clone()), mime_type: crate::types::blob::MimeType::from(mime.clone()), size: size_val, })); diff --git a/crates/jacquard-common/src/types/value/parsing.rs b/crates/jacquard-common/src/types/value/parsing.rs index 053ed9ca..125b4f6c 100644 --- a/crates/jacquard-common/src/types/value/parsing.rs +++ b/crates/jacquard-common/src/types/value/parsing.rs @@ -43,7 +43,11 @@ pub fn insert_string<'s>( } LexiconStringType::AtUri => { if let Ok(value) = AtUri::new(value) { - map.insert(key.to_smolstr(), Data::String(AtprotoStr::AtUri(value))); + // AtprotoStr::AtUri stores AtUri<'static>; convert to owned. + map.insert( + key.to_smolstr(), + Data::String(AtprotoStr::AtUri(value.into_static())), + ); } else { map.insert( key.to_smolstr(), @@ -95,7 +99,7 @@ pub fn insert_string<'s>( } } LexiconStringType::Cid => { - if let Ok(value) = Cid::new(value.as_bytes()) { + if let Ok(value) = Cid::>::new_owned(value.as_bytes()) { map.insert(key.to_smolstr(), Data::String(AtprotoStr::Cid(value))); } else { map.insert( @@ -125,10 +129,15 @@ pub fn insert_string<'s>( } } LexiconStringType::RecordKey => { - if let Ok(value) = Rkey::new(value) { + // Validate the rkey without shadowing the original `value: &'s str`. + if Rkey::new(value).is_ok() { map.insert( key.to_smolstr(), - Data::String(AtprotoStr::RecordKey(RecordKey::from(value))), + // Rkey already validated above; borrow the original &'s str directly. + Data::String(AtprotoStr::RecordKey( + RecordKey::any_cow(CowStr::Borrowed(value)) + .expect("Rkey validation passed"), + )), ); } else { map.insert( @@ -138,7 +147,8 @@ pub fn insert_string<'s>( } } LexiconStringType::Uri(_) => { - if let Ok(uri) = UriValue::new(value) { + // 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))); } else { map.insert( @@ -155,7 +165,7 @@ pub fn insert_string<'s>( } /// smarter parsing to avoid trying as many posibilities. -pub fn parse_string<'s>(string: &'s str) -> AtprotoStr<'s> { +pub fn parse_string<'s>(string: &'s str) -> AtprotoStr> { if string.len() < 2048 && string.starts_with("did:") { if let Ok(did) = Did::new_cow(string.to_cowstr()) { return AtprotoStr::Did(did); @@ -166,8 +176,9 @@ pub fn parse_string<'s>(string: &'s str) -> AtprotoStr<'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) { - return AtprotoStr::AtUri(uri); + return AtprotoStr::AtUri(uri.into_static()); } } else if string.starts_with("https://") { if let Ok(uri) = Uri::parse(string) { @@ -178,7 +189,11 @@ pub fn parse_string<'s>(string: &'s str) -> AtprotoStr<'s> { return AtprotoStr::Uri(UriValue::Wss(uri.to_owned())); } } else if string.starts_with("ipfs://") { - return AtprotoStr::Uri(UriValue::Cid(Cid::str(string))); + // 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']) { // Dotted strings without a scheme could be handles, NSIDs, or URIs. // Use TLD lookup and camelCase heuristic to disambiguate. @@ -219,7 +234,8 @@ pub fn parse_string<'s>(string: &'s str) -> AtprotoStr<'s> { } else if let Ok(nsid) = Nsid::new_cow(string.to_cowstr()) { return AtprotoStr::Nsid(nsid); } else if string.contains("://") && Uri::<&str>::parse(string).is_ok() { - return AtprotoStr::Uri(UriValue::Any(string.into())); + // AtprotoStr::Uri stores UriValue<'static>; convert to owned. + return AtprotoStr::Uri(UriValue::Any(CowStr::Owned(string.to_smolstr()))); } } else if string.len() == 13 { if let Ok(tid) = Tid::new(string) { @@ -228,7 +244,7 @@ pub fn parse_string<'s>(string: &'s str) -> AtprotoStr<'s> { } 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(string.as_bytes()) { + if let Ok(cid) = Cid::>::new_owned(string.as_bytes()) { return AtprotoStr::Cid(cid); } } @@ -267,10 +283,10 @@ pub fn string_key_type_guess(key: &str) -> DataModelType { } /// Convert an ipld map to a atproto data model blob if it matches the format -pub fn cbor_to_blob<'b>(blob: &'b BTreeMap) -> Option> { +pub fn cbor_to_blob<'b>(blob: &'b BTreeMap) -> Option>> { let mime_type = blob.get("mimeType").and_then(|o| { if let Ipld::String(string) = o { - Some(string) + Some(string.as_str()) } else { None } @@ -285,16 +301,16 @@ 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::ipld(*value), - mime_type: MimeType::raw(mime_type), + r#ref: CidLink::>::ipld(*value), + mime_type: MimeType::new_cow(CowStr::Borrowed(mime_type)), size: size as usize, }); } } else if let Some(Ipld::String(value)) = blob.get("cid") { if let Some(mime_type) = mime_type { return Some(Blob { - r#ref: CidLink::str(value), - mime_type: MimeType::raw(mime_type), + r#ref: CidLink::cow_str(CowStr::Borrowed(value.as_str())), + mime_type: MimeType::new_cow(CowStr::Borrowed(mime_type)), size: 0, }); } @@ -304,7 +320,9 @@ pub fn cbor_to_blob<'b>(blob: &'b BTreeMap) -> Option> { } /// convert a JSON object to an atproto data model blob if it matches the format -pub fn json_to_blob<'b>(blob: &'b serde_json::Map) -> Option> { +pub fn json_to_blob<'b>( + blob: &'b serde_json::Map, +) -> Option>> { let mime_type = blob.get("mimeType").and_then(|v| v.as_str()); if let Some(value) = blob.get("ref") { if let Some(value) = value @@ -315,8 +333,8 @@ pub fn json_to_blob<'b>(blob: &'b serde_json::Map) -> let size = blob.get("size").and_then(|v| v.as_u64()); if let (Some(mime_type), Some(size)) = (mime_type, size) { return Some(Blob { - r#ref: CidLink::str(value), - mime_type: MimeType::raw(mime_type), + r#ref: CidLink::cow_str(CowStr::Borrowed(value)), + mime_type: MimeType::new_cow(CowStr::Borrowed(mime_type)), size: size as usize, }); } @@ -324,8 +342,8 @@ pub fn json_to_blob<'b>(blob: &'b serde_json::Map) -> } else if let Some(value) = blob.get("cid").and_then(|v| v.as_str()) { if let Some(mime_type) = mime_type { return Some(Blob { - r#ref: CidLink::str(value), - mime_type: MimeType::raw(mime_type), + r#ref: CidLink::cow_str(CowStr::Borrowed(value)), + mime_type: MimeType::new_cow(CowStr::Borrowed(mime_type)), size: 0, }); } diff --git a/crates/jacquard-common/src/types/value/serde_impl.rs b/crates/jacquard-common/src/types/value/serde_impl.rs index 27275e3e..33b1d4a8 100644 --- a/crates/jacquard-common/src/types/value/serde_impl.rs +++ b/crates/jacquard-common/src/types/value/serde_impl.rs @@ -251,7 +251,7 @@ impl<'de: 'v, 'v> serde::de::Visitor<'v> for DataVisitor { continue; } else { // Only key, return CidLink - return Ok(Data::CidLink(Cid::from(cid_str))); + return Ok(Data::CidLink(Cid::cow_str(CowStr::from(cid_str)))); } } else if key.as_str() == "$bytes" { // {"$bytes": "base64_string"} pattern @@ -326,7 +326,8 @@ fn apply_type_inference<'s>(mut map: BTreeMap>) -> Result>; wrap directly. + r#ref: CidLink(ref_cid), mime_type: MimeType::from(mime_cowstr), size, })); @@ -344,9 +345,7 @@ fn apply_type_inference<'s>(mut map: BTreeMap>) -> Result { - Data::CidLink(Cid::from_str(&s).unwrap()) - } + DataModelType::CidLink if key.as_str() == "$link" => Data::CidLink(Cid::cow_str(s)), _ => continue, // no refinement needed }; *value = refined; @@ -376,7 +375,7 @@ fn refine_string_by_type<'s>(s: CowStr<'s>, string_type: LexiconStringType) -> D 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()) + 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) @@ -385,8 +384,8 @@ fn refine_string_by_type<'s>(s: CowStr<'s>, string_type: LexiconStringType) -> D 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_owned(s.clone()) - .map(|rkey| Data::String(AtprotoStr::RecordKey(RecordKey::from(rkey)))) + LexiconStringType::RecordKey => Rkey::new_cow(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))) @@ -676,7 +675,7 @@ impl<'de: 'v, 'v> serde::de::Visitor<'v> for RawDataVisitor { continue; } else { // Only key, return CidLink - return Ok(RawData::CidLink(Cid::from(cid_str))); + return Ok(RawData::CidLink(Cid::cow_str(CowStr::from(cid_str)))); } } else if key.as_str() == "$bytes" { // {"$bytes": "base64_string"} pattern @@ -813,7 +812,8 @@ fn apply_raw_type_inference<'s>( if let (Some(ref_cid), Some(mime_cowstr), Some(size)) = (ref_cid, mime_type, size) { return Ok(RawData::Blob(Blob { - r#ref: CidLink::str(ref_cid.as_str()).into_static(), + // ref_cid is already Cid>; wrap directly. + r#ref: CidLink(ref_cid), mime_type: MimeType::from(mime_cowstr), size, })); @@ -1036,12 +1036,12 @@ impl serde::de::Error for DataDeserializerError { // MapAccess implementation for Blob - allows borrowing from blob fields struct BlobDeserializer<'de> { - blob: &'de Blob<'de>, + blob: &'de Blob>, field_index: usize, } impl<'de> BlobDeserializer<'de> { - fn new(blob: &'de Blob<'de>) -> Self { + fn new(blob: &'de Blob>) -> Self { Self { blob, field_index: 0, @@ -1084,12 +1084,12 @@ impl<'de> serde::de::MapAccess<'de> for BlobDeserializer<'de> { } struct OwnedBlobDeserializer { - blob: Blob<'static>, + blob: Blob>, field_index: usize, } impl OwnedBlobDeserializer { - fn new(blob: Blob<'_>) -> Self { + fn new(blob: Blob>) -> Self { Self { blob: blob.into_static(), field_index: 0, diff --git a/crates/jacquard-common/src/types/value/tests.rs b/crates/jacquard-common/src/types/value/tests.rs index 3dc0ae7c..7e47df2c 100644 --- a/crates/jacquard-common/src/types/value/tests.rs +++ b/crates/jacquard-common/src/types/value/tests.rs @@ -77,9 +77,9 @@ fn serialize_deserialize_bytes_json() { #[test] fn serialize_deserialize_cid_link_json() { - let data = Data::CidLink(Cid::str( + let data = Data::CidLink(Cid::cow_str(CowStr::Borrowed( "bafyreih4g7bvo6hdq2juolev5bfzpbo4ewkxh5mzxwgvkjp3kitc6hqkha", - )); + ))); // JSON: should be {"$link": "cid_string"} let json = serde_json::to_string(&data).unwrap(); @@ -470,7 +470,7 @@ fn test_atproto_types_deserialization() { #[serde(borrow)] did: Did>, handle: Handle>, - cid: Cid<'a>, + cid: Cid>, } let mut map = BTreeMap::new(); @@ -486,9 +486,9 @@ fn test_atproto_types_deserialization() { ); map.insert( SmolStr::new_static("cid"), - Data::String(AtprotoStr::Cid(Cid::str( + Data::String(AtprotoStr::Cid(Cid::cow_str(CowStr::Borrowed( "bafyreih4g7bvo6hdq2juolev5bfzpbo4ewkxh5mzxwgvkjp3kitc6hqkha", - ))), + )))), ); let data = Data::Object(Object(map)); diff --git a/crates/jacquard-common/src/xrpc/atproto.rs b/crates/jacquard-common/src/xrpc/atproto.rs index 0ee29728..26469c7d 100644 --- a/crates/jacquard-common/src/xrpc/atproto.rs +++ b/crates/jacquard-common/src/xrpc/atproto.rs @@ -87,7 +87,7 @@ impl IntoStatic for ListRecordsOutput<'_> { pub struct ListRecordsRecord<'a> { #[serde(skip_serializing_if = "Option::is_none")] #[serde(borrow)] - pub cid: Option>, + pub cid: Option>>, #[serde(borrow)] pub uri: AtUri<'a>, #[serde(borrow)] @@ -133,7 +133,7 @@ impl<'a> XrpcRequest for ListRecords<'a> { pub struct GetRecord<'a> { #[serde(skip_serializing_if = "Option::is_none")] #[serde(borrow)] - pub cid: Option>, + pub cid: Option>>, #[serde(borrow)] pub collection: Nsid>, #[serde(borrow)] @@ -162,7 +162,7 @@ impl IntoStatic for GetRecord<'_> { pub struct GetRecordOutput<'a> { #[serde(skip_serializing_if = "Option::is_none")] #[serde(borrow)] - pub cid: Option>, + pub cid: Option>>, #[serde(borrow)] pub uri: AtUri<'a>, #[serde(borrow)]