From aa87f30d21f0a72b628e093168cd3ace89aa3d8a Mon Sep 17 00:00:00 2001 From: Orual Date: Fri, 3 Oct 2025 20:14:39 -0400 Subject: [PATCH] minimal auth client, plus some fixes for codegen for bytes upload and encoding --- .../src/app_bsky/feed/get_suggested_feeds.rs | 5 +- .../get_onboarding_suggested_starter_packs.rs | 5 +- .../src/app_bsky/video/upload_video.rs | 19 +- .../src/com_atproto/repo/import_repo.rs | 19 +- .../src/com_atproto/repo/upload_blob.rs | 19 +- crates/jacquard-common/src/types/xrpc.rs | 30 ++- crates/jacquard-lexicon/src/codegen.rs | 105 +++++++--- .../target/test_codegen_output/app_bsky.rs | 7 +- .../test_codegen_output/app_bsky/embed.rs | 7 +- .../app_bsky/embed/external.rs | 84 +++++++- .../app_bsky/embed/images.rs | 84 +++++++- .../app_bsky/embed/record.rs | 147 ++++++++++++-- .../app_bsky/embed/record_with_media.rs | 106 +++++++++- .../app_bsky/embed/video.rs | 88 +++++++-- .../test_codegen_output/app_bsky/feed.rs | 7 +- .../app_bsky/feed/get_author_feed.rs | 101 ++++++++-- .../test_codegen_output/app_bsky/feed/post.rs | 177 ++++++++++++++--- .../test_codegen_output/app_bsky/richtext.rs | 7 +- .../app_bsky/richtext/facet.rs | 75 ++++++- .../target/test_codegen_output/com_atproto.rs | 7 +- .../test_codegen_output/com_atproto/label.rs | 185 +++++++++++++++--- .../test_codegen_output/com_atproto/repo.rs | 7 +- .../com_atproto/repo/strong_ref.rs | 21 ++ .../target/test_codegen_output/lib.rs | 7 +- crates/jacquard/src/client.rs | 174 +++++++++++++--- crates/jacquard/src/client/error.rs | 17 +- crates/jacquard/src/client/response.rs | 121 ++++++++++-- 27 files changed, 1403 insertions(+), 228 deletions(-) diff --git a/crates/jacquard-api/src/app_bsky/feed/get_suggested_feeds.rs b/crates/jacquard-api/src/app_bsky/feed/get_suggested_feeds.rs index 3035eef6..7ee72877 100644 --- a/crates/jacquard-api/src/app_bsky/feed/get_suggested_feeds.rs +++ b/crates/jacquard-api/src/app_bsky/feed/get_suggested_feeds.rs @@ -50,9 +50,8 @@ impl jacquard_common::IntoStatic for GetSuggestedFeedsOutput<'_> { impl jacquard_common::types::xrpc::XrpcRequest for GetSuggestedFeeds<'_> { const NSID: &'static str = "app.bsky.feed.getSuggestedFeeds"; - const METHOD: jacquard_common::types::xrpc::XrpcMethod = - jacquard_common::types::xrpc::XrpcMethod::Query; + const METHOD: jacquard_common::types::xrpc::XrpcMethod = jacquard_common::types::xrpc::XrpcMethod::Query; const OUTPUT_ENCODING: &'static str = "application/json"; type Output<'de> = GetSuggestedFeedsOutput<'de>; type Err<'de> = jacquard_common::types::xrpc::GenericError<'de>; -} +} \ No newline at end of file diff --git a/crates/jacquard-api/src/app_bsky/unspecced/get_onboarding_suggested_starter_packs.rs b/crates/jacquard-api/src/app_bsky/unspecced/get_onboarding_suggested_starter_packs.rs index d81b331e..b8ed85a2 100644 --- a/crates/jacquard-api/src/app_bsky/unspecced/get_onboarding_suggested_starter_packs.rs +++ b/crates/jacquard-api/src/app_bsky/unspecced/get_onboarding_suggested_starter_packs.rs @@ -40,9 +40,8 @@ impl jacquard_common::IntoStatic for GetOnboardingSuggestedStarterPacksOutput<'_ impl jacquard_common::types::xrpc::XrpcRequest for GetOnboardingSuggestedStarterPacks { const NSID: &'static str = "app.bsky.unspecced.getOnboardingSuggestedStarterPacks"; - const METHOD: jacquard_common::types::xrpc::XrpcMethod = - jacquard_common::types::xrpc::XrpcMethod::Query; + const METHOD: jacquard_common::types::xrpc::XrpcMethod = jacquard_common::types::xrpc::XrpcMethod::Query; const OUTPUT_ENCODING: &'static str = "application/json"; type Output<'de> = GetOnboardingSuggestedStarterPacksOutput<'de>; type Err<'de> = jacquard_common::types::xrpc::GenericError<'de>; -} +} \ No newline at end of file diff --git a/crates/jacquard-api/src/app_bsky/video/upload_video.rs b/crates/jacquard-api/src/app_bsky/video/upload_video.rs index a7f78302..9e1f4290 100644 --- a/crates/jacquard-api/src/app_bsky/video/upload_video.rs +++ b/crates/jacquard-api/src/app_bsky/video/upload_video.rs @@ -5,16 +5,16 @@ // This file was automatically generated from Lexicon schemas. // Any manual changes will be overwritten on the next regeneration. -#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] -pub struct UploadVideo<'a> {} -impl jacquard_common::IntoStatic for UploadVideo<'_> { - type Output = UploadVideo<'static>; +pub struct UploadVideo { + pub body: bytes::Bytes, +} + +impl jacquard_common::IntoStatic for UploadVideo { + type Output = UploadVideo; fn into_static(self) -> Self::Output { - UploadVideo { - extra_data: self.extra_data.into_static(), - } + self } } @@ -36,7 +36,7 @@ impl jacquard_common::IntoStatic for UploadVideoOutput<'_> { } } -impl jacquard_common::types::xrpc::XrpcRequest for UploadVideo<'_> { +impl jacquard_common::types::xrpc::XrpcRequest for UploadVideo { const NSID: &'static str = "app.bsky.video.uploadVideo"; const METHOD: jacquard_common::types::xrpc::XrpcMethod = jacquard_common::types::xrpc::XrpcMethod::Procedure( "video/mp4", @@ -44,4 +44,7 @@ impl jacquard_common::types::xrpc::XrpcRequest for UploadVideo<'_> { const OUTPUT_ENCODING: &'static str = "application/json"; type Output<'de> = UploadVideoOutput<'de>; type Err<'de> = jacquard_common::types::xrpc::GenericError<'de>; + fn encode_body(&self) -> Result, jacquard_common::types::xrpc::EncodeError> { + Ok(self.body.to_vec()) + } } \ No newline at end of file diff --git a/crates/jacquard-api/src/com_atproto/repo/import_repo.rs b/crates/jacquard-api/src/com_atproto/repo/import_repo.rs index 5c010eeb..d6e95a80 100644 --- a/crates/jacquard-api/src/com_atproto/repo/import_repo.rs +++ b/crates/jacquard-api/src/com_atproto/repo/import_repo.rs @@ -5,20 +5,20 @@ // This file was automatically generated from Lexicon schemas. // Any manual changes will be overwritten on the next regeneration. -#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] -pub struct ImportRepo<'a> {} -impl jacquard_common::IntoStatic for ImportRepo<'_> { - type Output = ImportRepo<'static>; +pub struct ImportRepo { + pub body: bytes::Bytes, +} + +impl jacquard_common::IntoStatic for ImportRepo { + type Output = ImportRepo; fn into_static(self) -> Self::Output { - ImportRepo { - extra_data: self.extra_data.into_static(), - } + self } } -impl jacquard_common::types::xrpc::XrpcRequest for ImportRepo<'_> { +impl jacquard_common::types::xrpc::XrpcRequest for ImportRepo { const NSID: &'static str = "com.atproto.repo.importRepo"; const METHOD: jacquard_common::types::xrpc::XrpcMethod = jacquard_common::types::xrpc::XrpcMethod::Procedure( "application/vnd.ipld.car", @@ -26,4 +26,7 @@ impl jacquard_common::types::xrpc::XrpcRequest for ImportRepo<'_> { const OUTPUT_ENCODING: &'static str = "application/json"; type Output<'de> = (); type Err<'de> = jacquard_common::types::xrpc::GenericError<'de>; + fn encode_body(&self) -> Result, jacquard_common::types::xrpc::EncodeError> { + Ok(self.body.to_vec()) + } } \ No newline at end of file diff --git a/crates/jacquard-api/src/com_atproto/repo/upload_blob.rs b/crates/jacquard-api/src/com_atproto/repo/upload_blob.rs index 443fd1e6..c5e44340 100644 --- a/crates/jacquard-api/src/com_atproto/repo/upload_blob.rs +++ b/crates/jacquard-api/src/com_atproto/repo/upload_blob.rs @@ -5,16 +5,16 @@ // This file was automatically generated from Lexicon schemas. // Any manual changes will be overwritten on the next regeneration. -#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] -pub struct UploadBlob<'a> {} -impl jacquard_common::IntoStatic for UploadBlob<'_> { - type Output = UploadBlob<'static>; +pub struct UploadBlob { + pub body: bytes::Bytes, +} + +impl jacquard_common::IntoStatic for UploadBlob { + type Output = UploadBlob; fn into_static(self) -> Self::Output { - UploadBlob { - extra_data: self.extra_data.into_static(), - } + self } } @@ -36,7 +36,7 @@ impl jacquard_common::IntoStatic for UploadBlobOutput<'_> { } } -impl jacquard_common::types::xrpc::XrpcRequest for UploadBlob<'_> { +impl jacquard_common::types::xrpc::XrpcRequest for UploadBlob { const NSID: &'static str = "com.atproto.repo.uploadBlob"; const METHOD: jacquard_common::types::xrpc::XrpcMethod = jacquard_common::types::xrpc::XrpcMethod::Procedure( "*/*", @@ -44,4 +44,7 @@ impl jacquard_common::types::xrpc::XrpcRequest for UploadBlob<'_> { const OUTPUT_ENCODING: &'static str = "application/json"; type Output<'de> = UploadBlobOutput<'de>; type Err<'de> = jacquard_common::types::xrpc::GenericError<'de>; + fn encode_body(&self) -> Result, jacquard_common::types::xrpc::EncodeError> { + Ok(self.body.to_vec()) + } } \ No newline at end of file diff --git a/crates/jacquard-common/src/types/xrpc.rs b/crates/jacquard-common/src/types/xrpc.rs index 223a1549..12836a68 100644 --- a/crates/jacquard-common/src/types/xrpc.rs +++ b/crates/jacquard-common/src/types/xrpc.rs @@ -1,4 +1,3 @@ -use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use std::error::Error; use std::fmt::{self, Debug}; @@ -6,6 +5,28 @@ use std::fmt::{self, Debug}; use crate::IntoStatic; use crate::types::value::Data; +/// Error type for encoding XRPC requests +#[derive(Debug, thiserror::Error, miette::Diagnostic)] +pub enum EncodeError { + /// Failed to serialize query parameters + #[error("Failed to serialize query: {0}")] + Query( + #[from] + #[source] + serde_html_form::ser::Error, + ), + /// Failed to serialize JSON body + #[error("Failed to serialize JSON: {0}")] + Json( + #[from] + #[source] + serde_json::Error, + ), + /// Other encoding error + #[error("Encoding error: {0}")] + Other(String), +} + /// XRPC method type #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum XrpcMethod { @@ -53,6 +74,13 @@ pub trait XrpcRequest: Serialize { /// Error type for this request type Err<'de>: Error + Deserialize<'de> + IntoStatic; + + /// Encode the request body for procedures. + /// + /// Default implementation serializes to JSON. Override for non-JSON encodings. + fn encode_body(&self) -> Result, EncodeError> { + Ok(serde_json::to_vec(self)?) + } } /// Error type for XRPC endpoints that don't define any errors diff --git a/crates/jacquard-lexicon/src/codegen.rs b/crates/jacquard-lexicon/src/codegen.rs index 05b1a08c..ec62d964 100644 --- a/crates/jacquard-lexicon/src/codegen.rs +++ b/crates/jacquard-lexicon/src/codegen.rs @@ -662,6 +662,7 @@ impl<'c> CodeGenerator<'c> { params_has_lifetime, has_output, has_errors, + false, // queries never have binary inputs )?; output.push(xrpc_impl); @@ -680,8 +681,16 @@ impl<'c> CodeGenerator<'c> { let type_base = self.def_to_type_name(nsid, def_name); let mut output = Vec::new(); - // Input bodies always have lifetimes (they get #[lexicon] attribute) - let params_has_lifetime = proc.input.is_some(); + // Check if input is a binary body (no schema) + let is_binary_input = proc + .input + .as_ref() + .map(|i| i.schema.is_none()) + .unwrap_or(false); + + // Input bodies with schemas have lifetimes (they get #[lexicon] attribute) + // Binary inputs don't have lifetimes + let params_has_lifetime = proc.input.is_some() && !is_binary_input; let has_input = proc.input.is_some(); let has_output = proc.output.is_some(); let has_errors = proc.errors.is_some(); @@ -726,6 +735,7 @@ impl<'c> CodeGenerator<'c> { params_has_lifetime, has_output, has_errors, + is_binary_input, )?; output.push(xrpc_impl); @@ -1424,23 +1434,41 @@ impl<'c> CodeGenerator<'c> { ) -> Result { let ident = syn::Ident::new(type_base, proc_macro2::Span::call_site()); + // Check if this is a binary body (no schema, just raw bytes) + let is_binary_body = body.schema.is_none(); + let fields = if let Some(schema) = &body.schema { self.generate_body_fields("", type_base, schema)? } else { - quote! {} + // Binary body: just a bytes field + quote! { + pub body: bytes::Bytes, + } }; let doc = self.generate_doc_comment(body.description.as_ref()); - // Input structs always get a lifetime since they have the #[lexicon] attribute - // which adds extra_data: BTreeMap<..., Data<'a>> - let struct_def = quote! { - #doc - #[jacquard_derive::lexicon] - #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] - #[serde(rename_all = "camelCase")] - pub struct #ident<'a> { - #fields + // Binary bodies don't need #[lexicon] attribute or lifetime + let struct_def = if is_binary_body { + quote! { + #doc + #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] + #[serde(rename_all = "camelCase")] + pub struct #ident { + #fields + } + } + } else { + // Input structs with schemas get a lifetime since they have the #[lexicon] attribute + // which adds extra_data: BTreeMap<..., Data<'a>> + quote! { + #doc + #[jacquard_derive::lexicon] + #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] + #[serde(rename_all = "camelCase")] + pub struct #ident<'a> { + #fields + } } }; @@ -1458,21 +1486,32 @@ impl<'c> CodeGenerator<'c> { } // Generate IntoStatic impl - let field_names: Vec<&str> = match &body.schema { - Some(crate::lexicon::LexXrpcBodySchema::Object(obj)) => { - obj.properties.keys().map(|k| k.as_str()).collect() - } - Some(_) => { - // For Ref or Union schemas, there's just a single flattened field - vec!["value"] - } - None => { - // No schema means no fields, just extra_data - vec![] + let into_static_impl = if is_binary_body { + // Binary bodies: simple clone of the Bytes field + quote! { + impl jacquard_common::IntoStatic for #ident { + type Output = #ident; + fn into_static(self) -> Self::Output { + self + } + } } + } else { + let field_names: Vec<&str> = match &body.schema { + Some(crate::lexicon::LexXrpcBodySchema::Object(obj)) => { + obj.properties.keys().map(|k| k.as_str()).collect() + } + Some(_) => { + // For Ref or Union schemas, there's just a single flattened field + vec!["value"] + } + None => { + // No schema means no fields, just extra_data + vec![] + } + }; + self.generate_into_static_for_struct(type_base, &field_names, true, true) }; - let into_static_impl = - self.generate_into_static_for_struct(type_base, &field_names, true, true); Ok(quote! { #struct_def @@ -1923,6 +1962,7 @@ impl<'c> CodeGenerator<'c> { params_has_lifetime: bool, has_output: bool, has_errors: bool, + is_binary_input: bool, ) -> Result { let output_type = if has_output { let output_ident = syn::Ident::new( @@ -1944,6 +1984,17 @@ impl<'c> CodeGenerator<'c> { quote! { jacquard_common::types::xrpc::GenericError<'de> } }; + // Generate encode_body() method for binary inputs + let encode_body_method = if is_binary_input { + quote! { + fn encode_body(&self) -> Result, jacquard_common::types::xrpc::EncodeError> { + Ok(self.body.to_vec()) + } + } + } else { + quote! {} + }; + if has_params { // Implement on the params/input struct itself let request_ident = syn::Ident::new(type_base, proc_macro2::Span::call_site()); @@ -1961,6 +2012,8 @@ impl<'c> CodeGenerator<'c> { type Output<'de> = #output_type; type Err<'de> = #error_type; + + #encode_body_method } }) } else { @@ -2311,7 +2364,7 @@ mod tests { println!("\n{}\n", formatted); // Check structure - assert!(formatted.contains("struct GetAuthorFeedParams")); + assert!(formatted.contains("struct GetAuthorFeed")); assert!(formatted.contains("struct GetAuthorFeedOutput")); assert!(formatted.contains("enum GetAuthorFeedError")); assert!(formatted.contains("pub actor")); diff --git a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky.rs b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky.rs index 4b64a3a6..0fd8ebd8 100644 --- a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky.rs +++ b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky.rs @@ -1,3 +1,8 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + pub mod embed; pub mod feed; -pub mod richtext; +pub mod richtext; \ No newline at end of file diff --git a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed.rs b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed.rs index d8198290..456ea3a9 100644 --- a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed.rs +++ b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed.rs @@ -1,5 +1,10 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + pub mod external; pub mod images; pub mod record; pub mod record_with_media; -pub mod video; +pub mod video; \ No newline at end of file diff --git a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/external.rs b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/external.rs index 7d0a0043..164c74d8 100644 --- a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/external.rs +++ b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/external.rs @@ -1,29 +1,99 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// Lexicon: app.bsky.embed.external +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct External<'a> { + #[serde(borrow)] pub description: jacquard_common::CowStr<'a>, - #[serde(skip_serializing_if = "Option::is_none")] - pub thumb: Option>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub thumb: std::option::Option>, + #[serde(borrow)] pub title: jacquard_common::CowStr<'a>, + #[serde(borrow)] pub uri: jacquard_common::types::string::Uri<'a>, } + +impl jacquard_common::IntoStatic for External<'_> { + type Output = External<'static>; + fn into_static(self) -> Self::Output { + External { + description: self.description.into_static(), + thumb: self.thumb.into_static(), + title: self.title.into_static(), + uri: self.uri.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + ///A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post). +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] -pub struct External<'a> { - pub external: jacquard_common::types::value::Data<'a>, +pub struct ExternalRecord<'a> { + #[serde(borrow)] + pub external: test_generated::app_bsky::embed::external::External<'a>, } + +impl jacquard_common::IntoStatic for ExternalRecord<'_> { + type Output = ExternalRecord<'static>; + fn into_static(self) -> Self::Output { + ExternalRecord { + external: self.external.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct View<'a> { - pub external: jacquard_common::types::value::Data<'a>, + #[serde(borrow)] + pub external: test_generated::app_bsky::embed::external::ViewExternal<'a>, +} + +impl jacquard_common::IntoStatic for View<'_> { + type Output = View<'static>; + fn into_static(self) -> Self::Output { + View { + external: self.external.into_static(), + extra_data: self.extra_data.into_static(), + } + } } + +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct ViewExternal<'a> { + #[serde(borrow)] pub description: jacquard_common::CowStr<'a>, - #[serde(skip_serializing_if = "Option::is_none")] - pub thumb: Option>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub thumb: std::option::Option>, + #[serde(borrow)] pub title: jacquard_common::CowStr<'a>, + #[serde(borrow)] pub uri: jacquard_common::types::string::Uri<'a>, } + +impl jacquard_common::IntoStatic for ViewExternal<'_> { + type Output = ViewExternal<'static>; + fn into_static(self) -> Self::Output { + ViewExternal { + description: self.description.into_static(), + thumb: self.thumb.into_static(), + title: self.title.into_static(), + uri: self.uri.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} \ No newline at end of file diff --git a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/images.rs b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/images.rs index e44333a5..c7e4f5d5 100644 --- a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/images.rs +++ b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/images.rs @@ -1,27 +1,99 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// Lexicon: app.bsky.embed.images +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Image<'a> { + ///Alt text description of the image, for accessibility. + #[serde(borrow)] pub alt: jacquard_common::CowStr<'a>, - #[serde(skip_serializing_if = "Option::is_none")] - pub aspect_ratio: Option>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub aspect_ratio: std::option::Option>, + #[serde(borrow)] pub image: jacquard_common::types::blob::Blob<'a>, } + +impl jacquard_common::IntoStatic for Image<'_> { + type Output = Image<'static>; + fn into_static(self) -> Self::Output { + Image { + alt: self.alt.into_static(), + aspect_ratio: self.aspect_ratio.into_static(), + image: self.image.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Images<'a> { - pub images: Vec>, + #[serde(borrow)] + pub images: Vec>, +} + +impl jacquard_common::IntoStatic for Images<'_> { + type Output = Images<'static>; + fn into_static(self) -> Self::Output { + Images { + images: self.images.into_static(), + extra_data: self.extra_data.into_static(), + } + } } + +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct View<'a> { - pub images: Vec>, + #[serde(borrow)] + pub images: Vec>, +} + +impl jacquard_common::IntoStatic for View<'_> { + type Output = View<'static>; + fn into_static(self) -> Self::Output { + View { + images: self.images.into_static(), + extra_data: self.extra_data.into_static(), + } + } } + +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct ViewImage<'a> { + ///Alt text description of the image, for accessibility. + #[serde(borrow)] pub alt: jacquard_common::CowStr<'a>, - #[serde(skip_serializing_if = "Option::is_none")] - pub aspect_ratio: Option>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub aspect_ratio: std::option::Option>, + ///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. + #[serde(borrow)] pub fullsize: jacquard_common::types::string::Uri<'a>, + ///Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View. + #[serde(borrow)] pub thumb: jacquard_common::types::string::Uri<'a>, } + +impl jacquard_common::IntoStatic for ViewImage<'_> { + type Output = ViewImage<'static>; + fn into_static(self) -> Self::Output { + ViewImage { + alt: self.alt.into_static(), + aspect_ratio: self.aspect_ratio.into_static(), + fullsize: self.fullsize.into_static(), + thumb: self.thumb.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} \ No newline at end of file diff --git a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/record.rs b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/record.rs index 928f5031..1c6d2daa 100644 --- a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/record.rs +++ b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/record.rs @@ -1,50 +1,169 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// Lexicon: app.bsky.embed.record +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Record<'a> { - pub record: test_generated::com_atproto::repo::StrongRef<'a>, + #[serde(borrow)] + pub record: test_generated::com_atproto::repo::strong_ref::StrongRef<'a>, } + +impl jacquard_common::IntoStatic for Record<'_> { + type Output = Record<'static>; + fn into_static(self) -> Self::Output { + Record { + record: self.record.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct View<'a> { - pub record: RecordRecord<'a>, + #[serde(borrow)] + pub record: ViewRecordRecord<'a>, +} + +#[jacquard_derive::open_union] +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(tag = "$type")] +#[serde(bound(deserialize = "'de: 'a"))] +pub enum ViewRecordRecord<'a> {} +impl jacquard_common::IntoStatic for ViewRecordRecord<'_> { + type Output = ViewRecordRecord<'static>; + fn into_static(self) -> Self::Output { + match self { + ViewRecordRecord::Unknown(v) => ViewRecordRecord::Unknown(v.into_static()), + } + } +} + +impl jacquard_common::IntoStatic for View<'_> { + type Output = View<'static>; + fn into_static(self) -> Self::Output { + View { + record: self.record.into_static(), + extra_data: self.extra_data.into_static(), + } + } } + +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct ViewBlocked<'a> { + #[serde(borrow)] pub author: jacquard_common::types::value::Data<'a>, pub blocked: bool, + #[serde(borrow)] pub uri: jacquard_common::types::string::AtUri<'a>, } + +impl jacquard_common::IntoStatic for ViewBlocked<'_> { + type Output = ViewBlocked<'static>; + fn into_static(self) -> Self::Output { + ViewBlocked { + author: self.author.into_static(), + blocked: self.blocked.into_static(), + uri: self.uri.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct ViewDetached<'a> { pub detached: bool, + #[serde(borrow)] pub uri: jacquard_common::types::string::AtUri<'a>, } + +impl jacquard_common::IntoStatic for ViewDetached<'_> { + type Output = ViewDetached<'static>; + fn into_static(self) -> Self::Output { + ViewDetached { + detached: self.detached.into_static(), + uri: self.uri.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct ViewNotFound<'a> { pub not_found: bool, + #[serde(borrow)] pub uri: jacquard_common::types::string::AtUri<'a>, } + +impl jacquard_common::IntoStatic for ViewNotFound<'_> { + type Output = ViewNotFound<'static>; + fn into_static(self) -> Self::Output { + ViewNotFound { + not_found: self.not_found.into_static(), + uri: self.uri.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct ViewRecord<'a> { + #[serde(borrow)] pub author: jacquard_common::types::value::Data<'a>, + #[serde(borrow)] pub cid: jacquard_common::types::string::Cid<'a>, - #[serde(skip_serializing_if = "Option::is_none")] - pub embeds: Option>>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub embeds: std::option::Option>>, pub indexed_at: jacquard_common::types::string::Datetime, - #[serde(skip_serializing_if = "Option::is_none")] - pub labels: Option>>, - #[serde(skip_serializing_if = "Option::is_none")] - pub like_count: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub quote_count: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub reply_count: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub repost_count: Option, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub labels: std::option::Option>>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + pub like_count: std::option::Option, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + pub quote_count: std::option::Option, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + pub reply_count: std::option::Option, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + pub repost_count: std::option::Option, + #[serde(borrow)] pub uri: jacquard_common::types::string::AtUri<'a>, + ///The record data itself. + #[serde(borrow)] pub value: jacquard_common::types::value::Data<'a>, } + +impl jacquard_common::IntoStatic for ViewRecord<'_> { + type Output = ViewRecord<'static>; + fn into_static(self) -> Self::Output { + ViewRecord { + author: self.author.into_static(), + cid: self.cid.into_static(), + embeds: self.embeds.into_static(), + indexed_at: self.indexed_at.into_static(), + labels: self.labels.into_static(), + like_count: self.like_count.into_static(), + quote_count: self.quote_count.into_static(), + reply_count: self.reply_count.into_static(), + repost_count: self.repost_count.into_static(), + uri: self.uri.into_static(), + value: self.value.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} \ No newline at end of file diff --git a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/record_with_media.rs b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/record_with_media.rs index f3d6c4b1..28fd9065 100644 --- a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/record_with_media.rs +++ b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/record_with_media.rs @@ -1,12 +1,110 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// Lexicon: app.bsky.embed.recordWithMedia +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct RecordWithMedia<'a> { - pub media: RecordMedia<'a>, - pub record: test_generated::app_bsky::embed::Record<'a>, + #[serde(borrow)] + pub media: RecordWithMediaRecordMedia<'a>, + #[serde(borrow)] + pub record: test_generated::app_bsky::embed::record::Record<'a>, } + +#[jacquard_derive::open_union] +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(tag = "$type")] +#[serde(bound(deserialize = "'de: 'a"))] +pub enum RecordWithMediaRecordMedia<'a> { + #[serde(rename = "app.bsky.embed.images")] + Images(Box>), + #[serde(rename = "app.bsky.embed.video")] + Video(Box>), + #[serde(rename = "app.bsky.embed.external")] + External(Box>), +} + +impl jacquard_common::IntoStatic for RecordWithMediaRecordMedia<'_> { + type Output = RecordWithMediaRecordMedia<'static>; + fn into_static(self) -> Self::Output { + match self { + RecordWithMediaRecordMedia::Images(v) => { + RecordWithMediaRecordMedia::Images(v.into_static()) + } + RecordWithMediaRecordMedia::Video(v) => { + RecordWithMediaRecordMedia::Video(v.into_static()) + } + RecordWithMediaRecordMedia::External(v) => { + RecordWithMediaRecordMedia::External(v.into_static()) + } + RecordWithMediaRecordMedia::Unknown(v) => { + RecordWithMediaRecordMedia::Unknown(v.into_static()) + } + } + } +} + +impl jacquard_common::IntoStatic for RecordWithMedia<'_> { + type Output = RecordWithMedia<'static>; + fn into_static(self) -> Self::Output { + RecordWithMedia { + media: self.media.into_static(), + record: self.record.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct View<'a> { - pub media: RecordMedia<'a>, - pub record: test_generated::app_bsky::embed::View<'a>, + #[serde(borrow)] + pub media: ViewRecordMedia<'a>, + #[serde(borrow)] + pub record: test_generated::app_bsky::embed::record::View<'a>, +} + +#[jacquard_derive::open_union] +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(tag = "$type")] +#[serde(bound(deserialize = "'de: 'a"))] +pub enum ViewRecordMedia<'a> { + #[serde(rename = "app.bsky.embed.images#view")] + ImagesView(Box>), + #[serde(rename = "app.bsky.embed.video#view")] + VideoView(Box>), + #[serde(rename = "app.bsky.embed.external#view")] + ExternalView(Box>), +} + +impl jacquard_common::IntoStatic for ViewRecordMedia<'_> { + type Output = ViewRecordMedia<'static>; + fn into_static(self) -> Self::Output { + match self { + ViewRecordMedia::ImagesView(v) => { + ViewRecordMedia::ImagesView(v.into_static()) + } + ViewRecordMedia::VideoView(v) => ViewRecordMedia::VideoView(v.into_static()), + ViewRecordMedia::ExternalView(v) => { + ViewRecordMedia::ExternalView(v.into_static()) + } + ViewRecordMedia::Unknown(v) => ViewRecordMedia::Unknown(v.into_static()), + } + } } + +impl jacquard_common::IntoStatic for View<'_> { + type Output = View<'static>; + fn into_static(self) -> Self::Output { + View { + media: self.media.into_static(), + record: self.record.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} \ No newline at end of file diff --git a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/video.rs b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/video.rs index 27fb0456..4a6856f2 100644 --- a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/video.rs +++ b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/video.rs @@ -1,29 +1,93 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// Lexicon: app.bsky.embed.video +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Caption<'a> { + #[serde(borrow)] pub file: jacquard_common::types::blob::Blob<'a>, pub lang: jacquard_common::types::string::Language, } + +impl jacquard_common::IntoStatic for Caption<'_> { + type Output = Caption<'static>; + fn into_static(self) -> Self::Output { + Caption { + file: self.file.into_static(), + lang: self.lang.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Video<'a> { - #[serde(skip_serializing_if = "Option::is_none")] - pub alt: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub aspect_ratio: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub captions: Option>>, + ///Alt text description of the video, for accessibility. + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub alt: std::option::Option>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub aspect_ratio: std::option::Option>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub captions: std::option::Option< + Vec>, + >, + ///The mp4 video file. May be up to 100mb, formerly limited to 50mb. + #[serde(borrow)] pub video: jacquard_common::types::blob::Blob<'a>, } + +impl jacquard_common::IntoStatic for Video<'_> { + type Output = Video<'static>; + fn into_static(self) -> Self::Output { + Video { + alt: self.alt.into_static(), + aspect_ratio: self.aspect_ratio.into_static(), + captions: self.captions.into_static(), + video: self.video.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct View<'a> { - #[serde(skip_serializing_if = "Option::is_none")] - pub alt: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub aspect_ratio: Option>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub alt: std::option::Option>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub aspect_ratio: std::option::Option>, + #[serde(borrow)] pub cid: jacquard_common::types::string::Cid<'a>, + #[serde(borrow)] pub playlist: jacquard_common::types::string::Uri<'a>, - #[serde(skip_serializing_if = "Option::is_none")] - pub thumbnail: Option>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub thumbnail: std::option::Option>, } + +impl jacquard_common::IntoStatic for View<'_> { + type Output = View<'static>; + fn into_static(self) -> Self::Output { + View { + alt: self.alt.into_static(), + aspect_ratio: self.aspect_ratio.into_static(), + cid: self.cid.into_static(), + playlist: self.playlist.into_static(), + thumbnail: self.thumbnail.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} \ No newline at end of file diff --git a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/feed.rs b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/feed.rs index 8ef66c7c..8de94672 100644 --- a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/feed.rs +++ b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/feed.rs @@ -1,2 +1,7 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + pub mod get_author_feed; -pub mod post; +pub mod post; \ No newline at end of file diff --git a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/feed/get_author_feed.rs b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/feed/get_author_feed.rs index f63e9a28..d0f1ec71 100644 --- a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/feed/get_author_feed.rs +++ b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/feed/get_author_feed.rs @@ -1,23 +1,66 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// Lexicon: app.bsky.feed.getAuthorFeed +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] -pub struct GetAuthorFeedParams<'a> { +pub struct GetAuthorFeed<'a> { + #[serde(borrow)] pub actor: jacquard_common::types::ident::AtIdentifier<'a>, - #[serde(skip_serializing_if = "Option::is_none")] - pub cursor: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub filter: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub include_pins: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub limit: Option, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub cursor: std::option::Option>, + ///(default: "posts_with_replies") + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub filter: std::option::Option>, + ///(default: false) + #[serde(skip_serializing_if = "std::option::Option::is_none")] + pub include_pins: std::option::Option, + ///(default: 50, min: 1, max: 100) + #[serde(skip_serializing_if = "std::option::Option::is_none")] + pub limit: std::option::Option, } + +impl jacquard_common::IntoStatic for GetAuthorFeed<'_> { + type Output = GetAuthorFeed<'static>; + fn into_static(self) -> Self::Output { + GetAuthorFeed { + actor: self.actor.into_static(), + cursor: self.cursor.into_static(), + filter: self.filter.into_static(), + include_pins: self.include_pins.into_static(), + limit: self.limit.into_static(), + } + } +} + +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct GetAuthorFeedOutput<'a> { - #[serde(skip_serializing_if = "Option::is_none")] - pub cursor: Option>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub cursor: std::option::Option>, + #[serde(borrow)] pub feed: Vec>, } + +impl jacquard_common::IntoStatic for GetAuthorFeedOutput<'_> { + type Output = GetAuthorFeedOutput<'static>; + fn into_static(self) -> Self::Output { + GetAuthorFeedOutput { + cursor: self.cursor.into_static(), + feed: self.feed.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + +#[jacquard_derive::open_union] #[derive( serde::Serialize, serde::Deserialize, @@ -29,13 +72,15 @@ pub struct GetAuthorFeedOutput<'a> { miette::Diagnostic )] #[serde(tag = "error", content = "message")] -pub enum GetAuthorFeedError { +#[serde(bound(deserialize = "'de: 'a"))] +pub enum GetAuthorFeedError<'a> { #[serde(rename = "BlockedActor")] - BlockedActor(Option>), + BlockedActor(std::option::Option), #[serde(rename = "BlockedByActor")] - BlockedByActor(Option>), + BlockedByActor(std::option::Option), } -impl std::fmt::Display for GetAuthorFeedError { + +impl std::fmt::Display for GetAuthorFeedError<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::BlockedActor(msg) => { @@ -52,6 +97,32 @@ impl std::fmt::Display for GetAuthorFeedError { } Ok(()) } + Self::Unknown(err) => write!(f, "Unknown error: {:?}", err), + } + } +} + +impl jacquard_common::IntoStatic for GetAuthorFeedError<'_> { + type Output = GetAuthorFeedError<'static>; + fn into_static(self) -> Self::Output { + match self { + GetAuthorFeedError::BlockedActor(v) => { + GetAuthorFeedError::BlockedActor(v.into_static()) + } + GetAuthorFeedError::BlockedByActor(v) => { + GetAuthorFeedError::BlockedByActor(v.into_static()) + } + GetAuthorFeedError::Unknown(v) => { + GetAuthorFeedError::Unknown(v.into_static()) + } } } } + +impl jacquard_common::types::xrpc::XrpcRequest for GetAuthorFeed<'_> { + const NSID: &'static str = "app.bsky.feed.getAuthorFeed"; + const METHOD: jacquard_common::types::xrpc::XrpcMethod = jacquard_common::types::xrpc::XrpcMethod::Query; + const OUTPUT_ENCODING: &'static str = "application/json"; + type Output<'de> = GetAuthorFeedOutput<'de>; + type Err<'de> = GetAuthorFeedError<'de>; +} \ No newline at end of file diff --git a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/feed/post.rs b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/feed/post.rs index de59f0c9..4fdc87b3 100644 --- a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/feed/post.rs +++ b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/feed/post.rs @@ -1,65 +1,192 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// Lexicon: app.bsky.feed.post +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + ///Deprecated: use facets instead. +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Entity<'a> { - pub index: jacquard_common::types::value::Data<'a>, + #[serde(borrow)] + pub index: test_generated::app_bsky::feed::post::TextSlice<'a>, + ///Expected values are 'mention' and 'link'. + #[serde(borrow)] pub r#type: jacquard_common::CowStr<'a>, + #[serde(borrow)] pub value: jacquard_common::CowStr<'a>, } + +impl jacquard_common::IntoStatic for Entity<'_> { + type Output = Entity<'static>; + fn into_static(self) -> Self::Output { + Entity { + index: self.index.into_static(), + r#type: self.r#type.into_static(), + value: self.value.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + ///Record containing a Bluesky post. #[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Post<'a> { + ///Client-declared timestamp when this post was originally created. pub created_at: jacquard_common::types::string::Datetime, - #[serde(skip_serializing_if = "Option::is_none")] - pub embed: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub entities: Option>>, - #[serde(skip_serializing_if = "Option::is_none")] - pub facets: Option>>, - #[serde(skip_serializing_if = "Option::is_none")] - pub labels: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub langs: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub reply: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub tags: Option>>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub embed: std::option::Option>, + ///DEPRECATED: replaced by app.bsky.richtext.facet. + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub entities: std::option::Option< + Vec>, + >, + ///Annotations of text (mentions, URLs, hashtags, etc) + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub facets: std::option::Option< + Vec>, + >, + ///Self-label values for this post. Effectively content warnings. + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub labels: std::option::Option>, + ///Indicates human language of post primary text content. + #[serde(skip_serializing_if = "std::option::Option::is_none")] + pub langs: std::option::Option>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub reply: std::option::Option>, + ///Additional hashtags, in addition to any included in post text and facets. + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub tags: std::option::Option>>, + ///The primary post content. May be an empty string, if there are embeds. + #[serde(borrow)] pub text: jacquard_common::CowStr<'a>, } + #[jacquard_derive::open_union] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(tag = "$type")] -pub enum RecordEmbed<'a> { +#[serde(bound(deserialize = "'de: 'a"))] +pub enum PostRecordEmbed<'a> { #[serde(rename = "app.bsky.embed.images")] - Images(Box>), + Images(Box>), #[serde(rename = "app.bsky.embed.video")] - Video(Box>), + Video(Box>), #[serde(rename = "app.bsky.embed.external")] - External(Box>), + External(Box>), #[serde(rename = "app.bsky.embed.record")] - Record(Box>), + Record(Box>), #[serde(rename = "app.bsky.embed.recordWithMedia")] - RecordWithMedia(Box>), + RecordWithMedia( + Box>, + ), +} + +impl jacquard_common::IntoStatic for PostRecordEmbed<'_> { + type Output = PostRecordEmbed<'static>; + fn into_static(self) -> Self::Output { + match self { + PostRecordEmbed::Images(v) => PostRecordEmbed::Images(v.into_static()), + PostRecordEmbed::Video(v) => PostRecordEmbed::Video(v.into_static()), + PostRecordEmbed::External(v) => PostRecordEmbed::External(v.into_static()), + PostRecordEmbed::Record(v) => PostRecordEmbed::Record(v.into_static()), + PostRecordEmbed::RecordWithMedia(v) => { + PostRecordEmbed::RecordWithMedia(v.into_static()) + } + PostRecordEmbed::Unknown(v) => PostRecordEmbed::Unknown(v.into_static()), + } + } } + #[jacquard_derive::open_union] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(tag = "$type")] -pub enum RecordLabels<'a> { +#[serde(bound(deserialize = "'de: 'a"))] +pub enum PostRecordLabels<'a> { #[serde(rename = "com.atproto.label.defs#selfLabels")] - SelfLabels(Box>), + DefsSelfLabels(Box>), +} + +impl jacquard_common::IntoStatic for PostRecordLabels<'_> { + type Output = PostRecordLabels<'static>; + fn into_static(self) -> Self::Output { + match self { + PostRecordLabels::DefsSelfLabels(v) => { + PostRecordLabels::DefsSelfLabels(v.into_static()) + } + PostRecordLabels::Unknown(v) => PostRecordLabels::Unknown(v.into_static()), + } + } +} + +impl jacquard_common::types::collection::Collection for Post<'_> { + const NSID: &'static str = "app.bsky.feed.post"; } + +impl jacquard_common::IntoStatic for Post<'_> { + type Output = Post<'static>; + fn into_static(self) -> Self::Output { + Post { + created_at: self.created_at.into_static(), + embed: self.embed.into_static(), + entities: self.entities.into_static(), + facets: self.facets.into_static(), + labels: self.labels.into_static(), + langs: self.langs.into_static(), + reply: self.reply.into_static(), + tags: self.tags.into_static(), + text: self.text.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct ReplyRef<'a> { - pub parent: test_generated::com_atproto::repo::StrongRef<'a>, - pub root: test_generated::com_atproto::repo::StrongRef<'a>, + #[serde(borrow)] + pub parent: test_generated::com_atproto::repo::strong_ref::StrongRef<'a>, + #[serde(borrow)] + pub root: test_generated::com_atproto::repo::strong_ref::StrongRef<'a>, } + +impl jacquard_common::IntoStatic for ReplyRef<'_> { + type Output = ReplyRef<'static>; + fn into_static(self) -> Self::Output { + ReplyRef { + parent: self.parent.into_static(), + root: self.root.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + ///Deprecated. Use app.bsky.richtext instead -- A text segment. Start is inclusive, end is exclusive. Indices are for utf16-encoded strings. +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct TextSlice<'a> { pub end: i64, pub start: i64, } + +impl jacquard_common::IntoStatic for TextSlice<'_> { + type Output = TextSlice<'static>; + fn into_static(self) -> Self::Output { + TextSlice { + end: self.end.into_static(), + start: self.start.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} \ No newline at end of file diff --git a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/richtext.rs b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/richtext.rs index d9893e86..b7b3177b 100644 --- a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/richtext.rs +++ b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/richtext.rs @@ -1 +1,6 @@ -pub mod facet; +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +pub mod facet; \ No newline at end of file diff --git a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/richtext/facet.rs b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/richtext/facet.rs index 49ed379c..fec1aacb 100644 --- a/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/richtext/facet.rs +++ b/crates/jacquard-lexicon/target/test_codegen_output/app_bsky/richtext/facet.rs @@ -1,32 +1,105 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// Lexicon: app.bsky.richtext.facet +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + ///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. +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct ByteSlice<'a> { pub byte_end: i64, pub byte_start: i64, } + +impl jacquard_common::IntoStatic for ByteSlice<'_> { + type Output = ByteSlice<'static>; + fn into_static(self) -> Self::Output { + ByteSlice { + byte_end: self.byte_end.into_static(), + byte_start: self.byte_start.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + ///Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL. +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Link<'a> { + #[serde(borrow)] pub uri: jacquard_common::types::string::Uri<'a>, } + +impl jacquard_common::IntoStatic for Link<'_> { + type Output = Link<'static>; + fn into_static(self) -> Self::Output { + Link { + uri: self.uri.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + ///Annotation of a sub-string within rich text. +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Facet<'a> { + #[serde(borrow)] pub features: Vec>, - pub index: jacquard_common::types::value::Data<'a>, + #[serde(borrow)] + pub index: test_generated::app_bsky::richtext::facet::ByteSlice<'a>, } + +impl jacquard_common::IntoStatic for Facet<'_> { + type Output = Facet<'static>; + fn into_static(self) -> Self::Output { + Facet { + features: self.features.into_static(), + index: self.index.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + ///Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID. +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Mention<'a> { + #[serde(borrow)] pub did: jacquard_common::types::string::Did<'a>, } + +impl jacquard_common::IntoStatic for Mention<'_> { + type Output = Mention<'static>; + fn into_static(self) -> Self::Output { + Mention { + did: self.did.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + ///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'). +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Tag<'a> { + #[serde(borrow)] pub tag: jacquard_common::CowStr<'a>, } + +impl jacquard_common::IntoStatic for Tag<'_> { + type Output = Tag<'static>; + fn into_static(self) -> Self::Output { + Tag { + tag: self.tag.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} \ No newline at end of file diff --git a/crates/jacquard-lexicon/target/test_codegen_output/com_atproto.rs b/crates/jacquard-lexicon/target/test_codegen_output/com_atproto.rs index 135de11a..09e30369 100644 --- a/crates/jacquard-lexicon/target/test_codegen_output/com_atproto.rs +++ b/crates/jacquard-lexicon/target/test_codegen_output/com_atproto.rs @@ -1,2 +1,7 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + pub mod label; -pub mod repo; +pub mod repo; \ No newline at end of file diff --git a/crates/jacquard-lexicon/target/test_codegen_output/com_atproto/label.rs b/crates/jacquard-lexicon/target/test_codegen_output/com_atproto/label.rs index f3403a26..38e9842d 100644 --- a/crates/jacquard-lexicon/target/test_codegen_output/com_atproto/label.rs +++ b/crates/jacquard-lexicon/target/test_codegen_output/com_atproto/label.rs @@ -1,49 +1,78 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// Lexicon: com.atproto.label.defs +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + ///Metadata tag on an atproto resource (eg, repo or record). +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Label<'a> { - #[serde(skip_serializing_if = "Option::is_none")] - pub cid: Option>, + ///Optionally, CID specifying the specific version of 'uri' resource this label applies to. + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub cid: std::option::Option>, + ///Timestamp when this label was created. pub cts: jacquard_common::types::string::Datetime, - #[serde(skip_serializing_if = "Option::is_none")] - pub exp: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub neg: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub sig: Option, + ///Timestamp at which this label expires (no longer applies). + #[serde(skip_serializing_if = "std::option::Option::is_none")] + pub exp: std::option::Option, + ///If true, this is a negation label, overwriting a previous label. + #[serde(skip_serializing_if = "std::option::Option::is_none")] + pub neg: std::option::Option, + ///Signature of dag-cbor encoded label. + #[serde(skip_serializing_if = "std::option::Option::is_none")] + pub sig: std::option::Option, + ///DID of the actor who created this label. + #[serde(borrow)] pub src: jacquard_common::types::string::Did<'a>, + ///AT URI of the record, repository (account), or other resource that this label applies to. + #[serde(borrow)] pub uri: jacquard_common::types::string::Uri<'a>, + ///The short string name of the value or type of this label. + #[serde(borrow)] pub val: jacquard_common::CowStr<'a>, - #[serde(skip_serializing_if = "Option::is_none")] - pub ver: Option, + ///The AT Protocol version of the label object. + #[serde(skip_serializing_if = "std::option::Option::is_none")] + pub ver: std::option::Option, } + +impl jacquard_common::IntoStatic for Label<'_> { + type Output = Label<'static>; + fn into_static(self) -> Self::Output { + Label { + cid: self.cid.into_static(), + cts: self.cts.into_static(), + exp: self.exp.into_static(), + neg: self.neg.into_static(), + sig: self.sig.into_static(), + src: self.src.into_static(), + uri: self.uri.into_static(), + val: self.val.into_static(), + ver: self.ver.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum LabelValue<'a> { - #[serde(rename = "!hide")] Hide, - #[serde(rename = "!no-promote")] NoPromote, - #[serde(rename = "!warn")] Warn, - #[serde(rename = "!no-unauthenticated")] NoUnauthenticated, - #[serde(rename = "dmca-violation")] DmcaViolation, - #[serde(rename = "doxxing")] Doxxing, - #[serde(rename = "porn")] Porn, - #[serde(rename = "sexual")] Sexual, - #[serde(rename = "nudity")] Nudity, - #[serde(rename = "nsfl")] Nsfl, - #[serde(rename = "gore")] Gore, - #[serde(untagged)] Other(jacquard_common::CowStr<'a>), } + impl<'a> LabelValue<'a> { pub fn as_str(&self) -> &str { match self { @@ -62,6 +91,7 @@ impl<'a> LabelValue<'a> { } } } + impl<'a> From<&'a str> for LabelValue<'a> { fn from(s: &'a str) -> Self { match s { @@ -80,6 +110,7 @@ impl<'a> From<&'a str> for LabelValue<'a> { } } } + impl<'a> From for LabelValue<'a> { fn from(s: String) -> Self { match s.as_str() { @@ -98,11 +129,13 @@ impl<'a> From for LabelValue<'a> { } } } + impl<'a> AsRef for LabelValue<'a> { fn as_ref(&self) -> &str { self.as_str() } } + impl<'a> serde::Serialize for LabelValue<'a> { fn serialize(&self, serializer: S) -> Result where @@ -111,6 +144,7 @@ impl<'a> serde::Serialize for LabelValue<'a> { serializer.serialize_str(self.as_str()) } } + impl<'de, 'a> serde::Deserialize<'de> for LabelValue<'a> where 'de: 'a, @@ -123,36 +157,131 @@ where Ok(Self::from(s)) } } + +impl jacquard_common::IntoStatic for LabelValue<'_> { + type Output = LabelValue<'static>; + fn into_static(self) -> Self::Output { + match self { + LabelValue::Hide => LabelValue::Hide, + LabelValue::NoPromote => LabelValue::NoPromote, + LabelValue::Warn => LabelValue::Warn, + LabelValue::NoUnauthenticated => LabelValue::NoUnauthenticated, + LabelValue::DmcaViolation => LabelValue::DmcaViolation, + LabelValue::Doxxing => LabelValue::Doxxing, + LabelValue::Porn => LabelValue::Porn, + LabelValue::Sexual => LabelValue::Sexual, + LabelValue::Nudity => LabelValue::Nudity, + LabelValue::Nsfl => LabelValue::Nsfl, + LabelValue::Gore => LabelValue::Gore, + LabelValue::Other(v) => LabelValue::Other(v.into_static()), + } + } +} + ///Declares a label value and its expected interpretations and behaviors. +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct LabelValueDefinition<'a> { - #[serde(skip_serializing_if = "Option::is_none")] - pub adult_only: Option, + ///Does the user need to have adult content enabled in order to configure this label? + #[serde(skip_serializing_if = "std::option::Option::is_none")] + pub adult_only: std::option::Option, + ///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. + #[serde(borrow)] pub blurs: jacquard_common::CowStr<'a>, - #[serde(skip_serializing_if = "Option::is_none")] - pub default_setting: Option>, + ///The default setting for this label. + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub default_setting: std::option::Option>, + ///The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+). + #[serde(borrow)] pub identifier: jacquard_common::CowStr<'a>, - pub locales: Vec>, + #[serde(borrow)] + pub locales: Vec< + test_generated::com_atproto::label::LabelValueDefinitionStrings<'a>, + >, + ///How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing. + #[serde(borrow)] pub severity: jacquard_common::CowStr<'a>, } + +impl jacquard_common::IntoStatic for LabelValueDefinition<'_> { + type Output = LabelValueDefinition<'static>; + fn into_static(self) -> Self::Output { + LabelValueDefinition { + adult_only: self.adult_only.into_static(), + blurs: self.blurs.into_static(), + default_setting: self.default_setting.into_static(), + identifier: self.identifier.into_static(), + locales: self.locales.into_static(), + severity: self.severity.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + ///Strings which describe the label in the UI, localized into a specific language. +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct LabelValueDefinitionStrings<'a> { + ///A longer description of what the label means and why it might be applied. + #[serde(borrow)] pub description: jacquard_common::CowStr<'a>, + ///The code of the language these strings are written in. pub lang: jacquard_common::types::string::Language, + ///A short human-readable name for the label. + #[serde(borrow)] pub name: jacquard_common::CowStr<'a>, } + +impl jacquard_common::IntoStatic for LabelValueDefinitionStrings<'_> { + type Output = LabelValueDefinitionStrings<'static>; + fn into_static(self) -> Self::Output { + LabelValueDefinitionStrings { + description: self.description.into_static(), + lang: self.lang.into_static(), + name: self.name.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + ///Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel. +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct SelfLabel<'a> { + ///The short string name of the value or type of this label. + #[serde(borrow)] pub val: jacquard_common::CowStr<'a>, } + +impl jacquard_common::IntoStatic for SelfLabel<'_> { + type Output = SelfLabel<'static>; + fn into_static(self) -> Self::Output { + SelfLabel { + val: self.val.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} + ///Metadata tags on an atproto record, published by the author within the record. +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct SelfLabels<'a> { - pub values: Vec>, + #[serde(borrow)] + pub values: Vec>, } + +impl jacquard_common::IntoStatic for SelfLabels<'_> { + type Output = SelfLabels<'static>; + fn into_static(self) -> Self::Output { + SelfLabels { + values: self.values.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} \ No newline at end of file diff --git a/crates/jacquard-lexicon/target/test_codegen_output/com_atproto/repo.rs b/crates/jacquard-lexicon/target/test_codegen_output/com_atproto/repo.rs index 2d56ce52..3c65f8fd 100644 --- a/crates/jacquard-lexicon/target/test_codegen_output/com_atproto/repo.rs +++ b/crates/jacquard-lexicon/target/test_codegen_output/com_atproto/repo.rs @@ -1 +1,6 @@ -pub mod strong_ref; +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +pub mod strong_ref; \ No newline at end of file diff --git a/crates/jacquard-lexicon/target/test_codegen_output/com_atproto/repo/strong_ref.rs b/crates/jacquard-lexicon/target/test_codegen_output/com_atproto/repo/strong_ref.rs index 5cd290c9..4108c3aa 100644 --- a/crates/jacquard-lexicon/target/test_codegen_output/com_atproto/repo/strong_ref.rs +++ b/crates/jacquard-lexicon/target/test_codegen_output/com_atproto/repo/strong_ref.rs @@ -1,6 +1,27 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// Lexicon: com.atproto.repo.strongRef +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +#[jacquard_derive::lexicon] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct StrongRef<'a> { + #[serde(borrow)] pub cid: jacquard_common::types::string::Cid<'a>, + #[serde(borrow)] pub uri: jacquard_common::types::string::AtUri<'a>, } + +impl jacquard_common::IntoStatic for StrongRef<'_> { + type Output = StrongRef<'static>; + fn into_static(self) -> Self::Output { + StrongRef { + cid: self.cid.into_static(), + uri: self.uri.into_static(), + extra_data: self.extra_data.into_static(), + } + } +} \ No newline at end of file diff --git a/crates/jacquard-lexicon/target/test_codegen_output/lib.rs b/crates/jacquard-lexicon/target/test_codegen_output/lib.rs index 16f69b66..b1981ef8 100644 --- a/crates/jacquard-lexicon/target/test_codegen_output/lib.rs +++ b/crates/jacquard-lexicon/target/test_codegen_output/lib.rs @@ -1,2 +1,7 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + pub mod app_bsky; -pub mod com_atproto; +pub mod com_atproto; \ No newline at end of file diff --git a/crates/jacquard/src/client.rs b/crates/jacquard/src/client.rs index 82b9fade..83619a70 100644 --- a/crates/jacquard/src/client.rs +++ b/crates/jacquard/src/client.rs @@ -4,16 +4,57 @@ mod response; use std::fmt::Display; use std::future::Future; -pub use error::{ClientError, Result}; use bytes::Bytes; +pub use error::{ClientError, Result}; use http::{ HeaderName, HeaderValue, Request, header::{AUTHORIZATION, CONTENT_TYPE, InvalidHeaderValue}, }; pub use response::Response; -use serde::Serialize; -use jacquard_common::{CowStr, types::xrpc::{XrpcMethod, XrpcRequest}}; +use jacquard_common::{ + CowStr, IntoStatic, + types::{ + string::{Did, Handle}, + xrpc::{XrpcMethod, XrpcRequest}, + }, +}; + +/// Implement HttpClient for reqwest::Client +impl HttpClient for reqwest::Client { + type Error = reqwest::Error; + + async fn send_http( + &self, + request: Request>, + ) -> core::result::Result>, Self::Error> { + // Convert http::Request to reqwest::Request + let (parts, body) = request.into_parts(); + + let mut req = self.request(parts.method, parts.uri.to_string()).body(body); + + // Copy headers + for (name, value) in parts.headers.iter() { + req = req.header(name.as_str(), value.as_bytes()); + } + + // Send request + let resp = req.send().await?; + + // Convert reqwest::Response to http::Response + let mut builder = http::Response::builder().status(resp.status()); + + // Copy headers + for (name, value) in resp.headers().iter() { + builder = builder.header(name.as_str(), value.as_bytes()); + } + + // Read body + let body = resp.bytes().await?.to_vec(); + + Ok(builder.body(body).expect("Failed to build response")) + } +} pub trait HttpClient { type Error: std::error::Error + Display + Send + Sync + 'static; @@ -98,11 +139,10 @@ where // Add query parameters for Query methods if let XrpcMethod::Query = R::METHOD { - if let Ok(qs) = serde_html_form::to_string(&request) { - if !qs.is_empty() { - uri.push('?'); - uri.push_str(&qs); - } + let qs = serde_html_form::to_string(&request).map_err(error::EncodeError::from)?; + if !qs.is_empty() { + uri.push('?'); + uri.push_str(&qs); } } @@ -139,33 +179,117 @@ where } // Serialize body for procedures - let body = if let XrpcMethod::Procedure(encoding) = R::METHOD { - if encoding == "application/json" { - serde_json::to_vec(&request).map_err(error::EncodeError::Json)? - } else { - // For other encodings, we'd need different serialization - vec![] - } + let body = if let XrpcMethod::Procedure(_) = R::METHOD { + request.encode_body()? } else { vec![] }; + // TODO: make this not panic let http_request = builder.body(body).expect("Failed to build HTTP request"); // Send HTTP request - let http_response = client.send_http(http_request).await.map_err(|e| { - error::TransportError::Other(Box::new(e)) - })?; + let http_response = client + .send_http(http_request) + .await + .map_err(|e| error::TransportError::Other(Box::new(e)))?; + + let status = http_response.status(); + let buffer = Bytes::from(http_response.into_body()); - // Check status - if !http_response.status().is_success() { + // XRPC errors come as 400/401 with structured error bodies + // Other error status codes (404, 500, etc.) are generic HTTP errors + if !status.is_success() && !matches!(status.as_u16(), 400 | 401) { return Err(ClientError::Http(error::HttpError { - status: http_response.status(), - body: Some(Bytes::from(http_response.body().clone())), + status, + body: Some(buffer), })); } - // Convert to Response - let buffer = Bytes::from(http_response.into_body()); - Ok(Response::new(buffer)) + // Response will parse XRPC errors for 400/401, or output for 2xx + Ok(Response::new(buffer, status)) +} + +/// Session information from createSession +#[derive(Debug, Clone)] +pub struct Session { + pub access_jwt: CowStr<'static>, + pub refresh_jwt: CowStr<'static>, + pub did: Did<'static>, + pub handle: Handle<'static>, +} + +impl From> for Session { + fn from( + output: jacquard_api::com_atproto::server::create_session::CreateSessionOutput<'_>, + ) -> Self { + Self { + access_jwt: output.access_jwt.into_static(), + refresh_jwt: output.refresh_jwt.into_static(), + did: output.did.into_static(), + handle: output.handle.into_static(), + } + } +} + +/// Authenticated XRPC client that includes session tokens +pub struct AuthenticatedClient { + client: C, + base_uri: CowStr<'static>, + session: Option, +} + +impl AuthenticatedClient { + /// Create a new authenticated client with a base URI + pub fn new(client: C, base_uri: CowStr<'static>) -> Self { + Self { + client, + base_uri: base_uri, + session: None, + } + } + + /// Set the session + pub fn set_session(&mut self, session: Session) { + self.session = Some(session); + } + + /// Get the current session + pub fn session(&self) -> Option<&Session> { + self.session.as_ref() + } + + /// Clear the session + pub fn clear_session(&mut self) { + self.session = None; + } +} + +impl HttpClient for AuthenticatedClient { + type Error = C::Error; + + fn send_http( + &self, + request: Request>, + ) -> impl Future>, Self::Error>> { + self.client.send_http(request) + } +} + +impl XrpcClient for AuthenticatedClient { + fn base_uri(&self) -> CowStr<'_> { + self.base_uri.clone() + } + + async fn authorization_token(&self, is_refresh: bool) -> Option> { + if is_refresh { + self.session + .as_ref() + .map(|s| AuthorizationToken::Bearer(s.refresh_jwt.clone())) + } else { + self.session + .as_ref() + .map(|s| AuthorizationToken::Bearer(s.access_jwt.clone())) + } + } } diff --git a/crates/jacquard/src/client/error.rs b/crates/jacquard/src/client/error.rs index ee366a21..170fb7a2 100644 --- a/crates/jacquard/src/client/error.rs +++ b/crates/jacquard/src/client/error.rs @@ -59,21 +59,8 @@ pub enum TransportError { Other(Box), } -#[derive(Debug, thiserror::Error, miette::Diagnostic)] -pub enum EncodeError { - #[error("Failed to serialize query: {0}")] - Query( - #[from] - #[source] - serde_html_form::ser::Error, - ), - #[error("Failed to serialize JSON: {0}")] - Json( - #[from] - #[source] - serde_json::Error, - ), -} +// Re-export EncodeError from common +pub use jacquard_common::types::xrpc::EncodeError; #[derive(Debug, thiserror::Error, miette::Diagnostic)] pub enum DecodeError { diff --git a/crates/jacquard/src/client/response.rs b/crates/jacquard/src/client/response.rs index 36307811..57522984 100644 --- a/crates/jacquard/src/client/response.rs +++ b/crates/jacquard/src/client/response.rs @@ -1,25 +1,36 @@ use bytes::Bytes; +use http::StatusCode; use jacquard_common::IntoStatic; use jacquard_common::types::xrpc::XrpcRequest; +use serde::Deserialize; use std::marker::PhantomData; +use super::error::AuthError; + /// XRPC response wrapper that owns the response buffer /// /// Allows borrowing from the buffer when parsing to avoid unnecessary allocations. pub struct Response { buffer: Bytes, + status: StatusCode, _marker: PhantomData, } impl Response { - /// Create a new response from a buffer - pub fn new(buffer: Bytes) -> Self { + /// Create a new response from a buffer and status code + pub fn new(buffer: Bytes, status: StatusCode) -> Self { Self { buffer, + status, _marker: PhantomData, } } + /// Get the HTTP status code + pub fn status(&self) -> StatusCode { + self.status + } + /// Parse the response, borrowing from the internal buffer pub fn parse(&self) -> Result, XrpcError>> { // Use a helper to make lifetime inference work @@ -35,13 +46,41 @@ impl Response { serde_json::from_slice(buffer) } - let output = parse_output::(&self.buffer); - if let Ok(output) = output { - Ok(output) - } else { - // Try to parse as error + // 200: parse as output + if self.status.is_success() { + match parse_output::(&self.buffer) { + Ok(output) => Ok(output), + Err(e) => Err(XrpcError::Decode(e)), + } + // 400: try typed XRPC error, fallback to generic error + } else if self.status.as_u16() == 400 { match parse_error::(&self.buffer) { Ok(error) => Err(XrpcError::Xrpc(error)), + Err(_) => { + // Fallback to generic error (InvalidRequest, ExpiredToken, etc.) + match serde_json::from_slice::(&self.buffer) { + Ok(generic) => { + // Map auth-related errors to AuthError + match generic.error.as_str() { + "ExpiredToken" => Err(XrpcError::Auth(AuthError::TokenExpired)), + "InvalidToken" => Err(XrpcError::Auth(AuthError::InvalidToken)), + _ => Err(XrpcError::Generic(generic)), + } + } + Err(e) => Err(XrpcError::Decode(e)), + } + } + } + // 401: always auth error + } else { + match serde_json::from_slice::(&self.buffer) { + Ok(generic) => { + match generic.error.as_str() { + "ExpiredToken" => Err(XrpcError::Auth(AuthError::TokenExpired)), + "InvalidToken" => Err(XrpcError::Auth(AuthError::InvalidToken)), + _ => Err(XrpcError::Auth(AuthError::NotAuthenticated)), + } + } Err(e) => Err(XrpcError::Decode(e)), } } @@ -66,13 +105,41 @@ impl Response { serde_json::from_slice(buffer) } - let output = parse_output::(&self.buffer); - if let Ok(output) = output { - Ok(output.into_static()) - } else { - // Try to parse as error + // 200: parse as output + if self.status.is_success() { + match parse_output::(&self.buffer) { + Ok(output) => Ok(output.into_static()), + Err(e) => Err(XrpcError::Decode(e)), + } + // 400: try typed XRPC error, fallback to generic error + } else if self.status.as_u16() == 400 { match parse_error::(&self.buffer) { Ok(error) => Err(XrpcError::Xrpc(error.into_static())), + Err(_) => { + // Fallback to generic error (InvalidRequest, ExpiredToken, etc.) + match serde_json::from_slice::(&self.buffer) { + Ok(generic) => { + // Map auth-related errors to AuthError + match generic.error.as_str() { + "ExpiredToken" => Err(XrpcError::Auth(AuthError::TokenExpired)), + "InvalidToken" => Err(XrpcError::Auth(AuthError::InvalidToken)), + _ => Err(XrpcError::Generic(generic)), + } + } + Err(e) => Err(XrpcError::Decode(e)), + } + } + } + // 401: always auth error + } else { + match serde_json::from_slice::(&self.buffer) { + Ok(generic) => { + match generic.error.as_str() { + "ExpiredToken" => Err(XrpcError::Auth(AuthError::TokenExpired)), + "InvalidToken" => Err(XrpcError::Auth(AuthError::InvalidToken)), + _ => Err(XrpcError::Auth(AuthError::NotAuthenticated)), + } + } Err(e) => Err(XrpcError::Decode(e)), } } @@ -84,10 +151,40 @@ impl Response { } } +/// Generic XRPC error format (for InvalidRequest, etc.) +#[derive(Debug, Clone, Deserialize)] +pub struct GenericXrpcError { + pub error: String, + pub message: Option, +} + +impl std::fmt::Display for GenericXrpcError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(msg) = &self.message { + write!(f, "{}: {}", self.error, msg) + } else { + write!(f, "{}", self.error) + } + } +} + +impl std::error::Error for GenericXrpcError {} + #[derive(Debug, thiserror::Error, miette::Diagnostic)] pub enum XrpcError { + /// Typed XRPC error from the endpoint's error enum #[error("XRPC error: {0}")] Xrpc(E), + + /// Authentication error (ExpiredToken, InvalidToken, etc.) + #[error("Authentication error: {0}")] + Auth(#[from] AuthError), + + /// Generic XRPC error (InvalidRequest, etc.) + #[error("XRPC error: {0}")] + Generic(GenericXrpcError), + + /// Failed to decode response #[error("Failed to decode response: {0}")] Decode(#[from] serde_json::Error), } -- 2.51.2