use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote, quote_spanned};
use ruststream_sqlx_dialect::Role;
use syn::spanned::Spanned;
use syn::{DeriveInput, Generics, Ident, parse_quote_spanned};
use crate::parse::{Inbox, playing};
pub(crate) fn accessors(
input: &DeriveInput,
generics: &Generics,
inbox: &Inbox<'_>,
) -> TokenStream2 {
let name = &input.ident;
let accessor = |role: Role,
accessor: &str,
column: Option<&str>,
items: &dyn Fn(&Ident, TokenStream2) -> TokenStream2| {
playing(inbox, role).map(|field| {
let accessor = format_ident!("{accessor}");
let ty = field.ty;
let spanned = quote_spanned!(ty.span()=> #ty);
let mut generics = generics.clone();
if let Some(column) = column {
let column = format_ident!("{column}");
generics.make_where_clause().predicates.push(parse_quote_spanned!(ty.span()=>
for<'__c> #ty: ::ruststream_sqlx::#column
));
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let items = items(field.ident, spanned);
quote! {
#[automatically_derived]
impl #impl_generics ::ruststream_sqlx::#accessor for #name #ty_generics #where_clause {
#items
}
}
})
};
let key = accessor(
Role::PartitionKey,
"KeyRow",
Some("KeyColumn"),
&|ident, ty| {
quote! {
type Key = #ty;
fn partition_key(&self) -> &#ty {
&self.#ident
}
}
},
);
let attempt = accessor(
Role::Attempt,
"AttemptRow",
Some("AttemptColumn"),
&|ident, ty| {
quote! {
type Attempt = #ty;
fn attempt(&self) -> &#ty {
&self.#ident
}
}
},
);
let headers = accessor(
Role::Headers,
"HeaderRow",
Some("HeaderColumn"),
&|ident, ty| {
quote! {
type Column = #ty;
fn headers_mut(&mut self) -> &mut #ty {
&mut self.#ident
}
}
},
);
let message = accessor(Role::Payload, "PayloadRow", None, &|ident, ty| {
let bytes =
quote_spanned!(ty.span()=> ::core::convert::AsRef::<[u8]>::as_ref(&self.#ident));
quote! {
type Column = #ty;
fn payload(&self) -> &[u8] {
#bytes
}
}
})
.unwrap_or_else(|| carried(input, generics));
quote!(#key #attempt #headers #message)
}
pub(crate) fn carried(input: &DeriveInput, generics: &Generics) -> TokenStream2 {
let name = &input.ident;
let (_, ty_generics, _) = generics.split_for_impl();
let mut bounded = generics.clone();
bounded
.make_where_clause()
.predicates
.push(parse_quote_spanned!(name.span()=> #name #ty_generics: ::core::clone::Clone));
let (impl_generics, _, where_clause) = bounded.split_for_impl();
let private = quote!(::ruststream_sqlx::__private);
quote! {
#[automatically_derived]
impl #impl_generics #private::Input for #name #ty_generics #where_clause {
type Axis = #private::SoloCarried<Self>;
}
}
}
#[cfg(test)]
mod tests {
use syn::{DeriveInput, parse_quote};
use crate::inbox::tests::expanded;
#[test]
fn a_struct_without_a_payload_field_puts_its_row_on_the_carried_lane() -> syn::Result<()> {
let input: DeriveInput = parse_quote! {
#[inbox(table = "jobs")]
struct Job { #[field(id)] id: i64, #[field(headers)] meta: Json<Map>, note: String }
};
let impls = expanded(&input)?;
for expected in [
"typeTable=::ruststream_sqlx::InboxSpec<(::ruststream_sqlx::spec::Headers,)>;",
"impl::ruststream_sqlx::__private::InputforJobwhereJob:::core::clone::Clone\
{typeAxis=::ruststream_sqlx::__private::SoloCarried<Self>;}",
"impl::ruststream_sqlx::HeaderRowforJobwherefor<'__c>Json<Map>:::ruststream_sqlx::HeaderColumn\
{typeColumn=Json<Map>;\
fnheaders_mut(&mutself)->&mutJson<Map>{&mutself.meta}}",
] {
assert!(impls.contains(expected), "{expected}\n{impls}");
}
assert!(!impls.contains("PayloadRow"), "{impls}");
Ok(())
}
}