A better Rust ATProto crate

minimal auth client, plus some fixes for codegen for bytes upload and encoding

Orual aa87f30d 32f38c8a

Changed files
+1403 -228
crates
jacquard
jacquard-api
jacquard-common
src
types
jacquard-lexicon
+2 -3
crates/jacquard-api/src/app_bsky/feed/get_suggested_feeds.rs
··· 50 50 51 51 impl jacquard_common::types::xrpc::XrpcRequest for GetSuggestedFeeds<'_> { 52 52 const NSID: &'static str = "app.bsky.feed.getSuggestedFeeds"; 53 - const METHOD: jacquard_common::types::xrpc::XrpcMethod = 54 - jacquard_common::types::xrpc::XrpcMethod::Query; 53 + const METHOD: jacquard_common::types::xrpc::XrpcMethod = jacquard_common::types::xrpc::XrpcMethod::Query; 55 54 const OUTPUT_ENCODING: &'static str = "application/json"; 56 55 type Output<'de> = GetSuggestedFeedsOutput<'de>; 57 56 type Err<'de> = jacquard_common::types::xrpc::GenericError<'de>; 58 - } 57 + }
+2 -3
crates/jacquard-api/src/app_bsky/unspecced/get_onboarding_suggested_starter_packs.rs
··· 40 40 41 41 impl jacquard_common::types::xrpc::XrpcRequest for GetOnboardingSuggestedStarterPacks { 42 42 const NSID: &'static str = "app.bsky.unspecced.getOnboardingSuggestedStarterPacks"; 43 - const METHOD: jacquard_common::types::xrpc::XrpcMethod = 44 - jacquard_common::types::xrpc::XrpcMethod::Query; 43 + const METHOD: jacquard_common::types::xrpc::XrpcMethod = jacquard_common::types::xrpc::XrpcMethod::Query; 45 44 const OUTPUT_ENCODING: &'static str = "application/json"; 46 45 type Output<'de> = GetOnboardingSuggestedStarterPacksOutput<'de>; 47 46 type Err<'de> = jacquard_common::types::xrpc::GenericError<'de>; 48 - } 47 + }
+11 -8
crates/jacquard-api/src/app_bsky/video/upload_video.rs
··· 5 5 // This file was automatically generated from Lexicon schemas. 6 6 // Any manual changes will be overwritten on the next regeneration. 7 7 8 - #[jacquard_derive::lexicon] 9 8 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 10 9 #[serde(rename_all = "camelCase")] 11 - pub struct UploadVideo<'a> {} 12 - impl jacquard_common::IntoStatic for UploadVideo<'_> { 13 - type Output = UploadVideo<'static>; 10 + pub struct UploadVideo { 11 + pub body: bytes::Bytes, 12 + } 13 + 14 + impl jacquard_common::IntoStatic for UploadVideo { 15 + type Output = UploadVideo; 14 16 fn into_static(self) -> Self::Output { 15 - UploadVideo { 16 - extra_data: self.extra_data.into_static(), 17 - } 17 + self 18 18 } 19 19 } 20 20 ··· 36 36 } 37 37 } 38 38 39 - impl jacquard_common::types::xrpc::XrpcRequest for UploadVideo<'_> { 39 + impl jacquard_common::types::xrpc::XrpcRequest for UploadVideo { 40 40 const NSID: &'static str = "app.bsky.video.uploadVideo"; 41 41 const METHOD: jacquard_common::types::xrpc::XrpcMethod = jacquard_common::types::xrpc::XrpcMethod::Procedure( 42 42 "video/mp4", ··· 44 44 const OUTPUT_ENCODING: &'static str = "application/json"; 45 45 type Output<'de> = UploadVideoOutput<'de>; 46 46 type Err<'de> = jacquard_common::types::xrpc::GenericError<'de>; 47 + fn encode_body(&self) -> Result<Vec<u8>, jacquard_common::types::xrpc::EncodeError> { 48 + Ok(self.body.to_vec()) 49 + } 47 50 }
+11 -8
crates/jacquard-api/src/com_atproto/repo/import_repo.rs
··· 5 5 // This file was automatically generated from Lexicon schemas. 6 6 // Any manual changes will be overwritten on the next regeneration. 7 7 8 - #[jacquard_derive::lexicon] 9 8 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 10 9 #[serde(rename_all = "camelCase")] 11 - pub struct ImportRepo<'a> {} 12 - impl jacquard_common::IntoStatic for ImportRepo<'_> { 13 - type Output = ImportRepo<'static>; 10 + pub struct ImportRepo { 11 + pub body: bytes::Bytes, 12 + } 13 + 14 + impl jacquard_common::IntoStatic for ImportRepo { 15 + type Output = ImportRepo; 14 16 fn into_static(self) -> Self::Output { 15 - ImportRepo { 16 - extra_data: self.extra_data.into_static(), 17 - } 17 + self 18 18 } 19 19 } 20 20 21 - impl jacquard_common::types::xrpc::XrpcRequest for ImportRepo<'_> { 21 + impl jacquard_common::types::xrpc::XrpcRequest for ImportRepo { 22 22 const NSID: &'static str = "com.atproto.repo.importRepo"; 23 23 const METHOD: jacquard_common::types::xrpc::XrpcMethod = jacquard_common::types::xrpc::XrpcMethod::Procedure( 24 24 "application/vnd.ipld.car", ··· 26 26 const OUTPUT_ENCODING: &'static str = "application/json"; 27 27 type Output<'de> = (); 28 28 type Err<'de> = jacquard_common::types::xrpc::GenericError<'de>; 29 + fn encode_body(&self) -> Result<Vec<u8>, jacquard_common::types::xrpc::EncodeError> { 30 + Ok(self.body.to_vec()) 31 + } 29 32 }
+11 -8
crates/jacquard-api/src/com_atproto/repo/upload_blob.rs
··· 5 5 // This file was automatically generated from Lexicon schemas. 6 6 // Any manual changes will be overwritten on the next regeneration. 7 7 8 - #[jacquard_derive::lexicon] 9 8 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 10 9 #[serde(rename_all = "camelCase")] 11 - pub struct UploadBlob<'a> {} 12 - impl jacquard_common::IntoStatic for UploadBlob<'_> { 13 - type Output = UploadBlob<'static>; 10 + pub struct UploadBlob { 11 + pub body: bytes::Bytes, 12 + } 13 + 14 + impl jacquard_common::IntoStatic for UploadBlob { 15 + type Output = UploadBlob; 14 16 fn into_static(self) -> Self::Output { 15 - UploadBlob { 16 - extra_data: self.extra_data.into_static(), 17 - } 17 + self 18 18 } 19 19 } 20 20 ··· 36 36 } 37 37 } 38 38 39 - impl jacquard_common::types::xrpc::XrpcRequest for UploadBlob<'_> { 39 + impl jacquard_common::types::xrpc::XrpcRequest for UploadBlob { 40 40 const NSID: &'static str = "com.atproto.repo.uploadBlob"; 41 41 const METHOD: jacquard_common::types::xrpc::XrpcMethod = jacquard_common::types::xrpc::XrpcMethod::Procedure( 42 42 "*/*", ··· 44 44 const OUTPUT_ENCODING: &'static str = "application/json"; 45 45 type Output<'de> = UploadBlobOutput<'de>; 46 46 type Err<'de> = jacquard_common::types::xrpc::GenericError<'de>; 47 + fn encode_body(&self) -> Result<Vec<u8>, jacquard_common::types::xrpc::EncodeError> { 48 + Ok(self.body.to_vec()) 49 + } 47 50 }
+29 -1
crates/jacquard-common/src/types/xrpc.rs
··· 1 - use serde::de::DeserializeOwned; 2 1 use serde::{Deserialize, Serialize}; 3 2 use std::error::Error; 4 3 use std::fmt::{self, Debug}; 5 4 6 5 use crate::IntoStatic; 7 6 use crate::types::value::Data; 7 + 8 + /// Error type for encoding XRPC requests 9 + #[derive(Debug, thiserror::Error, miette::Diagnostic)] 10 + pub enum EncodeError { 11 + /// Failed to serialize query parameters 12 + #[error("Failed to serialize query: {0}")] 13 + Query( 14 + #[from] 15 + #[source] 16 + serde_html_form::ser::Error, 17 + ), 18 + /// Failed to serialize JSON body 19 + #[error("Failed to serialize JSON: {0}")] 20 + Json( 21 + #[from] 22 + #[source] 23 + serde_json::Error, 24 + ), 25 + /// Other encoding error 26 + #[error("Encoding error: {0}")] 27 + Other(String), 28 + } 8 29 9 30 /// XRPC method type 10 31 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] ··· 53 74 54 75 /// Error type for this request 55 76 type Err<'de>: Error + Deserialize<'de> + IntoStatic; 77 + 78 + /// Encode the request body for procedures. 79 + /// 80 + /// Default implementation serializes to JSON. Override for non-JSON encodings. 81 + fn encode_body(&self) -> Result<Vec<u8>, EncodeError> { 82 + Ok(serde_json::to_vec(self)?) 83 + } 56 84 } 57 85 58 86 /// Error type for XRPC endpoints that don't define any errors
+79 -26
crates/jacquard-lexicon/src/codegen.rs
··· 662 662 params_has_lifetime, 663 663 has_output, 664 664 has_errors, 665 + false, // queries never have binary inputs 665 666 )?; 666 667 output.push(xrpc_impl); 667 668 ··· 680 681 let type_base = self.def_to_type_name(nsid, def_name); 681 682 let mut output = Vec::new(); 682 683 683 - // Input bodies always have lifetimes (they get #[lexicon] attribute) 684 - let params_has_lifetime = proc.input.is_some(); 684 + // Check if input is a binary body (no schema) 685 + let is_binary_input = proc 686 + .input 687 + .as_ref() 688 + .map(|i| i.schema.is_none()) 689 + .unwrap_or(false); 690 + 691 + // Input bodies with schemas have lifetimes (they get #[lexicon] attribute) 692 + // Binary inputs don't have lifetimes 693 + let params_has_lifetime = proc.input.is_some() && !is_binary_input; 685 694 let has_input = proc.input.is_some(); 686 695 let has_output = proc.output.is_some(); 687 696 let has_errors = proc.errors.is_some(); ··· 726 735 params_has_lifetime, 727 736 has_output, 728 737 has_errors, 738 + is_binary_input, 729 739 )?; 730 740 output.push(xrpc_impl); 731 741 ··· 1424 1434 ) -> Result<TokenStream> { 1425 1435 let ident = syn::Ident::new(type_base, proc_macro2::Span::call_site()); 1426 1436 1437 + // Check if this is a binary body (no schema, just raw bytes) 1438 + let is_binary_body = body.schema.is_none(); 1439 + 1427 1440 let fields = if let Some(schema) = &body.schema { 1428 1441 self.generate_body_fields("", type_base, schema)? 1429 1442 } else { 1430 - quote! {} 1443 + // Binary body: just a bytes field 1444 + quote! { 1445 + pub body: bytes::Bytes, 1446 + } 1431 1447 }; 1432 1448 1433 1449 let doc = self.generate_doc_comment(body.description.as_ref()); 1434 1450 1435 - // Input structs always get a lifetime since they have the #[lexicon] attribute 1436 - // which adds extra_data: BTreeMap<..., Data<'a>> 1437 - let struct_def = quote! { 1438 - #doc 1439 - #[jacquard_derive::lexicon] 1440 - #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 1441 - #[serde(rename_all = "camelCase")] 1442 - pub struct #ident<'a> { 1443 - #fields 1451 + // Binary bodies don't need #[lexicon] attribute or lifetime 1452 + let struct_def = if is_binary_body { 1453 + quote! { 1454 + #doc 1455 + #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 1456 + #[serde(rename_all = "camelCase")] 1457 + pub struct #ident { 1458 + #fields 1459 + } 1460 + } 1461 + } else { 1462 + // Input structs with schemas get a lifetime since they have the #[lexicon] attribute 1463 + // which adds extra_data: BTreeMap<..., Data<'a>> 1464 + quote! { 1465 + #doc 1466 + #[jacquard_derive::lexicon] 1467 + #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 1468 + #[serde(rename_all = "camelCase")] 1469 + pub struct #ident<'a> { 1470 + #fields 1471 + } 1444 1472 } 1445 1473 }; 1446 1474 ··· 1458 1486 } 1459 1487 1460 1488 // Generate IntoStatic impl 1461 - let field_names: Vec<&str> = match &body.schema { 1462 - Some(crate::lexicon::LexXrpcBodySchema::Object(obj)) => { 1463 - obj.properties.keys().map(|k| k.as_str()).collect() 1464 - } 1465 - Some(_) => { 1466 - // For Ref or Union schemas, there's just a single flattened field 1467 - vec!["value"] 1468 - } 1469 - None => { 1470 - // No schema means no fields, just extra_data 1471 - vec![] 1489 + let into_static_impl = if is_binary_body { 1490 + // Binary bodies: simple clone of the Bytes field 1491 + quote! { 1492 + impl jacquard_common::IntoStatic for #ident { 1493 + type Output = #ident; 1494 + fn into_static(self) -> Self::Output { 1495 + self 1496 + } 1497 + } 1472 1498 } 1499 + } else { 1500 + let field_names: Vec<&str> = match &body.schema { 1501 + Some(crate::lexicon::LexXrpcBodySchema::Object(obj)) => { 1502 + obj.properties.keys().map(|k| k.as_str()).collect() 1503 + } 1504 + Some(_) => { 1505 + // For Ref or Union schemas, there's just a single flattened field 1506 + vec!["value"] 1507 + } 1508 + None => { 1509 + // No schema means no fields, just extra_data 1510 + vec![] 1511 + } 1512 + }; 1513 + self.generate_into_static_for_struct(type_base, &field_names, true, true) 1473 1514 }; 1474 - let into_static_impl = 1475 - self.generate_into_static_for_struct(type_base, &field_names, true, true); 1476 1515 1477 1516 Ok(quote! { 1478 1517 #struct_def ··· 1923 1962 params_has_lifetime: bool, 1924 1963 has_output: bool, 1925 1964 has_errors: bool, 1965 + is_binary_input: bool, 1926 1966 ) -> Result<TokenStream> { 1927 1967 let output_type = if has_output { 1928 1968 let output_ident = syn::Ident::new( ··· 1944 1984 quote! { jacquard_common::types::xrpc::GenericError<'de> } 1945 1985 }; 1946 1986 1987 + // Generate encode_body() method for binary inputs 1988 + let encode_body_method = if is_binary_input { 1989 + quote! { 1990 + fn encode_body(&self) -> Result<Vec<u8>, jacquard_common::types::xrpc::EncodeError> { 1991 + Ok(self.body.to_vec()) 1992 + } 1993 + } 1994 + } else { 1995 + quote! {} 1996 + }; 1997 + 1947 1998 if has_params { 1948 1999 // Implement on the params/input struct itself 1949 2000 let request_ident = syn::Ident::new(type_base, proc_macro2::Span::call_site()); ··· 1961 2012 1962 2013 type Output<'de> = #output_type; 1963 2014 type Err<'de> = #error_type; 2015 + 2016 + #encode_body_method 1964 2017 } 1965 2018 }) 1966 2019 } else { ··· 2311 2364 println!("\n{}\n", formatted); 2312 2365 2313 2366 // Check structure 2314 - assert!(formatted.contains("struct GetAuthorFeedParams")); 2367 + assert!(formatted.contains("struct GetAuthorFeed")); 2315 2368 assert!(formatted.contains("struct GetAuthorFeedOutput")); 2316 2369 assert!(formatted.contains("enum GetAuthorFeedError")); 2317 2370 assert!(formatted.contains("pub actor"));
+6 -1
crates/jacquard-lexicon/target/test_codegen_output/app_bsky.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // This file was automatically generated from Lexicon schemas. 4 + // Any manual changes will be overwritten on the next regeneration. 5 + 1 6 pub mod embed; 2 7 pub mod feed; 3 - pub mod richtext; 8 + pub mod richtext;
+6 -1
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // This file was automatically generated from Lexicon schemas. 4 + // Any manual changes will be overwritten on the next regeneration. 5 + 1 6 pub mod external; 2 7 pub mod images; 3 8 pub mod record; 4 9 pub mod record_with_media; 5 - pub mod video; 10 + pub mod video;
+77 -7
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/external.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: app.bsky.embed.external 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[jacquard_derive::lexicon] 1 9 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 2 10 #[serde(rename_all = "camelCase")] 3 11 pub struct External<'a> { 12 + #[serde(borrow)] 4 13 pub description: jacquard_common::CowStr<'a>, 5 - #[serde(skip_serializing_if = "Option::is_none")] 6 - pub thumb: Option<jacquard_common::types::blob::Blob<'a>>, 14 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 15 + #[serde(borrow)] 16 + pub thumb: std::option::Option<jacquard_common::types::blob::Blob<'a>>, 17 + #[serde(borrow)] 7 18 pub title: jacquard_common::CowStr<'a>, 19 + #[serde(borrow)] 8 20 pub uri: jacquard_common::types::string::Uri<'a>, 9 21 } 22 + 23 + impl jacquard_common::IntoStatic for External<'_> { 24 + type Output = External<'static>; 25 + fn into_static(self) -> Self::Output { 26 + External { 27 + description: self.description.into_static(), 28 + thumb: self.thumb.into_static(), 29 + title: self.title.into_static(), 30 + uri: self.uri.into_static(), 31 + extra_data: self.extra_data.into_static(), 32 + } 33 + } 34 + } 35 + 10 36 ///A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post). 37 + #[jacquard_derive::lexicon] 11 38 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 12 39 #[serde(rename_all = "camelCase")] 13 - pub struct External<'a> { 14 - pub external: jacquard_common::types::value::Data<'a>, 40 + pub struct ExternalRecord<'a> { 41 + #[serde(borrow)] 42 + pub external: test_generated::app_bsky::embed::external::External<'a>, 15 43 } 44 + 45 + impl jacquard_common::IntoStatic for ExternalRecord<'_> { 46 + type Output = ExternalRecord<'static>; 47 + fn into_static(self) -> Self::Output { 48 + ExternalRecord { 49 + external: self.external.into_static(), 50 + extra_data: self.extra_data.into_static(), 51 + } 52 + } 53 + } 54 + 55 + #[jacquard_derive::lexicon] 16 56 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 17 57 #[serde(rename_all = "camelCase")] 18 58 pub struct View<'a> { 19 - pub external: jacquard_common::types::value::Data<'a>, 59 + #[serde(borrow)] 60 + pub external: test_generated::app_bsky::embed::external::ViewExternal<'a>, 61 + } 62 + 63 + impl jacquard_common::IntoStatic for View<'_> { 64 + type Output = View<'static>; 65 + fn into_static(self) -> Self::Output { 66 + View { 67 + external: self.external.into_static(), 68 + extra_data: self.extra_data.into_static(), 69 + } 70 + } 20 71 } 72 + 73 + #[jacquard_derive::lexicon] 21 74 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 22 75 #[serde(rename_all = "camelCase")] 23 76 pub struct ViewExternal<'a> { 77 + #[serde(borrow)] 24 78 pub description: jacquard_common::CowStr<'a>, 25 - #[serde(skip_serializing_if = "Option::is_none")] 26 - pub thumb: Option<jacquard_common::types::string::Uri<'a>>, 79 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 80 + #[serde(borrow)] 81 + pub thumb: std::option::Option<jacquard_common::types::string::Uri<'a>>, 82 + #[serde(borrow)] 27 83 pub title: jacquard_common::CowStr<'a>, 84 + #[serde(borrow)] 28 85 pub uri: jacquard_common::types::string::Uri<'a>, 29 86 } 87 + 88 + impl jacquard_common::IntoStatic for ViewExternal<'_> { 89 + type Output = ViewExternal<'static>; 90 + fn into_static(self) -> Self::Output { 91 + ViewExternal { 92 + description: self.description.into_static(), 93 + thumb: self.thumb.into_static(), 94 + title: self.title.into_static(), 95 + uri: self.uri.into_static(), 96 + extra_data: self.extra_data.into_static(), 97 + } 98 + } 99 + }
+78 -6
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/images.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: app.bsky.embed.images 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[jacquard_derive::lexicon] 1 9 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 2 10 #[serde(rename_all = "camelCase")] 3 11 pub struct Image<'a> { 12 + ///Alt text description of the image, for accessibility. 13 + #[serde(borrow)] 4 14 pub alt: jacquard_common::CowStr<'a>, 5 - #[serde(skip_serializing_if = "Option::is_none")] 6 - pub aspect_ratio: Option<jacquard_common::types::value::Data<'a>>, 15 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 16 + #[serde(borrow)] 17 + pub aspect_ratio: std::option::Option<jacquard_common::types::value::Data<'a>>, 18 + #[serde(borrow)] 7 19 pub image: jacquard_common::types::blob::Blob<'a>, 8 20 } 21 + 22 + impl jacquard_common::IntoStatic for Image<'_> { 23 + type Output = Image<'static>; 24 + fn into_static(self) -> Self::Output { 25 + Image { 26 + alt: self.alt.into_static(), 27 + aspect_ratio: self.aspect_ratio.into_static(), 28 + image: self.image.into_static(), 29 + extra_data: self.extra_data.into_static(), 30 + } 31 + } 32 + } 33 + 34 + #[jacquard_derive::lexicon] 9 35 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 10 36 #[serde(rename_all = "camelCase")] 11 37 pub struct Images<'a> { 12 - pub images: Vec<jacquard_common::types::value::Data<'a>>, 38 + #[serde(borrow)] 39 + pub images: Vec<test_generated::app_bsky::embed::images::Image<'a>>, 13 40 } 41 + 42 + impl jacquard_common::IntoStatic for Images<'_> { 43 + type Output = Images<'static>; 44 + fn into_static(self) -> Self::Output { 45 + Images { 46 + images: self.images.into_static(), 47 + extra_data: self.extra_data.into_static(), 48 + } 49 + } 50 + } 51 + 52 + #[jacquard_derive::lexicon] 14 53 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 15 54 #[serde(rename_all = "camelCase")] 16 55 pub struct View<'a> { 17 - pub images: Vec<jacquard_common::types::value::Data<'a>>, 56 + #[serde(borrow)] 57 + pub images: Vec<test_generated::app_bsky::embed::images::ViewImage<'a>>, 58 + } 59 + 60 + impl jacquard_common::IntoStatic for View<'_> { 61 + type Output = View<'static>; 62 + fn into_static(self) -> Self::Output { 63 + View { 64 + images: self.images.into_static(), 65 + extra_data: self.extra_data.into_static(), 66 + } 67 + } 18 68 } 69 + 70 + #[jacquard_derive::lexicon] 19 71 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 20 72 #[serde(rename_all = "camelCase")] 21 73 pub struct ViewImage<'a> { 74 + ///Alt text description of the image, for accessibility. 75 + #[serde(borrow)] 22 76 pub alt: jacquard_common::CowStr<'a>, 23 - #[serde(skip_serializing_if = "Option::is_none")] 24 - pub aspect_ratio: Option<jacquard_common::types::value::Data<'a>>, 77 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 78 + #[serde(borrow)] 79 + pub aspect_ratio: std::option::Option<jacquard_common::types::value::Data<'a>>, 80 + ///Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View. 81 + #[serde(borrow)] 25 82 pub fullsize: jacquard_common::types::string::Uri<'a>, 83 + ///Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View. 84 + #[serde(borrow)] 26 85 pub thumb: jacquard_common::types::string::Uri<'a>, 27 86 } 87 + 88 + impl jacquard_common::IntoStatic for ViewImage<'_> { 89 + type Output = ViewImage<'static>; 90 + fn into_static(self) -> Self::Output { 91 + ViewImage { 92 + alt: self.alt.into_static(), 93 + aspect_ratio: self.aspect_ratio.into_static(), 94 + fullsize: self.fullsize.into_static(), 95 + thumb: self.thumb.into_static(), 96 + extra_data: self.extra_data.into_static(), 97 + } 98 + } 99 + }
+133 -14
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/record.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: app.bsky.embed.record 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[jacquard_derive::lexicon] 1 9 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 2 10 #[serde(rename_all = "camelCase")] 3 11 pub struct Record<'a> { 4 - pub record: test_generated::com_atproto::repo::StrongRef<'a>, 12 + #[serde(borrow)] 13 + pub record: test_generated::com_atproto::repo::strong_ref::StrongRef<'a>, 14 + } 15 + 16 + impl jacquard_common::IntoStatic for Record<'_> { 17 + type Output = Record<'static>; 18 + fn into_static(self) -> Self::Output { 19 + Record { 20 + record: self.record.into_static(), 21 + extra_data: self.extra_data.into_static(), 22 + } 23 + } 5 24 } 25 + 26 + #[jacquard_derive::lexicon] 6 27 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 7 28 #[serde(rename_all = "camelCase")] 8 29 pub struct View<'a> { 9 - pub record: RecordRecord<'a>, 30 + #[serde(borrow)] 31 + pub record: ViewRecordRecord<'a>, 10 32 } 33 + 34 + #[jacquard_derive::open_union] 35 + #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 36 + #[serde(tag = "$type")] 37 + #[serde(bound(deserialize = "'de: 'a"))] 38 + pub enum ViewRecordRecord<'a> {} 39 + impl jacquard_common::IntoStatic for ViewRecordRecord<'_> { 40 + type Output = ViewRecordRecord<'static>; 41 + fn into_static(self) -> Self::Output { 42 + match self { 43 + ViewRecordRecord::Unknown(v) => ViewRecordRecord::Unknown(v.into_static()), 44 + } 45 + } 46 + } 47 + 48 + impl jacquard_common::IntoStatic for View<'_> { 49 + type Output = View<'static>; 50 + fn into_static(self) -> Self::Output { 51 + View { 52 + record: self.record.into_static(), 53 + extra_data: self.extra_data.into_static(), 54 + } 55 + } 56 + } 57 + 58 + #[jacquard_derive::lexicon] 11 59 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 12 60 #[serde(rename_all = "camelCase")] 13 61 pub struct ViewBlocked<'a> { 62 + #[serde(borrow)] 14 63 pub author: jacquard_common::types::value::Data<'a>, 15 64 pub blocked: bool, 65 + #[serde(borrow)] 16 66 pub uri: jacquard_common::types::string::AtUri<'a>, 17 67 } 68 + 69 + impl jacquard_common::IntoStatic for ViewBlocked<'_> { 70 + type Output = ViewBlocked<'static>; 71 + fn into_static(self) -> Self::Output { 72 + ViewBlocked { 73 + author: self.author.into_static(), 74 + blocked: self.blocked.into_static(), 75 + uri: self.uri.into_static(), 76 + extra_data: self.extra_data.into_static(), 77 + } 78 + } 79 + } 80 + 81 + #[jacquard_derive::lexicon] 18 82 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 19 83 #[serde(rename_all = "camelCase")] 20 84 pub struct ViewDetached<'a> { 21 85 pub detached: bool, 86 + #[serde(borrow)] 22 87 pub uri: jacquard_common::types::string::AtUri<'a>, 23 88 } 89 + 90 + impl jacquard_common::IntoStatic for ViewDetached<'_> { 91 + type Output = ViewDetached<'static>; 92 + fn into_static(self) -> Self::Output { 93 + ViewDetached { 94 + detached: self.detached.into_static(), 95 + uri: self.uri.into_static(), 96 + extra_data: self.extra_data.into_static(), 97 + } 98 + } 99 + } 100 + 101 + #[jacquard_derive::lexicon] 24 102 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 25 103 #[serde(rename_all = "camelCase")] 26 104 pub struct ViewNotFound<'a> { 27 105 pub not_found: bool, 106 + #[serde(borrow)] 28 107 pub uri: jacquard_common::types::string::AtUri<'a>, 29 108 } 109 + 110 + impl jacquard_common::IntoStatic for ViewNotFound<'_> { 111 + type Output = ViewNotFound<'static>; 112 + fn into_static(self) -> Self::Output { 113 + ViewNotFound { 114 + not_found: self.not_found.into_static(), 115 + uri: self.uri.into_static(), 116 + extra_data: self.extra_data.into_static(), 117 + } 118 + } 119 + } 120 + 121 + #[jacquard_derive::lexicon] 30 122 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 31 123 #[serde(rename_all = "camelCase")] 32 124 pub struct ViewRecord<'a> { 125 + #[serde(borrow)] 33 126 pub author: jacquard_common::types::value::Data<'a>, 127 + #[serde(borrow)] 34 128 pub cid: jacquard_common::types::string::Cid<'a>, 35 - #[serde(skip_serializing_if = "Option::is_none")] 36 - pub embeds: Option<Vec<jacquard_common::types::value::Data<'a>>>, 129 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 130 + #[serde(borrow)] 131 + pub embeds: std::option::Option<Vec<jacquard_common::types::value::Data<'a>>>, 37 132 pub indexed_at: jacquard_common::types::string::Datetime, 38 - #[serde(skip_serializing_if = "Option::is_none")] 39 - pub labels: Option<Vec<test_generated::com_atproto::label::Label<'a>>>, 40 - #[serde(skip_serializing_if = "Option::is_none")] 41 - pub like_count: Option<i64>, 42 - #[serde(skip_serializing_if = "Option::is_none")] 43 - pub quote_count: Option<i64>, 44 - #[serde(skip_serializing_if = "Option::is_none")] 45 - pub reply_count: Option<i64>, 46 - #[serde(skip_serializing_if = "Option::is_none")] 47 - pub repost_count: Option<i64>, 133 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 134 + #[serde(borrow)] 135 + pub labels: std::option::Option<Vec<test_generated::com_atproto::label::Label<'a>>>, 136 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 137 + pub like_count: std::option::Option<i64>, 138 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 139 + pub quote_count: std::option::Option<i64>, 140 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 141 + pub reply_count: std::option::Option<i64>, 142 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 143 + pub repost_count: std::option::Option<i64>, 144 + #[serde(borrow)] 48 145 pub uri: jacquard_common::types::string::AtUri<'a>, 146 + ///The record data itself. 147 + #[serde(borrow)] 49 148 pub value: jacquard_common::types::value::Data<'a>, 50 149 } 150 + 151 + impl jacquard_common::IntoStatic for ViewRecord<'_> { 152 + type Output = ViewRecord<'static>; 153 + fn into_static(self) -> Self::Output { 154 + ViewRecord { 155 + author: self.author.into_static(), 156 + cid: self.cid.into_static(), 157 + embeds: self.embeds.into_static(), 158 + indexed_at: self.indexed_at.into_static(), 159 + labels: self.labels.into_static(), 160 + like_count: self.like_count.into_static(), 161 + quote_count: self.quote_count.into_static(), 162 + reply_count: self.reply_count.into_static(), 163 + repost_count: self.repost_count.into_static(), 164 + uri: self.uri.into_static(), 165 + value: self.value.into_static(), 166 + extra_data: self.extra_data.into_static(), 167 + } 168 + } 169 + }
+102 -4
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/record_with_media.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: app.bsky.embed.recordWithMedia 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[jacquard_derive::lexicon] 1 9 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 2 10 #[serde(rename_all = "camelCase")] 3 11 pub struct RecordWithMedia<'a> { 4 - pub media: RecordMedia<'a>, 5 - pub record: test_generated::app_bsky::embed::Record<'a>, 12 + #[serde(borrow)] 13 + pub media: RecordWithMediaRecordMedia<'a>, 14 + #[serde(borrow)] 15 + pub record: test_generated::app_bsky::embed::record::Record<'a>, 6 16 } 17 + 18 + #[jacquard_derive::open_union] 19 + #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 20 + #[serde(tag = "$type")] 21 + #[serde(bound(deserialize = "'de: 'a"))] 22 + pub enum RecordWithMediaRecordMedia<'a> { 23 + #[serde(rename = "app.bsky.embed.images")] 24 + Images(Box<test_generated::app_bsky::embed::images::Images<'a>>), 25 + #[serde(rename = "app.bsky.embed.video")] 26 + Video(Box<test_generated::app_bsky::embed::video::Video<'a>>), 27 + #[serde(rename = "app.bsky.embed.external")] 28 + External(Box<test_generated::app_bsky::embed::external::ExternalRecord<'a>>), 29 + } 30 + 31 + impl jacquard_common::IntoStatic for RecordWithMediaRecordMedia<'_> { 32 + type Output = RecordWithMediaRecordMedia<'static>; 33 + fn into_static(self) -> Self::Output { 34 + match self { 35 + RecordWithMediaRecordMedia::Images(v) => { 36 + RecordWithMediaRecordMedia::Images(v.into_static()) 37 + } 38 + RecordWithMediaRecordMedia::Video(v) => { 39 + RecordWithMediaRecordMedia::Video(v.into_static()) 40 + } 41 + RecordWithMediaRecordMedia::External(v) => { 42 + RecordWithMediaRecordMedia::External(v.into_static()) 43 + } 44 + RecordWithMediaRecordMedia::Unknown(v) => { 45 + RecordWithMediaRecordMedia::Unknown(v.into_static()) 46 + } 47 + } 48 + } 49 + } 50 + 51 + impl jacquard_common::IntoStatic for RecordWithMedia<'_> { 52 + type Output = RecordWithMedia<'static>; 53 + fn into_static(self) -> Self::Output { 54 + RecordWithMedia { 55 + media: self.media.into_static(), 56 + record: self.record.into_static(), 57 + extra_data: self.extra_data.into_static(), 58 + } 59 + } 60 + } 61 + 62 + #[jacquard_derive::lexicon] 7 63 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 8 64 #[serde(rename_all = "camelCase")] 9 65 pub struct View<'a> { 10 - pub media: RecordMedia<'a>, 11 - pub record: test_generated::app_bsky::embed::View<'a>, 66 + #[serde(borrow)] 67 + pub media: ViewRecordMedia<'a>, 68 + #[serde(borrow)] 69 + pub record: test_generated::app_bsky::embed::record::View<'a>, 70 + } 71 + 72 + #[jacquard_derive::open_union] 73 + #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 74 + #[serde(tag = "$type")] 75 + #[serde(bound(deserialize = "'de: 'a"))] 76 + pub enum ViewRecordMedia<'a> { 77 + #[serde(rename = "app.bsky.embed.images#view")] 78 + ImagesView(Box<test_generated::app_bsky::embed::images::View<'a>>), 79 + #[serde(rename = "app.bsky.embed.video#view")] 80 + VideoView(Box<test_generated::app_bsky::embed::video::View<'a>>), 81 + #[serde(rename = "app.bsky.embed.external#view")] 82 + ExternalView(Box<test_generated::app_bsky::embed::external::View<'a>>), 83 + } 84 + 85 + impl jacquard_common::IntoStatic for ViewRecordMedia<'_> { 86 + type Output = ViewRecordMedia<'static>; 87 + fn into_static(self) -> Self::Output { 88 + match self { 89 + ViewRecordMedia::ImagesView(v) => { 90 + ViewRecordMedia::ImagesView(v.into_static()) 91 + } 92 + ViewRecordMedia::VideoView(v) => ViewRecordMedia::VideoView(v.into_static()), 93 + ViewRecordMedia::ExternalView(v) => { 94 + ViewRecordMedia::ExternalView(v.into_static()) 95 + } 96 + ViewRecordMedia::Unknown(v) => ViewRecordMedia::Unknown(v.into_static()), 97 + } 98 + } 12 99 } 100 + 101 + impl jacquard_common::IntoStatic for View<'_> { 102 + type Output = View<'static>; 103 + fn into_static(self) -> Self::Output { 104 + View { 105 + media: self.media.into_static(), 106 + record: self.record.into_static(), 107 + extra_data: self.extra_data.into_static(), 108 + } 109 + } 110 + }
+76 -12
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/video.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: app.bsky.embed.video 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[jacquard_derive::lexicon] 1 9 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 2 10 #[serde(rename_all = "camelCase")] 3 11 pub struct Caption<'a> { 12 + #[serde(borrow)] 4 13 pub file: jacquard_common::types::blob::Blob<'a>, 5 14 pub lang: jacquard_common::types::string::Language, 6 15 } 16 + 17 + impl jacquard_common::IntoStatic for Caption<'_> { 18 + type Output = Caption<'static>; 19 + fn into_static(self) -> Self::Output { 20 + Caption { 21 + file: self.file.into_static(), 22 + lang: self.lang.into_static(), 23 + extra_data: self.extra_data.into_static(), 24 + } 25 + } 26 + } 27 + 28 + #[jacquard_derive::lexicon] 7 29 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 8 30 #[serde(rename_all = "camelCase")] 9 31 pub struct Video<'a> { 10 - #[serde(skip_serializing_if = "Option::is_none")] 11 - pub alt: Option<jacquard_common::CowStr<'a>>, 12 - #[serde(skip_serializing_if = "Option::is_none")] 13 - pub aspect_ratio: Option<jacquard_common::types::value::Data<'a>>, 14 - #[serde(skip_serializing_if = "Option::is_none")] 15 - pub captions: Option<Vec<jacquard_common::types::value::Data<'a>>>, 32 + ///Alt text description of the video, for accessibility. 33 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 34 + #[serde(borrow)] 35 + pub alt: std::option::Option<jacquard_common::CowStr<'a>>, 36 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 37 + #[serde(borrow)] 38 + pub aspect_ratio: std::option::Option<jacquard_common::types::value::Data<'a>>, 39 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 40 + #[serde(borrow)] 41 + pub captions: std::option::Option< 42 + Vec<test_generated::app_bsky::embed::video::Caption<'a>>, 43 + >, 44 + ///The mp4 video file. May be up to 100mb, formerly limited to 50mb. 45 + #[serde(borrow)] 16 46 pub video: jacquard_common::types::blob::Blob<'a>, 17 47 } 48 + 49 + impl jacquard_common::IntoStatic for Video<'_> { 50 + type Output = Video<'static>; 51 + fn into_static(self) -> Self::Output { 52 + Video { 53 + alt: self.alt.into_static(), 54 + aspect_ratio: self.aspect_ratio.into_static(), 55 + captions: self.captions.into_static(), 56 + video: self.video.into_static(), 57 + extra_data: self.extra_data.into_static(), 58 + } 59 + } 60 + } 61 + 62 + #[jacquard_derive::lexicon] 18 63 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 19 64 #[serde(rename_all = "camelCase")] 20 65 pub struct View<'a> { 21 - #[serde(skip_serializing_if = "Option::is_none")] 22 - pub alt: Option<jacquard_common::CowStr<'a>>, 23 - #[serde(skip_serializing_if = "Option::is_none")] 24 - pub aspect_ratio: Option<jacquard_common::types::value::Data<'a>>, 66 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 67 + #[serde(borrow)] 68 + pub alt: std::option::Option<jacquard_common::CowStr<'a>>, 69 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 70 + #[serde(borrow)] 71 + pub aspect_ratio: std::option::Option<jacquard_common::types::value::Data<'a>>, 72 + #[serde(borrow)] 25 73 pub cid: jacquard_common::types::string::Cid<'a>, 74 + #[serde(borrow)] 26 75 pub playlist: jacquard_common::types::string::Uri<'a>, 27 - #[serde(skip_serializing_if = "Option::is_none")] 28 - pub thumbnail: Option<jacquard_common::types::string::Uri<'a>>, 76 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 77 + #[serde(borrow)] 78 + pub thumbnail: std::option::Option<jacquard_common::types::string::Uri<'a>>, 29 79 } 80 + 81 + impl jacquard_common::IntoStatic for View<'_> { 82 + type Output = View<'static>; 83 + fn into_static(self) -> Self::Output { 84 + View { 85 + alt: self.alt.into_static(), 86 + aspect_ratio: self.aspect_ratio.into_static(), 87 + cid: self.cid.into_static(), 88 + playlist: self.playlist.into_static(), 89 + thumbnail: self.thumbnail.into_static(), 90 + extra_data: self.extra_data.into_static(), 91 + } 92 + } 93 + }
+6 -1
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/feed.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // This file was automatically generated from Lexicon schemas. 4 + // Any manual changes will be overwritten on the next regeneration. 5 + 1 6 pub mod get_author_feed; 2 - pub mod post; 7 + pub mod post;
+86 -15
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/feed/get_author_feed.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: app.bsky.feed.getAuthorFeed 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 1 8 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 2 9 #[serde(rename_all = "camelCase")] 3 - pub struct GetAuthorFeedParams<'a> { 10 + pub struct GetAuthorFeed<'a> { 11 + #[serde(borrow)] 4 12 pub actor: jacquard_common::types::ident::AtIdentifier<'a>, 5 - #[serde(skip_serializing_if = "Option::is_none")] 6 - pub cursor: Option<jacquard_common::CowStr<'a>>, 7 - #[serde(skip_serializing_if = "Option::is_none")] 8 - pub filter: Option<jacquard_common::CowStr<'a>>, 9 - #[serde(skip_serializing_if = "Option::is_none")] 10 - pub include_pins: Option<bool>, 11 - #[serde(skip_serializing_if = "Option::is_none")] 12 - pub limit: Option<i64>, 13 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 14 + #[serde(borrow)] 15 + pub cursor: std::option::Option<jacquard_common::CowStr<'a>>, 16 + ///(default: "posts_with_replies") 17 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 18 + #[serde(borrow)] 19 + pub filter: std::option::Option<jacquard_common::CowStr<'a>>, 20 + ///(default: false) 21 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 22 + pub include_pins: std::option::Option<bool>, 23 + ///(default: 50, min: 1, max: 100) 24 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 + pub limit: std::option::Option<i64>, 26 + } 27 + 28 + impl jacquard_common::IntoStatic for GetAuthorFeed<'_> { 29 + type Output = GetAuthorFeed<'static>; 30 + fn into_static(self) -> Self::Output { 31 + GetAuthorFeed { 32 + actor: self.actor.into_static(), 33 + cursor: self.cursor.into_static(), 34 + filter: self.filter.into_static(), 35 + include_pins: self.include_pins.into_static(), 36 + limit: self.limit.into_static(), 37 + } 38 + } 13 39 } 40 + 41 + #[jacquard_derive::lexicon] 14 42 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 15 43 #[serde(rename_all = "camelCase")] 16 44 pub struct GetAuthorFeedOutput<'a> { 17 - #[serde(skip_serializing_if = "Option::is_none")] 18 - pub cursor: Option<jacquard_common::CowStr<'a>>, 45 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 46 + #[serde(borrow)] 47 + pub cursor: std::option::Option<jacquard_common::CowStr<'a>>, 48 + #[serde(borrow)] 19 49 pub feed: Vec<jacquard_common::types::value::Data<'a>>, 20 50 } 51 + 52 + impl jacquard_common::IntoStatic for GetAuthorFeedOutput<'_> { 53 + type Output = GetAuthorFeedOutput<'static>; 54 + fn into_static(self) -> Self::Output { 55 + GetAuthorFeedOutput { 56 + cursor: self.cursor.into_static(), 57 + feed: self.feed.into_static(), 58 + extra_data: self.extra_data.into_static(), 59 + } 60 + } 61 + } 62 + 63 + #[jacquard_derive::open_union] 21 64 #[derive( 22 65 serde::Serialize, 23 66 serde::Deserialize, ··· 29 72 miette::Diagnostic 30 73 )] 31 74 #[serde(tag = "error", content = "message")] 32 - pub enum GetAuthorFeedError { 75 + #[serde(bound(deserialize = "'de: 'a"))] 76 + pub enum GetAuthorFeedError<'a> { 33 77 #[serde(rename = "BlockedActor")] 34 - BlockedActor(Option<jacquard_common::CowStr<'static>>), 78 + BlockedActor(std::option::Option<String>), 35 79 #[serde(rename = "BlockedByActor")] 36 - BlockedByActor(Option<jacquard_common::CowStr<'static>>), 80 + BlockedByActor(std::option::Option<String>), 37 81 } 38 - impl std::fmt::Display for GetAuthorFeedError { 82 + 83 + impl std::fmt::Display for GetAuthorFeedError<'_> { 39 84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 40 85 match self { 41 86 Self::BlockedActor(msg) => { ··· 52 97 } 53 98 Ok(()) 54 99 } 100 + Self::Unknown(err) => write!(f, "Unknown error: {:?}", err), 55 101 } 56 102 } 57 103 } 104 + 105 + impl jacquard_common::IntoStatic for GetAuthorFeedError<'_> { 106 + type Output = GetAuthorFeedError<'static>; 107 + fn into_static(self) -> Self::Output { 108 + match self { 109 + GetAuthorFeedError::BlockedActor(v) => { 110 + GetAuthorFeedError::BlockedActor(v.into_static()) 111 + } 112 + GetAuthorFeedError::BlockedByActor(v) => { 113 + GetAuthorFeedError::BlockedByActor(v.into_static()) 114 + } 115 + GetAuthorFeedError::Unknown(v) => { 116 + GetAuthorFeedError::Unknown(v.into_static()) 117 + } 118 + } 119 + } 120 + } 121 + 122 + impl jacquard_common::types::xrpc::XrpcRequest for GetAuthorFeed<'_> { 123 + const NSID: &'static str = "app.bsky.feed.getAuthorFeed"; 124 + const METHOD: jacquard_common::types::xrpc::XrpcMethod = jacquard_common::types::xrpc::XrpcMethod::Query; 125 + const OUTPUT_ENCODING: &'static str = "application/json"; 126 + type Output<'de> = GetAuthorFeedOutput<'de>; 127 + type Err<'de> = GetAuthorFeedError<'de>; 128 + }
+152 -25
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/feed/post.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: app.bsky.feed.post 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 1 8 ///Deprecated: use facets instead. 9 + #[jacquard_derive::lexicon] 2 10 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 3 11 #[serde(rename_all = "camelCase")] 4 12 pub struct Entity<'a> { 5 - pub index: jacquard_common::types::value::Data<'a>, 13 + #[serde(borrow)] 14 + pub index: test_generated::app_bsky::feed::post::TextSlice<'a>, 15 + ///Expected values are 'mention' and 'link'. 16 + #[serde(borrow)] 6 17 pub r#type: jacquard_common::CowStr<'a>, 18 + #[serde(borrow)] 7 19 pub value: jacquard_common::CowStr<'a>, 8 20 } 21 + 22 + impl jacquard_common::IntoStatic for Entity<'_> { 23 + type Output = Entity<'static>; 24 + fn into_static(self) -> Self::Output { 25 + Entity { 26 + index: self.index.into_static(), 27 + r#type: self.r#type.into_static(), 28 + value: self.value.into_static(), 29 + extra_data: self.extra_data.into_static(), 30 + } 31 + } 32 + } 33 + 9 34 ///Record containing a Bluesky post. 10 35 #[jacquard_derive::lexicon] 11 36 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 12 37 #[serde(rename_all = "camelCase")] 13 38 pub struct Post<'a> { 39 + ///Client-declared timestamp when this post was originally created. 14 40 pub created_at: jacquard_common::types::string::Datetime, 15 - #[serde(skip_serializing_if = "Option::is_none")] 16 - pub embed: Option<RecordEmbed<'a>>, 17 - #[serde(skip_serializing_if = "Option::is_none")] 18 - pub entities: Option<Vec<jacquard_common::types::value::Data<'a>>>, 19 - #[serde(skip_serializing_if = "Option::is_none")] 20 - pub facets: Option<Vec<test_generated::app_bsky::richtext::Facet<'a>>>, 21 - #[serde(skip_serializing_if = "Option::is_none")] 22 - pub labels: Option<RecordLabels<'a>>, 23 - #[serde(skip_serializing_if = "Option::is_none")] 24 - pub langs: Option<Vec<jacquard_common::types::string::Language>>, 25 - #[serde(skip_serializing_if = "Option::is_none")] 26 - pub reply: Option<jacquard_common::types::value::Data<'a>>, 27 - #[serde(skip_serializing_if = "Option::is_none")] 28 - pub tags: Option<Vec<jacquard_common::CowStr<'a>>>, 41 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 42 + #[serde(borrow)] 43 + pub embed: std::option::Option<PostRecordEmbed<'a>>, 44 + ///DEPRECATED: replaced by app.bsky.richtext.facet. 45 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 46 + #[serde(borrow)] 47 + pub entities: std::option::Option< 48 + Vec<test_generated::app_bsky::feed::post::Entity<'a>>, 49 + >, 50 + ///Annotations of text (mentions, URLs, hashtags, etc) 51 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 52 + #[serde(borrow)] 53 + pub facets: std::option::Option< 54 + Vec<test_generated::app_bsky::richtext::facet::Facet<'a>>, 55 + >, 56 + ///Self-label values for this post. Effectively content warnings. 57 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 58 + #[serde(borrow)] 59 + pub labels: std::option::Option<PostRecordLabels<'a>>, 60 + ///Indicates human language of post primary text content. 61 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 62 + pub langs: std::option::Option<Vec<jacquard_common::types::string::Language>>, 63 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 64 + #[serde(borrow)] 65 + pub reply: std::option::Option<test_generated::app_bsky::feed::post::ReplyRef<'a>>, 66 + ///Additional hashtags, in addition to any included in post text and facets. 67 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 68 + #[serde(borrow)] 69 + pub tags: std::option::Option<Vec<jacquard_common::CowStr<'a>>>, 70 + ///The primary post content. May be an empty string, if there are embeds. 71 + #[serde(borrow)] 29 72 pub text: jacquard_common::CowStr<'a>, 30 73 } 74 + 31 75 #[jacquard_derive::open_union] 32 76 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 33 77 #[serde(tag = "$type")] 34 - pub enum RecordEmbed<'a> { 78 + #[serde(bound(deserialize = "'de: 'a"))] 79 + pub enum PostRecordEmbed<'a> { 35 80 #[serde(rename = "app.bsky.embed.images")] 36 - Images(Box<test_generated::app_bsky::embed::Images<'a>>), 81 + Images(Box<test_generated::app_bsky::embed::images::Images<'a>>), 37 82 #[serde(rename = "app.bsky.embed.video")] 38 - Video(Box<test_generated::app_bsky::embed::Video<'a>>), 83 + Video(Box<test_generated::app_bsky::embed::video::Video<'a>>), 39 84 #[serde(rename = "app.bsky.embed.external")] 40 - External(Box<test_generated::app_bsky::embed::External<'a>>), 85 + External(Box<test_generated::app_bsky::embed::external::ExternalRecord<'a>>), 41 86 #[serde(rename = "app.bsky.embed.record")] 42 - Record(Box<test_generated::app_bsky::embed::Record<'a>>), 87 + Record(Box<test_generated::app_bsky::embed::record::Record<'a>>), 43 88 #[serde(rename = "app.bsky.embed.recordWithMedia")] 44 - RecordWithMedia(Box<test_generated::app_bsky::embed::RecordWithMedia<'a>>), 89 + RecordWithMedia( 90 + Box<test_generated::app_bsky::embed::record_with_media::RecordWithMedia<'a>>, 91 + ), 92 + } 93 + 94 + impl jacquard_common::IntoStatic for PostRecordEmbed<'_> { 95 + type Output = PostRecordEmbed<'static>; 96 + fn into_static(self) -> Self::Output { 97 + match self { 98 + PostRecordEmbed::Images(v) => PostRecordEmbed::Images(v.into_static()), 99 + PostRecordEmbed::Video(v) => PostRecordEmbed::Video(v.into_static()), 100 + PostRecordEmbed::External(v) => PostRecordEmbed::External(v.into_static()), 101 + PostRecordEmbed::Record(v) => PostRecordEmbed::Record(v.into_static()), 102 + PostRecordEmbed::RecordWithMedia(v) => { 103 + PostRecordEmbed::RecordWithMedia(v.into_static()) 104 + } 105 + PostRecordEmbed::Unknown(v) => PostRecordEmbed::Unknown(v.into_static()), 106 + } 107 + } 45 108 } 109 + 46 110 #[jacquard_derive::open_union] 47 111 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 48 112 #[serde(tag = "$type")] 49 - pub enum RecordLabels<'a> { 113 + #[serde(bound(deserialize = "'de: 'a"))] 114 + pub enum PostRecordLabels<'a> { 50 115 #[serde(rename = "com.atproto.label.defs#selfLabels")] 51 - SelfLabels(Box<test_generated::com_atproto::label::SelfLabels<'a>>), 116 + DefsSelfLabels(Box<test_generated::com_atproto::label::SelfLabels<'a>>), 52 117 } 118 + 119 + impl jacquard_common::IntoStatic for PostRecordLabels<'_> { 120 + type Output = PostRecordLabels<'static>; 121 + fn into_static(self) -> Self::Output { 122 + match self { 123 + PostRecordLabels::DefsSelfLabels(v) => { 124 + PostRecordLabels::DefsSelfLabels(v.into_static()) 125 + } 126 + PostRecordLabels::Unknown(v) => PostRecordLabels::Unknown(v.into_static()), 127 + } 128 + } 129 + } 130 + 131 + impl jacquard_common::types::collection::Collection for Post<'_> { 132 + const NSID: &'static str = "app.bsky.feed.post"; 133 + } 134 + 135 + impl jacquard_common::IntoStatic for Post<'_> { 136 + type Output = Post<'static>; 137 + fn into_static(self) -> Self::Output { 138 + Post { 139 + created_at: self.created_at.into_static(), 140 + embed: self.embed.into_static(), 141 + entities: self.entities.into_static(), 142 + facets: self.facets.into_static(), 143 + labels: self.labels.into_static(), 144 + langs: self.langs.into_static(), 145 + reply: self.reply.into_static(), 146 + tags: self.tags.into_static(), 147 + text: self.text.into_static(), 148 + extra_data: self.extra_data.into_static(), 149 + } 150 + } 151 + } 152 + 153 + #[jacquard_derive::lexicon] 53 154 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 54 155 #[serde(rename_all = "camelCase")] 55 156 pub struct ReplyRef<'a> { 56 - pub parent: test_generated::com_atproto::repo::StrongRef<'a>, 57 - pub root: test_generated::com_atproto::repo::StrongRef<'a>, 157 + #[serde(borrow)] 158 + pub parent: test_generated::com_atproto::repo::strong_ref::StrongRef<'a>, 159 + #[serde(borrow)] 160 + pub root: test_generated::com_atproto::repo::strong_ref::StrongRef<'a>, 58 161 } 162 + 163 + impl jacquard_common::IntoStatic for ReplyRef<'_> { 164 + type Output = ReplyRef<'static>; 165 + fn into_static(self) -> Self::Output { 166 + ReplyRef { 167 + parent: self.parent.into_static(), 168 + root: self.root.into_static(), 169 + extra_data: self.extra_data.into_static(), 170 + } 171 + } 172 + } 173 + 59 174 ///Deprecated. Use app.bsky.richtext instead -- A text segment. Start is inclusive, end is exclusive. Indices are for utf16-encoded strings. 175 + #[jacquard_derive::lexicon] 60 176 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 61 177 #[serde(rename_all = "camelCase")] 62 178 pub struct TextSlice<'a> { 63 179 pub end: i64, 64 180 pub start: i64, 65 181 } 182 + 183 + impl jacquard_common::IntoStatic for TextSlice<'_> { 184 + type Output = TextSlice<'static>; 185 + fn into_static(self) -> Self::Output { 186 + TextSlice { 187 + end: self.end.into_static(), 188 + start: self.start.into_static(), 189 + extra_data: self.extra_data.into_static(), 190 + } 191 + } 192 + }
+6 -1
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/richtext.rs
··· 1 - pub mod facet; 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // This file was automatically generated from Lexicon schemas. 4 + // Any manual changes will be overwritten on the next regeneration. 5 + 6 + pub mod facet;
+74 -1
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/richtext/facet.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: app.bsky.richtext.facet 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 1 8 ///Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets. 9 + #[jacquard_derive::lexicon] 2 10 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 3 11 #[serde(rename_all = "camelCase")] 4 12 pub struct ByteSlice<'a> { 5 13 pub byte_end: i64, 6 14 pub byte_start: i64, 7 15 } 16 + 17 + impl jacquard_common::IntoStatic for ByteSlice<'_> { 18 + type Output = ByteSlice<'static>; 19 + fn into_static(self) -> Self::Output { 20 + ByteSlice { 21 + byte_end: self.byte_end.into_static(), 22 + byte_start: self.byte_start.into_static(), 23 + extra_data: self.extra_data.into_static(), 24 + } 25 + } 26 + } 27 + 8 28 ///Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL. 29 + #[jacquard_derive::lexicon] 9 30 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 10 31 #[serde(rename_all = "camelCase")] 11 32 pub struct Link<'a> { 33 + #[serde(borrow)] 12 34 pub uri: jacquard_common::types::string::Uri<'a>, 13 35 } 36 + 37 + impl jacquard_common::IntoStatic for Link<'_> { 38 + type Output = Link<'static>; 39 + fn into_static(self) -> Self::Output { 40 + Link { 41 + uri: self.uri.into_static(), 42 + extra_data: self.extra_data.into_static(), 43 + } 44 + } 45 + } 46 + 14 47 ///Annotation of a sub-string within rich text. 48 + #[jacquard_derive::lexicon] 15 49 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 16 50 #[serde(rename_all = "camelCase")] 17 51 pub struct Facet<'a> { 52 + #[serde(borrow)] 18 53 pub features: Vec<jacquard_common::types::value::Data<'a>>, 19 - pub index: jacquard_common::types::value::Data<'a>, 54 + #[serde(borrow)] 55 + pub index: test_generated::app_bsky::richtext::facet::ByteSlice<'a>, 56 + } 57 + 58 + impl jacquard_common::IntoStatic for Facet<'_> { 59 + type Output = Facet<'static>; 60 + fn into_static(self) -> Self::Output { 61 + Facet { 62 + features: self.features.into_static(), 63 + index: self.index.into_static(), 64 + extra_data: self.extra_data.into_static(), 65 + } 66 + } 20 67 } 68 + 21 69 ///Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID. 70 + #[jacquard_derive::lexicon] 22 71 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 23 72 #[serde(rename_all = "camelCase")] 24 73 pub struct Mention<'a> { 74 + #[serde(borrow)] 25 75 pub did: jacquard_common::types::string::Did<'a>, 26 76 } 77 + 78 + impl jacquard_common::IntoStatic for Mention<'_> { 79 + type Output = Mention<'static>; 80 + fn into_static(self) -> Self::Output { 81 + Mention { 82 + did: self.did.into_static(), 83 + extra_data: self.extra_data.into_static(), 84 + } 85 + } 86 + } 87 + 27 88 ///Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags'). 89 + #[jacquard_derive::lexicon] 28 90 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 29 91 #[serde(rename_all = "camelCase")] 30 92 pub struct Tag<'a> { 93 + #[serde(borrow)] 31 94 pub tag: jacquard_common::CowStr<'a>, 32 95 } 96 + 97 + impl jacquard_common::IntoStatic for Tag<'_> { 98 + type Output = Tag<'static>; 99 + fn into_static(self) -> Self::Output { 100 + Tag { 101 + tag: self.tag.into_static(), 102 + extra_data: self.extra_data.into_static(), 103 + } 104 + } 105 + }
+6 -1
crates/jacquard-lexicon/target/test_codegen_output/com_atproto.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // This file was automatically generated from Lexicon schemas. 4 + // Any manual changes will be overwritten on the next regeneration. 5 + 1 6 pub mod label; 2 - pub mod repo; 7 + pub mod repo;
+157 -28
crates/jacquard-lexicon/target/test_codegen_output/com_atproto/label.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: com.atproto.label.defs 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 1 8 ///Metadata tag on an atproto resource (eg, repo or record). 9 + #[jacquard_derive::lexicon] 2 10 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 3 11 #[serde(rename_all = "camelCase")] 4 12 pub struct Label<'a> { 5 - #[serde(skip_serializing_if = "Option::is_none")] 6 - pub cid: Option<jacquard_common::types::string::Cid<'a>>, 13 + ///Optionally, CID specifying the specific version of 'uri' resource this label applies to. 14 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 15 + #[serde(borrow)] 16 + pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>, 17 + ///Timestamp when this label was created. 7 18 pub cts: jacquard_common::types::string::Datetime, 8 - #[serde(skip_serializing_if = "Option::is_none")] 9 - pub exp: Option<jacquard_common::types::string::Datetime>, 10 - #[serde(skip_serializing_if = "Option::is_none")] 11 - pub neg: Option<bool>, 12 - #[serde(skip_serializing_if = "Option::is_none")] 13 - pub sig: Option<jacquard_common::types::value::Bytes>, 19 + ///Timestamp at which this label expires (no longer applies). 20 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 21 + pub exp: std::option::Option<jacquard_common::types::string::Datetime>, 22 + ///If true, this is a negation label, overwriting a previous label. 23 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 + pub neg: std::option::Option<bool>, 25 + ///Signature of dag-cbor encoded label. 26 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 27 + pub sig: std::option::Option<bytes::Bytes>, 28 + ///DID of the actor who created this label. 29 + #[serde(borrow)] 14 30 pub src: jacquard_common::types::string::Did<'a>, 31 + ///AT URI of the record, repository (account), or other resource that this label applies to. 32 + #[serde(borrow)] 15 33 pub uri: jacquard_common::types::string::Uri<'a>, 34 + ///The short string name of the value or type of this label. 35 + #[serde(borrow)] 16 36 pub val: jacquard_common::CowStr<'a>, 17 - #[serde(skip_serializing_if = "Option::is_none")] 18 - pub ver: Option<i64>, 37 + ///The AT Protocol version of the label object. 38 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 39 + pub ver: std::option::Option<i64>, 40 + } 41 + 42 + impl jacquard_common::IntoStatic for Label<'_> { 43 + type Output = Label<'static>; 44 + fn into_static(self) -> Self::Output { 45 + Label { 46 + cid: self.cid.into_static(), 47 + cts: self.cts.into_static(), 48 + exp: self.exp.into_static(), 49 + neg: self.neg.into_static(), 50 + sig: self.sig.into_static(), 51 + src: self.src.into_static(), 52 + uri: self.uri.into_static(), 53 + val: self.val.into_static(), 54 + ver: self.ver.into_static(), 55 + extra_data: self.extra_data.into_static(), 56 + } 57 + } 19 58 } 59 + 20 60 #[derive(Debug, Clone, PartialEq, Eq, Hash)] 21 61 pub enum LabelValue<'a> { 22 - #[serde(rename = "!hide")] 23 62 Hide, 24 - #[serde(rename = "!no-promote")] 25 63 NoPromote, 26 - #[serde(rename = "!warn")] 27 64 Warn, 28 - #[serde(rename = "!no-unauthenticated")] 29 65 NoUnauthenticated, 30 - #[serde(rename = "dmca-violation")] 31 66 DmcaViolation, 32 - #[serde(rename = "doxxing")] 33 67 Doxxing, 34 - #[serde(rename = "porn")] 35 68 Porn, 36 - #[serde(rename = "sexual")] 37 69 Sexual, 38 - #[serde(rename = "nudity")] 39 70 Nudity, 40 - #[serde(rename = "nsfl")] 41 71 Nsfl, 42 - #[serde(rename = "gore")] 43 72 Gore, 44 - #[serde(untagged)] 45 73 Other(jacquard_common::CowStr<'a>), 46 74 } 75 + 47 76 impl<'a> LabelValue<'a> { 48 77 pub fn as_str(&self) -> &str { 49 78 match self { ··· 62 91 } 63 92 } 64 93 } 94 + 65 95 impl<'a> From<&'a str> for LabelValue<'a> { 66 96 fn from(s: &'a str) -> Self { 67 97 match s { ··· 80 110 } 81 111 } 82 112 } 113 + 83 114 impl<'a> From<String> for LabelValue<'a> { 84 115 fn from(s: String) -> Self { 85 116 match s.as_str() { ··· 98 129 } 99 130 } 100 131 } 132 + 101 133 impl<'a> AsRef<str> for LabelValue<'a> { 102 134 fn as_ref(&self) -> &str { 103 135 self.as_str() 104 136 } 105 137 } 138 + 106 139 impl<'a> serde::Serialize for LabelValue<'a> { 107 140 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 108 141 where ··· 111 144 serializer.serialize_str(self.as_str()) 112 145 } 113 146 } 147 + 114 148 impl<'de, 'a> serde::Deserialize<'de> for LabelValue<'a> 115 149 where 116 150 'de: 'a, ··· 123 157 Ok(Self::from(s)) 124 158 } 125 159 } 160 + 161 + impl jacquard_common::IntoStatic for LabelValue<'_> { 162 + type Output = LabelValue<'static>; 163 + fn into_static(self) -> Self::Output { 164 + match self { 165 + LabelValue::Hide => LabelValue::Hide, 166 + LabelValue::NoPromote => LabelValue::NoPromote, 167 + LabelValue::Warn => LabelValue::Warn, 168 + LabelValue::NoUnauthenticated => LabelValue::NoUnauthenticated, 169 + LabelValue::DmcaViolation => LabelValue::DmcaViolation, 170 + LabelValue::Doxxing => LabelValue::Doxxing, 171 + LabelValue::Porn => LabelValue::Porn, 172 + LabelValue::Sexual => LabelValue::Sexual, 173 + LabelValue::Nudity => LabelValue::Nudity, 174 + LabelValue::Nsfl => LabelValue::Nsfl, 175 + LabelValue::Gore => LabelValue::Gore, 176 + LabelValue::Other(v) => LabelValue::Other(v.into_static()), 177 + } 178 + } 179 + } 180 + 126 181 ///Declares a label value and its expected interpretations and behaviors. 182 + #[jacquard_derive::lexicon] 127 183 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 128 184 #[serde(rename_all = "camelCase")] 129 185 pub struct LabelValueDefinition<'a> { 130 - #[serde(skip_serializing_if = "Option::is_none")] 131 - pub adult_only: Option<bool>, 186 + ///Does the user need to have adult content enabled in order to configure this label? 187 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 188 + pub adult_only: std::option::Option<bool>, 189 + ///What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing. 190 + #[serde(borrow)] 132 191 pub blurs: jacquard_common::CowStr<'a>, 133 - #[serde(skip_serializing_if = "Option::is_none")] 134 - pub default_setting: Option<jacquard_common::CowStr<'a>>, 192 + ///The default setting for this label. 193 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 194 + #[serde(borrow)] 195 + pub default_setting: std::option::Option<jacquard_common::CowStr<'a>>, 196 + ///The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+). 197 + #[serde(borrow)] 135 198 pub identifier: jacquard_common::CowStr<'a>, 136 - pub locales: Vec<jacquard_common::types::value::Data<'a>>, 199 + #[serde(borrow)] 200 + pub locales: Vec< 201 + test_generated::com_atproto::label::LabelValueDefinitionStrings<'a>, 202 + >, 203 + ///How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing. 204 + #[serde(borrow)] 137 205 pub severity: jacquard_common::CowStr<'a>, 138 206 } 207 + 208 + impl jacquard_common::IntoStatic for LabelValueDefinition<'_> { 209 + type Output = LabelValueDefinition<'static>; 210 + fn into_static(self) -> Self::Output { 211 + LabelValueDefinition { 212 + adult_only: self.adult_only.into_static(), 213 + blurs: self.blurs.into_static(), 214 + default_setting: self.default_setting.into_static(), 215 + identifier: self.identifier.into_static(), 216 + locales: self.locales.into_static(), 217 + severity: self.severity.into_static(), 218 + extra_data: self.extra_data.into_static(), 219 + } 220 + } 221 + } 222 + 139 223 ///Strings which describe the label in the UI, localized into a specific language. 224 + #[jacquard_derive::lexicon] 140 225 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 141 226 #[serde(rename_all = "camelCase")] 142 227 pub struct LabelValueDefinitionStrings<'a> { 228 + ///A longer description of what the label means and why it might be applied. 229 + #[serde(borrow)] 143 230 pub description: jacquard_common::CowStr<'a>, 231 + ///The code of the language these strings are written in. 144 232 pub lang: jacquard_common::types::string::Language, 233 + ///A short human-readable name for the label. 234 + #[serde(borrow)] 145 235 pub name: jacquard_common::CowStr<'a>, 146 236 } 237 + 238 + impl jacquard_common::IntoStatic for LabelValueDefinitionStrings<'_> { 239 + type Output = LabelValueDefinitionStrings<'static>; 240 + fn into_static(self) -> Self::Output { 241 + LabelValueDefinitionStrings { 242 + description: self.description.into_static(), 243 + lang: self.lang.into_static(), 244 + name: self.name.into_static(), 245 + extra_data: self.extra_data.into_static(), 246 + } 247 + } 248 + } 249 + 147 250 ///Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel. 251 + #[jacquard_derive::lexicon] 148 252 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 149 253 #[serde(rename_all = "camelCase")] 150 254 pub struct SelfLabel<'a> { 255 + ///The short string name of the value or type of this label. 256 + #[serde(borrow)] 151 257 pub val: jacquard_common::CowStr<'a>, 152 258 } 259 + 260 + impl jacquard_common::IntoStatic for SelfLabel<'_> { 261 + type Output = SelfLabel<'static>; 262 + fn into_static(self) -> Self::Output { 263 + SelfLabel { 264 + val: self.val.into_static(), 265 + extra_data: self.extra_data.into_static(), 266 + } 267 + } 268 + } 269 + 153 270 ///Metadata tags on an atproto record, published by the author within the record. 271 + #[jacquard_derive::lexicon] 154 272 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 155 273 #[serde(rename_all = "camelCase")] 156 274 pub struct SelfLabels<'a> { 157 - pub values: Vec<jacquard_common::types::value::Data<'a>>, 275 + #[serde(borrow)] 276 + pub values: Vec<test_generated::com_atproto::label::SelfLabel<'a>>, 158 277 } 278 + 279 + impl jacquard_common::IntoStatic for SelfLabels<'_> { 280 + type Output = SelfLabels<'static>; 281 + fn into_static(self) -> Self::Output { 282 + SelfLabels { 283 + values: self.values.into_static(), 284 + extra_data: self.extra_data.into_static(), 285 + } 286 + } 287 + }
+6 -1
crates/jacquard-lexicon/target/test_codegen_output/com_atproto/repo.rs
··· 1 - pub mod strong_ref; 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // This file was automatically generated from Lexicon schemas. 4 + // Any manual changes will be overwritten on the next regeneration. 5 + 6 + pub mod strong_ref;
+21
crates/jacquard-lexicon/target/test_codegen_output/com_atproto/repo/strong_ref.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: com.atproto.repo.strongRef 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[jacquard_derive::lexicon] 1 9 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 2 10 #[serde(rename_all = "camelCase")] 3 11 pub struct StrongRef<'a> { 12 + #[serde(borrow)] 4 13 pub cid: jacquard_common::types::string::Cid<'a>, 14 + #[serde(borrow)] 5 15 pub uri: jacquard_common::types::string::AtUri<'a>, 6 16 } 17 + 18 + impl jacquard_common::IntoStatic for StrongRef<'_> { 19 + type Output = StrongRef<'static>; 20 + fn into_static(self) -> Self::Output { 21 + StrongRef { 22 + cid: self.cid.into_static(), 23 + uri: self.uri.into_static(), 24 + extra_data: self.extra_data.into_static(), 25 + } 26 + } 27 + }
+6 -1
crates/jacquard-lexicon/target/test_codegen_output/lib.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // This file was automatically generated from Lexicon schemas. 4 + // Any manual changes will be overwritten on the next regeneration. 5 + 1 6 pub mod app_bsky; 2 - pub mod com_atproto; 7 + pub mod com_atproto;
+149 -25
crates/jacquard/src/client.rs
··· 4 4 use std::fmt::Display; 5 5 use std::future::Future; 6 6 7 - pub use error::{ClientError, Result}; 8 7 use bytes::Bytes; 8 + pub use error::{ClientError, Result}; 9 9 use http::{ 10 10 HeaderName, HeaderValue, Request, 11 11 header::{AUTHORIZATION, CONTENT_TYPE, InvalidHeaderValue}, 12 12 }; 13 13 pub use response::Response; 14 - use serde::Serialize; 14 + 15 + use jacquard_common::{ 16 + CowStr, IntoStatic, 17 + types::{ 18 + string::{Did, Handle}, 19 + xrpc::{XrpcMethod, XrpcRequest}, 20 + }, 21 + }; 22 + 23 + /// Implement HttpClient for reqwest::Client 24 + impl HttpClient for reqwest::Client { 25 + type Error = reqwest::Error; 26 + 27 + async fn send_http( 28 + &self, 29 + request: Request<Vec<u8>>, 30 + ) -> core::result::Result<http::Response<Vec<u8>>, Self::Error> { 31 + // Convert http::Request to reqwest::Request 32 + let (parts, body) = request.into_parts(); 15 33 16 - use jacquard_common::{CowStr, types::xrpc::{XrpcMethod, XrpcRequest}}; 34 + let mut req = self.request(parts.method, parts.uri.to_string()).body(body); 35 + 36 + // Copy headers 37 + for (name, value) in parts.headers.iter() { 38 + req = req.header(name.as_str(), value.as_bytes()); 39 + } 40 + 41 + // Send request 42 + let resp = req.send().await?; 43 + 44 + // Convert reqwest::Response to http::Response 45 + let mut builder = http::Response::builder().status(resp.status()); 46 + 47 + // Copy headers 48 + for (name, value) in resp.headers().iter() { 49 + builder = builder.header(name.as_str(), value.as_bytes()); 50 + } 51 + 52 + // Read body 53 + let body = resp.bytes().await?.to_vec(); 54 + 55 + Ok(builder.body(body).expect("Failed to build response")) 56 + } 57 + } 17 58 18 59 pub trait HttpClient { 19 60 type Error: std::error::Error + Display + Send + Sync + 'static; ··· 98 139 99 140 // Add query parameters for Query methods 100 141 if let XrpcMethod::Query = R::METHOD { 101 - if let Ok(qs) = serde_html_form::to_string(&request) { 102 - if !qs.is_empty() { 103 - uri.push('?'); 104 - uri.push_str(&qs); 105 - } 142 + let qs = serde_html_form::to_string(&request).map_err(error::EncodeError::from)?; 143 + if !qs.is_empty() { 144 + uri.push('?'); 145 + uri.push_str(&qs); 106 146 } 107 147 } 108 148 ··· 139 179 } 140 180 141 181 // Serialize body for procedures 142 - let body = if let XrpcMethod::Procedure(encoding) = R::METHOD { 143 - if encoding == "application/json" { 144 - serde_json::to_vec(&request).map_err(error::EncodeError::Json)? 145 - } else { 146 - // For other encodings, we'd need different serialization 147 - vec![] 148 - } 182 + let body = if let XrpcMethod::Procedure(_) = R::METHOD { 183 + request.encode_body()? 149 184 } else { 150 185 vec![] 151 186 }; 152 187 188 + // TODO: make this not panic 153 189 let http_request = builder.body(body).expect("Failed to build HTTP request"); 154 190 155 191 // Send HTTP request 156 - let http_response = client.send_http(http_request).await.map_err(|e| { 157 - error::TransportError::Other(Box::new(e)) 158 - })?; 192 + let http_response = client 193 + .send_http(http_request) 194 + .await 195 + .map_err(|e| error::TransportError::Other(Box::new(e)))?; 196 + 197 + let status = http_response.status(); 198 + let buffer = Bytes::from(http_response.into_body()); 159 199 160 - // Check status 161 - if !http_response.status().is_success() { 200 + // XRPC errors come as 400/401 with structured error bodies 201 + // Other error status codes (404, 500, etc.) are generic HTTP errors 202 + if !status.is_success() && !matches!(status.as_u16(), 400 | 401) { 162 203 return Err(ClientError::Http(error::HttpError { 163 - status: http_response.status(), 164 - body: Some(Bytes::from(http_response.body().clone())), 204 + status, 205 + body: Some(buffer), 165 206 })); 166 207 } 167 208 168 - // Convert to Response 169 - let buffer = Bytes::from(http_response.into_body()); 170 - Ok(Response::new(buffer)) 209 + // Response will parse XRPC errors for 400/401, or output for 2xx 210 + Ok(Response::new(buffer, status)) 211 + } 212 + 213 + /// Session information from createSession 214 + #[derive(Debug, Clone)] 215 + pub struct Session { 216 + pub access_jwt: CowStr<'static>, 217 + pub refresh_jwt: CowStr<'static>, 218 + pub did: Did<'static>, 219 + pub handle: Handle<'static>, 220 + } 221 + 222 + impl From<jacquard_api::com_atproto::server::create_session::CreateSessionOutput<'_>> for Session { 223 + fn from( 224 + output: jacquard_api::com_atproto::server::create_session::CreateSessionOutput<'_>, 225 + ) -> Self { 226 + Self { 227 + access_jwt: output.access_jwt.into_static(), 228 + refresh_jwt: output.refresh_jwt.into_static(), 229 + did: output.did.into_static(), 230 + handle: output.handle.into_static(), 231 + } 232 + } 233 + } 234 + 235 + /// Authenticated XRPC client that includes session tokens 236 + pub struct AuthenticatedClient<C> { 237 + client: C, 238 + base_uri: CowStr<'static>, 239 + session: Option<Session>, 240 + } 241 + 242 + impl<C> AuthenticatedClient<C> { 243 + /// Create a new authenticated client with a base URI 244 + pub fn new(client: C, base_uri: CowStr<'static>) -> Self { 245 + Self { 246 + client, 247 + base_uri: base_uri, 248 + session: None, 249 + } 250 + } 251 + 252 + /// Set the session 253 + pub fn set_session(&mut self, session: Session) { 254 + self.session = Some(session); 255 + } 256 + 257 + /// Get the current session 258 + pub fn session(&self) -> Option<&Session> { 259 + self.session.as_ref() 260 + } 261 + 262 + /// Clear the session 263 + pub fn clear_session(&mut self) { 264 + self.session = None; 265 + } 266 + } 267 + 268 + impl<C: HttpClient> HttpClient for AuthenticatedClient<C> { 269 + type Error = C::Error; 270 + 271 + fn send_http( 272 + &self, 273 + request: Request<Vec<u8>>, 274 + ) -> impl Future<Output = core::result::Result<http::Response<Vec<u8>>, Self::Error>> { 275 + self.client.send_http(request) 276 + } 277 + } 278 + 279 + impl<C: HttpClient> XrpcClient for AuthenticatedClient<C> { 280 + fn base_uri(&self) -> CowStr<'_> { 281 + self.base_uri.clone() 282 + } 283 + 284 + async fn authorization_token(&self, is_refresh: bool) -> Option<AuthorizationToken<'_>> { 285 + if is_refresh { 286 + self.session 287 + .as_ref() 288 + .map(|s| AuthorizationToken::Bearer(s.refresh_jwt.clone())) 289 + } else { 290 + self.session 291 + .as_ref() 292 + .map(|s| AuthorizationToken::Bearer(s.access_jwt.clone())) 293 + } 294 + } 171 295 }
+2 -15
crates/jacquard/src/client/error.rs
··· 59 59 Other(Box<dyn std::error::Error + Send + Sync>), 60 60 } 61 61 62 - #[derive(Debug, thiserror::Error, miette::Diagnostic)] 63 - pub enum EncodeError { 64 - #[error("Failed to serialize query: {0}")] 65 - Query( 66 - #[from] 67 - #[source] 68 - serde_html_form::ser::Error, 69 - ), 70 - #[error("Failed to serialize JSON: {0}")] 71 - Json( 72 - #[from] 73 - #[source] 74 - serde_json::Error, 75 - ), 76 - } 62 + // Re-export EncodeError from common 63 + pub use jacquard_common::types::xrpc::EncodeError; 77 64 78 65 #[derive(Debug, thiserror::Error, miette::Diagnostic)] 79 66 pub enum DecodeError {
+109 -12
crates/jacquard/src/client/response.rs
··· 1 1 use bytes::Bytes; 2 + use http::StatusCode; 2 3 use jacquard_common::IntoStatic; 3 4 use jacquard_common::types::xrpc::XrpcRequest; 5 + use serde::Deserialize; 4 6 use std::marker::PhantomData; 7 + 8 + use super::error::AuthError; 5 9 6 10 /// XRPC response wrapper that owns the response buffer 7 11 /// 8 12 /// Allows borrowing from the buffer when parsing to avoid unnecessary allocations. 9 13 pub struct Response<R: XrpcRequest> { 10 14 buffer: Bytes, 15 + status: StatusCode, 11 16 _marker: PhantomData<R>, 12 17 } 13 18 14 19 impl<R: XrpcRequest> Response<R> { 15 - /// Create a new response from a buffer 16 - pub fn new(buffer: Bytes) -> Self { 20 + /// Create a new response from a buffer and status code 21 + pub fn new(buffer: Bytes, status: StatusCode) -> Self { 17 22 Self { 18 23 buffer, 24 + status, 19 25 _marker: PhantomData, 20 26 } 27 + } 28 + 29 + /// Get the HTTP status code 30 + pub fn status(&self) -> StatusCode { 31 + self.status 21 32 } 22 33 23 34 /// Parse the response, borrowing from the internal buffer ··· 35 46 serde_json::from_slice(buffer) 36 47 } 37 48 38 - let output = parse_output::<R>(&self.buffer); 39 - if let Ok(output) = output { 40 - Ok(output) 41 - } else { 42 - // Try to parse as error 49 + // 200: parse as output 50 + if self.status.is_success() { 51 + match parse_output::<R>(&self.buffer) { 52 + Ok(output) => Ok(output), 53 + Err(e) => Err(XrpcError::Decode(e)), 54 + } 55 + // 400: try typed XRPC error, fallback to generic error 56 + } else if self.status.as_u16() == 400 { 43 57 match parse_error::<R>(&self.buffer) { 44 58 Ok(error) => Err(XrpcError::Xrpc(error)), 59 + Err(_) => { 60 + // Fallback to generic error (InvalidRequest, ExpiredToken, etc.) 61 + match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { 62 + Ok(generic) => { 63 + // Map auth-related errors to AuthError 64 + match generic.error.as_str() { 65 + "ExpiredToken" => Err(XrpcError::Auth(AuthError::TokenExpired)), 66 + "InvalidToken" => Err(XrpcError::Auth(AuthError::InvalidToken)), 67 + _ => Err(XrpcError::Generic(generic)), 68 + } 69 + } 70 + Err(e) => Err(XrpcError::Decode(e)), 71 + } 72 + } 73 + } 74 + // 401: always auth error 75 + } else { 76 + match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { 77 + Ok(generic) => { 78 + match generic.error.as_str() { 79 + "ExpiredToken" => Err(XrpcError::Auth(AuthError::TokenExpired)), 80 + "InvalidToken" => Err(XrpcError::Auth(AuthError::InvalidToken)), 81 + _ => Err(XrpcError::Auth(AuthError::NotAuthenticated)), 82 + } 83 + } 45 84 Err(e) => Err(XrpcError::Decode(e)), 46 85 } 47 86 } ··· 66 105 serde_json::from_slice(buffer) 67 106 } 68 107 69 - let output = parse_output::<R>(&self.buffer); 70 - if let Ok(output) = output { 71 - Ok(output.into_static()) 72 - } else { 73 - // Try to parse as error 108 + // 200: parse as output 109 + if self.status.is_success() { 110 + match parse_output::<R>(&self.buffer) { 111 + Ok(output) => Ok(output.into_static()), 112 + Err(e) => Err(XrpcError::Decode(e)), 113 + } 114 + // 400: try typed XRPC error, fallback to generic error 115 + } else if self.status.as_u16() == 400 { 74 116 match parse_error::<R>(&self.buffer) { 75 117 Ok(error) => Err(XrpcError::Xrpc(error.into_static())), 118 + Err(_) => { 119 + // Fallback to generic error (InvalidRequest, ExpiredToken, etc.) 120 + match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { 121 + Ok(generic) => { 122 + // Map auth-related errors to AuthError 123 + match generic.error.as_str() { 124 + "ExpiredToken" => Err(XrpcError::Auth(AuthError::TokenExpired)), 125 + "InvalidToken" => Err(XrpcError::Auth(AuthError::InvalidToken)), 126 + _ => Err(XrpcError::Generic(generic)), 127 + } 128 + } 129 + Err(e) => Err(XrpcError::Decode(e)), 130 + } 131 + } 132 + } 133 + // 401: always auth error 134 + } else { 135 + match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { 136 + Ok(generic) => { 137 + match generic.error.as_str() { 138 + "ExpiredToken" => Err(XrpcError::Auth(AuthError::TokenExpired)), 139 + "InvalidToken" => Err(XrpcError::Auth(AuthError::InvalidToken)), 140 + _ => Err(XrpcError::Auth(AuthError::NotAuthenticated)), 141 + } 142 + } 76 143 Err(e) => Err(XrpcError::Decode(e)), 77 144 } 78 145 } ··· 84 151 } 85 152 } 86 153 154 + /// Generic XRPC error format (for InvalidRequest, etc.) 155 + #[derive(Debug, Clone, Deserialize)] 156 + pub struct GenericXrpcError { 157 + pub error: String, 158 + pub message: Option<String>, 159 + } 160 + 161 + impl std::fmt::Display for GenericXrpcError { 162 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 163 + if let Some(msg) = &self.message { 164 + write!(f, "{}: {}", self.error, msg) 165 + } else { 166 + write!(f, "{}", self.error) 167 + } 168 + } 169 + } 170 + 171 + impl std::error::Error for GenericXrpcError {} 172 + 87 173 #[derive(Debug, thiserror::Error, miette::Diagnostic)] 88 174 pub enum XrpcError<E: std::error::Error + IntoStatic> { 175 + /// Typed XRPC error from the endpoint's error enum 89 176 #[error("XRPC error: {0}")] 90 177 Xrpc(E), 178 + 179 + /// Authentication error (ExpiredToken, InvalidToken, etc.) 180 + #[error("Authentication error: {0}")] 181 + Auth(#[from] AuthError), 182 + 183 + /// Generic XRPC error (InvalidRequest, etc.) 184 + #[error("XRPC error: {0}")] 185 + Generic(GenericXrpcError), 186 + 187 + /// Failed to decode response 91 188 #[error("Failed to decode response: {0}")] 92 189 Decode(#[from] serde_json::Error), 93 190 }