A better Rust ATProto crate
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

updated (with some transitional cows) except for AtUri and Data

+868 -1018
+176 -314
crates/jacquard-api/src/app_bsky/actor.rs
··· 15 15 pub mod search_actors_typeahead; 16 16 pub mod status; 17 17 18 - 19 18 #[allow(unused_imports)] 20 19 use alloc::collections::BTreeMap; 21 20 ··· 25 24 26 25 #[allow(unused_imports)] 27 26 use jacquard_common::deps::codegen::unicode_segmentation::UnicodeSegmentation; 28 - use jacquard_common::types::string::{Did, Handle, AtUri, Cid, Datetime, UriValue}; 27 + use jacquard_common::types::string::{AtUri, Cid, Datetime, Did, Handle, UriValue}; 29 28 use jacquard_common::types::value::Data; 30 29 use jacquard_derive::{IntoStatic, lexicon, open_union}; 31 30 use jacquard_lexicon::lexicon::LexiconDoc; 32 31 use jacquard_lexicon::schema::LexiconSchema; 33 32 34 - #[allow(unused_imports)] 35 - use jacquard_lexicon::validation::{ConstraintError, ValidationPath}; 36 - use serde::{Serialize, Deserialize}; 33 + use crate::app_bsky::actor; 37 34 use crate::app_bsky::embed::external::View; 38 35 use crate::app_bsky::feed::postgate::DisableRule; 39 36 use crate::app_bsky::feed::threadgate::FollowerRule; ··· 45 42 use crate::app_bsky::notification::ActivitySubscription; 46 43 use crate::com_atproto::label::Label; 47 44 use crate::com_atproto::repo::strong_ref::StrongRef; 48 - use crate::app_bsky::actor; 45 + #[allow(unused_imports)] 46 + use jacquard_lexicon::validation::{ConstraintError, ValidationPath}; 47 + use serde::{Deserialize, Serialize}; 49 48 50 49 #[lexicon] 51 50 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] ··· 85 84 pub queued_nudges: Option<Vec<CowStr<'a>>>, 86 85 } 87 86 88 - 89 87 #[lexicon] 90 88 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic, Default)] 91 89 #[serde(rename_all = "camelCase")] ··· 99 97 #[serde(borrow)] 100 98 pub visibility: ContentLabelPrefVisibility<'a>, 101 99 } 102 - 103 100 104 101 #[derive(Debug, Clone, PartialEq, Eq, Hash)] 105 102 pub enum ContentLabelPrefVisibility<'a> { ··· 218 215 pub is_over_age18: Option<bool>, 219 216 } 220 217 221 - 222 218 #[lexicon] 223 219 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic, Default)] 224 220 #[serde(rename_all = "camelCase")] ··· 244 240 pub hide_reposts: Option<bool>, 245 241 } 246 242 247 - 248 243 #[lexicon] 249 244 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 250 245 #[serde(rename_all = "camelCase")] ··· 253 248 #[serde(borrow)] 254 249 pub items: Vec<AtUri<'a>>, 255 250 } 256 - 257 251 258 252 #[lexicon] 259 253 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] ··· 275 269 pub followers: Vec<actor::ProfileViewBasic<'a>>, 276 270 } 277 271 278 - 279 272 #[lexicon] 280 273 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 281 274 #[serde(rename_all = "camelCase")] ··· 283 276 #[serde(borrow)] 284 277 pub did: Did<'a>, 285 278 } 286 - 287 279 288 280 #[lexicon] 289 281 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] ··· 417 409 fn into_static(self) -> Self::Output { 418 410 match self { 419 411 MutedWordActorTarget::All => MutedWordActorTarget::All, 420 - MutedWordActorTarget::ExcludeFollowing => { 421 - MutedWordActorTarget::ExcludeFollowing 422 - } 423 - MutedWordActorTarget::Other(v) => { 424 - MutedWordActorTarget::Other(v.into_static()) 425 - } 412 + MutedWordActorTarget::ExcludeFollowing => MutedWordActorTarget::ExcludeFollowing, 413 + MutedWordActorTarget::Other(v) => MutedWordActorTarget::Other(v.into_static()), 426 414 } 427 415 } 428 416 } 429 - 430 417 431 418 #[derive(Debug, Clone, PartialEq, Eq, Hash)] 432 419 pub enum MutedWordTarget<'a> { ··· 510 497 } 511 498 } 512 499 513 - 514 500 #[lexicon] 515 501 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 516 502 #[serde(rename_all = "camelCase")] ··· 540 526 pub id: CowStr<'a>, 541 527 } 542 528 543 - 544 529 #[lexicon] 545 530 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic, Default)] 546 531 #[serde(rename_all = "camelCase")] ··· 563 548 ///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. 564 549 #[serde(skip_serializing_if = "Option::is_none")] 565 550 #[serde(borrow)] 566 - pub threadgate_allow_rules: Option< 567 - Vec<PostInteractionSettingsPrefThreadgateAllowRulesItem<'a>>, 568 - >, 551 + pub threadgate_allow_rules: 552 + Option<Vec<PostInteractionSettingsPrefThreadgateAllowRulesItem<'a>>>, 569 553 } 570 - 571 554 572 555 #[open_union] 573 556 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] ··· 583 566 ThreadgateListRule(Box<ListRule<'a>>), 584 567 } 585 568 586 - 587 569 #[open_union] 588 570 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 589 571 #[serde(tag = "$type", bound(deserialize = "'de: 'a"))] ··· 647 629 pub starter_packs: Option<i64>, 648 630 } 649 631 650 - 651 632 #[lexicon] 652 633 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic, Default)] 653 634 #[serde(rename_all = "camelCase")] ··· 655 636 #[serde(borrow)] 656 637 pub allow_subscriptions: ProfileAssociatedActivitySubscriptionAllowSubscriptions<'a>, 657 638 } 658 - 659 639 660 640 #[derive(Debug, Clone, PartialEq, Eq, Hash)] 661 641 pub enum ProfileAssociatedActivitySubscriptionAllowSubscriptions<'a> { ··· 698 678 } 699 679 } 700 680 701 - impl<'a> core::fmt::Display 702 - for ProfileAssociatedActivitySubscriptionAllowSubscriptions<'a> { 681 + impl<'a> core::fmt::Display for ProfileAssociatedActivitySubscriptionAllowSubscriptions<'a> { 703 682 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 704 683 write!(f, "{}", self.as_str()) 705 684 } ··· 711 690 } 712 691 } 713 692 714 - impl<'a> serde::Serialize 715 - for ProfileAssociatedActivitySubscriptionAllowSubscriptions<'a> { 693 + impl<'a> serde::Serialize for ProfileAssociatedActivitySubscriptionAllowSubscriptions<'a> { 716 694 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 717 695 where 718 696 S: serde::Serializer, ··· 722 700 } 723 701 724 702 impl<'de, 'a> serde::Deserialize<'de> 725 - for ProfileAssociatedActivitySubscriptionAllowSubscriptions<'a> 703 + for ProfileAssociatedActivitySubscriptionAllowSubscriptions<'a> 726 704 where 727 705 'de: 'a, 728 706 { ··· 741 719 } 742 720 } 743 721 744 - impl jacquard_common::IntoStatic 745 - for ProfileAssociatedActivitySubscriptionAllowSubscriptions<'_> { 722 + impl jacquard_common::IntoStatic for ProfileAssociatedActivitySubscriptionAllowSubscriptions<'_> { 746 723 type Output = ProfileAssociatedActivitySubscriptionAllowSubscriptions<'static>; 747 724 fn into_static(self) -> Self::Output { 748 725 match self { ··· 756 733 ProfileAssociatedActivitySubscriptionAllowSubscriptions::None 757 734 } 758 735 ProfileAssociatedActivitySubscriptionAllowSubscriptions::Other(v) => { 759 - ProfileAssociatedActivitySubscriptionAllowSubscriptions::Other( 760 - v.into_static(), 761 - ) 736 + ProfileAssociatedActivitySubscriptionAllowSubscriptions::Other(v.into_static()) 762 737 } 763 738 } 764 739 } 765 740 } 766 - 767 741 768 742 #[lexicon] 769 743 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic, Default)] ··· 772 746 #[serde(borrow)] 773 747 pub allow_incoming: ProfileAssociatedChatAllowIncoming<'a>, 774 748 } 775 - 776 749 777 750 #[derive(Debug, Clone, PartialEq, Eq, Hash)] 778 751 pub enum ProfileAssociatedChatAllowIncoming<'a> { ··· 859 832 type Output = ProfileAssociatedChatAllowIncoming<'static>; 860 833 fn into_static(self) -> Self::Output { 861 834 match self { 862 - ProfileAssociatedChatAllowIncoming::All => { 863 - ProfileAssociatedChatAllowIncoming::All 864 - } 865 - ProfileAssociatedChatAllowIncoming::None => { 866 - ProfileAssociatedChatAllowIncoming::None 867 - } 835 + ProfileAssociatedChatAllowIncoming::All => ProfileAssociatedChatAllowIncoming::All, 836 + ProfileAssociatedChatAllowIncoming::None => ProfileAssociatedChatAllowIncoming::None, 868 837 ProfileAssociatedChatAllowIncoming::Following => { 869 838 ProfileAssociatedChatAllowIncoming::Following 870 839 } ··· 874 843 } 875 844 } 876 845 } 877 - 878 846 879 847 #[lexicon] 880 848 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] ··· 885 853 #[serde(borrow)] 886 854 pub show_button_to: ProfileAssociatedGermShowButtonTo<'a>, 887 855 } 888 - 889 856 890 857 #[derive(Debug, Clone, PartialEq, Eq, Hash)] 891 858 pub enum ProfileAssociatedGermShowButtonTo<'a> { ··· 980 947 } 981 948 } 982 949 } 983 - 984 950 985 951 #[lexicon] 986 952 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] ··· 1027 993 pub viewer: Option<actor::ViewerState<'a>>, 1028 994 } 1029 995 1030 - 1031 996 #[lexicon] 1032 997 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 1033 998 #[serde(rename_all = "camelCase")] ··· 1067 1032 #[serde(borrow)] 1068 1033 pub viewer: Option<actor::ViewerState<'a>>, 1069 1034 } 1070 - 1071 1035 1072 1036 #[lexicon] 1073 1037 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] ··· 1132 1096 pub website: Option<UriValue<'a>>, 1133 1097 } 1134 1098 1135 - 1136 1099 #[lexicon] 1137 1100 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 1138 1101 #[serde(rename_all = "camelCase")] ··· 1145 1108 #[serde(borrow)] 1146 1109 pub value: CowStr<'a>, 1147 1110 } 1148 - 1149 1111 1150 1112 #[derive(Debug, Clone, PartialEq, Eq, Hash)] 1151 1113 pub enum SavedFeedType<'a> { ··· 1240 1202 } 1241 1203 } 1242 1204 1243 - 1244 1205 #[lexicon] 1245 1206 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 1246 1207 #[serde(rename_all = "camelCase")] ··· 1253 1214 pub timeline_index: Option<i64>, 1254 1215 } 1255 1216 1256 - 1257 1217 #[lexicon] 1258 1218 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 1259 1219 #[serde(rename_all = "camelCase")] ··· 1261 1221 #[serde(borrow)] 1262 1222 pub items: Vec<actor::SavedFeed<'a>>, 1263 1223 } 1264 - 1265 1224 1266 1225 #[lexicon] 1267 1226 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] ··· 1377 1336 } 1378 1337 } 1379 1338 } 1380 - 1381 1339 1382 1340 #[lexicon] 1383 1341 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic, Default)] ··· 1713 1671 type Output = VerificationStateVerifiedStatus<'static>; 1714 1672 fn into_static(self) -> Self::Output { 1715 1673 match self { 1716 - VerificationStateVerifiedStatus::Valid => { 1717 - VerificationStateVerifiedStatus::Valid 1718 - } 1719 - VerificationStateVerifiedStatus::Invalid => { 1720 - VerificationStateVerifiedStatus::Invalid 1721 - } 1722 - VerificationStateVerifiedStatus::None => { 1723 - VerificationStateVerifiedStatus::None 1724 - } 1674 + VerificationStateVerifiedStatus::Valid => VerificationStateVerifiedStatus::Valid, 1675 + VerificationStateVerifiedStatus::Invalid => VerificationStateVerifiedStatus::Invalid, 1676 + VerificationStateVerifiedStatus::None => VerificationStateVerifiedStatus::None, 1725 1677 VerificationStateVerifiedStatus::Other(v) => { 1726 1678 VerificationStateVerifiedStatus::Other(v.into_static()) 1727 1679 } ··· 2543 2495 2544 2496 pub mod adult_content_pref_state { 2545 2497 2546 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 2498 + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; 2547 2499 #[allow(unused)] 2548 2500 use ::core::marker::PhantomData; 2549 2501 mod sealed { ··· 2642 2594 } 2643 2595 2644 2596 fn lexicon_doc_app_bsky_actor_defs() -> LexiconDoc<'static> { 2597 + use alloc::collections::BTreeMap; 2645 2598 #[allow(unused_imports)] 2646 2599 use jacquard_common::{CowStr, deps::smol_str::SmolStr, types::blob::MimeType}; 2647 2600 use jacquard_lexicon::lexicon::*; 2648 - use alloc::collections::BTreeMap; 2649 2601 LexiconDoc { 2650 2602 lexicon: Lexicon::Lexicon1, 2651 2603 id: CowStr::new_static("app.bsky.actor.defs"), ··· 2882 2834 map.insert( 2883 2835 SmolStr::new_static("items"), 2884 2836 LexObjectProperty::Array(LexArray { 2885 - description: Some( 2886 - CowStr::new_static( 2887 - "A list of URIs of posts the account owner has hidden.", 2888 - ), 2889 - ), 2837 + description: Some(CowStr::new_static( 2838 + "A list of URIs of posts the account owner has hidden.", 2839 + )), 2890 2840 items: LexArrayItem::String(LexString { 2891 2841 format: Some(LexStringFormat::AtUri), 2892 2842 ..Default::default() ··· 2931 2881 map.insert( 2932 2882 SmolStr::new_static("knownFollowers"), 2933 2883 LexUserType::Object(LexObject { 2934 - description: Some( 2935 - CowStr::new_static( 2936 - "The subject's followers whom you also follow", 2937 - ), 2938 - ), 2939 - required: Some( 2940 - vec![ 2941 - SmolStr::new_static("count"), 2942 - SmolStr::new_static("followers") 2943 - ], 2944 - ), 2884 + description: Some(CowStr::new_static( 2885 + "The subject's followers whom you also follow", 2886 + )), 2887 + required: Some(vec![ 2888 + SmolStr::new_static("count"), 2889 + SmolStr::new_static("followers"), 2890 + ]), 2945 2891 properties: { 2946 2892 #[allow(unused_mut)] 2947 2893 let mut map = BTreeMap::new(); ··· 3012 2958 map.insert( 3013 2959 SmolStr::new_static("liveEventPreferences"), 3014 2960 LexUserType::Object(LexObject { 3015 - description: Some( 3016 - CowStr::new_static("Preferences for live events."), 3017 - ), 2961 + description: Some(CowStr::new_static("Preferences for live events.")), 3018 2962 properties: { 3019 2963 #[allow(unused_mut)] 3020 2964 let mut map = BTreeMap::new(); 3021 2965 map.insert( 3022 2966 SmolStr::new_static("hiddenFeedIds"), 3023 2967 LexObjectProperty::Array(LexArray { 3024 - description: Some( 3025 - CowStr::new_static( 3026 - "A list of feed IDs that the user has hidden from live events.", 3027 - ), 3028 - ), 2968 + description: Some(CowStr::new_static( 2969 + "A list of feed IDs that the user has hidden from live events.", 2970 + )), 3029 2971 items: LexArrayItem::String(LexString { 3030 2972 ..Default::default() 3031 2973 }), ··· 3135 3077 map.insert( 3136 3078 SmolStr::new_static("items"), 3137 3079 LexObjectProperty::Array(LexArray { 3138 - description: Some( 3139 - CowStr::new_static( 3140 - "A list of words the account owner has muted.", 3141 - ), 3142 - ), 3080 + description: Some(CowStr::new_static( 3081 + "A list of words the account owner has muted.", 3082 + )), 3143 3083 items: LexArrayItem::Ref(LexRef { 3144 3084 r#ref: CowStr::new_static("app.bsky.actor.defs#mutedWord"), 3145 3085 ..Default::default() ··· 3216 3156 map.insert( 3217 3157 SmolStr::new_static("birthDate"), 3218 3158 LexObjectProperty::String(LexString { 3219 - description: Some( 3220 - CowStr::new_static("The birth date of account owner."), 3221 - ), 3159 + description: Some(CowStr::new_static( 3160 + "The birth date of account owner.", 3161 + )), 3222 3162 format: Some(LexStringFormat::Datetime), 3223 3163 ..Default::default() 3224 3164 }), ··· 3304 3244 CowStr::new_static("#labelersPref"), 3305 3245 CowStr::new_static("#postInteractionSettingsPref"), 3306 3246 CowStr::new_static("#verificationPrefs"), 3307 - CowStr::new_static("#liveEventPreferences") 3247 + CowStr::new_static("#liveEventPreferences"), 3308 3248 ], 3309 3249 ..Default::default() 3310 3250 }), ··· 3320 3260 map.insert( 3321 3261 SmolStr::new_static("activitySubscription"), 3322 3262 LexObjectProperty::Ref(LexRef { 3323 - r#ref: CowStr::new_static( 3324 - "#profileAssociatedActivitySubscription", 3325 - ), 3263 + r#ref: CowStr::new_static("#profileAssociatedActivitySubscription"), 3326 3264 ..Default::default() 3327 3265 }), 3328 3266 ); ··· 3378 3316 let mut map = BTreeMap::new(); 3379 3317 map.insert( 3380 3318 SmolStr::new_static("allowSubscriptions"), 3381 - LexObjectProperty::String(LexString { ..Default::default() }), 3319 + LexObjectProperty::String(LexString { 3320 + ..Default::default() 3321 + }), 3382 3322 ); 3383 3323 map 3384 3324 }, ··· 3394 3334 let mut map = BTreeMap::new(); 3395 3335 map.insert( 3396 3336 SmolStr::new_static("allowIncoming"), 3397 - LexObjectProperty::String(LexString { ..Default::default() }), 3337 + LexObjectProperty::String(LexString { 3338 + ..Default::default() 3339 + }), 3398 3340 ); 3399 3341 map 3400 3342 }, ··· 3404 3346 map.insert( 3405 3347 SmolStr::new_static("profileAssociatedGerm"), 3406 3348 LexUserType::Object(LexObject { 3407 - required: Some( 3408 - vec![ 3409 - SmolStr::new_static("showButtonTo"), 3410 - SmolStr::new_static("messageMeUrl") 3411 - ], 3412 - ), 3349 + required: Some(vec![ 3350 + SmolStr::new_static("showButtonTo"), 3351 + SmolStr::new_static("messageMeUrl"), 3352 + ]), 3413 3353 properties: { 3414 3354 #[allow(unused_mut)] 3415 3355 let mut map = BTreeMap::new(); ··· 3422 3362 ); 3423 3363 map.insert( 3424 3364 SmolStr::new_static("showButtonTo"), 3425 - LexObjectProperty::String(LexString { ..Default::default() }), 3365 + LexObjectProperty::String(LexString { 3366 + ..Default::default() 3367 + }), 3426 3368 ); 3427 3369 map 3428 3370 }, ··· 3432 3374 map.insert( 3433 3375 SmolStr::new_static("profileView"), 3434 3376 LexUserType::Object(LexObject { 3435 - required: Some( 3436 - vec![SmolStr::new_static("did"), SmolStr::new_static("handle")], 3437 - ), 3377 + required: Some(vec![ 3378 + SmolStr::new_static("did"), 3379 + SmolStr::new_static("handle"), 3380 + ]), 3438 3381 properties: { 3439 3382 #[allow(unused_mut)] 3440 3383 let mut map = BTreeMap::new(); ··· 3514 3457 ); 3515 3458 map.insert( 3516 3459 SmolStr::new_static("pronouns"), 3517 - LexObjectProperty::String(LexString { ..Default::default() }), 3460 + LexObjectProperty::String(LexString { 3461 + ..Default::default() 3462 + }), 3518 3463 ); 3519 3464 map.insert( 3520 3465 SmolStr::new_static("status"), ··· 3545 3490 map.insert( 3546 3491 SmolStr::new_static("profileViewBasic"), 3547 3492 LexUserType::Object(LexObject { 3548 - required: Some( 3549 - vec![SmolStr::new_static("did"), SmolStr::new_static("handle")], 3550 - ), 3493 + required: Some(vec![ 3494 + SmolStr::new_static("did"), 3495 + SmolStr::new_static("handle"), 3496 + ]), 3551 3497 properties: { 3552 3498 #[allow(unused_mut)] 3553 3499 let mut map = BTreeMap::new(); ··· 3612 3558 ); 3613 3559 map.insert( 3614 3560 SmolStr::new_static("pronouns"), 3615 - LexObjectProperty::String(LexString { ..Default::default() }), 3561 + LexObjectProperty::String(LexString { 3562 + ..Default::default() 3563 + }), 3616 3564 ); 3617 3565 map.insert( 3618 3566 SmolStr::new_static("status"), ··· 3643 3591 map.insert( 3644 3592 SmolStr::new_static("profileViewDetailed"), 3645 3593 LexUserType::Object(LexObject { 3646 - required: Some( 3647 - vec![SmolStr::new_static("did"), SmolStr::new_static("handle")], 3648 - ), 3594 + required: Some(vec![ 3595 + SmolStr::new_static("did"), 3596 + SmolStr::new_static("handle"), 3597 + ]), 3649 3598 properties: { 3650 3599 #[allow(unused_mut)] 3651 3600 let mut map = BTreeMap::new(); ··· 3766 3715 ); 3767 3716 map.insert( 3768 3717 SmolStr::new_static("pronouns"), 3769 - LexObjectProperty::String(LexString { ..Default::default() }), 3718 + LexObjectProperty::String(LexString { 3719 + ..Default::default() 3720 + }), 3770 3721 ); 3771 3722 map.insert( 3772 3723 SmolStr::new_static("status"), ··· 3804 3755 map.insert( 3805 3756 SmolStr::new_static("savedFeed"), 3806 3757 LexUserType::Object(LexObject { 3807 - required: Some( 3808 - vec![ 3809 - SmolStr::new_static("id"), SmolStr::new_static("type"), 3810 - SmolStr::new_static("value"), SmolStr::new_static("pinned") 3811 - ], 3812 - ), 3758 + required: Some(vec![ 3759 + SmolStr::new_static("id"), 3760 + SmolStr::new_static("type"), 3761 + SmolStr::new_static("value"), 3762 + SmolStr::new_static("pinned"), 3763 + ]), 3813 3764 properties: { 3814 3765 #[allow(unused_mut)] 3815 3766 let mut map = BTreeMap::new(); 3816 3767 map.insert( 3817 3768 SmolStr::new_static("id"), 3818 - LexObjectProperty::String(LexString { ..Default::default() }), 3769 + LexObjectProperty::String(LexString { 3770 + ..Default::default() 3771 + }), 3819 3772 ); 3820 3773 map.insert( 3821 3774 SmolStr::new_static("pinned"), ··· 3825 3778 ); 3826 3779 map.insert( 3827 3780 SmolStr::new_static("type"), 3828 - LexObjectProperty::String(LexString { ..Default::default() }), 3781 + LexObjectProperty::String(LexString { 3782 + ..Default::default() 3783 + }), 3829 3784 ); 3830 3785 map.insert( 3831 3786 SmolStr::new_static("value"), 3832 - LexObjectProperty::String(LexString { ..Default::default() }), 3787 + LexObjectProperty::String(LexString { 3788 + ..Default::default() 3789 + }), 3833 3790 ); 3834 3791 map 3835 3792 }, ··· 3839 3796 map.insert( 3840 3797 SmolStr::new_static("savedFeedsPref"), 3841 3798 LexUserType::Object(LexObject { 3842 - required: Some( 3843 - vec![SmolStr::new_static("pinned"), SmolStr::new_static("saved")], 3844 - ), 3799 + required: Some(vec![ 3800 + SmolStr::new_static("pinned"), 3801 + SmolStr::new_static("saved"), 3802 + ]), 3845 3803 properties: { 3846 3804 #[allow(unused_mut)] 3847 3805 let mut map = BTreeMap::new(); ··· 3990 3948 map.insert( 3991 3949 SmolStr::new_static("sort"), 3992 3950 LexObjectProperty::String(LexString { 3993 - description: Some( 3994 - CowStr::new_static("Sorting mode for threads."), 3995 - ), 3951 + description: Some(CowStr::new_static("Sorting mode for threads.")), 3996 3952 ..Default::default() 3997 3953 }), 3998 3954 ); ··· 4004 3960 map.insert( 4005 3961 SmolStr::new_static("verificationPrefs"), 4006 3962 LexUserType::Object(LexObject { 4007 - description: Some( 4008 - CowStr::new_static( 4009 - "Preferences for how verified accounts appear in the app.", 4010 - ), 4011 - ), 3963 + description: Some(CowStr::new_static( 3964 + "Preferences for how verified accounts appear in the app.", 3965 + )), 4012 3966 required: Some(vec![]), 4013 3967 properties: { 4014 3968 #[allow(unused_mut)] ··· 4087 4041 map.insert( 4088 4042 SmolStr::new_static("verificationView"), 4089 4043 LexUserType::Object(LexObject { 4090 - description: Some( 4091 - CowStr::new_static( 4092 - "An individual verification for an associated subject.", 4093 - ), 4094 - ), 4095 - required: Some( 4096 - vec![ 4097 - SmolStr::new_static("issuer"), SmolStr::new_static("uri"), 4098 - SmolStr::new_static("isValid"), 4099 - SmolStr::new_static("createdAt") 4100 - ], 4101 - ), 4044 + description: Some(CowStr::new_static( 4045 + "An individual verification for an associated subject.", 4046 + )), 4047 + required: Some(vec![ 4048 + SmolStr::new_static("issuer"), 4049 + SmolStr::new_static("uri"), 4050 + SmolStr::new_static("isValid"), 4051 + SmolStr::new_static("createdAt"), 4052 + ]), 4102 4053 properties: { 4103 4054 #[allow(unused_mut)] 4104 4055 let mut map = BTreeMap::new(); 4105 4056 map.insert( 4106 4057 SmolStr::new_static("createdAt"), 4107 4058 LexObjectProperty::String(LexString { 4108 - description: Some( 4109 - CowStr::new_static( 4110 - "Timestamp when the verification was created.", 4111 - ), 4112 - ), 4059 + description: Some(CowStr::new_static( 4060 + "Timestamp when the verification was created.", 4061 + )), 4113 4062 format: Some(LexStringFormat::Datetime), 4114 4063 ..Default::default() 4115 4064 }), ··· 4123 4072 map.insert( 4124 4073 SmolStr::new_static("issuer"), 4125 4074 LexObjectProperty::String(LexString { 4126 - description: Some( 4127 - CowStr::new_static("The user who issued this verification."), 4128 - ), 4075 + description: Some(CowStr::new_static( 4076 + "The user who issued this verification.", 4077 + )), 4129 4078 format: Some(LexStringFormat::Did), 4130 4079 ..Default::default() 4131 4080 }), ··· 4133 4082 map.insert( 4134 4083 SmolStr::new_static("uri"), 4135 4084 LexObjectProperty::String(LexString { 4136 - description: Some( 4137 - CowStr::new_static("The AT-URI of the verification record."), 4138 - ), 4085 + description: Some(CowStr::new_static( 4086 + "The AT-URI of the verification record.", 4087 + )), 4139 4088 format: Some(LexStringFormat::AtUri), 4140 4089 ..Default::default() 4141 4090 }), ··· 4240 4189 4241 4190 pub mod hidden_posts_pref_state { 4242 4191 4243 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 4192 + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; 4244 4193 #[allow(unused)] 4245 4194 use ::core::marker::PhantomData; 4246 4195 mod sealed { ··· 4340 4289 4341 4290 pub mod interests_pref_state { 4342 4291 4343 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 4292 + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; 4344 4293 #[allow(unused)] 4345 4294 use ::core::marker::PhantomData; 4346 4295 mod sealed { ··· 4440 4389 4441 4390 pub mod known_followers_state { 4442 4391 4443 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 4392 + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; 4444 4393 #[allow(unused)] 4445 4394 use ::core::marker::PhantomData; 4446 4395 mod sealed { ··· 4574 4523 4575 4524 pub mod labeler_pref_item_state { 4576 4525 4577 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 4526 + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; 4578 4527 #[allow(unused)] 4579 4528 use ::core::marker::PhantomData; 4580 4529 mod sealed { ··· 4674 4623 4675 4624 pub mod labelers_pref_state { 4676 4625 4677 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 4626 + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; 4678 4627 #[allow(unused)] 4679 4628 use ::core::marker::PhantomData; 4680 4629 mod sealed { ··· 4788 4737 4789 4738 pub mod muted_word_state { 4790 4739 4791 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 4740 + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; 4792 4741 #[allow(unused)] 4793 4742 use ::core::marker::PhantomData; 4794 4743 mod sealed { ··· 4863 4812 4864 4813 impl<'a, S: muted_word_state::State> MutedWordBuilder<'a, S> { 4865 4814 /// Set the `actorTarget` field (optional) 4866 - pub fn actor_target( 4867 - mut self, 4868 - value: impl Into<Option<MutedWordActorTarget<'a>>>, 4869 - ) -> Self { 4815 + pub fn actor_target(mut self, value: impl Into<Option<MutedWordActorTarget<'a>>>) -> Self { 4870 4816 self._fields.0 = value.into(); 4871 4817 self 4872 4818 } 4873 4819 /// Set the `actorTarget` field to an Option value (optional) 4874 - pub fn maybe_actor_target( 4875 - mut self, 4876 - value: Option<MutedWordActorTarget<'a>>, 4877 - ) -> Self { 4820 + pub fn maybe_actor_target(mut self, value: Option<MutedWordActorTarget<'a>>) -> Self { 4878 4821 self._fields.0 = value; 4879 4822 self 4880 4823 } ··· 4979 4922 4980 4923 pub mod muted_words_pref_state { 4981 4924 4982 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 4925 + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; 4983 4926 #[allow(unused)] 4984 4927 use ::core::marker::PhantomData; 4985 4928 mod sealed { ··· 5083 5026 5084 5027 pub mod nux_state { 5085 5028 5086 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 5029 + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; 5087 5030 #[allow(unused)] 5088 5031 use ::core::marker::PhantomData; 5089 5032 mod sealed { ··· 5128 5071 /// Builder for constructing an instance of this type 5129 5072 pub struct NuxBuilder<'a, S: nux_state::State> { 5130 5073 _state: PhantomData<fn() -> S>, 5131 - _fields: (Option<bool>, Option<CowStr<'a>>, Option<Datetime>, Option<CowStr<'a>>), 5074 + _fields: ( 5075 + Option<bool>, 5076 + Option<CowStr<'a>>, 5077 + Option<Datetime>, 5078 + Option<CowStr<'a>>, 5079 + ), 5132 5080 _lifetime: PhantomData<&'a ()>, 5133 5081 } 5134 5082 ··· 5201 5149 S::Id: nux_state::IsUnset, 5202 5150 { 5203 5151 /// Set the `id` field (required) 5204 - pub fn id( 5205 - mut self, 5206 - value: impl Into<CowStr<'a>>, 5207 - ) -> NuxBuilder<'a, nux_state::SetId<S>> { 5152 + pub fn id(mut self, value: impl Into<CowStr<'a>>) -> NuxBuilder<'a, nux_state::SetId<S>> { 5208 5153 self._fields.3 = Option::Some(value.into()); 5209 5154 NuxBuilder { 5210 5155 _state: PhantomData, ··· 5247 5192 5248 5193 pub mod profile_associated_germ_state { 5249 5194 5250 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 5195 + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; 5251 5196 #[allow(unused)] 5252 5197 use ::core::marker::PhantomData; 5253 5198 mod sealed { ··· 5292 5237 /// Builder for constructing an instance of this type 5293 5238 pub struct ProfileAssociatedGermBuilder<'a, S: profile_associated_germ_state::State> { 5294 5239 _state: PhantomData<fn() -> S>, 5295 - _fields: (Option<UriValue<'a>>, Option<ProfileAssociatedGermShowButtonTo<'a>>), 5240 + _fields: ( 5241 + Option<UriValue<'a>>, 5242 + Option<ProfileAssociatedGermShowButtonTo<'a>>, 5243 + ), 5296 5244 _lifetime: PhantomData<&'a ()>, 5297 5245 } 5298 5246 5299 5247 impl<'a> ProfileAssociatedGerm<'a> { 5300 5248 /// Create a new builder for this type 5301 - pub fn new() -> ProfileAssociatedGermBuilder< 5302 - 'a, 5303 - profile_associated_germ_state::Empty, 5304 - > { 5249 + pub fn new() -> ProfileAssociatedGermBuilder<'a, profile_associated_germ_state::Empty> { 5305 5250 ProfileAssociatedGermBuilder::new() 5306 5251 } 5307 5252 } ··· 5326 5271 pub fn message_me_url( 5327 5272 mut self, 5328 5273 value: impl Into<UriValue<'a>>, 5329 - ) -> ProfileAssociatedGermBuilder< 5330 - 'a, 5331 - profile_associated_germ_state::SetMessageMeUrl<S>, 5332 - > { 5274 + ) -> ProfileAssociatedGermBuilder<'a, profile_associated_germ_state::SetMessageMeUrl<S>> { 5333 5275 self._fields.0 = Option::Some(value.into()); 5334 5276 ProfileAssociatedGermBuilder { 5335 5277 _state: PhantomData, ··· 5348 5290 pub fn show_button_to( 5349 5291 mut self, 5350 5292 value: impl Into<ProfileAssociatedGermShowButtonTo<'a>>, 5351 - ) -> ProfileAssociatedGermBuilder< 5352 - 'a, 5353 - profile_associated_germ_state::SetShowButtonTo<S>, 5354 - > { 5293 + ) -> ProfileAssociatedGermBuilder<'a, profile_associated_germ_state::SetShowButtonTo<S>> { 5355 5294 self._fields.1 = Option::Some(value.into()); 5356 5295 ProfileAssociatedGermBuilder { 5357 5296 _state: PhantomData, ··· 5390 5329 5391 5330 pub mod profile_view_state { 5392 5331 5393 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 5332 + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; 5394 5333 #[allow(unused)] 5395 5334 use ::core::marker::PhantomData; 5396 5335 mod sealed { ··· 5467 5406 ProfileViewBuilder { 5468 5407 _state: PhantomData, 5469 5408 _fields: ( 5470 - None, 5471 - None, 5472 - None, 5473 - None, 5474 - None, 5475 - None, 5476 - None, 5477 - None, 5478 - None, 5479 - None, 5480 - None, 5481 - None, 5482 - None, 5483 - None, 5409 + None, None, None, None, None, None, None, None, None, None, None, None, None, None, 5484 5410 ), 5485 5411 _lifetime: PhantomData, 5486 5412 } ··· 5489 5415 5490 5416 impl<'a, S: profile_view_state::State> ProfileViewBuilder<'a, S> { 5491 5417 /// Set the `associated` field (optional) 5492 - pub fn associated( 5493 - mut self, 5494 - value: impl Into<Option<actor::ProfileAssociated<'a>>>, 5495 - ) -> Self { 5418 + pub fn associated(mut self, value: impl Into<Option<actor::ProfileAssociated<'a>>>) -> Self { 5496 5419 self._fields.0 = value.into(); 5497 5420 self 5498 5421 } 5499 5422 /// Set the `associated` field to an Option value (optional) 5500 - pub fn maybe_associated( 5501 - mut self, 5502 - value: Option<actor::ProfileAssociated<'a>>, 5503 - ) -> Self { 5423 + pub fn maybe_associated(mut self, value: Option<actor::ProfileAssociated<'a>>) -> Self { 5504 5424 self._fields.0 = value; 5505 5425 self 5506 5426 } ··· 5663 5583 5664 5584 impl<'a, S: profile_view_state::State> ProfileViewBuilder<'a, S> { 5665 5585 /// Set the `verification` field (optional) 5666 - pub fn verification( 5667 - mut self, 5668 - value: impl Into<Option<actor::VerificationState<'a>>>, 5669 - ) -> Self { 5586 + pub fn verification(mut self, value: impl Into<Option<actor::VerificationState<'a>>>) -> Self { 5670 5587 self._fields.12 = value.into(); 5671 5588 self 5672 5589 } 5673 5590 /// Set the `verification` field to an Option value (optional) 5674 - pub fn maybe_verification( 5675 - mut self, 5676 - value: Option<actor::VerificationState<'a>>, 5677 - ) -> Self { 5591 + pub fn maybe_verification(mut self, value: Option<actor::VerificationState<'a>>) -> Self { 5678 5592 self._fields.12 = value; 5679 5593 self 5680 5594 } ··· 5746 5660 5747 5661 pub mod profile_view_basic_state { 5748 5662 5749 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 5663 + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; 5750 5664 #[allow(unused)] 5751 5665 use ::core::marker::PhantomData; 5752 5666 mod sealed { ··· 5821 5735 ProfileViewBasicBuilder { 5822 5736 _state: PhantomData, 5823 5737 _fields: ( 5824 - None, 5825 - None, 5826 - None, 5827 - None, 5828 - None, 5829 - None, 5830 - None, 5831 - None, 5832 - None, 5833 - None, 5834 - None, 5835 - None, 5738 + None, None, None, None, None, None, None, None, None, None, None, None, 5836 5739 ), 5837 5740 _lifetime: PhantomData, 5838 5741 } ··· 5841 5744 5842 5745 impl<'a, S: profile_view_basic_state::State> ProfileViewBasicBuilder<'a, S> { 5843 5746 /// Set the `associated` field (optional) 5844 - pub fn associated( 5845 - mut self, 5846 - value: impl Into<Option<actor::ProfileAssociated<'a>>>, 5847 - ) -> Self { 5747 + pub fn associated(mut self, value: impl Into<Option<actor::ProfileAssociated<'a>>>) -> Self { 5848 5748 self._fields.0 = value.into(); 5849 5749 self 5850 5750 } 5851 5751 /// Set the `associated` field to an Option value (optional) 5852 - pub fn maybe_associated( 5853 - mut self, 5854 - value: Option<actor::ProfileAssociated<'a>>, 5855 - ) -> Self { 5752 + pub fn maybe_associated(mut self, value: Option<actor::ProfileAssociated<'a>>) -> Self { 5856 5753 self._fields.0 = value; 5857 5754 self 5858 5755 } ··· 5989 5886 5990 5887 impl<'a, S: profile_view_basic_state::State> ProfileViewBasicBuilder<'a, S> { 5991 5888 /// Set the `verification` field (optional) 5992 - pub fn verification( 5993 - mut self, 5994 - value: impl Into<Option<actor::VerificationState<'a>>>, 5995 - ) -> Self { 5889 + pub fn verification(mut self, value: impl Into<Option<actor::VerificationState<'a>>>) -> Self { 5996 5890 self._fields.10 = value.into(); 5997 5891 self 5998 5892 } 5999 5893 /// Set the `verification` field to an Option value (optional) 6000 - pub fn maybe_verification( 6001 - mut self, 6002 - value: Option<actor::VerificationState<'a>>, 6003 - ) -> Self { 5894 + pub fn maybe_verification(mut self, value: Option<actor::VerificationState<'a>>) -> Self { 6004 5895 self._fields.10 = value; 6005 5896 self 6006 5897 } ··· 6068 5959 6069 5960 pub mod profile_view_detailed_state { 6070 5961 6071 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 5962 + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; 6072 5963 #[allow(unused)] 6073 5964 use ::core::marker::PhantomData; 6074 5965 mod sealed { ··· 6152 6043 ProfileViewDetailedBuilder { 6153 6044 _state: PhantomData, 6154 6045 _fields: ( 6155 - None, 6156 - None, 6157 - None, 6158 - None, 6159 - None, 6160 - None, 6161 - None, 6162 - None, 6163 - None, 6164 - None, 6165 - None, 6166 - None, 6167 - None, 6168 - None, 6169 - None, 6170 - None, 6171 - None, 6172 - None, 6173 - None, 6174 - None, 6175 - None, 6046 + None, None, None, None, None, None, None, None, None, None, None, None, None, None, 6047 + None, None, None, None, None, None, None, 6176 6048 ), 6177 6049 _lifetime: PhantomData, 6178 6050 } ··· 6181 6053 6182 6054 impl<'a, S: profile_view_detailed_state::State> ProfileViewDetailedBuilder<'a, S> { 6183 6055 /// Set the `associated` field (optional) 6184 - pub fn associated( 6185 - mut self, 6186 - value: impl Into<Option<actor::ProfileAssociated<'a>>>, 6187 - ) -> Self { 6056 + pub fn associated(mut self, value: impl Into<Option<actor::ProfileAssociated<'a>>>) -> Self { 6188 6057 self._fields.0 = value.into(); 6189 6058 self 6190 6059 } 6191 6060 /// Set the `associated` field to an Option value (optional) 6192 - pub fn maybe_associated( 6193 - mut self, 6194 - value: Option<actor::ProfileAssociated<'a>>, 6195 - ) -> Self { 6061 + pub fn maybe_associated(mut self, value: Option<actor::ProfileAssociated<'a>>) -> Self { 6196 6062 self._fields.0 = value; 6197 6063 self 6198 6064 } ··· 6439 6305 6440 6306 impl<'a, S: profile_view_detailed_state::State> ProfileViewDetailedBuilder<'a, S> { 6441 6307 /// Set the `verification` field (optional) 6442 - pub fn verification( 6443 - mut self, 6444 - value: impl Into<Option<actor::VerificationState<'a>>>, 6445 - ) -> Self { 6308 + pub fn verification(mut self, value: impl Into<Option<actor::VerificationState<'a>>>) -> Self { 6446 6309 self._fields.18 = value.into(); 6447 6310 self 6448 6311 } 6449 6312 /// Set the `verification` field to an Option value (optional) 6450 - pub fn maybe_verification( 6451 - mut self, 6452 - value: Option<actor::VerificationState<'a>>, 6453 - ) -> Self { 6313 + pub fn maybe_verification(mut self, value: Option<actor::VerificationState<'a>>) -> Self { 6454 6314 self._fields.18 = value; 6455 6315 self 6456 6316 } ··· 6549 6409 6550 6410 pub mod saved_feed_state { 6551 6411 6552 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 6412 + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; 6553 6413 #[allow(unused)] 6554 6414 use ::core::marker::PhantomData; 6555 6415 mod sealed { ··· 6762 6622 6763 6623 pub mod saved_feeds_pref_state { 6764 6624 6765 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 6625 + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; 6766 6626 #[allow(unused)] 6767 6627 use ::core::marker::PhantomData; 6768 6628 mod sealed { ··· 6911 6771 6912 6772 pub mod saved_feeds_pref_v2_state { 6913 6773 6914 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 6774 + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; 6915 6775 #[allow(unused)] 6916 6776 use ::core::marker::PhantomData; 6917 6777 mod sealed { ··· 7011 6871 7012 6872 pub mod status_view_state { 7013 6873 7014 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 6874 + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; 7015 6875 #[allow(unused)] 7016 6876 use ::core::marker::PhantomData; 7017 6877 mod sealed { ··· 7257 7117 7258 7118 pub mod verification_state_state { 7259 7119 7260 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 7120 + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; 7261 7121 #[allow(unused)] 7262 7122 use ::core::marker::PhantomData; 7263 7123 mod sealed { ··· 7351 7211 pub fn trusted_verifier_status( 7352 7212 mut self, 7353 7213 value: impl Into<VerificationStateTrustedVerifierStatus<'a>>, 7354 - ) -> VerificationStateBuilder< 7355 - 'a, 7356 - verification_state_state::SetTrustedVerifierStatus<S>, 7357 - > { 7214 + ) -> VerificationStateBuilder<'a, verification_state_state::SetTrustedVerifierStatus<S>> { 7358 7215 self._fields.0 = Option::Some(value.into()); 7359 7216 VerificationStateBuilder { 7360 7217 _state: PhantomData, ··· 7434 7291 7435 7292 pub mod verification_view_state { 7436 7293 7437 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 7294 + pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; 7438 7295 #[allow(unused)] 7439 7296 use ::core::marker::PhantomData; 7440 7297 mod sealed { ··· 7509 7366 /// Builder for constructing an instance of this type 7510 7367 pub struct VerificationViewBuilder<'a, S: verification_view_state::State> { 7511 7368 _state: PhantomData<fn() -> S>, 7512 - _fields: (Option<Datetime>, Option<bool>, Option<Did<'a>>, Option<AtUri<'a>>), 7369 + _fields: ( 7370 + Option<Datetime>, 7371 + Option<bool>, 7372 + Option<Did<'a>>, 7373 + Option<AtUri<'a>>, 7374 + ), 7513 7375 _lifetime: PhantomData<&'a ()>, 7514 7376 } 7515 7377 ··· 7638 7500 extra_data: Some(extra_data), 7639 7501 } 7640 7502 } 7641 - } 7503 + }
+2 -2
crates/jacquard-common/src/jetstream.rs
··· 95 95 pub collection: Nsid<CowStr<'a>>, 96 96 /// Record key 97 97 #[serde(borrow)] 98 - pub rkey: Rkey<'a>, 98 + pub rkey: Rkey<CowStr<'a>>, 99 99 /// Record data (present for create/update) 100 100 #[serde(skip_serializing_if = "Option::is_none")] 101 101 #[serde(borrow)] ··· 103 103 /// Content identifier 104 104 #[serde(skip_serializing_if = "Option::is_none")] 105 105 #[serde(borrow)] 106 - pub cid: Option<Cid<'a>>, 106 + pub cid: Option<Cid<CowStr<'a>>>, 107 107 } 108 108 109 109 /// Identity event details
+14 -14
crates/jacquard-common/src/types/aturi.rs
··· 76 76 let collection = 77 77 unsafe { Nsid::unchecked(CowStr::Borrowed(collection.as_str())) }; 78 78 let rkey = if let Some(rkey) = parts.name("rkey") { 79 - let rkey = unsafe { RecordKey::from(Rkey::unchecked(rkey.as_str())) }; 79 + let rkey = unsafe { RecordKey(Rkey::unchecked_cow(CowStr::Borrowed(rkey.as_str()))) }; 80 80 Some(rkey) 81 81 } else { 82 82 None ··· 112 112 /// Collection NSID (e.g., `app.bsky.feed.post`) 113 113 pub collection: Nsid<CowStr<'u>>, 114 114 /// Optional record key identifying a specific record 115 - pub rkey: Option<RecordKey<Rkey<'u>>>, 115 + pub rkey: Option<RecordKey<Rkey<CowStr<'u>>>>, 116 116 } 117 117 118 118 impl fmt::Display for RepoPath<'_> { ··· 157 157 .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?; 158 158 let rkey = if let Some(rkey) = parts.name("rkey") { 159 159 let rkey = 160 - RecordKey::from(Rkey::new(rkey.as_str()).map_err(|e| { 160 + RecordKey(Rkey::new_cow(CowStr::Borrowed(rkey.as_str())).map_err(|e| { 161 161 AtStrError::wrap("at-uri-scheme", uri.to_string(), e) 162 162 })?); 163 163 Some(rkey) ··· 203 203 let path = if let Some(collection) = parts.name("collection") { 204 204 let collection = Nsid::new_cow(CowStr::Borrowed(collection.as_str())).unwrap(); 205 205 let rkey = if let Some(rkey) = parts.name("rkey") { 206 - let rkey = RecordKey::from(Rkey::raw(rkey.as_str())); 206 + let rkey = RecordKey(Rkey::new_cow(CowStr::Borrowed(rkey.as_str())).unwrap()); 207 207 Some(rkey) 208 208 } else { 209 209 None ··· 246 246 let collection = 247 247 unsafe { Nsid::unchecked(CowStr::Borrowed(collection.as_str())) }; 248 248 let rkey = if let Some(rkey) = parts.name("rkey") { 249 - let rkey = RecordKey::from(unsafe { Rkey::unchecked(rkey.as_str()) }); 249 + let rkey = unsafe { RecordKey(Rkey::unchecked_cow(CowStr::Borrowed(rkey.as_str()))) }; 250 250 Some(rkey) 251 251 } else { 252 252 None ··· 350 350 } 351 351 352 352 /// Get the record key from the path, if present 353 - pub fn rkey(&self) -> Option<&RecordKey<Rkey<'_>>> { 353 + pub fn rkey(&self) -> Option<&RecordKey<Rkey<CowStr<'_>>>> { 354 354 self.inner 355 355 .borrow_path() 356 356 .as_ref() ··· 414 414 AtStrError::wrap("at-uri-scheme", uri.as_ref().to_string(), e) 415 415 })?; 416 416 let rkey = if let Some(rkey) = parts.name("rkey") { 417 - let rkey = RecordKey::from(Rkey::new(rkey.as_str()).map_err(|e| { 417 + let rkey = RecordKey(Rkey::new_cow(CowStr::Borrowed(rkey.as_str())).map_err(|e| { 418 418 AtStrError::wrap("at-uri-scheme", uri.as_ref().to_string(), e) 419 419 })?); 420 420 Some(rkey) ··· 446 446 }; 447 447 let rkey = if let Some(rkey) = parts.name("rkey") { 448 448 let rkey = unsafe { 449 - RecordKey::from(Rkey::unchecked(rkey.as_str())) 449 + RecordKey(Rkey::unchecked_cow(CowStr::Borrowed(rkey.as_str()))) 450 450 }; 451 451 Some(rkey) 452 452 } else { ··· 497 497 .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?; 498 498 let rkey = if let Some(rkey) = parts.name("rkey") { 499 499 let rkey = 500 - RecordKey::from(Rkey::new_static(rkey.as_str()).map_err(|e| { 500 + RecordKey(Rkey::new_cow(CowStr::Borrowed(rkey.as_str())).map_err(|e| { 501 501 AtStrError::wrap("at-uri-scheme", uri.to_string(), e) 502 502 })?); 503 503 Some(rkey) ··· 549 549 .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?; 550 550 let rkey = if let Some(rkey) = parts.name("rkey") { 551 551 let rkey = 552 - RecordKey::from(Rkey::new(rkey.as_str()).map_err(|e| { 552 + RecordKey(Rkey::new_cow(CowStr::Borrowed(rkey.as_str())).map_err(|e| { 553 553 AtStrError::wrap("at-uri-scheme", uri.to_string(), e) 554 554 })?); 555 555 Some(rkey) ··· 581 581 }; 582 582 let rkey = if let Some(rkey) = parts.name("rkey") { 583 583 let rkey = unsafe { 584 - RecordKey::from(Rkey::unchecked(rkey.as_str())) 584 + RecordKey(Rkey::unchecked_cow(CowStr::Borrowed(rkey.as_str()))) 585 585 }; 586 586 Some(rkey) 587 587 } else { ··· 644 644 unsafe { Nsid::unchecked(CowStr::Borrowed(collection.as_str())) }; 645 645 let rkey = if let Some(rkey) = parts.name("rkey") { 646 646 let rkey = 647 - unsafe { RecordKey::from(Rkey::unchecked(rkey.as_str())) }; 647 + unsafe { RecordKey(Rkey::unchecked_cow(CowStr::Borrowed(rkey.as_str()))) }; 648 648 Some(rkey) 649 649 } else { 650 650 None ··· 733 733 .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?; 734 734 let rkey = if let Some(rkey) = parts.name("rkey") { 735 735 let rkey = 736 - RecordKey::from(Rkey::new(rkey.as_str()).map_err(|e| { 736 + RecordKey(Rkey::new_cow(CowStr::Borrowed(rkey.as_str())).map_err(|e| { 737 737 AtStrError::wrap("at-uri-scheme", uri.to_string(), e) 738 738 })?); 739 739 Some(rkey) ··· 765 765 }; 766 766 let rkey = if let Some(rkey) = parts.name("rkey") { 767 767 let rkey = 768 - unsafe { RecordKey::from(Rkey::unchecked(rkey.as_str())) }; 768 + unsafe { RecordKey(Rkey::unchecked_cow(CowStr::Borrowed(rkey.as_str()))) }; 769 769 Some(rkey) 770 770 } else { 771 771 None
+98 -100
crates/jacquard-common/src/types/blob.rs
··· 1 + use crate::bos::{Bos, DefaultStr}; 1 2 use crate::types::cid::Cid; 2 3 use crate::{CowStr, IntoStatic, types::cid::CidLink}; 3 4 use alloc::string::{String, ToString}; 4 5 use core::convert::Infallible; 5 6 use core::{fmt, hash::Hash, ops::Deref, str::FromStr}; 6 7 use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; 7 - use smol_str::ToSmolStr; 8 + use smol_str::{SmolStr, ToSmolStr}; 8 9 9 - /// Blob reference for binary data in AT Protocol 10 - /// 11 - /// Blobs represent uploaded binary data (images, videos, etc.) stored separately from records. 12 - /// They include a CID reference, MIME type, and size information. 10 + /// Blob reference for binary data in AT Protocol. 13 11 /// 14 12 /// Serialization differs between formats: 15 13 /// - JSON: `ref` is serialized as `{"$link": "cid_string"}` 16 14 /// - CBOR: `ref` is the raw CID 17 15 #[derive(Deserialize, Debug, Clone, PartialEq, Eq, Hash)] 18 16 #[serde(rename_all = "camelCase")] 19 - pub struct Blob<'b> { 20 - /// CID (Content Identifier) reference to the blob data 21 - pub r#ref: CidLink<'b>, 22 - /// MIME type of the blob (e.g., "image/png", "video/mp4") 23 - #[serde(borrow)] 24 - pub mime_type: MimeType<'b>, 25 - /// Size of the blob in bytes 17 + #[serde(bound(deserialize = "S: Bos<str> + AsRef<str> + Deserialize<'de>"))] 18 + pub struct Blob<S: Bos<str> + AsRef<str> = DefaultStr> { 19 + /// CID (Content Identifier) reference to the blob data. 20 + pub r#ref: CidLink<S>, 21 + /// MIME type of the blob (e.g., "image/png", "video/mp4"). 22 + pub mime_type: MimeType<S>, 23 + /// Size of the blob in bytes. 26 24 pub size: usize, 27 25 } 28 26 29 - impl<'b> Blob<'b> { 30 - /// Create a new Blob reference with the given CID, MIME type, and size. 31 - pub fn cid(&self) -> &Cid<'b> { 27 + impl<S: Bos<str> + AsRef<str>> Blob<S> { 28 + /// Get the inner CID reference. 29 + pub fn cid(&self) -> &Cid<S> { 32 30 &self.r#ref.0 33 31 } 34 32 } 35 33 36 - impl Serialize for Blob<'_> { 37 - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 34 + impl<S: Bos<str> + AsRef<str> + Serialize> Serialize for Blob<S> { 35 + fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error> 38 36 where 39 - S: Serializer, 37 + Ser: Serializer, 40 38 { 41 39 use serde::ser::SerializeMap; 42 40 43 41 if serializer.is_human_readable() { 44 - // JSON: ref needs to be {"$link": "cid"} 42 + // JSON: ref needs to be {"$link": "cid"}. 45 43 let mut map = serializer.serialize_map(Some(4))?; 46 44 map.serialize_entry("$type", "blob")?; 47 45 48 - // Serialize ref as {"$link": "cid_string"} 46 + // Serialize ref as {"$link": "cid_string"}. 49 47 let mut ref_map = alloc::collections::BTreeMap::new(); 50 48 ref_map.insert("$link", self.r#ref.as_str()); 51 49 map.serialize_entry("ref", &ref_map)?; ··· 54 52 map.serialize_entry("size", &self.size)?; 55 53 map.end() 56 54 } else { 57 - // CBOR: ref is just the CID directly 55 + // CBOR: ref is just the CID directly. 58 56 let mut map = serializer.serialize_map(Some(4))?; 59 57 map.serialize_entry("$type", "blob")?; 60 58 map.serialize_entry("ref", &self.r#ref)?; ··· 65 63 } 66 64 } 67 65 68 - impl IntoStatic for Blob<'_> { 69 - type Output = Blob<'static>; 66 + impl<S: Bos<str> + AsRef<str> + IntoStatic> IntoStatic for Blob<S> 67 + where 68 + S::Output: Bos<str> + AsRef<str>, 69 + { 70 + type Output = Blob<S::Output>; 70 71 71 72 fn into_static(self) -> Self::Output { 72 73 Blob { ··· 77 78 } 78 79 } 79 80 80 - /// Tagged blob reference with `$type` field for serde 81 - /// 82 - /// This enum provides the `{"$type": "blob"}` wrapper expected by AT Protocol's JSON format. 83 - /// Currently only contains the `Blob` variant, but the enum structure supports future extensions. 81 + /// Tagged blob reference with `$type` field for serde. 84 82 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 85 83 #[serde(tag = "$type", rename_all = "lowercase")] 86 - pub enum BlobRef<'r> { 87 - /// Blob variant with embedded blob data 88 - #[serde(borrow)] 89 - Blob(Blob<'r>), 84 + #[serde(bound( 85 + serialize = "S: Bos<str> + AsRef<str> + Serialize", 86 + deserialize = "S: Bos<str> + AsRef<str> + Deserialize<'de>" 87 + ))] 88 + pub enum BlobRef<S: Bos<str> + AsRef<str> = DefaultStr> { 89 + /// Blob variant with embedded blob data. 90 + Blob(Blob<S>), 90 91 } 91 92 92 - impl<'r> BlobRef<'r> { 93 - /// Get the inner blob reference 94 - pub fn blob(&self) -> &Blob<'r> { 93 + impl<S: Bos<str> + AsRef<str>> BlobRef<S> { 94 + /// Get the inner blob reference. 95 + pub fn blob(&self) -> &Blob<S> { 95 96 match self { 96 97 BlobRef::Blob(blob) => blob, 97 98 } 98 99 } 99 100 } 100 101 101 - impl<'b> From<BlobRef<'b>> for Blob<'b> { 102 - fn from(blob_ref: BlobRef<'b>) -> Self { 102 + impl<S: Bos<str> + AsRef<str>> From<BlobRef<S>> for Blob<S> { 103 + fn from(blob_ref: BlobRef<S>) -> Self { 103 104 match blob_ref { 104 105 BlobRef::Blob(blob) => blob, 105 106 } 106 107 } 107 108 } 108 109 109 - impl<'b> From<Blob<'b>> for BlobRef<'b> { 110 - fn from(blob: Blob<'b>) -> Self { 110 + impl<S: Bos<str> + AsRef<str>> From<Blob<S>> for BlobRef<S> { 111 + fn from(blob: Blob<S>) -> Self { 111 112 BlobRef::Blob(blob) 112 113 } 113 114 } 114 115 115 - impl IntoStatic for BlobRef<'_> { 116 - type Output = BlobRef<'static>; 116 + impl<S: Bos<str> + AsRef<str> + IntoStatic> IntoStatic for BlobRef<S> 117 + where 118 + S::Output: Bos<str> + AsRef<str>, 119 + { 120 + type Output = BlobRef<S::Output>; 117 121 118 122 fn into_static(self) -> Self::Output { 119 123 match self { ··· 122 126 } 123 127 } 124 128 125 - /// MIME type identifier for blob data 129 + /// MIME type identifier for blob data. 126 130 /// 127 131 /// Used to specify the content type of blobs. Supports patterns like "image/*" and "*/*". 132 + /// No validation is performed — any string is accepted. 128 133 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)] 129 134 #[serde(transparent)] 130 135 #[repr(transparent)] 131 - pub struct MimeType<'m>(pub CowStr<'m>); 136 + pub struct MimeType<S: Bos<str> = DefaultStr>(pub S); 132 137 133 - impl<'m> MimeType<'m> { 134 - /// Fallible constructor, validates, borrows from input 135 - pub fn new(mime_type: &'m str) -> Result<MimeType<'m>, &'static str> { 136 - Ok(Self(CowStr::Borrowed(mime_type))) 138 + impl<S: Bos<str> + AsRef<str>> MimeType<S> { 139 + /// Get the MIME type as a string slice. 140 + pub fn as_str(&self) -> &str { 141 + self.0.as_ref() 137 142 } 143 + } 138 144 139 - /// Fallible constructor, validates, takes ownership 140 - pub fn new_owned(mime_type: impl AsRef<str>) -> Self { 141 - Self(CowStr::Owned(mime_type.as_ref().to_smolstr())) 145 + impl<'m> MimeType<&'m str> { 146 + /// Infallible constructor, borrows from input. 147 + pub fn new(mime_type: &'m str) -> Self { 148 + Self(mime_type) 142 149 } 143 150 144 - /// Fallible constructor, validates, doesn't allocate 145 - pub fn new_static(mime_type: &'static str) -> Self { 146 - Self(CowStr::new_static(mime_type)) 151 + /// Infallible constructor for trusted MIME type strings. 152 + pub fn raw(mime_type: &'m str) -> Self { 153 + Self(mime_type) 147 154 } 155 + } 148 156 149 - /// Fallible constructor from an existing CowStr 150 - pub fn from_cowstr(mime_type: CowStr<'m>) -> Result<MimeType<'m>, &'static str> { 151 - Ok(Self(mime_type)) 157 + impl<S: Bos<str> + From<SmolStr>> MimeType<S> { 158 + /// Infallible constructor, takes ownership. 159 + pub fn new_owned(mime_type: impl AsRef<str>) -> Self { 160 + Self(S::from(mime_type.as_ref().to_smolstr())) 152 161 } 153 162 154 - /// Fallible constructor, validates, borrows from input if possible 155 - pub fn new_cow(mime_type: CowStr<'m>) -> Result<MimeType<'m>, &'static str> { 156 - Self::from_cowstr(mime_type) 163 + /// Infallible constructor for static strings. 164 + pub fn new_static(mime_type: &'static str) -> Self { 165 + Self(S::from(SmolStr::new_static(mime_type))) 157 166 } 167 + } 158 168 159 - /// Infallible constructor for trusted MIME type strings 160 - pub fn raw(mime_type: &'m str) -> Self { 161 - Self(CowStr::Borrowed(mime_type)) 169 + impl<'m> MimeType<CowStr<'m>> { 170 + /// Infallible constructor, borrows if possible. 171 + pub fn new_cow(mime_type: CowStr<'m>) -> Self { 172 + Self(mime_type) 162 173 } 174 + } 163 175 164 - /// Get the MIME type as a string slice 165 - pub fn as_str(&self) -> &str { 166 - { 167 - let this = &self.0; 168 - this 169 - } 176 + impl<'de, S> Deserialize<'de> for MimeType<S> 177 + where 178 + S: Bos<str> + Deserialize<'de>, 179 + { 180 + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 181 + where 182 + D: Deserializer<'de>, 183 + { 184 + Ok(MimeType(S::deserialize(deserializer)?)) 170 185 } 171 186 } 172 187 173 - impl FromStr for MimeType<'_> { 188 + impl FromStr for MimeType { 174 189 type Err = Infallible; 175 190 176 - /// Has to take ownership due to the lifetime constraints of the FromStr trait. 177 191 fn from_str(s: &str) -> Result<Self, Self::Err> { 178 192 Ok(Self::new_owned(s)) 179 193 } 180 194 } 181 195 182 - impl IntoStatic for MimeType<'_> { 183 - type Output = MimeType<'static>; 196 + impl<S: Bos<str> + IntoStatic> IntoStatic for MimeType<S> 197 + where 198 + S::Output: Bos<str>, 199 + { 200 + type Output = MimeType<S::Output>; 184 201 185 202 fn into_static(self) -> Self::Output { 186 203 MimeType(self.0.into_static()) 187 204 } 188 205 } 189 206 190 - impl<'de, 'b> Deserialize<'de> for MimeType<'b> 191 - where 192 - 'de: 'b, 193 - { 194 - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 195 - where 196 - D: Deserializer<'de>, 197 - { 198 - let value = Deserialize::deserialize(deserializer)?; 199 - Self::new_cow(value).map_err(D::Error::custom) 200 - } 201 - } 202 - 203 - impl fmt::Display for MimeType<'_> { 207 + impl<S: Bos<str> + AsRef<str>> fmt::Display for MimeType<S> { 204 208 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 205 - f.write_str(&self.0) 206 - } 207 - } 208 - 209 - impl<'m> From<MimeType<'m>> for String { 210 - fn from(value: MimeType<'m>) -> Self { 211 - value.0.to_string() 209 + f.write_str(self.as_str()) 212 210 } 213 211 } 214 212 215 - impl<'m> From<MimeType<'m>> for CowStr<'m> { 216 - fn from(value: MimeType<'m>) -> Self { 217 - value.0 213 + impl<S: Bos<str> + AsRef<str>> From<MimeType<S>> for String { 214 + fn from(value: MimeType<S>) -> Self { 215 + value.as_str().to_string() 218 216 } 219 217 } 220 218 221 - impl From<String> for MimeType<'static> { 219 + impl From<String> for MimeType { 222 220 fn from(value: String) -> Self { 223 - Self(CowStr::Owned(value.to_smolstr())) 221 + Self::new_owned(value) 224 222 } 225 223 } 226 224 227 - impl<'m> From<CowStr<'m>> for MimeType<'m> { 225 + impl<'m> From<CowStr<'m>> for MimeType<CowStr<'m>> { 228 226 fn from(value: CowStr<'m>) -> Self { 229 227 Self(value) 230 228 } 231 229 } 232 230 233 - impl AsRef<str> for MimeType<'_> { 231 + impl<S: Bos<str> + AsRef<str>> AsRef<str> for MimeType<S> { 234 232 fn as_ref(&self) -> &str { 235 233 self.as_str() 236 234 } 237 235 } 238 236 239 - impl Deref for MimeType<'_> { 237 + impl<S: Bos<str> + AsRef<str>> Deref for MimeType<S> { 240 238 type Target = str; 241 239 242 240 fn deref(&self) -> &Self::Target {
+256 -226
crates/jacquard-common/src/types/cid.rs
··· 1 + use crate::bos::{Bos, DefaultStr}; 1 2 use crate::{CowStr, IntoStatic, cowstr::ToCowStr}; 2 3 use alloc::string::{String, ToString}; 3 4 pub use cid::Cid as IpldCid; 4 5 use core::{convert::Infallible, fmt, ops::Deref, str::FromStr}; 5 6 use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor}; 6 - use smol_str::ToSmolStr; 7 + use smol_str::{SmolStr, ToSmolStr}; 7 8 8 - /// CID codec for AT Protocol (raw) 9 + /// CID codec for AT Protocol (raw). 9 10 pub const ATP_CID_CODEC: u64 = 0x55; 10 11 11 - /// CID hash function for AT Protocol (SHA-256) 12 + /// CID hash function for AT Protocol (SHA-256). 12 13 pub const ATP_CID_HASH: u64 = 0x12; 13 14 14 - /// CID encoding base for AT Protocol (base32 lowercase) 15 + /// CID encoding base for AT Protocol (base32 lowercase). 15 16 pub const ATP_CID_BASE: multibase::Base = multibase::Base::Base32Lower; 16 17 17 - /// Content Identifier (CID) for IPLD data in AT Protocol 18 + /// Content Identifier (CID) for IPLD data in AT Protocol. 18 19 /// 19 20 /// CIDs are self-describing content addresses used to reference IPLD data. 20 21 /// This type supports both string and parsed IPLD forms, with string caching 21 - /// for the parsed form to optimize serialization. 22 + /// for the parsed form to optimise serialization. 22 23 /// 23 24 /// # Validation 24 25 /// ··· 29 30 /// 30 31 /// Byte deserialization (CBOR) parses immediately since the data is already in binary form. 31 32 #[derive(Debug, Clone, PartialEq, Eq, Hash)] 32 - pub enum Cid<'c> { 33 - /// Parsed IPLD CID with cached string representation 33 + pub enum Cid<S: Bos<str> = DefaultStr> { 34 + /// Parsed IPLD CID with cached string representation. 35 + /// The cached string is always SmolStr regardless of `S`. 34 36 Ipld { 35 - /// Parsed CID structure 37 + /// Parsed CID structure. 36 38 cid: IpldCid, 37 - /// Cached base32 string form 38 - s: CowStr<'c>, 39 + /// Cached base32 string form. 40 + s: SmolStr, 39 41 }, 40 - /// String-only form (not yet parsed) 41 - Str(CowStr<'c>), 42 + /// String-only form (not yet parsed). 43 + Str(S), 42 44 } 43 45 44 - /// Errors that can occur when working with CIDs 46 + /// Errors that can occur when working with CIDs. 45 47 #[derive(Debug, thiserror::Error, miette::Diagnostic)] 46 48 #[non_exhaustive] 47 49 pub enum Error { 48 - /// Invalid IPLD CID structure 50 + /// Invalid IPLD CID structure. 49 51 #[error("Invalid IPLD CID {:?}", 0)] 50 52 Ipld(#[from] cid::Error), 51 - /// Invalid UTF-8 in CID string 53 + /// Invalid UTF-8 in CID string. 52 54 #[error("{:?}", 0)] 53 55 Utf8(#[from] core::str::Utf8Error), 54 56 } 55 57 56 - impl<'c> Cid<'c> { 57 - /// Parse a CID from bytes (tries IPLD first, falls back to UTF-8 string) 58 - pub fn new(cid: &'c [u8]) -> Result<Self, Error> { 59 - if let Ok(cid) = IpldCid::try_from(cid.as_ref()) { 60 - Ok(Self::ipld(cid)) 61 - } else { 62 - let cid_str = CowStr::from_utf8(cid)?; 63 - Ok(Self::Str(cid_str)) 64 - } 65 - } 58 + // --------------------------------------------------------------------------- 59 + // Core methods 60 + // --------------------------------------------------------------------------- 66 61 67 - /// Parse a CID from bytes into an owned (static lifetime) value 68 - pub fn new_owned(cid: &[u8]) -> Result<Cid<'static>, Error> { 69 - if let Ok(cid) = IpldCid::try_from(cid.as_ref()) { 70 - Ok(Self::ipld(cid)) 71 - } else { 72 - let cid_str = CowStr::from_utf8(cid)?; 73 - Ok(Cid::Str(cid_str.into_static())) 62 + impl<S: Bos<str> + AsRef<str>> Cid<S> { 63 + /// Get the CID as a string slice. 64 + pub fn as_str(&self) -> &str { 65 + match self { 66 + Cid::Ipld { cid: _, s } => s.as_ref(), 67 + Cid::Str(s) => s.as_ref(), 74 68 } 75 69 } 76 70 77 - /// Construct a CID from a parsed IPLD CID 78 - pub fn ipld(cid: IpldCid) -> Cid<'static> { 79 - let s = CowStr::Owned( 80 - cid.to_string_of_base(ATP_CID_BASE) 81 - .unwrap_or_default() 82 - .to_smolstr(), 83 - ); 84 - Cid::Ipld { cid, s } 85 - } 86 - 87 - /// Construct a CID from a string slice (borrows) 88 - pub fn str(cid: &'c str) -> Self { 89 - Self::Str(CowStr::Borrowed(cid)) 90 - } 91 - 92 - /// Construct a CID from a CowStr 93 - pub fn cow_str(cid: CowStr<'c>) -> Self { 94 - Self::Str(cid) 95 - } 96 - 97 - /// Convert to a parsed IPLD CID (parses if needed) 71 + /// Convert to a parsed IPLD CID (parses if needed). 98 72 pub fn to_ipld(&self) -> Result<IpldCid, cid::Error> { 99 73 match self { 100 74 Cid::Ipld { cid, s: _ } => Ok(cid.clone()), 101 - Cid::Str(cow_str) => IpldCid::try_from(cow_str.as_ref()), 102 - } 103 - } 104 - 105 - /// Get the CID as a string slice 106 - pub fn as_str(&self) -> &str { 107 - match self { 108 - Cid::Ipld { cid: _, s } => s.as_ref(), 109 - Cid::Str(cow_str) => cow_str.as_ref(), 75 + Cid::Str(s) => IpldCid::try_from(s.as_ref()), 110 76 } 111 77 } 112 78 113 - /// Check if the CID string is valid without parsing 79 + /// Check if the CID string is valid without parsing. 114 80 /// 115 81 /// Returns `true` if the CID is already parsed (`Ipld` variant) or if 116 82 /// the string can be successfully parsed as an IPLD CID. ··· 122 88 } 123 89 } 124 90 125 - impl core::fmt::Display for Cid<'_> { 126 - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 127 - match self { 128 - Cid::Ipld { cid: _, s } => f.write_str(&s), 129 - Cid::Str(cow_str) => f.write_str(&cow_str), 130 - } 91 + // --------------------------------------------------------------------------- 92 + // Constructors 93 + // --------------------------------------------------------------------------- 94 + 95 + impl<S: Bos<str>> Cid<S> { 96 + /// Wrap a string directly as a CID without validation. 97 + /// 98 + /// # Safety 99 + /// 100 + /// The caller must ensure the string is a valid CID. 101 + pub unsafe fn unchecked_str(s: S) -> Self { 102 + Cid::Str(s) 103 + } 104 + 105 + /// Construct a CID from a parsed IPLD CID. 106 + pub fn ipld(cid: IpldCid) -> Self { 107 + let s = cid 108 + .to_string_of_base(ATP_CID_BASE) 109 + .unwrap_or_default() 110 + .to_smolstr(); 111 + Cid::Ipld { cid, s } 131 112 } 132 113 } 133 114 134 - impl FromStr for Cid<'_> { 135 - type Err = Infallible; 115 + impl<'c> Cid<&'c str> { 116 + /// Construct a CID from a string slice (borrows). 117 + pub fn str(cid: &'c str) -> Self { 118 + Self::Str(cid) 119 + } 136 120 137 - /// Has to take ownership due to the lifetime constraints of the FromStr trait. 138 - fn from_str(s: &str) -> Result<Self, Self::Err> { 139 - Ok(Cid::Str(CowStr::Owned(s.to_smolstr()))) 121 + /// Parse a CID from bytes (tries IPLD first, falls back to UTF-8 string). 122 + pub fn new(cid: &'c [u8]) -> Result<Self, Error> { 123 + if let Ok(cid) = IpldCid::try_from(cid.as_ref()) { 124 + Ok(Self::ipld(cid)) 125 + } else { 126 + let cid_str = core::str::from_utf8(cid)?; 127 + Ok(Self::Str(cid_str)) 128 + } 140 129 } 141 130 } 142 131 143 - impl IntoStatic for Cid<'_> { 144 - type Output = Cid<'static>; 145 - 146 - fn into_static(self) -> Self::Output { 147 - match self { 148 - Cid::Ipld { cid, s } => Cid::Ipld { 149 - cid, 150 - s: s.into_static(), 151 - }, 152 - Cid::Str(cow_str) => Cid::Str(cow_str.into_static()), 132 + impl<S: Bos<str> + From<SmolStr>> Cid<S> { 133 + /// Parse a CID from bytes into an owned value. 134 + pub fn new_owned(cid: &[u8]) -> Result<Self, Error> { 135 + if let Ok(cid) = IpldCid::try_from(cid.as_ref()) { 136 + Ok(Self::ipld(cid)) 137 + } else { 138 + let cid_str = core::str::from_utf8(cid)?; 139 + Ok(Cid::Str(S::from(cid_str.to_smolstr()))) 153 140 } 154 141 } 155 142 } 156 143 157 - impl Serialize for Cid<'_> { 158 - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 144 + impl<'c> Cid<CowStr<'c>> { 145 + /// Construct a CID from a CowStr. 146 + pub fn cow_str(cid: CowStr<'c>) -> Self { 147 + Self::Str(cid) 148 + } 149 + } 150 + 151 + // --------------------------------------------------------------------------- 152 + // Serialization — preserving existing logic exactly 153 + // --------------------------------------------------------------------------- 154 + 155 + impl<S: Bos<str> + AsRef<str>> Serialize for Cid<S> { 156 + fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error> 159 157 where 160 - S: Serializer, 158 + Ser: Serializer, 161 159 { 162 160 match self { 163 161 Cid::Ipld { cid, s: _ } => cid.serialize(serializer), 164 - Cid::Str(cow_str) => cow_str.serialize(serializer), 162 + Cid::Str(s) => s.as_ref().serialize(serializer), 165 163 } 166 164 } 167 165 } 168 166 169 - impl<'de, 'a> Deserialize<'de> for Cid<'a> 167 + // --------------------------------------------------------------------------- 168 + // Deserialization — preserving existing logic exactly 169 + // --------------------------------------------------------------------------- 170 + 171 + impl<'de, S> Deserialize<'de> for Cid<S> 170 172 where 171 - 'de: 'a, 173 + S: Bos<str> + AsRef<str> + Deserialize<'de>, 172 174 { 173 175 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 174 176 where 175 177 D: Deserializer<'de>, 176 178 { 177 179 if deserializer.is_human_readable() { 178 - // JSON: always a string 179 - struct StrVisitor; 180 - 181 - impl<'de> Visitor<'de> for StrVisitor { 182 - type Value = Cid<'de>; 183 - 184 - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 185 - formatter.write_str("a CID string") 186 - } 187 - 188 - fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E> 189 - where 190 - E: serde::de::Error, 191 - { 192 - Ok(Cid::str(v)) 193 - } 194 - 195 - fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 196 - where 197 - E: serde::de::Error, 198 - { 199 - Ok(FromStr::from_str(v).unwrap()) 200 - } 201 - } 202 - 203 - deserializer.deserialize_str(StrVisitor) 180 + // JSON: deserialize S (string), wrap in Str variant. 181 + let s = S::deserialize(deserializer)?; 182 + Ok(Cid::Str(s)) 204 183 } else { 205 - // CBOR: use IpldCid's deserializer which handles CBOR tag 42 184 + // CBOR: use IpldCid's deserializer which handles CBOR tag 42. 206 185 let cid = IpldCid::deserialize(deserializer)?; 207 186 Ok(Cid::ipld(cid)) 208 187 } 209 188 } 210 189 } 211 190 212 - impl From<Cid<'_>> for String { 213 - fn from(value: Cid) -> Self { 214 - let cow_str = match value { 215 - Cid::Ipld { cid: _, s } => s, 216 - Cid::Str(cow_str) => cow_str, 217 - }; 218 - cow_str.to_string() 191 + // --------------------------------------------------------------------------- 192 + // Trait impls 193 + // --------------------------------------------------------------------------- 194 + 195 + impl<S: Bos<str> + AsRef<str>> fmt::Display for Cid<S> { 196 + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 197 + match self { 198 + Cid::Ipld { cid: _, s } => f.write_str(s), 199 + Cid::Str(s) => f.write_str(s.as_ref()), 200 + } 219 201 } 220 202 } 221 203 222 - impl<'d> From<Cid<'d>> for CowStr<'d> { 223 - fn from(value: Cid<'d>) -> Self { 224 - match value { 225 - Cid::Ipld { cid: _, s } => s, 226 - Cid::Str(cow_str) => cow_str, 204 + impl FromStr for Cid { 205 + type Err = Infallible; 206 + 207 + fn from_str(s: &str) -> Result<Self, Self::Err> { 208 + Ok(Cid::Str(s.to_smolstr())) 209 + } 210 + } 211 + 212 + impl<S: Bos<str> + IntoStatic> IntoStatic for Cid<S> 213 + where 214 + S::Output: Bos<str>, 215 + { 216 + type Output = Cid<S::Output>; 217 + 218 + fn into_static(self) -> Self::Output { 219 + match self { 220 + Cid::Ipld { cid, s } => Cid::Ipld { cid, s }, 221 + Cid::Str(s) => Cid::Str(s.into_static()), 227 222 } 228 223 } 229 224 } 230 225 231 - impl From<String> for Cid<'_> { 226 + impl<S: Bos<str> + AsRef<str>> From<Cid<S>> for String { 227 + fn from(value: Cid<S>) -> Self { 228 + value.as_str().to_string() 229 + } 230 + } 231 + 232 + impl From<String> for Cid { 232 233 fn from(value: String) -> Self { 233 - Cid::Str(CowStr::Owned(value.to_smolstr())) 234 + Cid::Str(value.to_smolstr()) 234 235 } 235 236 } 236 237 237 - impl<'d> From<CowStr<'d>> for Cid<'d> { 238 + impl<'d> From<CowStr<'d>> for Cid<CowStr<'d>> { 238 239 fn from(value: CowStr<'d>) -> Self { 239 240 Cid::Str(value) 240 241 } 241 242 } 242 243 243 - impl From<IpldCid> for Cid<'_> { 244 + impl<S: Bos<str>> From<IpldCid> for Cid<S> { 244 245 fn from(value: IpldCid) -> Self { 245 246 Cid::ipld(value) 246 247 } 247 248 } 248 249 249 - impl AsRef<str> for Cid<'_> { 250 + impl<S: Bos<str> + AsRef<str>> AsRef<str> for Cid<S> { 250 251 fn as_ref(&self) -> &str { 251 252 self.as_str() 252 253 } 253 254 } 254 255 255 - impl Deref for Cid<'_> { 256 + impl<S: Bos<str> + AsRef<str>> Deref for Cid<S> { 256 257 type Target = str; 257 258 258 259 fn deref(&self) -> &Self::Target { ··· 260 261 } 261 262 } 262 263 263 - /// CID link wrapper for JSON `{"$link": "cid"}` serialization 264 + // =========================================================================== 265 + // CidLink 266 + // =========================================================================== 267 + 268 + /// CID link wrapper for JSON `{"$link": "cid"}` serialization. 264 269 /// 265 270 /// Wraps a `Cid` and handles format-specific serialization: 266 271 /// - JSON: `{"$link": "cid_string"}` 267 272 /// - CBOR: raw CID bytes 268 - /// 269 - /// Used in the AT Protocol data model to represent IPLD links in JSON. 270 273 #[derive(Debug, Clone, PartialEq, Eq, Hash)] 271 274 #[repr(transparent)] 272 - pub struct CidLink<'c>(pub Cid<'c>); 275 + pub struct CidLink<S: Bos<str> = DefaultStr>(pub Cid<S>); 273 276 274 - impl<'c> CidLink<'c> { 275 - /// Parse a CID link from bytes 276 - pub fn new(cid: &'c [u8]) -> Result<Self, Error> { 277 - Ok(Self(Cid::new(cid)?)) 277 + impl<S: Bos<str> + AsRef<str>> CidLink<S> { 278 + /// Get the CID as a string slice. 279 + pub fn as_str(&self) -> &str { 280 + self.0.as_str() 278 281 } 279 282 280 - /// Parse a CID link from bytes into an owned value 281 - pub fn new_owned(cid: &[u8]) -> Result<CidLink<'static>, Error> { 282 - Ok(CidLink(Cid::new_owned(cid)?)) 283 + /// Convert to a parsed IPLD CID. 284 + pub fn to_ipld(&self) -> Result<IpldCid, cid::Error> { 285 + self.0.to_ipld() 283 286 } 284 287 285 - /// Construct a CID link from a static string 286 - pub fn new_static(cid: &'static str) -> Self { 287 - Self(Cid::str(cid)) 288 + /// Unwrap into the inner Cid. 289 + pub fn into_inner(self) -> Cid<S> { 290 + self.0 288 291 } 292 + } 289 293 290 - /// Construct a CID link from a parsed IPLD CID 291 - pub fn ipld(cid: IpldCid) -> CidLink<'static> { 294 + impl<S: Bos<str>> CidLink<S> { 295 + /// Construct a CID link from a parsed IPLD CID. 296 + pub fn ipld(cid: IpldCid) -> Self { 292 297 CidLink(Cid::ipld(cid)) 293 298 } 294 - 295 - /// Construct a CID link from a string slice 296 - pub fn str(cid: &'c str) -> Self { 297 - Self(Cid::str(cid)) 298 - } 299 - 300 - /// Construct a CID link from a CowStr 301 - pub fn cow_str(cid: CowStr<'c>) -> Self { 302 - Self(Cid::cow_str(cid)) 303 - } 299 + } 304 300 305 - /// Get the CID as a string slice 306 - pub fn as_str(&self) -> &str { 307 - self.0.as_str() 301 + impl<'c> CidLink<&'c str> { 302 + /// Parse a CID link from bytes. 303 + pub fn new(cid: &'c [u8]) -> Result<Self, Error> { 304 + Ok(Self(Cid::new(cid)?)) 308 305 } 309 306 310 - /// Convert to a parsed IPLD CID 311 - pub fn to_ipld(&self) -> Result<IpldCid, cid::Error> { 312 - self.0.to_ipld() 307 + /// Construct a CID link from a string slice. 308 + pub fn str(cid: &'c str) -> Self { 309 + Self(Cid::str(cid)) 313 310 } 314 311 315 - /// Unwrap into the inner Cid 316 - pub fn into_inner(self) -> Cid<'c> { 317 - self.0 312 + /// Construct a CID link from a static string. 313 + pub fn new_static(cid: &'static str) -> Self { 314 + Self(Cid::str(cid)) 318 315 } 319 316 } 320 317 321 - impl fmt::Display for CidLink<'_> { 322 - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 323 - self.0.fmt(f) 318 + impl<S: Bos<str> + From<SmolStr>> CidLink<S> { 319 + /// Parse a CID link from bytes into an owned value. 320 + pub fn new_owned(cid: &[u8]) -> Result<Self, Error> { 321 + Ok(CidLink(Cid::new_owned(cid)?)) 324 322 } 325 323 } 326 324 327 - impl FromStr for CidLink<'_> { 328 - type Err = Infallible; 329 - 330 - fn from_str(s: &str) -> Result<Self, Self::Err> { 331 - Ok(CidLink(Cid::from_str(s)?)) 325 + impl<'c> CidLink<CowStr<'c>> { 326 + /// Construct a CID link from a CowStr. 327 + pub fn cow_str(cid: CowStr<'c>) -> Self { 328 + Self(Cid::cow_str(cid)) 332 329 } 333 330 } 334 331 335 - impl IntoStatic for CidLink<'_> { 336 - type Output = CidLink<'static>; 332 + // --------------------------------------------------------------------------- 333 + // CidLink serialization — preserving existing logic exactly 334 + // --------------------------------------------------------------------------- 337 335 338 - fn into_static(self) -> Self::Output { 339 - CidLink(self.0.into_static()) 340 - } 341 - } 342 - 343 - impl Serialize for CidLink<'_> { 344 - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 336 + impl<S: Bos<str> + AsRef<str>> Serialize for CidLink<S> { 337 + fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error> 345 338 where 346 - S: Serializer, 339 + Ser: Serializer, 347 340 { 348 341 if serializer.is_human_readable() { 349 342 // JSON: {"$link": "cid_string"} ··· 358 351 } 359 352 } 360 353 361 - impl<'de, 'a> Deserialize<'de> for CidLink<'a> 354 + // --------------------------------------------------------------------------- 355 + // CidLink deserialization — preserving existing logic exactly 356 + // --------------------------------------------------------------------------- 357 + 358 + impl<'de, S> Deserialize<'de> for CidLink<S> 362 359 where 363 - 'de: 'a, 360 + S: Bos<str> + AsRef<str> + Deserialize<'de>, 364 361 { 365 362 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 366 363 where 367 364 D: Deserializer<'de>, 368 365 { 366 + use core::marker::PhantomData; 367 + 369 368 if deserializer.is_human_readable() { 370 369 // JSON: expect {"$link": "cid_string"} 371 - struct LinkVisitor; 370 + struct LinkVisitor<S>(PhantomData<fn() -> S>); 372 371 373 - impl<'de> Visitor<'de> for LinkVisitor { 374 - type Value = CidLink<'static>; 372 + impl<'de, S> Visitor<'de> for LinkVisitor<S> 373 + where 374 + S: Bos<str> + AsRef<str> + Deserialize<'de>, 375 + { 376 + type Value = CidLink<S>; 375 377 376 378 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 377 379 formatter.write_str("a CID link object with $link field") ··· 381 383 where 382 384 E: serde::de::Error, 383 385 { 384 - // TODO: currently overly permissive, should fix 385 - Ok(CidLink::cow_str(v.to_cowstr()).into_static()) 386 + // TODO: currently overly permissive, should fix. 387 + // Delegate to S's Deserialize via a StrDeserializer. 388 + let s = S::deserialize(serde::de::value::StrDeserializer::<E>::new(v))?; 389 + Ok(CidLink(Cid::Str(s))) 386 390 } 387 391 388 392 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E> 389 393 where 390 394 E: serde::de::Error, 391 395 { 392 - Cid::new_owned(v).map(CidLink).map_err(E::custom) 396 + // Binary CID data — parse as IpldCid (produces Ipld variant with SmolStr). 397 + let cid = IpldCid::try_from(v).map_err(E::custom)?; 398 + Ok(CidLink(Cid::ipld(cid))) 393 399 } 394 400 395 401 fn visit_byte_buf<E>(self, v: alloc::vec::Vec<u8>) -> Result<Self::Value, E> ··· 399 405 self.visit_bytes(&v) 400 406 } 401 407 402 - fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error> 408 + fn visit_newtype_struct<D>( 409 + self, 410 + deserializer: D, 411 + ) -> Result<Self::Value, D::Error> 403 412 where 404 413 D: serde::de::Deserializer<'de>, 405 414 { ··· 436 445 { 437 446 use serde::de::Error; 438 447 439 - let mut link: Option<String> = None; 448 + // Deserialize the $link value as Cid<S>, which delegates 449 + // to S::deserialize for the string content. 450 + let mut link: Option<Cid<S>> = None; 440 451 441 - while let Some(key) = map.next_key::<String>()? { 452 + while let Some(key) = map.next_key::<&str>()? { 442 453 if key == "$link" { 443 454 link = Some(map.next_value()?); 444 455 } else { 445 - // Skip unknown fields 456 + // Skip unknown fields. 446 457 let _: serde::de::IgnoredAny = map.next_value()?; 447 458 } 448 459 } 449 460 450 - if let Some(cid_str) = link { 451 - Ok(CidLink(Cid::from(cid_str))) 461 + if let Some(cid) = link { 462 + Ok(CidLink(cid)) 452 463 } else { 453 464 Err(A::Error::missing_field("$link")) 454 465 } 455 466 } 456 467 } 457 468 458 - deserializer.deserialize_any(LinkVisitor) 469 + deserializer.deserialize_any(LinkVisitor(PhantomData)) 459 470 } else { 460 471 // CBOR: raw CID 461 472 Ok(CidLink(Cid::deserialize(deserializer)?)) ··· 463 474 } 464 475 } 465 476 466 - impl From<CidLink<'_>> for String { 467 - fn from(value: CidLink) -> Self { 468 - value.0.into() 477 + // --------------------------------------------------------------------------- 478 + // CidLink trait impls 479 + // --------------------------------------------------------------------------- 480 + 481 + impl<S: Bos<str> + AsRef<str>> fmt::Display for CidLink<S> { 482 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 483 + self.0.fmt(f) 469 484 } 470 485 } 471 486 472 - impl<'c> From<CidLink<'c>> for CowStr<'c> { 473 - fn from(value: CidLink<'c>) -> Self { 487 + impl FromStr for CidLink { 488 + type Err = Infallible; 489 + 490 + fn from_str(s: &str) -> Result<Self, Self::Err> { 491 + Ok(CidLink(Cid::from_str(s)?)) 492 + } 493 + } 494 + 495 + impl<S: Bos<str> + IntoStatic> IntoStatic for CidLink<S> 496 + where 497 + S::Output: Bos<str>, 498 + { 499 + type Output = CidLink<S::Output>; 500 + 501 + fn into_static(self) -> Self::Output { 502 + CidLink(self.0.into_static()) 503 + } 504 + } 505 + 506 + impl<S: Bos<str> + AsRef<str>> From<CidLink<S>> for String { 507 + fn from(value: CidLink<S>) -> Self { 474 508 value.0.into() 475 509 } 476 510 } 477 511 478 - impl From<String> for CidLink<'_> { 512 + impl From<String> for CidLink { 479 513 fn from(value: String) -> Self { 480 514 CidLink(Cid::from(value)) 481 515 } 482 516 } 483 517 484 - impl<'c> From<CowStr<'c>> for CidLink<'c> { 518 + impl<'c> From<CowStr<'c>> for CidLink<CowStr<'c>> { 485 519 fn from(value: CowStr<'c>) -> Self { 486 520 CidLink(Cid::from(value)) 487 521 } 488 522 } 489 523 490 - impl From<IpldCid> for CidLink<'_> { 524 + impl<S: Bos<str>> From<IpldCid> for CidLink<S> { 491 525 fn from(value: IpldCid) -> Self { 492 526 CidLink(Cid::from(value)) 493 527 } 494 528 } 495 529 496 - impl<'c> From<Cid<'c>> for CidLink<'c> { 497 - fn from(value: Cid<'c>) -> Self { 530 + impl<S: Bos<str>> From<Cid<S>> for CidLink<S> { 531 + fn from(value: Cid<S>) -> Self { 498 532 CidLink(value) 499 533 } 500 534 } 501 535 502 - impl<'c> From<CidLink<'c>> for Cid<'c> { 503 - fn from(value: CidLink<'c>) -> Self { 536 + impl<S: Bos<str>> From<CidLink<S>> for Cid<S> { 537 + fn from(value: CidLink<S>) -> Self { 504 538 value.0 505 539 } 506 540 } 507 541 508 - impl AsRef<str> for CidLink<'_> { 542 + impl<S: Bos<str> + AsRef<str>> AsRef<str> for CidLink<S> { 509 543 fn as_ref(&self) -> &str { 510 544 self.0.as_ref() 511 545 } 512 546 } 513 547 514 - impl Deref for CidLink<'_> { 548 + impl<S: Bos<str> + AsRef<str>> Deref for CidLink<S> { 515 549 type Target = str; 516 550 517 551 fn deref(&self) -> &Self::Target { ··· 547 581 let link = CidLink::str(TEST_CID); 548 582 let json = serde_json::to_string(&link).unwrap(); 549 583 let parsed: CidLink = serde_json::from_str(&json).unwrap(); 550 - assert_eq!(link, parsed); 584 + assert_eq!(link.as_str(), parsed.as_str()); 551 585 assert_eq!(link.as_str(), TEST_CID); 552 586 } 553 587 ··· 566 600 567 601 #[test] 568 602 fn cidlink_conversions() { 569 - let link = CidLink::str(TEST_CID); 603 + let link = CidLink::<SmolStr>::from(TEST_CID.to_string()); 570 604 571 605 // CidLink -> Cid 572 - let cid: Cid = link.clone().into(); 606 + let cid: Cid<SmolStr> = link.clone().into(); 573 607 assert_eq!(cid.as_str(), TEST_CID); 574 608 575 609 // Cid -> CidLink 576 - let link2: CidLink = cid.into(); 610 + let link2: CidLink<SmolStr> = cid.into(); 577 611 assert_eq!(link2.as_str(), TEST_CID); 578 612 579 613 // CidLink -> String 580 614 let s: String = link.clone().into(); 581 615 assert_eq!(s, TEST_CID); 582 - 583 - // CidLink -> CowStr 584 - let cow: CowStr = link.into(); 585 - assert_eq!(cow.as_ref(), TEST_CID); 586 616 } 587 617 588 618 #[test]
+5 -1
crates/jacquard-common/src/types/collection.rs
··· 53 53 ) -> RepoPath<'u> { 54 54 RepoPath { 55 55 collection: Self::nsid(), 56 - rkey: Some(RecordKey::from(Rkey::raw(rkey.as_ref()))), 56 + // Borrow the record key string with the caller's lifetime via CowStr. 57 + rkey: Some( 58 + RecordKey::any_cow(CowStr::Borrowed(rkey.as_ref())) 59 + .expect("RecordKey implements RecordKeyType, which guarantees a valid rkey"), 60 + ), 57 61 } 58 62 } 59 63 }
+1 -1
crates/jacquard-common/src/types/did.rs
··· 43 43 did.strip_prefix("at://").unwrap_or(did) 44 44 } 45 45 46 - fn validate_did(did: &str) -> Result<(), AtStrError> { 46 + pub(crate) fn validate_did(did: &str) -> Result<(), AtStrError> { 47 47 if did.len() > 2048 { 48 48 Err(AtStrError::too_long("did", did, 2048, did.len())) 49 49 } else if !DID_REGEX.is_match(did) {
+1 -1
crates/jacquard-common/src/types/handle.rs
··· 47 47 .unwrap_or(handle) 48 48 } 49 49 50 - fn validate_handle(handle: &str) -> Result<(), AtStrError> { 50 + pub(crate) fn validate_handle(handle: &str) -> Result<(), AtStrError> { 51 51 if handle.len() > 253 { 52 52 Err(AtStrError::too_long("handle", handle, 253, handle.len())) 53 53 } else if !HANDLE_REGEX.is_match(handle) {
+1 -1
crates/jacquard-common/src/types/nsid.rs
··· 30 30 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() 31 31 }); 32 32 33 - fn validate_nsid(nsid: &str) -> Result<(), AtStrError> { 33 + pub(crate) fn validate_nsid(nsid: &str) -> Result<(), AtStrError> { 34 34 if nsid.len() > 317 { 35 35 Err(AtStrError::too_long("nsid", nsid, 317, nsid.len())) 36 36 } else if !NSID_REGEX.is_match(nsid) {
+132 -147
crates/jacquard-common/src/types/recordkey.rs
··· 1 + use crate::bos::{Bos, DefaultStr}; 1 2 use crate::types::Literal; 2 3 use crate::types::string::AtStrError; 3 4 use crate::{CowStr, IntoStatic}; ··· 40 41 #[repr(transparent)] 41 42 pub struct RecordKey<T: RecordKeyType>(pub T); 42 43 43 - impl<'a> RecordKey<Rkey<'a>> { 44 - /// Create a new `RecordKey` from a string slice 44 + impl<'a> RecordKey<Rkey<&'a str>> { 45 + /// Create a new `RecordKey` from a string slice. 45 46 pub fn any(str: &'a str) -> Result<Self, AtStrError> { 46 47 Ok(RecordKey(Rkey::new(str)?)) 47 48 } 49 + } 50 + 51 + impl<S: Bos<str> + AsRef<str> + Clone + Serialize + From<SmolStr>> RecordKey<Rkey<S>> { 52 + /// Create a new `RecordKey` from a static string slice. 53 + pub fn any_static(str: &'static str) -> Result<Self, AtStrError> { 54 + Ok(RecordKey(Rkey::new_static(str)?)) 55 + } 56 + 57 + /// Create a new `RecordKey` from an owned string. 58 + pub fn any_owned(str: impl AsRef<str>) -> Result<Self, AtStrError> { 59 + Ok(RecordKey(Rkey::new_owned(str)?)) 60 + } 61 + } 48 62 49 - /// Create a new `RecordKey` from a CowStr 63 + impl<'a> RecordKey<Rkey<CowStr<'a>>> { 64 + /// Create a new `RecordKey` from a CowStr. 50 65 pub fn any_cow(str: CowStr<'a>) -> Result<Self, AtStrError> { 51 66 Ok(RecordKey(Rkey::new_cow(str)?)) 52 67 } 53 - 54 - /// Create a new `RecordKey` from a static string slice 55 - pub fn any_static(str: &'static str) -> Result<Self, AtStrError> { 56 - Ok(RecordKey(Rkey::new_static(str)?)) 57 - } 58 68 } 59 69 60 - impl<T> From<T> for RecordKey<Rkey<'_>> 70 + impl<T> From<T> for RecordKey<Rkey> 61 71 where 62 72 T: RecordKeyType, 63 73 { 64 74 fn from(value: T) -> Self { 65 - RecordKey(Rkey::from_str(value.as_str()).expect("Invalid rkey")) 75 + RecordKey(Rkey::new_owned(value.as_str()).expect("Invalid rkey")) 66 76 } 67 77 } 68 78 69 - impl FromStr for RecordKey<Rkey<'_>> { 79 + impl FromStr for RecordKey<Rkey> { 70 80 type Err = AtStrError; 71 81 72 82 fn from_str(s: &str) -> Result<Self, Self::Err> { ··· 108 118 /// - Any: flexible strings matching the validation rules 109 119 /// 110 120 /// See: <https://atproto.com/specs/record-key> 111 - #[derive(Clone, PartialEq, Eq, Serialize, Hash)] 121 + /// AT Protocol record key (generic "any" type). 122 + /// 123 + /// See: <https://atproto.com/specs/record-key> 124 + #[derive(Clone, PartialEq, Eq, Hash, Serialize)] 112 125 #[serde(transparent)] 113 126 #[repr(transparent)] 114 - pub struct Rkey<'r>(pub(crate) CowStr<'r>); 127 + pub struct Rkey<S: Bos<str> = DefaultStr>(pub(crate) S); 115 128 116 - unsafe impl<'r> RecordKeyType for Rkey<'r> { 129 + unsafe impl<S: Bos<str> + AsRef<str> + Clone + Serialize> RecordKeyType for Rkey<S> { 117 130 fn as_str(&self) -> &str { 118 131 self.0.as_ref() 119 132 } 120 133 } 121 134 122 - /// Regex for record key validation per AT Protocol spec 135 + /// Regex for record key validation per AT Protocol spec. 123 136 pub static RKEY_REGEX: Lazy<Regex> = 124 137 Lazy::new(|| Regex::new(r"^[a-zA-Z0-9.\-_:~]{1,512}$").unwrap()); 125 138 126 - impl<'r> Rkey<'r> { 127 - /// Fallible constructor, validates, borrows from input 139 + pub(crate) fn validate_rkey(rkey: &str) -> Result<(), AtStrError> { 140 + if [".", ".."].contains(&rkey) { 141 + Err(AtStrError::disallowed("record-key", rkey, &[".", ".."])) 142 + } else if !RKEY_REGEX.is_match(rkey) { 143 + Err(AtStrError::regex( 144 + "record-key", 145 + rkey, 146 + SmolStr::new_static("doesn't match 'any' schema"), 147 + )) 148 + } else { 149 + Ok(()) 150 + } 151 + } 152 + 153 + impl<S: Bos<str> + AsRef<str>> Rkey<S> { 154 + /// Get the record key as a string slice. 155 + pub fn as_str(&self) -> &str { 156 + self.0.as_ref() 157 + } 158 + } 159 + 160 + impl<S: Bos<str>> Rkey<S> { 161 + /// # Safety 162 + /// 163 + /// The caller must ensure the rkey is valid. 164 + pub unsafe fn unchecked(rkey: S) -> Self { 165 + Rkey(rkey) 166 + } 167 + } 168 + 169 + impl<'r> Rkey<&'r str> { 170 + /// Fallible constructor, validates, borrows from input. 128 171 pub fn new(rkey: &'r str) -> Result<Self, AtStrError> { 129 - if [".", ".."].contains(&rkey) { 130 - Err(AtStrError::disallowed("record-key", rkey, &[".", ".."])) 131 - } else if !RKEY_REGEX.is_match(rkey) { 132 - Err(AtStrError::regex( 133 - "record-key", 134 - rkey, 135 - SmolStr::new_static("doesn't match 'any' schema"), 136 - )) 137 - } else { 138 - Ok(Self(CowStr::Borrowed(rkey))) 139 - } 172 + validate_rkey(rkey)?; 173 + Ok(Self(rkey)) 174 + } 175 + 176 + /// Infallible constructor. Panics on invalid rkeys. 177 + pub fn raw(rkey: &'r str) -> Self { 178 + Self::new(rkey).expect("invalid rkey") 140 179 } 180 + } 141 181 142 - /// Fallible constructor, validates, takes ownership 182 + impl<S: Bos<str> + From<SmolStr>> Rkey<S> { 183 + /// Fallible constructor, validates, takes ownership. 143 184 pub fn new_owned(rkey: impl AsRef<str>) -> Result<Self, AtStrError> { 144 185 let rkey = rkey.as_ref(); 145 - if [".", ".."].contains(&rkey) { 146 - Err(AtStrError::disallowed("record-key", rkey, &[".", ".."])) 147 - } else if !RKEY_REGEX.is_match(rkey) { 148 - Err(AtStrError::regex( 149 - "record-key", 150 - rkey, 151 - SmolStr::new_static("doesn't match 'any' schema"), 152 - )) 153 - } else { 154 - Ok(Self(CowStr::Owned(rkey.to_smolstr()))) 155 - } 186 + validate_rkey(rkey)?; 187 + Ok(Self(S::from(rkey.to_smolstr()))) 156 188 } 157 189 158 - /// Fallible constructor, validates, doesn't allocate 190 + /// Fallible constructor for static strings. 159 191 pub fn new_static(rkey: &'static str) -> Result<Self, AtStrError> { 160 - if [".", ".."].contains(&rkey) { 161 - Err(AtStrError::disallowed("record-key", rkey, &[".", ".."])) 162 - } else if !RKEY_REGEX.is_match(rkey) { 163 - Err(AtStrError::regex( 164 - "record-key", 165 - rkey, 166 - SmolStr::new_static("doesn't match 'any' schema"), 167 - )) 168 - } else { 169 - Ok(Self(CowStr::new_static(rkey))) 170 - } 192 + validate_rkey(rkey)?; 193 + Ok(Self(S::from(SmolStr::new_static(rkey)))) 171 194 } 195 + } 172 196 173 - /// Fallible constructor, validates, borrows from input if possible 197 + impl<'r> Rkey<CowStr<'r>> { 198 + /// Fallible constructor, borrows if possible. 174 199 pub fn new_cow(rkey: CowStr<'r>) -> Result<Self, AtStrError> { 175 - if [".", ".."].contains(&rkey.as_ref()) { 176 - Err(AtStrError::disallowed("record-key", &rkey, &[".", ".."])) 177 - } else if !RKEY_REGEX.is_match(&rkey) { 178 - Err(AtStrError::regex( 179 - "record-key", 180 - &rkey, 181 - SmolStr::new_static("doesn't match 'any' schema"), 182 - )) 183 - } else { 184 - Ok(Self(rkey)) 185 - } 200 + validate_rkey(&rkey)?; 201 + Ok(Self(rkey)) 186 202 } 187 203 188 - /// Infallible constructor for when you *know* the string is a valid rkey. 189 - /// Will panic on invalid rkeys. If you're manually decoding atproto records 190 - /// or API values you know are valid (rather than using serde), this is the one to use. 191 - /// The From impls use the same logic. 192 - pub fn raw(rkey: &'r str) -> Self { 193 - if [".", ".."].contains(&rkey) { 194 - panic!("Disallowed rkey") 195 - } else if !RKEY_REGEX.is_match(rkey) { 196 - panic!("Invalid rkey") 197 - } else { 198 - Self(CowStr::Borrowed(rkey)) 199 - } 204 + /// Infallible unchecked constructor for CowStr. 205 + pub unsafe fn unchecked_cow(rkey: CowStr<'r>) -> Self { 206 + Self(rkey) 200 207 } 208 + } 201 209 202 - /// Infallible constructor for when you *know* the string is a valid rkey. 203 - /// Marked unsafe because responsibility for upholding the invariant is on the developer. 204 - pub unsafe fn unchecked(rkey: &'r str) -> Self { 205 - Self(CowStr::Borrowed(rkey)) 210 + impl<'de, S> Deserialize<'de> for Rkey<S> 211 + where 212 + S: Bos<str> + AsRef<str> + Deserialize<'de>, 213 + { 214 + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 215 + where 216 + D: Deserializer<'de>, 217 + { 218 + let s = S::deserialize(deserializer)?; 219 + validate_rkey(s.as_ref()).map_err(D::Error::custom)?; 220 + Ok(Rkey(s)) 206 221 } 222 + } 207 223 208 - /// Get the record key as a string slice 209 - pub fn as_str(&self) -> &str { 210 - { 211 - let this = &self.0; 212 - this 213 - } 224 + impl<S: Bos<str> + IntoStatic> IntoStatic for Rkey<S> 225 + where 226 + S::Output: Bos<str>, 227 + { 228 + type Output = Rkey<S::Output>; 229 + 230 + fn into_static(self) -> Self::Output { 231 + Rkey(self.0.into_static()) 214 232 } 215 233 } 216 234 217 - impl<'r> FromStr for Rkey<'r> { 235 + impl FromStr for Rkey { 218 236 type Err = AtStrError; 219 237 220 238 fn from_str(s: &str) -> Result<Self, Self::Err> { 221 - if [".", ".."].contains(&s) { 222 - Err(AtStrError::disallowed("record-key", s, &[".", ".."])) 223 - } else if !RKEY_REGEX.is_match(s) { 224 - Err(AtStrError::regex( 225 - "record-key", 226 - s, 227 - SmolStr::new_static("doesn't match 'any' schema"), 228 - )) 229 - } else { 230 - Ok(Self(CowStr::Owned(s.to_smolstr()))) 231 - } 239 + Self::new_owned(s) 232 240 } 233 241 } 234 242 235 - impl IntoStatic for Rkey<'_> { 236 - type Output = Rkey<'static>; 243 + impl FromStr for Rkey<CowStr<'static>> { 244 + type Err = AtStrError; 237 245 238 - fn into_static(self) -> Self::Output { 239 - Rkey(self.0.into_static()) 246 + fn from_str(s: &str) -> Result<Self, Self::Err> { 247 + Self::new_owned(s) 240 248 } 241 249 } 242 250 243 - impl<'de, 'a> Deserialize<'de> for Rkey<'a> 244 - where 245 - 'de: 'a, 246 - { 247 - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 248 - where 249 - D: Deserializer<'de>, 250 - { 251 - let value = Deserialize::deserialize(deserializer)?; 252 - Self::new_cow(value).map_err(D::Error::custom) 251 + impl FromStr for Rkey<String> { 252 + type Err = AtStrError; 253 + 254 + fn from_str(s: &str) -> Result<Self, Self::Err> { 255 + Self::new_owned(s) 253 256 } 254 257 } 255 258 256 - impl fmt::Display for Rkey<'_> { 259 + impl<S: Bos<str> + AsRef<str>> fmt::Display for Rkey<S> { 257 260 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 258 - f.write_str(&self.0) 261 + f.write_str(self.as_str()) 259 262 } 260 263 } 261 264 262 - impl fmt::Debug for Rkey<'_> { 265 + impl<S: Bos<str> + AsRef<str>> fmt::Debug for Rkey<S> { 263 266 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 264 - write!(f, "record-key:{}", self.0) 267 + write!(f, "record-key:{}", self.as_str()) 265 268 } 266 269 } 267 270 268 - impl From<Rkey<'_>> for String { 269 - fn from(value: Rkey<'_>) -> Self { 270 - value.0.to_string() 271 + impl<S: Bos<str> + AsRef<str>> From<Rkey<S>> for String { 272 + fn from(value: Rkey<S>) -> Self { 273 + value.as_str().to_string() 271 274 } 272 275 } 273 276 274 - impl<'r> From<Rkey<'r>> for CowStr<'r> { 275 - fn from(value: Rkey<'r>) -> Self { 276 - value.0 277 + impl<S: Bos<str> + AsRef<str>> From<Rkey<S>> for SmolStr { 278 + fn from(value: Rkey<S>) -> Self { 279 + value.as_str().to_smolstr() 277 280 } 278 281 } 279 282 280 - impl<'r> From<Rkey<'r>> for SmolStr { 281 - fn from(value: Rkey) -> Self { 282 - value.0.to_smolstr() 283 - } 284 - } 285 - 286 - impl<'r> From<String> for Rkey<'r> { 283 + impl From<String> for Rkey { 287 284 fn from(value: String) -> Self { 288 - if [".", ".."].contains(&value.as_str()) { 289 - panic!("Disallowed rkey") 290 - } else if !RKEY_REGEX.is_match(&value) { 291 - panic!("Invalid rkey") 292 - } else { 293 - Self(CowStr::Owned(value.to_smolstr())) 294 - } 285 + Self::new_owned(value).unwrap() 295 286 } 296 287 } 297 288 298 - impl<'r> From<CowStr<'r>> for Rkey<'r> { 289 + impl<'r> From<CowStr<'r>> for Rkey<CowStr<'r>> { 299 290 fn from(value: CowStr<'r>) -> Self { 300 - if [".", ".."].contains(&value.as_ref()) { 301 - panic!("Disallowed rkey") 302 - } else if !RKEY_REGEX.is_match(&value) { 303 - panic!("Invalid rkey") 304 - } else { 305 - Self(value) 306 - } 291 + Self::new_cow(value).unwrap() 307 292 } 308 293 } 309 294 310 - impl AsRef<str> for Rkey<'_> { 295 + impl<S: Bos<str> + AsRef<str>> AsRef<str> for Rkey<S> { 311 296 fn as_ref(&self) -> &str { 312 297 self.as_str() 313 298 } 314 299 } 315 300 316 - impl Deref for Rkey<'_> { 301 + impl<S: Bos<str> + AsRef<str>> Deref for Rkey<S> { 317 302 type Target = str; 318 303 319 304 fn deref(&self) -> &Self::Target { 320 - self.0.as_ref() 305 + self.as_str() 321 306 } 322 307 } 323 308
+82 -136
crates/jacquard-common/src/types/string.rs
··· 26 26 } 27 27 } 28 28 29 + use crate::bos::{Bos, DefaultStr}; 29 30 use crate::cowstr::ToCowStr; 30 31 pub use crate::{ 31 32 CowStr, ··· 57 58 /// record keys are intentionally NOT parsed from bare strings as the validation 58 59 /// is too permissive and would catch too many values. 59 60 #[derive(Debug, Clone, PartialEq, Eq, Hash)] 60 - pub enum AtprotoStr<'s> { 61 + pub enum AtprotoStr<S: Bos<str> + AsRef<str> + Clone + Serialize = DefaultStr> { 61 62 /// ISO 8601 datetime 62 63 Datetime(Datetime), 63 64 /// BCP 47 language tag ··· 65 66 /// Timestamp identifier 66 67 Tid(Tid), 67 68 /// Namespaced identifier 68 - Nsid(Nsid<CowStr<'s>>), 69 + Nsid(Nsid<S>), 69 70 /// Decentralized identifier 70 - Did(Did<CowStr<'s>>), 71 + Did(Did<S>), 71 72 /// Account handle 72 - Handle(Handle<CowStr<'s>>), 73 + Handle(Handle<S>), 73 74 /// Identifier (DID or handle) 74 - AtIdentifier(AtIdentifier<CowStr<'s>>), 75 + AtIdentifier(AtIdentifier<S>), 76 + // TODO(bos-migration): parameterise on S once AtUri is migrated. 75 77 /// AT URI 76 - AtUri(AtUri<'s>), 78 + AtUri(AtUri<'static>), 79 + // TODO(bos-migration): parameterise on S once UriValue is migrated. 77 80 /// Generic URI 78 - Uri(UriValue<'s>), 81 + Uri(UriValue<'static>), 79 82 /// Content identifier 80 - Cid(Cid<'s>), 83 + Cid(Cid<S>), 81 84 /// Record key 82 - RecordKey(RecordKey<Rkey<'s>>), 85 + RecordKey(RecordKey<Rkey<S>>), 83 86 /// Plain string (fallback) 84 - String(CowStr<'s>), 87 + String(S), 85 88 } 86 89 87 - impl<'s> AtprotoStr<'s> { 88 - /// Borrowing constructor for bare atproto string values 90 + use crate::types::cid::IpldCid; 91 + use crate::types::did::validate_did; 92 + use crate::types::handle::validate_handle; 93 + use crate::types::nsid::validate_nsid; 94 + 95 + impl<S: Bos<str> + AsRef<str> + Clone + Serialize> AtprotoStr<S> { 96 + /// Classify and wrap a string value into the appropriate variant. 97 + /// 89 98 /// This is fairly exhaustive and potentially **slow**, prefer using anything 90 99 /// that narrows down the search field quicker. 91 100 /// 92 - /// Note: We don't construct record keys from bare strings in this because 93 - /// the type is too permissive and too many things would be classified as rkeys. 94 - /// 95 - /// Value object deserialization checks against the field names for common 96 - /// names (uri, cid, did, handle, createdAt, indexedAt, etc.) to improve 97 - /// performance of the happy path. 98 - pub fn new(string: &'s str) -> Self { 99 - // TODO: do some quick prefix checks like in Uri to drop through faster 100 - if let Ok(datetime) = Datetime::from_str(string) { 101 - Self::Datetime(datetime) 102 - } else if let Ok(lang) = Language::new(string) { 103 - Self::Language(lang) 104 - } else if let Ok(tid) = Tid::from_str(string) { 105 - Self::Tid(tid) 106 - } else if let Ok(did) = Did::new_cow(string.to_cowstr()) { 107 - Self::Did(did) 108 - } else if let Ok(handle) = Handle::new_cow(string.to_cowstr()) { 109 - Self::Handle(handle) 110 - } else if let Ok(atid) = AtIdentifier::new_cow(string.to_cowstr()) { 111 - Self::AtIdentifier(atid) 112 - } else if let Ok(nsid) = Nsid::new_cow(string.to_cowstr()) { 113 - Self::Nsid(nsid) 114 - } else if let Ok(aturi) = AtUri::new(string) { 115 - Self::AtUri(aturi) 116 - } else if let Ok(uri) = UriValue::new(string) { 117 - Self::Uri(uri) 118 - } else if let Ok(cid) = Cid::new(string.as_bytes()) { 119 - Self::Cid(cid) 120 - } else { 121 - // We don't construct record keys from bare strings because the type is too permissive 122 - Self::String(CowStr::Borrowed(string)) 101 + /// Inspects the string content, validates against known AT Protocol types, 102 + /// and moves `string` into the matching variant via unchecked constructors 103 + /// (safe because we validate first). 104 + pub fn new(string: S) -> Self { 105 + let s: &str = string.as_ref(); 106 + // Non-string-backed types first (they don't consume S). 107 + if let Ok(datetime) = Datetime::from_str(s) { 108 + return Self::Datetime(datetime); 109 + } 110 + if let Ok(lang) = Language::new(s) { 111 + return Self::Language(lang); 112 + } 113 + if let Ok(tid) = Tid::from_str(s) { 114 + return Self::Tid(tid); 115 + } 116 + // String-backed types: validate then wrap S directly. 117 + if validate_did(s).is_ok() { 118 + return Self::Did(unsafe { Did::unchecked(string) }); 119 + } 120 + if validate_handle(s).is_ok() { 121 + return Self::Handle(unsafe { Handle::unchecked(string) }); 122 + } 123 + if validate_nsid(s).is_ok() { 124 + return Self::Nsid(unsafe { Nsid::unchecked(string) }); 125 + } 126 + // TODO(bos-migration): AtUri and UriValue still use lifetimes. 127 + // For now, construct owned versions for those variants. 128 + if let Ok(aturi) = AtUri::new_owned(s) { 129 + return Self::AtUri(aturi); 130 + } 131 + if let Ok(uri) = UriValue::new_owned(s) { 132 + return Self::Uri(uri); 133 + } 134 + // CID: try to parse as IPLD first, otherwise wrap as string CID. 135 + if IpldCid::try_from(s).is_ok() || s.starts_with("bafy") { 136 + return Self::Cid(unsafe { Cid::unchecked_str(string) }); 123 137 } 138 + // Fallback: plain string. 139 + Self::String(string) 124 140 } 125 141 126 - /// Get the string value regardless of variant 142 + /// Get the string value regardless of variant. 127 143 pub fn as_str(&self) -> &str { 128 144 match self { 129 145 Self::Datetime(datetime) => datetime.as_str(), ··· 141 157 } 142 158 } 143 159 144 - /// detailed string type 160 + /// Detailed string type classification. 145 161 pub fn string_type(&self) -> LexiconStringType { 146 162 match self { 147 163 Self::Datetime(_) => LexiconStringType::Datetime, ··· 167 183 } 168 184 } 169 185 170 - impl AtprotoStr<'static> { 171 - /// Owned constructor for bare atproto string values 172 - /// This is fairly exhaustive and potentially **slow**, prefer using anything 173 - /// that narrows down the search field quicker. 174 - /// 175 - /// Note: We don't construct record keys from bare strings in this because 176 - /// the type is too permissive and too many things would be classified as rkeys. 177 - /// 178 - /// Value object deserialization checks against the field names for common 179 - /// names (uri, cid, did, handle, createdAt, indexedAt, etc.) to improve 180 - /// performance of the happy path. 181 - pub fn new_owned(string: impl AsRef<str>) -> AtprotoStr<'static> { 182 - let string = string.as_ref(); 183 - // TODO: do some quick prefix checks like in Uri to drop through faster 184 - if let Ok(datetime) = Datetime::from_str(string) { 185 - Self::Datetime(datetime) 186 - } else if let Ok(lang) = Language::new(string) { 187 - Self::Language(lang) 188 - } else if let Ok(tid) = Tid::from_str(string) { 189 - Self::Tid(tid) 190 - } else if let Ok(did) = Did::new_owned(string) { 191 - Self::Did(did) 192 - } else if let Ok(handle) = Handle::new_owned(string) { 193 - Self::Handle(handle) 194 - } else if let Ok(atid) = AtIdentifier::new_owned(string) { 195 - Self::AtIdentifier(atid) 196 - } else if let Ok(nsid) = Nsid::new_owned(string) { 197 - Self::Nsid(nsid) 198 - } else if let Ok(aturi) = AtUri::new_owned(string) { 199 - Self::AtUri(aturi) 200 - } else if let Ok(uri) = UriValue::new_owned(string) { 201 - Self::Uri(uri) 202 - } else if let Ok(cid) = Cid::new_owned(string.as_bytes()) { 203 - Self::Cid(cid) 204 - } else { 205 - // We don't construct record keys from bare strings because the type is too permissive 206 - Self::String(CowStr::Owned(string.to_smolstr())) 207 - } 208 - } 209 - } 210 - 211 - impl<'s> AsRef<str> for AtprotoStr<'s> { 186 + impl<S: Bos<str> + AsRef<str> + Clone + Serialize> AsRef<str> for AtprotoStr<S> { 212 187 fn as_ref(&self) -> &str { 213 - match self { 214 - Self::Datetime(datetime) => datetime.as_str(), 215 - Self::Language(lang) => lang.as_ref(), 216 - Self::Tid(tid) => tid.as_ref(), 217 - Self::Did(did) => did.as_ref(), 218 - Self::Handle(handle) => handle.as_ref(), 219 - Self::AtIdentifier(atid) => atid.as_ref(), 220 - Self::Nsid(nsid) => nsid.as_ref(), 221 - Self::AtUri(aturi) => aturi.as_ref(), 222 - Self::Uri(uri) => uri.as_str(), 223 - Self::Cid(cid) => cid.as_ref(), 224 - Self::RecordKey(rkey) => rkey.as_ref(), 225 - Self::String(string) => string.as_ref(), 226 - } 188 + self.as_str() 227 189 } 228 190 } 229 191 230 - impl Serialize for AtprotoStr<'_> { 231 - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 192 + impl<S: Bos<str> + AsRef<str> + Clone + Serialize> Serialize for AtprotoStr<S> { 193 + fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error> 232 194 where 233 - S: Serializer, 195 + Ser: Serializer, 234 196 { 235 - serializer.serialize_str(self.as_ref()) 197 + serializer.serialize_str(self.as_str()) 236 198 } 237 199 } 238 200 239 - impl<'de, 'a> Deserialize<'de> for AtprotoStr<'a> 201 + impl<'de, S> Deserialize<'de> for AtprotoStr<S> 240 202 where 241 - 'de: 'a, 203 + S: Bos<str> + AsRef<str> + Clone + Serialize + Deserialize<'de>, 242 204 { 243 205 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 244 206 where 245 207 D: Deserializer<'de>, 246 208 { 247 - let value = Deserialize::deserialize(deserializer)?; 209 + let value = S::deserialize(deserializer)?; 248 210 Ok(Self::new(value)) 249 211 } 250 212 } 251 213 252 - impl IntoStatic for AtprotoStr<'_> { 253 - type Output = AtprotoStr<'static>; 214 + impl<S: Bos<str> + AsRef<str> + Clone + Serialize + IntoStatic> IntoStatic for AtprotoStr<S> 215 + where 216 + S::Output: Bos<str> + AsRef<str> + Clone + Serialize, 217 + { 218 + type Output = AtprotoStr<S::Output>; 254 219 255 220 fn into_static(self) -> Self::Output { 256 221 match self { ··· 261 226 AtprotoStr::Did(did) => AtprotoStr::Did(did.into_static()), 262 227 AtprotoStr::Handle(handle) => AtprotoStr::Handle(handle.into_static()), 263 228 AtprotoStr::AtIdentifier(ident) => AtprotoStr::AtIdentifier(ident.into_static()), 264 - AtprotoStr::AtUri(at_uri) => AtprotoStr::AtUri(at_uri.into_static()), 265 - AtprotoStr::Uri(uri) => AtprotoStr::Uri(uri.into_static()), 229 + // AtUri and UriValue are already 'static in this enum. 230 + AtprotoStr::AtUri(at_uri) => AtprotoStr::AtUri(at_uri), 231 + AtprotoStr::Uri(uri) => AtprotoStr::Uri(uri), 266 232 AtprotoStr::Cid(cid) => AtprotoStr::Cid(cid.into_static()), 267 233 AtprotoStr::RecordKey(record_key) => AtprotoStr::RecordKey(record_key.into_static()), 268 - AtprotoStr::String(cow_str) => AtprotoStr::String(cow_str.into_static()), 234 + AtprotoStr::String(s) => AtprotoStr::String(s.into_static()), 269 235 } 270 236 } 271 237 } 272 238 273 - impl From<AtprotoStr<'_>> for String { 274 - fn from(value: AtprotoStr<'_>) -> Self { 275 - match value { 276 - AtprotoStr::AtIdentifier(ident) => ident.to_string(), 277 - AtprotoStr::AtUri(at_uri) => at_uri.to_string(), 278 - AtprotoStr::Uri(uri) => match uri { 279 - UriValue::At(at_uri) => at_uri.to_string(), 280 - UriValue::Cid(cid) => cid.to_string(), 281 - UriValue::Did(did) => did.to_string(), 282 - UriValue::Https(url) => url.to_string(), 283 - UriValue::Wss(url) => url.to_string(), 284 - UriValue::Any(cow_str) => cow_str.to_string(), 285 - }, 286 - AtprotoStr::Cid(cid) => cid.to_string(), 287 - AtprotoStr::RecordKey(record_key) => record_key.as_ref().to_string(), 288 - AtprotoStr::String(cow_str) => cow_str.to_string(), 289 - AtprotoStr::Datetime(datetime) => datetime.to_string(), 290 - AtprotoStr::Language(language) => language.to_string(), 291 - AtprotoStr::Tid(tid) => tid.to_string(), 292 - AtprotoStr::Nsid(nsid) => nsid.to_string(), 293 - AtprotoStr::Did(did) => did.to_string(), 294 - AtprotoStr::Handle(handle) => handle.to_string(), 295 - } 239 + impl<S: Bos<str> + AsRef<str> + Clone + Serialize> From<AtprotoStr<S>> for String { 240 + fn from(value: AtprotoStr<S>) -> Self { 241 + value.as_str().to_string() 296 242 } 297 243 } 298 244
+23 -10
crates/jacquard-common/src/types/uri.rs
··· 28 28 /// WebSocket Secure URL 29 29 Wss(Uri<String>), 30 30 /// IPLD CID URI 31 - Cid(Cid<'u>), 31 + Cid(Cid<CowStr<'u>>), 32 32 /// Unrecognized URI scheme (catch-all) 33 33 Any(CowStr<'u>), 34 34 } ··· 60 60 } else if uri.starts_with("wss://") { 61 61 Ok(UriValue::Wss(Uri::parse(uri)?.to_owned())) 62 62 } else if uri.starts_with("ipld://") { 63 - match Cid::from_str(&uri[7..]) { 64 - Ok(cid) => Ok(UriValue::Cid(cid)), 65 - Err(_) => Ok(UriValue::Any(CowStr::Borrowed(uri))), 63 + // Borrow the slice after "ipld://" prefix (7 bytes) from the input &'u str. 64 + let cid_part = &uri[7..]; 65 + if cid_part.is_empty() { 66 + Ok(UriValue::Any(CowStr::Borrowed(uri))) 67 + } else { 68 + Ok(UriValue::Cid(Cid::cow_str(CowStr::Borrowed(cid_part)))) 66 69 } 67 70 } else { 68 71 Ok(UriValue::Any(CowStr::Borrowed(uri))) ··· 81 84 } else if uri.starts_with("wss://") { 82 85 Ok(UriValue::Wss(Uri::parse(uri)?.to_owned())) 83 86 } else if uri.starts_with("ipld://") { 84 - match Cid::from_str(&uri[7..]) { 85 - Ok(cid) => Ok(UriValue::Cid(cid)), 86 - Err(_) => Ok(UriValue::Any(CowStr::Owned(uri.to_smolstr()))), 87 + // Owned context: use SmolStr via CowStr::Owned. 88 + let cid_part = &uri[7..]; 89 + if cid_part.is_empty() { 90 + Ok(UriValue::Any(CowStr::Owned(uri.to_smolstr()))) 91 + } else { 92 + Ok(UriValue::Cid(Cid::cow_str(CowStr::Owned(cid_part.to_smolstr())))) 87 93 } 88 94 } else { 89 95 Ok(UriValue::Any(CowStr::Owned(uri.to_smolstr()))) ··· 101 107 } else if uri.starts_with("wss://") { 102 108 Ok(UriValue::Wss(Uri::parse(uri.as_ref())?.to_owned())) 103 109 } else if uri.starts_with("ipld://") { 104 - match Cid::from_str(&uri.as_str()[7..]) { 105 - Ok(cid) => Ok(UriValue::Cid(cid)), 106 - Err(_) => Ok(UriValue::Any(uri)), 110 + // Determine whether the CID part (after "ipld://") is non-empty before consuming uri. 111 + if uri.as_ref()[7..].is_empty() { 112 + Ok(UriValue::Any(uri)) 113 + } else { 114 + // Build a CowStr for the CID part, preserving the ownership variant. 115 + let cid_cow: CowStr<'u> = match uri { 116 + CowStr::Borrowed(s) => CowStr::Borrowed(&s[7..]), 117 + CowStr::Owned(ref s) => CowStr::Owned(s[7..].to_smolstr()), 118 + }; 119 + Ok(UriValue::Cid(Cid::cow_str(cid_cow))) 107 120 } 108 121 } else { 109 122 Ok(UriValue::Any(uri))
+6 -6
crates/jacquard-common/src/types/value.rs
··· 39 39 /// Integer value (no floats in AT Protocol) 40 40 Integer(i64), 41 41 /// String value (parsed into specific AT Protocol types when possible) 42 - String(AtprotoStr<'s>), 42 + String(AtprotoStr<CowStr<'s>>), 43 43 /// Raw bytes 44 44 Bytes(Bytes), 45 45 /// CID link reference 46 - CidLink(Cid<'s>), 46 + CidLink(Cid<CowStr<'s>>), 47 47 /// Array of values 48 48 Array(Array<'s>), 49 49 /// Object/map of values 50 50 Object(Object<'s>), 51 51 /// Blob reference with metadata 52 - Blob(Blob<'s>), 52 + Blob(Blob<CowStr<'s>>), 53 53 } 54 54 55 55 /// Errors that can occur when working with AT Protocol data ··· 170 170 } 171 171 172 172 /// Get as string if this is a String variant 173 - pub fn as_str_mut(&'s mut self) -> Option<&'s mut AtprotoStr<'s>> { 173 + pub fn as_str_mut(&'s mut self) -> Option<&'s mut AtprotoStr<CowStr<'s>>> { 174 174 if let Data::String(s) = self { 175 175 Some(s) 176 176 } else { ··· 609 609 /// Raw bytes 610 610 Bytes(Bytes), 611 611 /// CID link reference 612 - CidLink(Cid<'s>), 612 + CidLink(Cid<CowStr<'s>>), 613 613 /// Array of raw values 614 614 Array(Vec<RawData<'s>>), 615 615 /// Object/map of raw values 616 616 Object(BTreeMap<SmolStr, RawData<'s>>), 617 617 /// Valid blob reference 618 - Blob(Blob<'s>), 618 + Blob(Blob<CowStr<'s>>), 619 619 /// Invalid blob structure (captured for debugging) 620 620 InvalidBlob(Box<RawData<'s>>), 621 621 /// Invalid number format, generally a floating point number (captured as bytes)
+10 -16
crates/jacquard-common/src/types/value/convert.rs
··· 131 131 132 132 impl From<String> for Data<'_> { 133 133 fn from(t: String) -> Self { 134 - Data::String(AtprotoStr::new_owned(t)) 134 + Data::String(AtprotoStr::new(CowStr::from(t))) 135 135 } 136 136 } 137 137 138 138 impl<'a> From<&'a str> for Data<'a> { 139 139 fn from(t: &'a str) -> Self { 140 - Data::String(AtprotoStr::new(t)) 140 + Data::String(AtprotoStr::new(CowStr::Borrowed(t))) 141 141 } 142 142 } 143 143 ··· 149 149 150 150 impl<'s> From<CowStr<'s>> for Data<'s> { 151 151 fn from(t: CowStr<'s>) -> Self { 152 - match t { 153 - CowStr::Borrowed(s) => Data::String(AtprotoStr::new(s)), 154 - CowStr::Owned(s) => Data::String(AtprotoStr::new_owned(s)), 155 - } 152 + Data::String(AtprotoStr::new(t)) 156 153 } 157 154 } 158 155 159 156 impl From<SmolStr> for Data<'_> { 160 157 fn from(t: SmolStr) -> Self { 161 - Data::String(AtprotoStr::new_owned(t)) 158 + Data::String(AtprotoStr::new(CowStr::Owned(t))) 162 159 } 163 160 } 164 161 165 162 impl<'s> From<Cow<'s, str>> for Data<'s> { 166 163 fn from(t: Cow<'s, str>) -> Self { 167 - match t { 168 - Cow::Borrowed(s) => Data::String(AtprotoStr::new(s)), 169 - Cow::Owned(s) => Data::String(AtprotoStr::new_owned(s)), 170 - } 164 + Data::String(AtprotoStr::new(CowStr::from(t))) 171 165 } 172 166 } 173 167 ··· 242 236 derive_into_atproto!(Array, Array<'s>, into); 243 237 derive_into_atproto!(Object, Object<'s>, to_owned); 244 238 245 - derive_into_atproto!(CidLink, Cid<'s>, clone); 246 - derive_into_atproto!(CidLink, &Cid<'s>, to_owned); 239 + derive_into_atproto!(CidLink, Cid<CowStr<'s>>, clone); 240 + derive_into_atproto!(CidLink, &Cid<CowStr<'s>>, to_owned); 247 241 248 242 derive_try_from_atproto!(Boolean, bool); 249 243 derive_try_from_atproto!(Integer, i8); ··· 260 254 derive_try_from_atproto!(Integer, usize); 261 255 derive_try_from_atproto!(Bytes, Vec<u8>); 262 256 derive_try_from_atproto!(Object, Object<'static>); 263 - derive_try_from_atproto!(CidLink, Cid<'static>); 257 + derive_try_from_atproto!(CidLink, Cid<CowStr<'static>>); 264 258 265 259 derive_try_from_atproto_option!(Boolean, bool); 266 260 derive_try_from_atproto_option!(Integer, i8); ··· 279 273 derive_try_from_atproto_option!(Bytes, Vec<u8>); 280 274 derive_try_from_atproto_option!(Array, Array<'static>); 281 275 derive_try_from_atproto_option!(Object, Object<'static>); 282 - derive_try_from_atproto_option!(CidLink, Cid<'static>); 276 + derive_try_from_atproto_option!(CidLink, Cid<CowStr<'static>>); 283 277 284 278 /// Convert RawData to validated Data with type inference 285 279 impl<'s> TryFrom<RawData<'s>> for Data<'s> { ··· 329 323 } 330 324 }; 331 325 return Ok(Data::Blob(crate::types::blob::Blob { 332 - r#ref: CidLink::str(cid).into_static(), 326 + r#ref: CidLink(cid.clone()), 333 327 mime_type: crate::types::blob::MimeType::from(mime.clone()), 334 328 size: size_val, 335 329 }));
+39 -21
crates/jacquard-common/src/types/value/parsing.rs
··· 43 43 } 44 44 LexiconStringType::AtUri => { 45 45 if let Ok(value) = AtUri::new(value) { 46 - map.insert(key.to_smolstr(), Data::String(AtprotoStr::AtUri(value))); 46 + // AtprotoStr::AtUri stores AtUri<'static>; convert to owned. 47 + map.insert( 48 + key.to_smolstr(), 49 + Data::String(AtprotoStr::AtUri(value.into_static())), 50 + ); 47 51 } else { 48 52 map.insert( 49 53 key.to_smolstr(), ··· 95 99 } 96 100 } 97 101 LexiconStringType::Cid => { 98 - if let Ok(value) = Cid::new(value.as_bytes()) { 102 + if let Ok(value) = Cid::<CowStr<'s>>::new_owned(value.as_bytes()) { 99 103 map.insert(key.to_smolstr(), Data::String(AtprotoStr::Cid(value))); 100 104 } else { 101 105 map.insert( ··· 125 129 } 126 130 } 127 131 LexiconStringType::RecordKey => { 128 - if let Ok(value) = Rkey::new(value) { 132 + // Validate the rkey without shadowing the original `value: &'s str`. 133 + if Rkey::new(value).is_ok() { 129 134 map.insert( 130 135 key.to_smolstr(), 131 - Data::String(AtprotoStr::RecordKey(RecordKey::from(value))), 136 + // Rkey already validated above; borrow the original &'s str directly. 137 + Data::String(AtprotoStr::RecordKey( 138 + RecordKey::any_cow(CowStr::Borrowed(value)) 139 + .expect("Rkey validation passed"), 140 + )), 132 141 ); 133 142 } else { 134 143 map.insert( ··· 138 147 } 139 148 } 140 149 LexiconStringType::Uri(_) => { 141 - if let Ok(uri) = UriValue::new(value) { 150 + // AtprotoStr::Uri stores UriValue<'static>, so we must produce an owned value. 151 + if let Ok(uri) = UriValue::new_owned(value) { 142 152 map.insert(key.to_smolstr(), Data::String(AtprotoStr::Uri(uri))); 143 153 } else { 144 154 map.insert( ··· 155 165 } 156 166 157 167 /// smarter parsing to avoid trying as many posibilities. 158 - pub fn parse_string<'s>(string: &'s str) -> AtprotoStr<'s> { 168 + pub fn parse_string<'s>(string: &'s str) -> AtprotoStr<CowStr<'s>> { 159 169 if string.len() < 2048 && string.starts_with("did:") { 160 170 if let Ok(did) = Did::new_cow(string.to_cowstr()) { 161 171 return AtprotoStr::Did(did); ··· 166 176 return AtprotoStr::Datetime(datetime); 167 177 } 168 178 } else if string.starts_with("at://") { 179 + // AtprotoStr::AtUri stores AtUri<'static>; convert to owned. 169 180 if let Ok(uri) = AtUri::new(string) { 170 - return AtprotoStr::AtUri(uri); 181 + return AtprotoStr::AtUri(uri.into_static()); 171 182 } 172 183 } else if string.starts_with("https://") { 173 184 if let Ok(uri) = Uri::parse(string) { ··· 178 189 return AtprotoStr::Uri(UriValue::Wss(uri.to_owned())); 179 190 } 180 191 } else if string.starts_with("ipfs://") { 181 - return AtprotoStr::Uri(UriValue::Cid(Cid::str(string))); 192 + // URI variant must be 'static; convert to an owned CID. 193 + return AtprotoStr::Uri(UriValue::Cid( 194 + Cid::<CowStr<'static>>::new_owned(string.as_bytes()) 195 + .unwrap_or_else(|_| Cid::cow_str(CowStr::Owned(string.to_smolstr()))), 196 + )); 182 197 } else if string.contains('.') && !string.contains([' ', '\n']) { 183 198 // Dotted strings without a scheme could be handles, NSIDs, or URIs. 184 199 // Use TLD lookup and camelCase heuristic to disambiguate. ··· 219 234 } else if let Ok(nsid) = Nsid::new_cow(string.to_cowstr()) { 220 235 return AtprotoStr::Nsid(nsid); 221 236 } else if string.contains("://") && Uri::<&str>::parse(string).is_ok() { 222 - return AtprotoStr::Uri(UriValue::Any(string.into())); 237 + // AtprotoStr::Uri stores UriValue<'static>; convert to owned. 238 + return AtprotoStr::Uri(UriValue::Any(CowStr::Owned(string.to_smolstr()))); 223 239 } 224 240 } else if string.len() == 13 { 225 241 if let Ok(tid) = Tid::new(string) { ··· 228 244 } else if !string.contains([' ', '\n']) && string.len() > 20 { 229 245 // CID: must be longer than typical short strings to avoid false positives 230 246 // Most CIDs are 46+ chars (base32 encoded), minimum realistic is around 30 231 - if let Ok(cid) = Cid::new(string.as_bytes()) { 247 + if let Ok(cid) = Cid::<CowStr<'s>>::new_owned(string.as_bytes()) { 232 248 return AtprotoStr::Cid(cid); 233 249 } 234 250 } ··· 267 283 } 268 284 269 285 /// Convert an ipld map to a atproto data model blob if it matches the format 270 - pub fn cbor_to_blob<'b>(blob: &'b BTreeMap<String, Ipld>) -> Option<Blob<'b>> { 286 + pub fn cbor_to_blob<'b>(blob: &'b BTreeMap<String, Ipld>) -> Option<Blob<CowStr<'b>>> { 271 287 let mime_type = blob.get("mimeType").and_then(|o| { 272 288 if let Ipld::String(string) = o { 273 - Some(string) 289 + Some(string.as_str()) 274 290 } else { 275 291 None 276 292 } ··· 285 301 }); 286 302 if let (Some(mime_type), Some(size)) = (mime_type, size) { 287 303 return Some(Blob { 288 - r#ref: CidLink::ipld(*value), 289 - mime_type: MimeType::raw(mime_type), 304 + r#ref: CidLink::<CowStr<'b>>::ipld(*value), 305 + mime_type: MimeType::new_cow(CowStr::Borrowed(mime_type)), 290 306 size: size as usize, 291 307 }); 292 308 } 293 309 } else if let Some(Ipld::String(value)) = blob.get("cid") { 294 310 if let Some(mime_type) = mime_type { 295 311 return Some(Blob { 296 - r#ref: CidLink::str(value), 297 - mime_type: MimeType::raw(mime_type), 312 + r#ref: CidLink::cow_str(CowStr::Borrowed(value.as_str())), 313 + mime_type: MimeType::new_cow(CowStr::Borrowed(mime_type)), 298 314 size: 0, 299 315 }); 300 316 } ··· 304 320 } 305 321 306 322 /// convert a JSON object to an atproto data model blob if it matches the format 307 - pub fn json_to_blob<'b>(blob: &'b serde_json::Map<String, serde_json::Value>) -> Option<Blob<'b>> { 323 + pub fn json_to_blob<'b>( 324 + blob: &'b serde_json::Map<String, serde_json::Value>, 325 + ) -> Option<Blob<CowStr<'b>>> { 308 326 let mime_type = blob.get("mimeType").and_then(|v| v.as_str()); 309 327 if let Some(value) = blob.get("ref") { 310 328 if let Some(value) = value ··· 315 333 let size = blob.get("size").and_then(|v| v.as_u64()); 316 334 if let (Some(mime_type), Some(size)) = (mime_type, size) { 317 335 return Some(Blob { 318 - r#ref: CidLink::str(value), 319 - mime_type: MimeType::raw(mime_type), 336 + r#ref: CidLink::cow_str(CowStr::Borrowed(value)), 337 + mime_type: MimeType::new_cow(CowStr::Borrowed(mime_type)), 320 338 size: size as usize, 321 339 }); 322 340 } ··· 324 342 } else if let Some(value) = blob.get("cid").and_then(|v| v.as_str()) { 325 343 if let Some(mime_type) = mime_type { 326 344 return Some(Blob { 327 - r#ref: CidLink::str(value), 328 - mime_type: MimeType::raw(mime_type), 345 + r#ref: CidLink::cow_str(CowStr::Borrowed(value)), 346 + mime_type: MimeType::new_cow(CowStr::Borrowed(mime_type)), 329 347 size: 0, 330 348 }); 331 349 }
+14 -14
crates/jacquard-common/src/types/value/serde_impl.rs
··· 251 251 continue; 252 252 } else { 253 253 // Only key, return CidLink 254 - return Ok(Data::CidLink(Cid::from(cid_str))); 254 + return Ok(Data::CidLink(Cid::cow_str(CowStr::from(cid_str)))); 255 255 } 256 256 } else if key.as_str() == "$bytes" { 257 257 // {"$bytes": "base64_string"} pattern ··· 326 326 327 327 if let (Some(ref_cid), Some(mime_cowstr), Some(size)) = (ref_cid, mime_type, size) { 328 328 return Ok(Data::Blob(Blob { 329 - r#ref: CidLink::str(ref_cid.as_str()).into_static(), 329 + // ref_cid is already Cid<CowStr<'s>>; wrap directly. 330 + r#ref: CidLink(ref_cid), 330 331 mime_type: MimeType::from(mime_cowstr), 331 332 size, 332 333 })); ··· 344 345 // Decode base64 345 346 decode_bytes(&s) 346 347 } 347 - DataModelType::CidLink if key.as_str() == "$link" => { 348 - Data::CidLink(Cid::from_str(&s).unwrap()) 349 - } 348 + DataModelType::CidLink if key.as_str() == "$link" => Data::CidLink(Cid::cow_str(s)), 350 349 _ => continue, // no refinement needed 351 350 }; 352 351 *value = refined; ··· 376 375 LexiconStringType::Nsid => Nsid::new_owned(s.clone()) 377 376 .map(|nsid| Data::String(AtprotoStr::Nsid(nsid))) 378 377 .unwrap_or_else(|_| Data::String(AtprotoStr::String(s.clone()))), 379 - LexiconStringType::Cid => Cid::new_owned(s.as_bytes()) 378 + LexiconStringType::Cid => Cid::<CowStr<'s>>::new_owned(s.as_bytes()) 380 379 .map(|cid| Data::String(AtprotoStr::Cid(cid))) 381 380 .unwrap_or_else(|_| Data::String(AtprotoStr::String(s.into()))), 382 381 LexiconStringType::Language => Language::new(&s) ··· 385 384 LexiconStringType::Tid => Tid::new(s.clone()) 386 385 .map(|tid| Data::String(AtprotoStr::Tid(tid))) 387 386 .unwrap_or_else(|_| Data::String(AtprotoStr::String(s.clone()))), 388 - LexiconStringType::RecordKey => Rkey::new_owned(s.clone()) 389 - .map(|rkey| Data::String(AtprotoStr::RecordKey(RecordKey::from(rkey)))) 387 + LexiconStringType::RecordKey => Rkey::new_cow(s.clone()) 388 + .map(|rkey| Data::String(AtprotoStr::RecordKey(RecordKey(rkey)))) 390 389 .unwrap_or_else(|_| Data::String(AtprotoStr::String(s.clone()))), 391 390 LexiconStringType::Uri(_) => UriValue::new_owned(s.clone()) 392 391 .map(|uri| Data::String(AtprotoStr::Uri(uri))) ··· 676 675 continue; 677 676 } else { 678 677 // Only key, return CidLink 679 - return Ok(RawData::CidLink(Cid::from(cid_str))); 678 + return Ok(RawData::CidLink(Cid::cow_str(CowStr::from(cid_str)))); 680 679 } 681 680 } else if key.as_str() == "$bytes" { 682 681 // {"$bytes": "base64_string"} pattern ··· 813 812 814 813 if let (Some(ref_cid), Some(mime_cowstr), Some(size)) = (ref_cid, mime_type, size) { 815 814 return Ok(RawData::Blob(Blob { 816 - r#ref: CidLink::str(ref_cid.as_str()).into_static(), 815 + // ref_cid is already Cid<CowStr<'s>>; wrap directly. 816 + r#ref: CidLink(ref_cid), 817 817 mime_type: MimeType::from(mime_cowstr), 818 818 size, 819 819 })); ··· 1036 1036 1037 1037 // MapAccess implementation for Blob - allows borrowing from blob fields 1038 1038 struct BlobDeserializer<'de> { 1039 - blob: &'de Blob<'de>, 1039 + blob: &'de Blob<CowStr<'de>>, 1040 1040 field_index: usize, 1041 1041 } 1042 1042 1043 1043 impl<'de> BlobDeserializer<'de> { 1044 - fn new(blob: &'de Blob<'de>) -> Self { 1044 + fn new(blob: &'de Blob<CowStr<'de>>) -> Self { 1045 1045 Self { 1046 1046 blob, 1047 1047 field_index: 0, ··· 1084 1084 } 1085 1085 1086 1086 struct OwnedBlobDeserializer { 1087 - blob: Blob<'static>, 1087 + blob: Blob<CowStr<'static>>, 1088 1088 field_index: usize, 1089 1089 } 1090 1090 1091 1091 impl OwnedBlobDeserializer { 1092 - fn new(blob: Blob<'_>) -> Self { 1092 + fn new(blob: Blob<CowStr<'_>>) -> Self { 1093 1093 Self { 1094 1094 blob: blob.into_static(), 1095 1095 field_index: 0,
+5 -5
crates/jacquard-common/src/types/value/tests.rs
··· 77 77 78 78 #[test] 79 79 fn serialize_deserialize_cid_link_json() { 80 - let data = Data::CidLink(Cid::str( 80 + let data = Data::CidLink(Cid::cow_str(CowStr::Borrowed( 81 81 "bafyreih4g7bvo6hdq2juolev5bfzpbo4ewkxh5mzxwgvkjp3kitc6hqkha", 82 - )); 82 + ))); 83 83 84 84 // JSON: should be {"$link": "cid_string"} 85 85 let json = serde_json::to_string(&data).unwrap(); ··· 470 470 #[serde(borrow)] 471 471 did: Did<CowStr<'a>>, 472 472 handle: Handle<CowStr<'a>>, 473 - cid: Cid<'a>, 473 + cid: Cid<CowStr<'a>>, 474 474 } 475 475 476 476 let mut map = BTreeMap::new(); ··· 486 486 ); 487 487 map.insert( 488 488 SmolStr::new_static("cid"), 489 - Data::String(AtprotoStr::Cid(Cid::str( 489 + Data::String(AtprotoStr::Cid(Cid::cow_str(CowStr::Borrowed( 490 490 "bafyreih4g7bvo6hdq2juolev5bfzpbo4ewkxh5mzxwgvkjp3kitc6hqkha", 491 - ))), 491 + )))), 492 492 ); 493 493 let data = Data::Object(Object(map)); 494 494
+3 -3
crates/jacquard-common/src/xrpc/atproto.rs
··· 87 87 pub struct ListRecordsRecord<'a> { 88 88 #[serde(skip_serializing_if = "Option::is_none")] 89 89 #[serde(borrow)] 90 - pub cid: Option<Cid<'a>>, 90 + pub cid: Option<Cid<CowStr<'a>>>, 91 91 #[serde(borrow)] 92 92 pub uri: AtUri<'a>, 93 93 #[serde(borrow)] ··· 133 133 pub struct GetRecord<'a> { 134 134 #[serde(skip_serializing_if = "Option::is_none")] 135 135 #[serde(borrow)] 136 - pub cid: Option<Cid<'a>>, 136 + pub cid: Option<Cid<CowStr<'a>>>, 137 137 #[serde(borrow)] 138 138 pub collection: Nsid<CowStr<'a>>, 139 139 #[serde(borrow)] ··· 162 162 pub struct GetRecordOutput<'a> { 163 163 #[serde(skip_serializing_if = "Option::is_none")] 164 164 #[serde(borrow)] 165 - pub cid: Option<Cid<'a>>, 165 + pub cid: Option<Cid<CowStr<'a>>>, 166 166 #[serde(borrow)] 167 167 pub uri: AtUri<'a>, 168 168 #[serde(borrow)]