use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{Error, FnArg, Ident, ItemFn, LitStr, Pat, PatType, ReturnType, Type, TypePath};
use crate::parse::{
FailurePolicyArg, SubscriberArgs, WorkersArg, doc_description, position_type,
publish_result_reply, source_tokens, vec_element,
};
pub(crate) fn subscriber(args: &SubscriberArgs, func: &ItemFn) -> syn::Result<TokenStream> {
reject_raw_combinations(args)?;
let parts = handler_parts(args, func)?;
let body = if args.raw.is_some() {
match &args.publish_raw {
Some(reply_topic) => expand_publishing(&parts, func, reply_topic, true, true)?,
None if parts.out.is_some() || parts.seek.is_some() => expand_injected(&parts, true),
None => expand_subscribing(&parts, true),
}
} else if let Some(reply_topic) = &args.publish_raw {
expand_publishing(&parts, func, reply_topic, true, false)?
} else {
match (&args.batch, &args.publish) {
(true, Some(reply_topic)) => expand_batch_publishing(&parts, func, reply_topic)?,
(true, None) if parts.out.is_some() || parts.seek.is_some() => {
expand_batch_injected(&parts, func)
}
(true, None) => expand_batch(&parts, func),
(false, Some(reply_topic)) => {
expand_publishing(&parts, func, reply_topic, false, false)?
}
(false, None) if parts.out.is_some() || parts.seek.is_some() => {
expand_injected(&parts, false)
}
(false, None) => expand_subscribing(&parts, false),
}
};
Ok(body.into())
}
fn reject_raw_combinations(args: &SubscriberArgs) -> syn::Result<()> {
if let (Some(_), Some(publish_raw)) = (&args.publish, &args.publish_raw) {
return Err(Error::new(
publish_raw.span(),
"publish(..) and publish_raw(..) are mutually exclusive: one reply, one destination",
));
}
if let Some(publish_raw) = &args.publish_raw
&& args.batch
{
return Err(Error::new_spanned(
publish_raw,
"publish_raw(..) is not supported together with batch(..) yet; publish per message \
or use the encoded batch reply form",
));
}
let Some(raw) = &args.raw else {
return Ok(());
};
if args.publish.is_some() {
return Err(Error::new(
raw.span(),
"the reply of a raw handler is bytes and is never encoded; use \
publish_raw(\"dest\") instead of publish(..)",
));
}
if args.batch {
return Err(Error::new(
raw.span(),
"raw is not supported together with batch(..); a raw handler takes one delivery's \
payload as `&[u8]`",
));
}
if let Some(failure) = &args.on_failure
&& failure.decode.is_some()
{
return Err(Error::new(
raw.span(),
"on_failure(decode = ..) does not apply to raw: the payload is not decoded; keep \
only on_failure(panic = ..)",
));
}
Ok(())
}
struct HandlerParts<'a> {
vis: &'a syn::Visibility,
name: &'a Ident,
block: &'a syn::Block,
pat: &'a Pat,
input_ty: &'a Type,
description: TokenStream2,
source_ty: TokenStream2,
source_expr: TokenStream2,
input_schema: TokenStream2,
message_meta: TokenStream2,
ctx_param: TokenStream2,
ctx_ty: TokenStream2,
state_ty: Option<TokenStream2>,
extractors: Vec<(&'a Pat, &'a Type)>,
out: Option<(&'a Pat, &'a Type)>,
seek: Option<(&'a Pat, &'a Type)>,
workers_method: TokenStream2,
failure_method: TokenStream2,
}
fn context_type(func: &ItemFn) -> TokenStream2 {
let Some(FnArg::Typed(PatType { ty, .. })) = func.sig.inputs.get(1) else {
return quote!(());
};
if !is_context_param(ty) {
return inferred_context_type(func);
}
if let Type::Reference(reference) = &**ty
&& let Type::Path(path) = &*reference.elem
&& let Some(segment) = path.path.segments.last()
&& let syn::PathArguments::AngleBracketed(args) = &segment.arguments
{
for arg in &args.args {
if let syn::GenericArgument::Type(context_ty) = arg {
return quote!(#context_ty);
}
}
}
quote!(())
}
fn inferred_context_type(func: &ItemFn) -> TokenStream2 {
for arg in func.sig.inputs.iter().skip(1) {
if let FnArg::Typed(PatType { ty, .. }) = arg
&& let Some(key) = ctx_extractor_key(ty)
{
return quote!(<#key as ::ruststream::ContextField>::Context);
}
}
quote!(())
}
fn out_param_type(ty: &Type) -> Option<&Type> {
let Type::Path(path) = ty else {
return None;
};
let segment = path.path.segments.last()?;
if segment.ident != "Out" {
return None;
}
let syn::PathArguments::AngleBracketed(args) = &segment.arguments else {
return None;
};
let mut types = args.args.iter().filter_map(|arg| match arg {
syn::GenericArgument::Type(ty) => Some(ty),
_ => None,
});
let publisher = types.next()?;
if types.next().is_some() {
return None;
}
Some(publisher)
}
fn seek_param_type(ty: &Type) -> Option<&Type> {
let Type::Path(path) = ty else {
return None;
};
let segment = path.path.segments.last()?;
if segment.ident != "Seek" {
return None;
}
let syn::PathArguments::AngleBracketed(args) = &segment.arguments else {
return None;
};
let mut types = args.args.iter().filter_map(|arg| match arg {
syn::GenericArgument::Type(ty) => Some(ty),
_ => None,
});
let seeker = types.next()?;
if types.next().is_some() {
return None;
}
Some(seeker)
}
fn ctx_extractor_key(ty: &Type) -> Option<&Type> {
let Type::Path(path) = ty else {
return None;
};
let segment = path.path.segments.last()?;
if segment.ident != "Ctx" {
return None;
}
let syn::PathArguments::AngleBracketed(args) = &segment.arguments else {
return None;
};
let mut types = args.args.iter().filter_map(|arg| match arg {
syn::GenericArgument::Type(ty) => Some(ty),
_ => None,
});
let key = types.next()?;
if types.next().is_some() {
return None;
}
Some(key)
}
fn state_type(func: &ItemFn) -> Option<TokenStream2> {
let FnArg::Typed(PatType { ty, .. }) = func.sig.inputs.get(1)? else {
return None;
};
if !is_context_param(ty) {
return None;
}
if let Type::Reference(reference) = &**ty
&& let Type::Path(path) = &*reference.elem
&& let Some(segment) = path.path.segments.last()
&& let syn::PathArguments::AngleBracketed(args) = &segment.arguments
{
let mut types = args.args.iter().filter_map(|arg| match arg {
syn::GenericArgument::Type(ty) => Some(ty),
_ => None,
});
let _context_ty = types.next();
if let Some(state_ty) = types.next() {
return Some(quote!(#state_ty));
}
}
None
}
fn is_context_param(ty: &Type) -> bool {
if let Type::Reference(reference) = ty
&& reference.mutability.is_some()
&& let Type::Path(path) = &*reference.elem
&& let Some(segment) = path.path.segments.last()
{
return segment.ident == "Context";
}
false
}
fn collect_extractors(func: &ItemFn, ctx_present: bool) -> syn::Result<Vec<(&Pat, &Type)>> {
let start = if ctx_present { 2 } else { 1 };
let mut extractors = Vec::new();
for arg in func.sig.inputs.iter().skip(start) {
let FnArg::Typed(PatType { pat, ty, .. }) = arg else {
return Err(Error::new_spanned(
arg,
"a #[subscriber] handler cannot take `self`",
));
};
if is_context_param(ty) {
return Err(Error::new_spanned(
ty,
"the `&mut Context` parameter must come immediately after the message, before any \
extractor parameters",
));
}
extractors.push((&**pat, &**ty));
}
Ok(extractors)
}
fn extractor_where(
extractors: &[(&Pat, &Type)],
ctx_ty: &TokenStream2,
state: &TokenStream2,
) -> TokenStream2 {
if extractors.is_empty() {
return quote!();
}
let preds = extractors
.iter()
.map(|(_, ty)| quote!(#ty: ::ruststream::runtime::FromContext<#ctx_ty, #state>));
quote!(where #(#preds),*)
}
fn extractor_prelude(
extractors: &[(&Pat, &Type)],
ctx_param: &TokenStream2,
ctx_ty: &TokenStream2,
state: &TokenStream2,
reject: &TokenStream2,
) -> TokenStream2 {
let binds = extractors.iter().map(|(pat, ty)| {
quote! {
let #pat = match <#ty as ::ruststream::runtime::FromContext<#ctx_ty, #state>>::from_context(
&mut *#ctx_param,
)
.await
{
::core::result::Result::Ok(__rs_value) => __rs_value,
::core::result::Result::Err(__rs_err) => { #reject; }
};
}
});
quote!(#(#binds)*)
}
fn failure_method(args: &SubscriberArgs) -> TokenStream2 {
let Some(failure) = &args.on_failure else {
return quote!();
};
let panic = failure
.panic
.as_ref()
.map(failure_policy_tokens)
.map(|policy| quote!(.with_panic(#policy)));
let decode = failure
.decode
.as_ref()
.map(failure_policy_tokens)
.map(|policy| quote!(.with_decode(#policy)));
quote! {
fn failure_policies(&self) -> ::ruststream::runtime::FailurePolicies {
::ruststream::runtime::FailurePolicies::default() #panic #decode
}
}
}
fn failure_policy_tokens(policy: &FailurePolicyArg) -> TokenStream2 {
match policy {
FailurePolicyArg::FailFast => quote!(::ruststream::runtime::FailurePolicy::FailFast),
FailurePolicyArg::Drop => quote!(::ruststream::runtime::FailurePolicy::Drop),
FailurePolicyArg::Retry => quote!(::ruststream::runtime::FailurePolicy::Retry),
FailurePolicyArg::RetryAfter(expr) => {
quote!(::ruststream::runtime::FailurePolicy::RetryAfter(#expr))
}
FailurePolicyArg::Skip => quote!(::ruststream::runtime::FailurePolicy::Skip),
}
}
fn workers_method(args: &SubscriberArgs) -> syn::Result<TokenStream2> {
let Some(WorkersArg { count, by_key }) = &args.workers else {
return Ok(quote!());
};
if count.base10_parse::<usize>()? == 0 {
return Err(Error::new(
count.span(),
"workers(0) is not a policy; the minimum is 1",
));
}
if let Some(marker) = by_key {
if args.batch {
return Err(Error::new(
marker.span(),
"by_key lanes order single messages per key; they do not apply to batch(..) \
forms",
));
}
return Ok(quote! {
fn workers(&self) -> ::ruststream::runtime::Workers {
::ruststream::runtime::Workers::keyed(
match ::core::num::NonZeroUsize::new(#count) {
::core::option::Option::Some(count) => count,
::core::option::Option::None => ::core::num::NonZeroUsize::MIN,
},
)
}
});
}
Ok(quote! {
fn workers(&self) -> ::ruststream::runtime::Workers {
::ruststream::runtime::Workers::pool(
match ::core::num::NonZeroUsize::new(#count) {
::core::option::Option::Some(count) => count,
::core::option::Option::None => ::core::num::NonZeroUsize::MIN,
},
)
}
})
}
fn split_out<'a>(
extractors: &mut Vec<(&'a Pat, &'a Type)>,
) -> syn::Result<Option<(&'a Pat, &'a Type)>> {
let mut out = None;
extractors.retain(|(pat, ty)| {
if let Some(publisher_ty) = out_param_type(ty) {
if out.is_none() {
out = Some((*pat, publisher_ty));
return false;
}
}
true
});
if let Some((_, dup)) = extractors
.iter()
.find(|(_, ty)| out_param_type(ty).is_some())
{
return Err(Error::new_spanned(
dup,
"a #[subscriber] handler takes at most one Out parameter",
));
}
Ok(out)
}
fn split_seek<'a>(
extractors: &mut Vec<(&'a Pat, &'a Type)>,
) -> syn::Result<Option<(&'a Pat, &'a Type)>> {
let mut seek = None;
extractors.retain(|(pat, ty)| {
if let Some(seeker_ty) = seek_param_type(ty) {
if seek.is_none() {
seek = Some((*pat, seeker_ty));
return false;
}
}
true
});
if let Some((_, dup)) = extractors
.iter()
.find(|(_, ty)| seek_param_type(ty).is_some())
{
return Err(Error::new_spanned(
dup,
"a #[subscriber] handler takes at most one Seek parameter",
));
}
Ok(seek)
}
fn input_type<'a>(
args: &SubscriberArgs,
reference: &'a syn::TypeReference,
) -> syn::Result<&'a Type> {
if args.batch {
return match &*reference.elem {
Type::Slice(slice) => Ok(&slice.elem),
other => Err(Error::new_spanned(
other,
"a batch handler takes the whole batch as a slice: `&[T]`",
)),
};
}
if args.raw.is_some() {
return match &*reference.elem {
elem if is_u8_slice(elem) => Ok(elem),
other => Err(Error::new_spanned(
other,
"a raw subscriber receives the payload bytes: make the message parameter \
`&[u8]`, or drop `raw` to decode into a typed value",
)),
};
}
if matches!(&*reference.elem, Type::Slice(_)) {
return Err(Error::new_spanned(
&reference.elem,
"a slice parameter needs the batch source form: #[subscriber(batch(..))]; for the \
undecoded payload bytes use #[subscriber(.., raw)]",
));
}
Ok(&reference.elem)
}
fn is_u8_slice(ty: &Type) -> bool {
if let Type::Slice(slice) = ty
&& let Type::Path(TypePath {
qself: None, path, ..
}) = &*slice.elem
{
return path.is_ident("u8");
}
false
}
fn handler_parts<'a>(args: &SubscriberArgs, func: &'a ItemFn) -> syn::Result<HandlerParts<'a>> {
let first = func.sig.inputs.first().ok_or_else(|| {
Error::new_spanned(
&func.sig,
"a #[subscriber] handler must take exactly one message parameter",
)
})?;
let FnArg::Typed(PatType { pat, ty, .. }) = first else {
return Err(Error::new_spanned(
first,
"a #[subscriber] handler cannot take `self`",
));
};
let Type::Reference(reference) = &**ty else {
return Err(Error::new_spanned(
ty,
"the message parameter must be a reference `&T`",
));
};
let input_ty = input_type(args, reference)?;
let description = doc_description(&func.attrs);
let (source_ty, source_expr) = source_tokens(&args.source)?;
let (source_ty, source_expr) = match &args.start_at {
Some(position) => {
let position_ty = position_type(position)?;
(
quote!(::ruststream::StartAt<#source_ty, #position_ty>),
quote!(::ruststream::StartAt::new(#source_expr, #position)),
)
}
None => (source_ty, source_expr),
};
let input_schema = quote! {
fn input_schema(&self) -> ::core::option::Option<::std::string::String> {
#[allow(unused_imports)]
use ::ruststream::__private::NoSchemaProbe as _;
::ruststream::__private::Probe::<#input_ty>::new().schema_json()
}
};
let message_meta = quote! {
fn message_name(&self) -> ::core::option::Option<&'static str> {
#[allow(unused_imports)]
use ::ruststream::__private::NoMessageProbe as _;
::ruststream::__private::Probe::<#input_ty>::new().message_name()
}
fn message_description(&self) -> ::core::option::Option<&'static str> {
#[allow(unused_imports)]
use ::ruststream::__private::NoMessageProbe as _;
::ruststream::__private::Probe::<#input_ty>::new().message_description()
}
};
let ctx_arg = func
.sig
.inputs
.get(1)
.filter(|arg| matches!(arg, FnArg::Typed(pt) if is_context_param(&pt.ty)));
let ctx_param = if let Some(FnArg::Typed(PatType { pat, .. })) = ctx_arg {
quote!(#pat)
} else {
quote!(_ctx)
};
let mut extractors = collect_extractors(func, ctx_arg.is_some())?;
let out = split_out(&mut extractors)?;
let seek = split_seek(&mut extractors)?;
let ctx_ty = context_type(func);
let state_ty = state_type(func);
let workers_method = workers_method(args)?;
let failure_method = failure_method(args);
Ok(HandlerParts {
vis: &func.vis,
name: &func.sig.ident,
block: &func.block,
pat,
input_ty,
description,
source_ty,
source_expr,
input_schema,
message_meta,
ctx_param,
ctx_ty,
state_ty,
extractors,
out,
seek,
workers_method,
failure_method,
})
}
fn batch_reply_body<'a>(
declared_ty: &'a Type,
block: &syn::Block,
) -> syn::Result<(&'a Type, TokenStream2)> {
if let Some(ok_ty) = publish_result_reply(declared_ty) {
let Some(elem) = vec_element(ok_ty) else {
return Err(Error::new_spanned(
ok_ty,
"a batch publishing handler replies with a Vec: Result<Vec<Reply>, HandlerResult>",
));
};
Ok((elem, quote!((async move #block).await)))
} else {
let Some(elem) = vec_element(declared_ty) else {
return Err(Error::new_spanned(
declared_ty,
"a batch publishing handler returns the replies: Vec<Reply>, or \
Result<Vec<Reply>, HandlerResult>",
));
};
Ok((
elem,
quote!(::core::result::Result::Ok((async move #block).await)),
))
}
}
fn expand_batch_publishing(
parts: &HandlerParts<'_>,
func: &ItemFn,
reply_topic: &LitStr,
) -> syn::Result<TokenStream2> {
let HandlerParts {
vis,
name,
block,
pat,
input_ty,
description,
source_ty,
source_expr,
input_schema,
message_meta,
ctx_param,
ctx_ty: _,
state_ty,
extractors,
out,
seek,
workers_method,
failure_method,
} = parts;
let declared_ty = match &func.sig.output {
ReturnType::Type(_, ty) => &**ty,
ReturnType::Default => {
return Err(Error::new_spanned(
&func.sig,
"a batch publishing handler must return the replies: Vec<Reply>, or \
Result<Vec<Reply>, HandlerResult>",
));
}
};
let (reply_elem, call_body) = batch_reply_body(declared_ty, block)?;
let (injection_tys, injection_pats, _single_form) = injection_pieces(*out, *seek);
let form = if out.is_some() {
quote!(::ruststream::runtime::forms::BatchPublishingOut)
} else {
quote!(::ruststream::runtime::forms::BatchPublishing)
};
let (impl_generics, state_in_ctx) = match &state_ty {
Some(state_ty) => (quote!(), quote!(#state_ty)),
None => (
quote!(<__RsState: ::core::marker::Send + ::core::marker::Sync>),
quote!(__RsState),
),
};
let unit_ctx = quote!(());
let where_clause = extractor_where(extractors, &unit_ctx, &state_in_ctx);
let prelude = extractor_prelude(
extractors,
ctx_param,
&unit_ctx,
&state_in_ctx,
"e!(
return ::core::result::Result::Err(::core::convert::Into::<
::ruststream::runtime::HandlerResult,
>::into(__rs_err),)
),
);
Ok(quote! {
#[allow(non_camel_case_types)]
#vis struct #name;
impl ::ruststream::runtime::IncludeDef for #name {
type Form = #form;
}
impl ::ruststream::runtime::BatchPublishingDef for #name {
type Input = ::ruststream::runtime::Decoded<#input_ty>;
type Injections = (#(#injection_tys,)*);
type Reply = #reply_elem;
type Source = #source_ty;
fn source(&self) -> Self::Source { #source_expr }
fn reply_name(&self) -> &str { #reply_topic }
#workers_method
#failure_method
fn description(&self) -> ::core::option::Option<&str> {
#description
}
#input_schema
#message_meta
}
impl #impl_generics
::ruststream::runtime::BatchPublishingCall<#state_in_ctx> for #name
#where_clause
{
async fn call(
&self,
#pat: &[#input_ty],
__rs_inj: &Self::Injections,
#ctx_param: &mut ::ruststream::runtime::Context<'_, (), #state_in_ctx>,
) -> ::core::result::Result<
::std::vec::Vec<#reply_elem>,
::ruststream::runtime::HandlerResult,
> {
#prelude
let (#(#injection_pats,)*) = __rs_inj;
#call_body
}
}
})
}
fn expand_batch(parts: &HandlerParts<'_>, func: &ItemFn) -> TokenStream2 {
let HandlerParts {
vis,
name,
block,
pat,
input_ty,
description,
source_ty,
source_expr,
input_schema,
message_meta,
ctx_param,
ctx_ty: _,
state_ty,
extractors,
out: _,
seek: _,
workers_method,
failure_method,
} = parts;
let outcome_ty = match &func.sig.output {
ReturnType::Type(_, ty) => quote!(#ty),
ReturnType::Default => quote!(()),
};
let (impl_generics, state_in_ctx) = match &state_ty {
Some(state_ty) => (quote!(), quote!(#state_ty)),
None => (
quote!(<__RsState: ::core::marker::Send + ::core::marker::Sync>),
quote!(__RsState),
),
};
let unit_ctx = quote!(());
let where_clause = extractor_where(extractors, &unit_ctx, &state_in_ctx);
let prelude = extractor_prelude(
extractors,
ctx_param,
&unit_ctx,
&state_in_ctx,
"e!(
return ::ruststream::runtime::IntoBatchResult::into_batch_result(
::core::convert::Into::<::ruststream::runtime::HandlerResult>::into(__rs_err),
)
),
);
quote! {
#[derive(Clone, Copy)]
#[allow(non_camel_case_types)]
#vis struct #name;
impl #impl_generics
::ruststream::runtime::SliceHandler<#input_ty, #state_in_ctx> for #name
#where_clause
{
async fn handle_slice(
&self,
#pat: &[#input_ty],
#ctx_param: &mut ::ruststream::runtime::Context<'_, (), #state_in_ctx>,
) -> ::ruststream::runtime::BatchResult {
#prelude
let outcome: #outcome_ty = (async move #block).await;
::ruststream::runtime::IntoBatchResult::into_batch_result(outcome)
}
}
impl ::ruststream::runtime::IncludeDef for #name {
type Form = ::ruststream::runtime::forms::Batch;
}
impl ::ruststream::runtime::BatchDef for #name {
type Input = ::ruststream::runtime::Decoded<#input_ty>;
type Handler = Self;
type Source = #source_ty;
fn source(&self) -> Self::Source { #source_expr }
#workers_method
#failure_method
fn description(&self) -> ::core::option::Option<&str> {
#description
}
#input_schema
#message_meta
fn into_handler(self) -> Self { self }
}
}
}
fn expand_batch_injected(parts: &HandlerParts<'_>, func: &ItemFn) -> TokenStream2 {
let HandlerParts {
vis,
name,
block,
pat,
input_ty,
description,
source_ty,
source_expr,
input_schema,
message_meta,
ctx_param,
ctx_ty: _,
state_ty,
extractors,
out,
seek,
workers_method,
failure_method,
} = parts;
let (injection_tys, injection_pats, _single_form) = injection_pieces(*out, *seek);
let form = if out.is_some() {
quote!(::ruststream::runtime::forms::BatchOut)
} else {
quote!(::ruststream::runtime::forms::BatchSeek)
};
let outcome_ty = match &func.sig.output {
ReturnType::Type(_, ty) => quote!(#ty),
ReturnType::Default => quote!(()),
};
let (impl_generics, state_in_ctx) = match &state_ty {
Some(state_ty) => (quote!(), quote!(#state_ty)),
None => (
quote!(<__RsState: ::core::marker::Send + ::core::marker::Sync>),
quote!(__RsState),
),
};
let unit_ctx = quote!(());
let where_clause = extractor_where(extractors, &unit_ctx, &state_in_ctx);
let prelude = extractor_prelude(
extractors,
ctx_param,
&unit_ctx,
&state_in_ctx,
"e!(
return ::ruststream::runtime::IntoBatchResult::into_batch_result(
::core::convert::Into::<::ruststream::runtime::HandlerResult>::into(__rs_err),
)
),
);
quote! {
#[derive(Clone, Copy)]
#[allow(non_camel_case_types)]
#vis struct #name;
impl ::ruststream::runtime::IncludeDef for #name {
type Form = #form;
}
impl ::ruststream::runtime::BatchInjectDef for #name {
type Input = ::ruststream::runtime::Decoded<#input_ty>;
type Source = #source_ty;
type Injections = (#(#injection_tys,)*);
fn source(&self) -> Self::Source { #source_expr }
#workers_method
#failure_method
fn description(&self) -> ::core::option::Option<&str> {
#description
}
#input_schema
#message_meta
}
impl #impl_generics
::ruststream::runtime::BatchInjectCall<#state_in_ctx> for #name
#where_clause
{
async fn call(
&self,
#pat: &[#input_ty],
__rs_inj: &Self::Injections,
#ctx_param: &mut ::ruststream::runtime::Context<'_, (), #state_in_ctx>,
) -> ::ruststream::runtime::BatchResult {
#prelude
let (#(#injection_pats,)*) = __rs_inj;
let outcome: #outcome_ty = (async move #block).await;
::ruststream::runtime::IntoBatchResult::into_batch_result(outcome)
}
}
}
}
fn expand_publishing(
parts: &HandlerParts<'_>,
func: &ItemFn,
reply_topic: &LitStr,
bare: bool,
raw_input: bool,
) -> syn::Result<TokenStream2> {
let HandlerParts {
vis,
name,
block,
pat,
input_ty,
description,
source_ty,
source_expr,
input_schema,
message_meta,
ctx_param,
ctx_ty,
state_ty,
extractors,
out,
seek,
workers_method,
failure_method,
} = parts;
let (reply_ty, call_body) = publishing_reply(func, block, bare)?;
let (injection_tys, injection_pats, _single_form) = injection_pieces(*out, *seek);
let form = match (bare, out.is_some()) {
(false, false) => quote!(::ruststream::runtime::forms::Publishing),
(false, true) => quote!(::ruststream::runtime::forms::PublishingOut),
(true, false) => quote!(::ruststream::runtime::forms::RawReply),
(true, true) => quote!(::ruststream::runtime::forms::RawReplyOut),
};
let (input_kind, input_param, input_schema, message_meta) =
input_pieces(input_ty, input_schema, message_meta, raw_input);
let (impl_generics, state_in_ctx) = match &state_ty {
Some(state_ty) => (quote!(), quote!(#state_ty)),
None => (
quote!(<__RsState: ::core::marker::Send + ::core::marker::Sync>),
quote!(__RsState),
),
};
let where_clause = extractor_where(extractors, ctx_ty, &state_in_ctx);
let prelude = extractor_prelude(
extractors,
ctx_param,
ctx_ty,
&state_in_ctx,
"e!(
return ::core::result::Result::Err(::core::convert::Into::<
::ruststream::runtime::HandlerResult,
>::into(__rs_err),)
),
);
Ok(quote! {
#[allow(non_camel_case_types)]
#vis struct #name;
impl ::ruststream::runtime::IncludeDef for #name {
type Form = #form;
}
impl ::ruststream::runtime::PublishingDef for #name {
type Input = #input_kind;
type Injections = (#(#injection_tys,)*);
type Reply = #reply_ty;
type Context = #ctx_ty;
type Source = #source_ty;
fn source(&self) -> Self::Source { #source_expr }
fn reply_name(&self) -> &str { #reply_topic }
#workers_method
#failure_method
fn description(&self) -> ::core::option::Option<&str> {
#description
}
#input_schema
#message_meta
}
impl #impl_generics
::ruststream::runtime::PublishingCall<#state_in_ctx> for #name
#where_clause
{
async fn call(
&self,
#pat: #input_param,
__rs_inj: &Self::Injections,
#ctx_param: &mut ::ruststream::runtime::Context<'_, #ctx_ty, #state_in_ctx>,
) -> ::core::result::Result<#reply_ty, ::ruststream::runtime::HandlerResult> {
#prelude
let (#(#injection_pats,)*) = __rs_inj;
#call_body
}
}
})
}
fn publishing_reply<'a>(
func: &'a ItemFn,
block: &syn::Block,
bare: bool,
) -> syn::Result<(&'a Type, TokenStream2)> {
let declared_ty = match &func.sig.output {
ReturnType::Type(_, ty) => &**ty,
ReturnType::Default => {
return Err(Error::new_spanned(
&func.sig,
if bare {
"a publish_raw handler must return the reply bytes: Vec<u8>, or \
Result<Vec<u8>, HandlerResult>"
} else {
"a publishing handler must return the reply value"
},
));
}
};
Ok(match publish_result_reply(declared_ty) {
Some(reply_ty) => (reply_ty, quote!((async move #block).await)),
None => (
declared_ty,
quote!(::core::result::Result::Ok((async move #block).await)),
),
})
}
fn input_pieces(
input_ty: &Type,
input_schema: &TokenStream2,
message_meta: &TokenStream2,
raw: bool,
) -> (TokenStream2, TokenStream2, TokenStream2, TokenStream2) {
if raw {
(
quote!(::ruststream::runtime::RawBytes),
quote!(&[u8]),
quote!(),
quote!(),
)
} else {
(
quote!(::ruststream::runtime::Decoded<#input_ty>),
quote!(&#input_ty),
input_schema.clone(),
message_meta.clone(),
)
}
}
fn injection_pieces(
out: Option<(&Pat, &Type)>,
seek: Option<(&Pat, &Type)>,
) -> (Vec<TokenStream2>, Vec<TokenStream2>, TokenStream2) {
let mut injection_tys = Vec::new();
let mut injection_pats = Vec::new();
if let Some((out_pat, out_ty)) = out {
injection_tys.push(quote!(::ruststream::runtime::Out<#out_ty>));
injection_pats.push(quote!(#out_pat));
}
if let Some((seek_pat, seeker_ty)) = seek {
injection_tys.push(quote!(::ruststream::runtime::Seek<#seeker_ty>));
injection_pats.push(quote!(#seek_pat));
}
let form = if out.is_some() {
quote!(::ruststream::runtime::forms::Out)
} else {
quote!(::ruststream::runtime::forms::Seek)
};
(injection_tys, injection_pats, form)
}
fn expand_injected(parts: &HandlerParts<'_>, raw: bool) -> TokenStream2 {
let HandlerParts {
vis,
name,
block,
pat,
input_ty,
description,
source_ty,
source_expr,
input_schema,
message_meta,
ctx_param,
ctx_ty,
state_ty,
extractors,
out,
seek,
workers_method,
failure_method,
} = parts;
let (injection_tys, injection_pats, form) = injection_pieces(*out, *seek);
let (input_kind, input_param, input_schema, message_meta) =
input_pieces(input_ty, input_schema, message_meta, raw);
let (impl_generics, state_in_ctx) = match &state_ty {
Some(state_ty) => (quote!(), quote!(#state_ty)),
None => (
quote!(<__RsState: ::core::marker::Send + ::core::marker::Sync>),
quote!(__RsState),
),
};
let where_clause = extractor_where(extractors, ctx_ty, &state_in_ctx);
let prelude = extractor_prelude(
extractors,
ctx_param,
ctx_ty,
&state_in_ctx,
"e!(
return ::ruststream::runtime::IntoSettle::into_settle(::core::convert::Into::<
::ruststream::runtime::HandlerResult,
>::into(__rs_err),)
),
);
quote! {
#[derive(Clone, Copy)]
#[allow(non_camel_case_types)]
#vis struct #name;
impl ::ruststream::runtime::IncludeDef for #name {
type Form = #form;
}
impl ::ruststream::runtime::InjectDef for #name {
type Input = #input_kind;
type Context = #ctx_ty;
type Source = #source_ty;
type Injections = (#(#injection_tys,)*);
fn source(&self) -> Self::Source { #source_expr }
#workers_method
#failure_method
fn description(&self) -> ::core::option::Option<&str> {
#description
}
#input_schema
#message_meta
}
impl #impl_generics
::ruststream::runtime::InjectCall<#state_in_ctx> for #name
#where_clause
{
async fn call(
&self,
#pat: #input_param,
__rs_inj: &Self::Injections,
#ctx_param: &mut ::ruststream::runtime::Context<'_, #ctx_ty, #state_in_ctx>,
) -> ::ruststream::runtime::Settle {
#prelude
let (#(#injection_pats,)*) = __rs_inj;
::ruststream::runtime::IntoSettle::into_settle(
(async move #block).await,
)
}
}
}
}
fn expand_subscribing(parts: &HandlerParts<'_>, raw: bool) -> TokenStream2 {
let HandlerParts {
vis,
name,
block,
pat,
input_ty,
description,
source_ty,
source_expr,
input_schema,
message_meta,
ctx_param,
ctx_ty,
state_ty,
extractors,
out: _,
seek: _,
workers_method,
failure_method,
} = parts;
let (input_kind, input_param, input_schema, message_meta) =
input_pieces(input_ty, input_schema, message_meta, raw);
let input_target = if raw { quote!([u8]) } else { quote!(#input_ty) };
let form = if raw {
quote!(::ruststream::runtime::forms::RawSubscribing)
} else {
quote!(::ruststream::runtime::forms::Subscribing)
};
let (impl_generics, state_in_ctx) = match &state_ty {
Some(state_ty) => (quote!(), quote!(#state_ty)),
None => (
quote!(<__RsState: ::core::marker::Send + ::core::marker::Sync>),
quote!(__RsState),
),
};
let where_clause = extractor_where(extractors, ctx_ty, &state_in_ctx);
let prelude = extractor_prelude(
extractors,
ctx_param,
ctx_ty,
&state_in_ctx,
"e!(
return ::ruststream::runtime::IntoSettle::into_settle(::core::convert::Into::<
::ruststream::runtime::HandlerResult,
>::into(__rs_err),)
),
);
quote! {
#[derive(Clone, Copy)]
#[allow(non_camel_case_types)]
#vis struct #name;
impl #impl_generics
::ruststream::runtime::Handler<#input_target, #ctx_ty, #state_in_ctx> for #name
#where_clause
{
async fn handle(
&self,
#pat: #input_param,
#ctx_param: &mut ::ruststream::runtime::Context<'_, #ctx_ty, #state_in_ctx>,
) -> ::ruststream::runtime::Settle {
#prelude
::ruststream::runtime::IntoSettle::into_settle(
(async move #block).await,
)
}
}
impl ::ruststream::runtime::IncludeDef for #name {
type Form = #form;
}
impl ::ruststream::runtime::SubscriberDef for #name {
type Input = #input_kind;
type Context = #ctx_ty;
type Handler = Self;
type Source = #source_ty;
fn source(&self) -> Self::Source { #source_expr }
#workers_method
#failure_method
fn description(&self) -> ::core::option::Option<&str> {
#description
}
#input_schema
#message_meta
fn into_handler(self) -> Self { self }
}
}
}