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
···
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>;
-
}
+
}
+2 -3
crates/jacquard-api/src/app_bsky/unspecced/get_onboarding_suggested_starter_packs.rs
···
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>;
-
}
+
}
+11 -8
crates/jacquard-api/src/app_bsky/video/upload_video.rs
···
// 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
}
}
···
}
}
-
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",
···
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<Vec<u8>, jacquard_common::types::xrpc::EncodeError> {
+
Ok(self.body.to_vec())
+
}
}
+11 -8
crates/jacquard-api/src/com_atproto/repo/import_repo.rs
···
// 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",
···
const OUTPUT_ENCODING: &'static str = "application/json";
type Output<'de> = ();
type Err<'de> = jacquard_common::types::xrpc::GenericError<'de>;
+
fn encode_body(&self) -> Result<Vec<u8>, jacquard_common::types::xrpc::EncodeError> {
+
Ok(self.body.to_vec())
+
}
}
+11 -8
crates/jacquard-api/src/com_atproto/repo/upload_blob.rs
···
// 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
}
}
···
}
}
-
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(
"*/*",
···
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<Vec<u8>, jacquard_common::types::xrpc::EncodeError> {
+
Ok(self.body.to_vec())
+
}
}
+29 -1
crates/jacquard-common/src/types/xrpc.rs
···
-
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::error::Error;
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)]
···
/// 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<Vec<u8>, EncodeError> {
+
Ok(serde_json::to_vec(self)?)
+
}
}
/// Error type for XRPC endpoints that don't define any errors
+79 -26
crates/jacquard-lexicon/src/codegen.rs
···
params_has_lifetime,
has_output,
has_errors,
+
false, // queries never have binary inputs
)?;
output.push(xrpc_impl);
···
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();
···
params_has_lifetime,
has_output,
has_errors,
+
is_binary_input,
)?;
output.push(xrpc_impl);
···
) -> Result<TokenStream> {
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
+
}
};
···
// 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
···
params_has_lifetime: bool,
has_output: bool,
has_errors: bool,
+
is_binary_input: bool,
) -> Result<TokenStream> {
let output_type = if has_output {
let output_ident = syn::Ident::new(
···
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<Vec<u8>, 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());
···
type Output<'de> = #output_type;
type Err<'de> = #error_type;
+
+
#encode_body_method
})
} else {
···
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"));
+6 -1
crates/jacquard-lexicon/target/test_codegen_output/app_bsky.rs
···
+
// @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;
+6 -1
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed.rs
···
+
// @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;
+77 -7
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/external.rs
···
+
// @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<jacquard_common::types::blob::Blob<'a>>,
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub thumb: std::option::Option<jacquard_common::types::blob::Blob<'a>>,
+
#[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<jacquard_common::types::string::Uri<'a>>,
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub thumb: std::option::Option<jacquard_common::types::string::Uri<'a>>,
+
#[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(),
+
}
+
}
+
}
+78 -6
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/images.rs
···
+
// @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<jacquard_common::types::value::Data<'a>>,
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub aspect_ratio: std::option::Option<jacquard_common::types::value::Data<'a>>,
+
#[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<jacquard_common::types::value::Data<'a>>,
+
#[serde(borrow)]
+
pub images: Vec<test_generated::app_bsky::embed::images::Image<'a>>,
}
+
+
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<jacquard_common::types::value::Data<'a>>,
+
#[serde(borrow)]
+
pub images: Vec<test_generated::app_bsky::embed::images::ViewImage<'a>>,
+
}
+
+
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<jacquard_common::types::value::Data<'a>>,
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub aspect_ratio: std::option::Option<jacquard_common::types::value::Data<'a>>,
+
///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(),
+
}
+
}
+
}
+133 -14
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/record.rs
···
+
// @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<Vec<jacquard_common::types::value::Data<'a>>>,
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub embeds: std::option::Option<Vec<jacquard_common::types::value::Data<'a>>>,
pub indexed_at: jacquard_common::types::string::Datetime,
-
#[serde(skip_serializing_if = "Option::is_none")]
-
pub labels: Option<Vec<test_generated::com_atproto::label::Label<'a>>>,
-
#[serde(skip_serializing_if = "Option::is_none")]
-
pub like_count: Option<i64>,
-
#[serde(skip_serializing_if = "Option::is_none")]
-
pub quote_count: Option<i64>,
-
#[serde(skip_serializing_if = "Option::is_none")]
-
pub reply_count: Option<i64>,
-
#[serde(skip_serializing_if = "Option::is_none")]
-
pub repost_count: Option<i64>,
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub labels: std::option::Option<Vec<test_generated::com_atproto::label::Label<'a>>>,
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
pub like_count: std::option::Option<i64>,
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
pub quote_count: std::option::Option<i64>,
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
pub reply_count: std::option::Option<i64>,
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
pub repost_count: std::option::Option<i64>,
+
#[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(),
+
}
+
}
+
}
+102 -4
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/record_with_media.rs
···
+
// @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<test_generated::app_bsky::embed::images::Images<'a>>),
+
#[serde(rename = "app.bsky.embed.video")]
+
Video(Box<test_generated::app_bsky::embed::video::Video<'a>>),
+
#[serde(rename = "app.bsky.embed.external")]
+
External(Box<test_generated::app_bsky::embed::external::ExternalRecord<'a>>),
+
}
+
+
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<test_generated::app_bsky::embed::images::View<'a>>),
+
#[serde(rename = "app.bsky.embed.video#view")]
+
VideoView(Box<test_generated::app_bsky::embed::video::View<'a>>),
+
#[serde(rename = "app.bsky.embed.external#view")]
+
ExternalView(Box<test_generated::app_bsky::embed::external::View<'a>>),
+
}
+
+
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(),
+
}
+
}
+
}
+76 -12
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/embed/video.rs
···
+
// @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<jacquard_common::CowStr<'a>>,
-
#[serde(skip_serializing_if = "Option::is_none")]
-
pub aspect_ratio: Option<jacquard_common::types::value::Data<'a>>,
-
#[serde(skip_serializing_if = "Option::is_none")]
-
pub captions: Option<Vec<jacquard_common::types::value::Data<'a>>>,
+
///Alt text description of the video, for accessibility.
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub alt: std::option::Option<jacquard_common::CowStr<'a>>,
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub aspect_ratio: std::option::Option<jacquard_common::types::value::Data<'a>>,
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub captions: std::option::Option<
+
Vec<test_generated::app_bsky::embed::video::Caption<'a>>,
+
>,
+
///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<jacquard_common::CowStr<'a>>,
-
#[serde(skip_serializing_if = "Option::is_none")]
-
pub aspect_ratio: Option<jacquard_common::types::value::Data<'a>>,
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub alt: std::option::Option<jacquard_common::CowStr<'a>>,
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub aspect_ratio: std::option::Option<jacquard_common::types::value::Data<'a>>,
+
#[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<jacquard_common::types::string::Uri<'a>>,
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub thumbnail: std::option::Option<jacquard_common::types::string::Uri<'a>>,
}
+
+
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(),
+
}
+
}
+
}
+6 -1
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/feed.rs
···
+
// @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;
+86 -15
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/feed/get_author_feed.rs
···
+
// @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<jacquard_common::CowStr<'a>>,
-
#[serde(skip_serializing_if = "Option::is_none")]
-
pub filter: Option<jacquard_common::CowStr<'a>>,
-
#[serde(skip_serializing_if = "Option::is_none")]
-
pub include_pins: Option<bool>,
-
#[serde(skip_serializing_if = "Option::is_none")]
-
pub limit: Option<i64>,
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub cursor: std::option::Option<jacquard_common::CowStr<'a>>,
+
///(default: "posts_with_replies")
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub filter: std::option::Option<jacquard_common::CowStr<'a>>,
+
///(default: false)
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
pub include_pins: std::option::Option<bool>,
+
///(default: 50, min: 1, max: 100)
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
pub limit: std::option::Option<i64>,
+
}
+
+
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<jacquard_common::CowStr<'a>>,
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub cursor: std::option::Option<jacquard_common::CowStr<'a>>,
+
#[serde(borrow)]
pub feed: Vec<jacquard_common::types::value::Data<'a>>,
}
+
+
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,
···
miette::Diagnostic
)]
#[serde(tag = "error", content = "message")]
-
pub enum GetAuthorFeedError {
+
#[serde(bound(deserialize = "'de: 'a"))]
+
pub enum GetAuthorFeedError<'a> {
#[serde(rename = "BlockedActor")]
-
BlockedActor(Option<jacquard_common::CowStr<'static>>),
+
BlockedActor(std::option::Option<String>),
#[serde(rename = "BlockedByActor")]
-
BlockedByActor(Option<jacquard_common::CowStr<'static>>),
+
BlockedByActor(std::option::Option<String>),
}
-
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) => {
···
}
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>;
+
}
+152 -25
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/feed/post.rs
···
+
// @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<RecordEmbed<'a>>,
-
#[serde(skip_serializing_if = "Option::is_none")]
-
pub entities: Option<Vec<jacquard_common::types::value::Data<'a>>>,
-
#[serde(skip_serializing_if = "Option::is_none")]
-
pub facets: Option<Vec<test_generated::app_bsky::richtext::Facet<'a>>>,
-
#[serde(skip_serializing_if = "Option::is_none")]
-
pub labels: Option<RecordLabels<'a>>,
-
#[serde(skip_serializing_if = "Option::is_none")]
-
pub langs: Option<Vec<jacquard_common::types::string::Language>>,
-
#[serde(skip_serializing_if = "Option::is_none")]
-
pub reply: Option<jacquard_common::types::value::Data<'a>>,
-
#[serde(skip_serializing_if = "Option::is_none")]
-
pub tags: Option<Vec<jacquard_common::CowStr<'a>>>,
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub embed: std::option::Option<PostRecordEmbed<'a>>,
+
///DEPRECATED: replaced by app.bsky.richtext.facet.
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub entities: std::option::Option<
+
Vec<test_generated::app_bsky::feed::post::Entity<'a>>,
+
>,
+
///Annotations of text (mentions, URLs, hashtags, etc)
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub facets: std::option::Option<
+
Vec<test_generated::app_bsky::richtext::facet::Facet<'a>>,
+
>,
+
///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<PostRecordLabels<'a>>,
+
///Indicates human language of post primary text content.
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
pub langs: std::option::Option<Vec<jacquard_common::types::string::Language>>,
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub reply: std::option::Option<test_generated::app_bsky::feed::post::ReplyRef<'a>>,
+
///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<Vec<jacquard_common::CowStr<'a>>>,
+
///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<test_generated::app_bsky::embed::Images<'a>>),
+
Images(Box<test_generated::app_bsky::embed::images::Images<'a>>),
#[serde(rename = "app.bsky.embed.video")]
-
Video(Box<test_generated::app_bsky::embed::Video<'a>>),
+
Video(Box<test_generated::app_bsky::embed::video::Video<'a>>),
#[serde(rename = "app.bsky.embed.external")]
-
External(Box<test_generated::app_bsky::embed::External<'a>>),
+
External(Box<test_generated::app_bsky::embed::external::ExternalRecord<'a>>),
#[serde(rename = "app.bsky.embed.record")]
-
Record(Box<test_generated::app_bsky::embed::Record<'a>>),
+
Record(Box<test_generated::app_bsky::embed::record::Record<'a>>),
#[serde(rename = "app.bsky.embed.recordWithMedia")]
-
RecordWithMedia(Box<test_generated::app_bsky::embed::RecordWithMedia<'a>>),
+
RecordWithMedia(
+
Box<test_generated::app_bsky::embed::record_with_media::RecordWithMedia<'a>>,
+
),
+
}
+
+
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<test_generated::com_atproto::label::SelfLabels<'a>>),
+
DefsSelfLabels(Box<test_generated::com_atproto::label::SelfLabels<'a>>),
}
+
+
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(),
+
}
+
}
+
}
+6 -1
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/richtext.rs
···
-
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;
+74 -1
crates/jacquard-lexicon/target/test_codegen_output/app_bsky/richtext/facet.rs
···
+
// @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<jacquard_common::types::value::Data<'a>>,
-
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(),
+
}
+
}
+
}
+6 -1
crates/jacquard-lexicon/target/test_codegen_output/com_atproto.rs
···
+
// @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;
+157 -28
crates/jacquard-lexicon/target/test_codegen_output/com_atproto/label.rs
···
+
// @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<jacquard_common::types::string::Cid<'a>>,
+
///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<jacquard_common::types::string::Cid<'a>>,
+
///Timestamp when this label was created.
pub cts: jacquard_common::types::string::Datetime,
-
#[serde(skip_serializing_if = "Option::is_none")]
-
pub exp: Option<jacquard_common::types::string::Datetime>,
-
#[serde(skip_serializing_if = "Option::is_none")]
-
pub neg: Option<bool>,
-
#[serde(skip_serializing_if = "Option::is_none")]
-
pub sig: Option<jacquard_common::types::value::Bytes>,
+
///Timestamp at which this label expires (no longer applies).
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
pub exp: std::option::Option<jacquard_common::types::string::Datetime>,
+
///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<bool>,
+
///Signature of dag-cbor encoded label.
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
pub sig: std::option::Option<bytes::Bytes>,
+
///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<i64>,
+
///The AT Protocol version of the label object.
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
pub ver: std::option::Option<i64>,
+
}
+
+
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 {
···
}
}
}
+
impl<'a> From<&'a str> for LabelValue<'a> {
fn from(s: &'a str) -> Self {
match s {
···
}
}
}
+
impl<'a> From<String> for LabelValue<'a> {
fn from(s: String) -> Self {
match s.as_str() {
···
}
}
}
+
impl<'a> AsRef<str> for LabelValue<'a> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
+
impl<'a> serde::Serialize for LabelValue<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
···
serializer.serialize_str(self.as_str())
}
}
+
impl<'de, 'a> serde::Deserialize<'de> for LabelValue<'a>
where
'de: 'a,
···
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<bool>,
+
///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<bool>,
+
///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<jacquard_common::CowStr<'a>>,
+
///The default setting for this label.
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub default_setting: std::option::Option<jacquard_common::CowStr<'a>>,
+
///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<jacquard_common::types::value::Data<'a>>,
+
#[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<jacquard_common::types::value::Data<'a>>,
+
#[serde(borrow)]
+
pub values: Vec<test_generated::com_atproto::label::SelfLabel<'a>>,
}
+
+
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(),
+
}
+
}
+
}
+6 -1
crates/jacquard-lexicon/target/test_codegen_output/com_atproto/repo.rs
···
-
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;
+21
crates/jacquard-lexicon/target/test_codegen_output/com_atproto/repo/strong_ref.rs
···
+
// @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(),
+
}
+
}
+
}
+6 -1
crates/jacquard-lexicon/target/test_codegen_output/lib.rs
···
+
// @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;
+149 -25
crates/jacquard/src/client.rs
···
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, 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<Vec<u8>>,
+
) -> core::result::Result<http::Response<Vec<u8>>, Self::Error> {
+
// Convert http::Request to reqwest::Request
+
let (parts, body) = request.into_parts();
-
use jacquard_common::{CowStr, types::xrpc::{XrpcMethod, XrpcRequest}};
+
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;
···
// 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);
}
}
···
}
// 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<jacquard_api::com_atproto::server::create_session::CreateSessionOutput<'_>> 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<C> {
+
client: C,
+
base_uri: CowStr<'static>,
+
session: Option<Session>,
+
}
+
+
impl<C> AuthenticatedClient<C> {
+
/// 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<C: HttpClient> HttpClient for AuthenticatedClient<C> {
+
type Error = C::Error;
+
+
fn send_http(
+
&self,
+
request: Request<Vec<u8>>,
+
) -> impl Future<Output = core::result::Result<http::Response<Vec<u8>>, Self::Error>> {
+
self.client.send_http(request)
+
}
+
}
+
+
impl<C: HttpClient> XrpcClient for AuthenticatedClient<C> {
+
fn base_uri(&self) -> CowStr<'_> {
+
self.base_uri.clone()
+
}
+
+
async fn authorization_token(&self, is_refresh: bool) -> Option<AuthorizationToken<'_>> {
+
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()))
+
}
+
}
}
+2 -15
crates/jacquard/src/client/error.rs
···
Other(Box<dyn std::error::Error + Send + Sync>),
}
-
#[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 {
+109 -12
crates/jacquard/src/client/response.rs
···
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<R: XrpcRequest> {
buffer: Bytes,
+
status: StatusCode,
_marker: PhantomData<R>,
}
impl<R: XrpcRequest> Response<R> {
-
/// 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
···
serde_json::from_slice(buffer)
}
-
let output = parse_output::<R>(&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::<R>(&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::<R>(&self.buffer) {
Ok(error) => Err(XrpcError::Xrpc(error)),
+
Err(_) => {
+
// Fallback to generic error (InvalidRequest, ExpiredToken, etc.)
+
match serde_json::from_slice::<GenericXrpcError>(&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::<GenericXrpcError>(&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)),
}
}
···
serde_json::from_slice(buffer)
}
-
let output = parse_output::<R>(&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::<R>(&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::<R>(&self.buffer) {
Ok(error) => Err(XrpcError::Xrpc(error.into_static())),
+
Err(_) => {
+
// Fallback to generic error (InvalidRequest, ExpiredToken, etc.)
+
match serde_json::from_slice::<GenericXrpcError>(&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::<GenericXrpcError>(&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)),
}
}
···
}
}
+
/// Generic XRPC error format (for InvalidRequest, etc.)
+
#[derive(Debug, Clone, Deserialize)]
+
pub struct GenericXrpcError {
+
pub error: String,
+
pub message: Option<String>,
+
}
+
+
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<E: std::error::Error + IntoStatic> {
+
/// 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),
}