ruststream_macros/lib.rs
1//! Procedural macros for [RustStream](https://github.com/powersemmi/ruststream).
2//!
3//! Re-exported from the `ruststream` crate under the `macros` feature; depend on that rather than
4//! on this crate directly.
5
6#![forbid(unsafe_code)]
7
8mod expand;
9mod from_ref;
10mod lanes;
11mod outgoing;
12mod parse;
13
14use proc_macro::TokenStream;
15use proc_macro2::TokenStream as TokenStream2;
16use quote::{ToTokens, quote};
17use syn::punctuated::Punctuated;
18use syn::{Attribute, DeriveInput, Ident, ItemFn, Meta, Token, Type, parse_macro_input};
19
20use parse::{SubscriberArgs, doc_description};
21
22/// Turns an `async fn` handler into a mountable subscriber definition.
23///
24/// The attribute carries the declarative settings of the subscription; the *shape* of the
25/// handler - one message (`&T`) or a whole batch of them (`&[T]`) - is read off the signature,
26/// which is the only place it was ever really written. Which lane a type rides is the type's
27/// own business: a `serde` type decodes through the scope codec, a `#[derive(Deserialized)]`
28/// type constructs itself from the payload bytes, and a reply encodes through the reply codec
29/// unless its type is `#[derive(Serialized)]`, in which case its bytes leave as they are.
30///
31/// ```ignore
32/// /// Processes incoming orders.
33/// #[subscriber("orders")]
34/// async fn handle(order: &Order) -> HandlerOutcome { HandlerOutcome::ack() }
35/// // later: broker_scope.include(handle);
36///
37/// // reply form: the return value is encoded and published through the reply publisher
38/// // attached at the include site (the broker's default without it), at the destination its
39/// // own type declares.
40/// #[derive(Serialize, Outgoing)]
41/// #[outgoing(name = "responses")]
42/// struct Response { /* ... */ }
43///
44/// #[subscriber("requests", publish)]
45/// async fn reply(req: &Request) -> Response { /* ... */ }
46///
47/// // a reply type declaring no name takes the clause's:
48/// #[subscriber("requests", publish("responses"))]
49/// async fn reply_elsewhere(req: &Request) -> Receipt { /* ... */ }
50///
51/// // reply form with explicit ack control: `Ok` publishes the reply, `Err` skips it and the
52/// // dispatcher settles by the returned HandlerOutcome.
53/// #[subscriber("requests", publish("responses"))]
54/// async fn confirm(req: &Request) -> Result<Response, HandlerOutcome> { /* ... */ }
55///
56/// // batch form: the slice parameter says the handler takes the whole decoded batch; the
57/// // subscription's subscriber must implement BatchSubscriber (natively, or through the
58/// // buffer the mount site adds).
59/// #[subscriber("orders")]
60/// async fn bill(orders: &[Order]) -> HandlerOutcome { /* settles the whole batch */ }
61///
62/// // raw form: no codec, no serde - the input type deserializes itself from the payload
63/// // bytes. `Deserialize` means the framework's codec does it; `Deserialized` means it is
64/// // already done by the user's own type.
65/// #[derive(Deserialized)]
66/// struct Frame<'a>(&'a [u8]);
67///
68/// #[subscriber("frames")]
69/// async fn on_frame(frame: &Frame<'_>) -> HandlerOutcome { /* parse it yourself */ }
70///
71/// // raw batch: the batch shape on the same lane; the views borrow the batch's own messages
72/// // for the duration of the call.
73/// #[subscriber("frames")]
74/// async fn ingest(frames: &[Frame<'_>]) -> HandlerOutcome { /* parse them yourself */ }
75///
76/// // the settings the attribute leaves out are filled in at the mount site, through the same
77/// // builder the attribute expands into:
78/// #[subscriber]
79/// async fn audit(order: &Order) -> HandlerOutcome { HandlerOutcome::ack() }
80/// // later: broker_scope.include(audit.name(subject).workers(nonzero!(4)));
81///
82/// // raw reply: the reply type carries its own bytes, so they are published as they are -
83/// // no codec on the way out. `Serialize` means the framework's codec does it; `Serialized`
84/// // means it is already done by the user's own type. The same publish clause serves both
85/// // wires, and Result<Export, HandlerOutcome> gives the usual explicit ack control.
86/// #[derive(Outgoing, Serialized)]
87/// struct Export(Vec<u8>);
88///
89/// #[subscriber("frames", publish("frames-out"))]
90/// async fn mirror(frame: &Frame<'_>) -> Export { Export(frame.0.to_vec()) }
91///
92/// // the lanes mix freely (the gateway shape): the input decodes with the scope codec, the
93/// // returned bytes still go out unencoded - and the other diagonal (a Deserialized input
94/// // with an encoded serde reply) works the same way.
95/// #[subscriber("orders", publish("orders-wire"))]
96/// async fn encode(order: &Order) -> Export { /* your wire format */ }
97/// ```
98///
99/// A reply type derives `Outgoing`: `#[outgoing(name = "..")]` on it is the destination, and a
100/// derive without a name takes the clause's `publish("..")` as the destination instead. The two
101/// never disagree, because a declared name is the one that applies.
102///
103/// Without a `publish` clause the handler returns any accepted outcome shape (a `HandlerOutcome`,
104/// `()`, or `Result<_, E>`). Attach a post-settle continuation with
105/// `HandlerOutcome::ack().and_after` (any outcome works), which runs after the message is
106/// settled. With one it returns the reply value to publish, or
107/// `Result<Reply, HandlerOutcome>` to control acknowledgement: `Err(outcome)` publishes nothing
108/// and settles by `outcome`. The `Result` form is
109/// detected syntactically, so spell it out in the signature (a type alias is treated as a plain
110/// reply type). A failed reply publish nacks the delivery with requeue on both wires, so a
111/// reply is retried rather than silently lost.
112///
113/// A `&[T]` message parameter makes the definition a batch one: the handler runs once per batch
114/// pulled from the subscription's `BatchSubscriber` (added by the mount site's buffer for
115/// subscriptions without native batching). It returns one outcome
116/// for the whole batch (`HandlerOutcome`, `()`, `Result<_, E>`), or a per-element
117/// `Vec<HandlerOutcome>` to settle element `i` of the slice with outcome `i`, each element
118/// carrying its own optional `and_after` continuation. A `&[Message<H, T>]` parameter pairs
119/// each element with its typed
120/// header contract: the core decodes both under the subscriber's decode policy, and the body
121/// reads `element.headers` next to `element.body` (the single-message counterpart stays the
122/// `Headers<T>` extractor parameter). A batch body may name the broker's subscription-scoped
123/// batch context (`ctx: &mut Context<'_, MemoryBatchContext>`-style) to reach handles every
124/// delivery of the subscription shares, like a seeker.
125///
126/// Combining a batch handler with `publish(..)` produces a batch reply definition: the handler
127/// returns `Vec<Reply>` (or
128/// `Result<Vec<Reply>, HandlerOutcome>` for explicit ack control, all-or-nothing - selective
129/// outcomes do not compose with a transaction), every reply is published to the reply name, and
130/// the whole batch is acked after. Name the reply's policy with `.out(Reply, ..)` at the mount
131/// site for independent reply publishes, and chain `.transactional()` for one transaction per
132/// batch.
133///
134/// A `workers(n)` clause processes up to `n` deliveries (or batches) of this subscriber
135/// concurrently, each in its own task; global processing order is lost by design, and
136/// back-pressure holds at `n` in-flight deliveries. `workers(n, by_key)` switches to `n`
137/// sequential lanes keyed by the message's partition key, preserving per-key ordering
138/// (single-message forms only). The default is the sequential loop.
139///
140/// Clause values need not be literals: `workers(..)` takes any `usize` expression (a constant,
141/// a static, a function call - an integer literal keeps the compile-time zero rejection, a
142/// runtime value of zero panics at registration), `publish(..)` takes a `&'static str`
143/// expression, and `on_failure(..)` keys accept a `FailurePolicy` expression next to the keyword
144/// vocabulary.
145///
146/// An `Out(out): Out<P>` parameter injects a live publisher, paired at startup from the
147/// policy attached at the include site (`b.include(f).out(marker, policy).build()`); its optional third
148/// position declares the message set the handler publishes (`Out<impl Publisher, Marker,
149/// (A, B)>` - a tuple, a single type, or a `#[derive(OutMessages)]` set enum), narrowing what
150/// the publish builder accepts on it, and the generated document reports that narrowed list as
151/// the handler's send operations. `Out` parameters combine freely in one handler: with each
152/// other, with a byte input, with a batch handler, and with every reply form (the two `publish`
153/// spellings and the batch publishing form). An `Out` parameter's attachment is
154/// required at the include site: one `.out(marker, policy)` per slot, next to the reply's own
155/// optional `.out(Reply, policy)`, and a `.build()` to commit. Repositioning a
156/// subscription from inside the body is not a parameter but a broker context field: the broker
157/// publishes its position and seek-handle keys, and the handler reads them with the `Ctx`
158/// extractor like any other broker field.
159///
160/// A `start_at(<position>)` clause opens the subscription at that position instead of the
161/// broker's default, seeking before the first delivery ("start from the latest on deploy",
162/// "replay the whole log"). The position is the broker's own type, named by its constructor
163/// (`MemoryPosition::start()`, a Kafka-style `latest()`); the source's subscriber must
164/// implement the `Seekable` capability, so the clause does not compile against a broker
165/// without a replayable log. The position is forced on every startup; without the clause the
166/// subscription simply opens at the broker's default, and conditional defaults stay on the
167/// broker's own subscription descriptor.
168///
169/// ```ignore
170/// // Opens at the start of the log: entries published before the service started are
171/// // replayed into the fresh subscription.
172/// #[subscriber("audit", start_at(MemoryPosition::start()))]
173/// async fn record(entry: &Entry) -> HandlerOutcome { /* ... */ }
174/// ```
175///
176/// In both forms the handler may declare an optional second parameter, the per-delivery
177/// `&mut Context`, to read app state or publish manually. Any further parameter is an extractor: its
178/// type must implement
179/// [`FromContext`](../ruststream/runtime/trait.FromContext.html), and the generated handler resolves
180/// it from the delivery context (in declaration order) before the body runs, so dependencies arrive
181/// as arguments. A failed extraction settles the delivery by the rejection's `HandlerOutcome`
182/// without running the body.
183///
184/// ```ignore
185/// // `State<Db>` is resolved from the application state before the body runs.
186/// #[subscriber("orders")]
187/// async fn handle(order: &Order, State(db): State<Db>) -> HandlerOutcome { /* ... */ }
188/// ```
189///
190/// The attribute expands into the definition and then the settings builder over it, so the
191/// clauses above are exactly the calls a user would write at the mount site (see
192/// `SubscriberSettings` in the core crate). A setting the attribute names is fixed in the
193/// definition's type and its builder method no longer applies, so the two can never disagree.
194#[proc_macro_attribute]
195pub fn subscriber(attr: TokenStream, item: TokenStream) -> TokenStream {
196 let args = parse_macro_input!(attr as SubscriberArgs);
197 let func = parse_macro_input!(item as ItemFn);
198 expand::subscriber(&args, &func).unwrap_or_else(|err| err.to_compile_error().into())
199}
200
201/// Generates a `main` entry point for a `RustStream` service.
202///
203/// Place it on a synchronous, argument-free function that builds and returns an application -
204/// `impl App` (the recommended form, hiding the composed type parameters) or a concrete
205/// `RustStream<_>`. The expansion keeps the function and adds a `main` that hands it to
206/// `ruststream::runtime::cli::run_main`, producing a binary that understands the `run` and
207/// `asyncapi gen` commands with no hand-written runtime boilerplate.
208///
209/// ```ignore
210/// #[ruststream::app]
211/// fn app() -> impl App {
212/// RustStream::new(AppInfo::new("svc", "0.1.0")).register_broker(MemoryBroker::new())
213/// }
214/// ```
215#[proc_macro_attribute]
216pub fn app(attr: TokenStream, item: TokenStream) -> TokenStream {
217 let func = parse_macro_input!(item as ItemFn);
218 expand_app(&attr.into(), &func).unwrap_or_else(|err| err.to_compile_error().into())
219}
220
221fn expand_app(attr: &TokenStream2, func: &ItemFn) -> syn::Result<TokenStream> {
222 if !attr.is_empty() {
223 return Err(syn::Error::new_spanned(
224 attr,
225 "#[ruststream::app] takes no arguments",
226 ));
227 }
228 if let Some(asyncness) = func.sig.asyncness {
229 return Err(syn::Error::new_spanned(
230 asyncness,
231 "#[ruststream::app] requires a synchronous builder returning `impl App` or `RustStream`",
232 ));
233 }
234 if !func.sig.inputs.is_empty() {
235 return Err(syn::Error::new_spanned(
236 &func.sig.inputs,
237 "#[ruststream::app] builder must take no arguments",
238 ));
239 }
240 let name = &func.sig.ident;
241 Ok(quote! {
242 #func
243
244 fn main() -> ::std::process::ExitCode {
245 ::ruststream::runtime::cli::run_main(#name)
246 }
247 }
248 .into())
249}
250
251/// Derives [`Message`](../ruststream/trait.Message.html) metadata: the type name, its doc
252/// comment, and its header contract.
253///
254/// The optional `#[message(headers(Meta))]` attribute declares the type's typed header
255/// contract (see `MessageHeaders` in the core crate): `Meta` is serialized into the header map
256/// on the typed publish path and lifted into the message's `AsyncAPI` headers schema. Without
257/// the attribute the type declares no typed headers.
258///
259/// The derive describes a message the service receives, or replies with. A message the handler
260/// sends through an `Out` slot declares where it goes as well, so it derives
261/// [`Outgoing`](derive.Outgoing.html) instead - only that derive makes the type a declared
262/// message set (see `OutMessages` in the core crate) the third `Out` argument can name.
263///
264/// ```ignore
265/// /// An order placed by a customer.
266/// #[derive(MessageInfo)]
267/// struct Order { id: u32 }
268/// // Order::NAME == "Order", Order::DESCRIPTION == Some("An order placed by a customer.")
269///
270/// #[derive(Message, Serialize)]
271/// #[message(headers(ChunkMeta))]
272/// struct ChunkDone { output_key: String }
273/// ```
274#[proc_macro_derive(MessageInfo, attributes(message))]
275pub fn derive_message(item: TokenStream) -> TokenStream {
276 let input = parse_macro_input!(item as DeriveInput);
277 let name = &input.ident;
278 let name_str = name.to_string();
279 let description = doc_description(&input.attrs);
280 let contract = match message_headers_contract(&input.attrs) {
281 Ok(Some(headers)) => quote!(::ruststream::WithHeaders<#headers>),
282 Ok(None) => quote!(::ruststream::NoHeaders),
283 Err(err) => return err.into_compile_error().into(),
284 };
285 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
286
287 quote! {
288 impl #impl_generics ::ruststream::MessageInfo for #name #ty_generics #where_clause {
289 const NAME: &'static str = #name_str;
290 const DESCRIPTION: ::core::option::Option<&'static str> = #description;
291 }
292
293 impl #impl_generics ::ruststream::MessageHeaders for #name #ty_generics #where_clause {
294 type Contract = #contract;
295 }
296 }
297 .into()
298}
299
300/// Derives everything a message type declares about being sent, from one `key = value`
301/// attribute: its destination
302/// ([`OutgoingDestination`](../ruststream/trait.OutgoingDestination.html)), its optional typed
303/// header contract, and the [`Message`](../ruststream/trait.Message.html) metadata the generated
304/// document reads (the type's name and doc comment).
305///
306/// The destination decides which positions the publish builder demands at the call site:
307///
308/// * `#[outgoing(name = "orders.done")]` fixes it - `publisher.message(&done).publish()` names
309/// nothing.
310/// * `#[outgoing(name = "orders.{tenant}.v1")]` declares a space of names - `to()` opens one
311/// setter per placeholder, and the publish appears once the last one is bound.
312/// * the derive alone declares nothing - the call site names it: `.to("orders.archived")`.
313///
314/// The same declaration decides a reply's destination: `#[subscriber(.., publish)]` publishes the
315/// return value where its type says, and `publish("..")` supplies the destination of a reply type
316/// that declares none. A reply type derives this in every case; a name template does not resolve
317/// there, because the runtime has nothing to bind its placeholders with.
318///
319/// `headers = Meta` declares the typed header contract: `Meta` stays an ordinary serde struct
320/// the derive does not touch, and the publish builder then demands `with_headers(&meta)`.
321///
322/// Do not combine with `#[derive(MessageInfo)]` on the same type: this derive already provides the
323/// message metadata, and the two produce conflicting impls.
324///
325/// ```ignore
326/// /// A finished order.
327/// #[derive(Outgoing, Serialize)]
328/// #[outgoing(name = "orders.done", headers = OrderMeta)]
329/// struct OrderDone { id: u64 }
330///
331/// #[derive(Outgoing, Serialize)]
332/// #[outgoing(name = "orders.{tenant}.{region}.v1")]
333/// struct OrderPlaced { id: u64 }
334/// // out.message(&placed).to().tenant("acme").region("eu").publish().await?;
335///
336/// #[derive(Outgoing, Serialize)]
337/// struct OrderArchived { id: u64 }
338/// // out.message(&archived).to("orders.archived").publish().await?;
339/// ```
340#[proc_macro_derive(Outgoing, attributes(outgoing))]
341pub fn derive_outgoing(item: TokenStream) -> TokenStream {
342 let input = parse_macro_input!(item as DeriveInput);
343 outgoing::expand(&input)
344 .unwrap_or_else(syn::Error::into_compile_error)
345 .into()
346}
347
348/// Parses the optional `#[message(headers(Type))]` helper attribute of `#[derive(MessageInfo)]`.
349fn message_headers_contract(attrs: &[Attribute]) -> syn::Result<Option<Type>> {
350 let mut found: Option<Type> = None;
351 for attr in attrs.iter().filter(|attr| attr.path().is_ident("message")) {
352 let meta: Meta = attr.parse_args()?;
353 let Meta::List(list) = &meta else {
354 return Err(syn::Error::new_spanned(
355 meta,
356 "expected `headers(<Type>)` inside #[message(..)]",
357 ));
358 };
359 if !list.path.is_ident("headers") {
360 return Err(syn::Error::new_spanned(
361 &list.path,
362 "expected `headers(<Type>)` inside #[message(..)]",
363 ));
364 }
365 let headers: Type = list.parse_args()?;
366 if found.replace(headers).is_some() {
367 return Err(syn::Error::new_spanned(
368 attr,
369 "duplicate `headers(..)`: a message declares one header contract",
370 ));
371 }
372 }
373 Ok(found)
374}
375
376/// Derives [`FromRef`](../ruststream/runtime/trait.FromRef.html) for each field of an
377/// application-state struct, so `#[subscriber]` handlers can inject any field with
378/// `State<FieldType>` without a hand-written impl.
379///
380/// Each field gets a `FromRef` impl that clones it out of the state. Because the generated impl
381/// carries no generic parameter, it is legal even for fields whose type comes from another crate (a
382/// broker publisher, a client pool). A field that another field's type already claims, or that
383/// should not be injectable, opts out with `#[from_ref(skip)]`; two fields may not share a type
384/// (injection by type would be ambiguous).
385///
386/// ```ignore
387/// #[derive(FromRef)]
388/// struct AppState {
389/// orders: OrderService, // handlers can now take `State<OrderService>`
390/// #[from_ref(skip)]
391/// config: Config,
392/// }
393/// ```
394#[proc_macro_derive(FromRef, attributes(from_ref))]
395pub fn derive_from_ref(item: TokenStream) -> TokenStream {
396 let input = parse_macro_input!(item as DeriveInput);
397 from_ref::expand(&input)
398 .unwrap_or_else(syn::Error::into_compile_error)
399 .into()
400}
401
402/// Derives [`OutSlot`](../ruststream/runtime/trait.OutSlot.html) for a unit struct, making it a
403/// slot marker for `Out<impl Publisher, Marker>` handler parameters. The marker's
404/// [`NAME`](../ruststream/runtime/trait.OutSlot.html#associatedconstant.NAME) is the struct's
405/// identifier, used by startup errors and test assertions.
406///
407/// The optional `#[publishes(Type, ..)]` attribute lists the message types the slot may
408/// publish, which is what the generated document reports for a handler leaving its `Out`
409/// parameter unrestricted. The list is enforced, not only documented: publishing a type it does
410/// not name is a compile error (see `PublishedThrough` in the core crate), so the document cannot
411/// fall behind what handlers send. Each listed type declares its own destination with
412/// `#[derive(Outgoing)]`; listing one type twice is an error.
413///
414/// ```ignore
415/// #[derive(OutSlot)]
416/// struct Encoded;
417///
418/// #[derive(OutSlot)]
419/// #[publishes(ChunkDone, Progress)]
420/// struct Events;
421///
422/// #[subscriber("chunks")]
423/// async fn transcode(chunk: &[u8], Out(out): Out<impl Publisher, Encoded>) -> HandlerOutcome {
424/// /* ... */
425/// }
426/// // include site: b.include(transcode).out(Encoded, KafkaPublish::default());
427/// ```
428#[proc_macro_derive(OutSlot, attributes(publishes))]
429pub fn derive_out_slot(item: TokenStream) -> TokenStream {
430 let input = parse_macro_input!(item as DeriveInput);
431 let syn::Data::Struct(data) = &input.data else {
432 return syn::Error::new_spanned(&input.ident, "OutSlot is derived on a unit struct")
433 .into_compile_error()
434 .into();
435 };
436 if !matches!(data.fields, syn::Fields::Unit) {
437 return syn::Error::new_spanned(
438 &data.fields,
439 "an OutSlot marker is a unit struct: it identifies the slot and carries no data",
440 )
441 .into_compile_error()
442 .into();
443 }
444 if !input.generics.params.is_empty() {
445 return syn::Error::new_spanned(
446 &input.generics,
447 "an OutSlot marker takes no generic parameters",
448 )
449 .into_compile_error()
450 .into();
451 }
452 let dictionary = match publishes_dictionary(&input.attrs) {
453 Ok(dictionary) => dictionary,
454 Err(err) => return err.into_compile_error().into(),
455 };
456 let name = &input.ident;
457 let name_str = name.to_string();
458
459 let outgoing = slot_outgoing_metadata(name, &dictionary);
460 // The list is the whole membership: what the document reports as leaving the slot is exactly
461 // what the publish builder admits.
462 let memberships = dictionary.iter().map(|ty| {
463 quote! {
464 impl ::ruststream::runtime::PublishedThrough<#name> for #ty {}
465 }
466 });
467 let destination = slot_destination(&dictionary);
468
469 quote! {
470 impl ::ruststream::runtime::OutSlot for #name {
471 const NAME: &'static str = #name_str;
472
473 type Destination = #destination;
474
475 #outgoing
476 }
477
478 #(#memberships)*
479 }
480 .into()
481}
482
483/// What the marker offers a transform, folded out of its `#[publishes(..)]` list: the list as
484/// types, nested right and closed with `()`, projected through `ListOffer`. A marker with no list
485/// promises nothing, because it admits every declared message, so it offers `Reads`.
486fn slot_destination(dictionary: &[Type]) -> TokenStream2 {
487 if dictionary.is_empty() {
488 return quote!(::ruststream::runtime::Reads);
489 }
490 let list = dictionary
491 .iter()
492 .rev()
493 .fold(quote!(()), |tail, ty| quote!((#ty, #tail)));
494 quote!(<#list as ::ruststream::runtime::ListOffer>::Offer)
495}
496
497/// The marker's `outgoing()` override, reporting its `#[publishes(..)]` list as the document's
498/// outgoing messages: each listed type contributes what its own `#[derive(Outgoing)]` declares.
499/// An empty list keeps the trait's own default (nothing declared).
500fn slot_outgoing_metadata(name: &Ident, dictionary: &[Type]) -> TokenStream2 {
501 if dictionary.is_empty() {
502 return quote!();
503 }
504 let entries = dictionary.iter().map(|ty| {
505 quote! {
506 __rs_entries.extend(
507 <#ty as ::ruststream::runtime::OutMessages<#name>>::outgoing(),
508 );
509 }
510 });
511 quote! {
512 fn outgoing() -> ::std::vec::Vec<::ruststream::runtime::OutgoingMessageMetadata> {
513 let mut __rs_entries = ::std::vec::Vec::new();
514 #(#entries)*
515 __rs_entries
516 }
517 }
518}
519
520/// Derives a declared message set (`OutMessages` in the core crate) from an enum whose
521/// variants each wrap one message model. The enum is a type-level declaration: it is never
522/// constructed, its variants' inner types are the set.
523///
524/// Naming the enum as the third `Out` argument (`Out<impl Publisher, Marker, SendSet>`)
525/// declares that the handler publishes exactly those models: the publish builder compiles for
526/// each of them (and for nothing else), and the generated `AsyncAPI` document lists them as the
527/// handler's `send` operations. Each model declares where it goes with `#[derive(Outgoing)]`.
528///
529/// Do not combine with `#[derive(Outgoing)]` on the same enum: a type is a message or a set,
530/// and the two derives produce conflicting impls.
531///
532/// ```ignore
533/// #[derive(OutMessages)]
534/// enum ConvertSends {
535/// Progress(Progress),
536/// Done(ChunkDone),
537/// }
538///
539/// #[subscriber("chunks.raw")]
540/// async fn convert(
541/// chunk: &Chunk,
542/// Out(out): Out<impl Publisher, Events, ConvertSends>,
543/// ) -> HandlerOutcome { /* out.message(&Progress { .. }).publish() */ }
544/// ```
545#[proc_macro_derive(OutMessages)]
546pub fn derive_out_messages(item: TokenStream) -> TokenStream {
547 let input = parse_macro_input!(item as DeriveInput);
548 let syn::Data::Enum(data) = &input.data else {
549 return syn::Error::new_spanned(
550 &input.ident,
551 "OutMessages is derived on an enum whose variants each wrap one message model",
552 )
553 .into_compile_error()
554 .into();
555 };
556 if !input.generics.params.is_empty() {
557 return syn::Error::new_spanned(
558 &input.generics,
559 "an OutMessages set takes no generic parameters",
560 )
561 .into_compile_error()
562 .into();
563 }
564 let models = match out_messages_models(data) {
565 Ok(models) => models,
566 Err(err) => return err.into_compile_error().into(),
567 };
568 let name = &input.ident;
569 let memberships = models.iter().enumerate().map(|(index, model)| {
570 quote! {
571 impl ::ruststream::runtime::ContainsMessage<
572 #model,
573 ::ruststream::runtime::SlotPos<#index>,
574 > for #name
575 {
576 }
577 }
578 });
579 // Each model already declares itself as a one-element set; the enum's contribution is the
580 // concatenation, so a model's destination is described in exactly one place.
581 let entries = models.iter().map(|model| {
582 quote! {
583 __rs_entries.extend(
584 <#model as ::ruststream::runtime::OutMessages<__RsM>>::outgoing(),
585 );
586 }
587 });
588 quote! {
589 #(#memberships)*
590
591 impl<__RsM: ::ruststream::runtime::OutSlot> ::ruststream::runtime::OutMessages<__RsM>
592 for #name
593 where
594 #(#models: ::ruststream::runtime::OutMessages<__RsM>,)*
595 {
596 fn outgoing()
597 -> ::std::vec::Vec<::ruststream::runtime::OutgoingMessageMetadata>
598 {
599 let mut __rs_entries = ::std::vec::Vec::new();
600 #(#entries)*
601 __rs_entries
602 }
603 }
604 }
605 .into()
606}
607
608/// Derives a self-deserializing input type (`Deserialized` in the core crate): `Deserialize`
609/// means the framework's codec does it, `Deserialized` means it is already done by the user's
610/// own type.
611///
612/// Covers the obvious shape - a newtype or single-field struct over the payload view
613/// `&'a [u8]` - and emits the construction plus both `Input` spellings, so `&Frame<'_>` and
614/// `&[Frame<'_>]` bodies mount.
615///
616/// A type that deserializes *itself* - a generated Protobuf message, a flatbuffers root, a capnp
617/// reader - names its format's own reader with `#[wire(decode = <path>)]` instead, and then the
618/// shape of the type is its own business. The path is a function `fn(&[u8]) -> Result<Self, E>`
619/// (or `fn(&'a [u8]) -> Result<Self<'a>, E>` for a borrowing view); the derive calls it and
620/// nothing here depends on the format's crate. `#[wire(prost)]` is the shorthand for
621/// `prost::Message::decode`.
622///
623/// ```ignore
624/// #[derive(Deserialized)]
625/// struct Frame<'a>(&'a [u8]);
626///
627/// #[subscriber("frames")]
628/// async fn inspect(frame: &Frame<'_>) -> HandlerOutcome { /* parse it yourself */ }
629///
630/// // What a generator's config puts on every message it emits.
631/// #[derive(Clone, PartialEq, prost::Message, Deserialized)]
632/// #[wire(prost)]
633/// struct Order { #[prost(uint64, tag = "1")] id: u64 }
634/// ```
635#[proc_macro_derive(Deserialized, attributes(wire))]
636pub fn derive_deserialized(item: TokenStream) -> TokenStream {
637 let input = parse_macro_input!(item as DeriveInput);
638 lanes::derive_deserialized(&input)
639 .unwrap_or_else(syn::Error::into_compile_error)
640 .into()
641}
642
643/// Derives a self-serialized outgoing type (`Serialized` in the core crate): `Serialize` means
644/// the framework's codec does it, `Serialized` means it is already done by the user's own
645/// type.
646///
647/// Covers the obvious shape - a newtype or single-field struct over an owned byte buffer - and
648/// emits the bytes accessor plus the wire spellings that route the type onto the serialized
649/// wire (`MessageWire` for a typed publish, `ReplyShape` for the reply position), so a handler
650/// returning it - or passing it to `message(..)` - publishes those bytes as they are.
651///
652/// A type that serializes *itself* names its format's own writer with
653/// `#[wire(encode = <path>)]`, and then holds fields rather than bytes: the path is a function
654/// `fn(&Self, &mut impl BufMut)`, returning either nothing or `Result<(), E>`, and the derive
655/// calls it against the buffer the publish path already carries - so the value is encoded once,
656/// into the buffer that leaves. Nothing here depends on the format's crate. `#[wire(prost)]` is
657/// the shorthand for `prost::Message::encode`.
658///
659/// ```ignore
660/// #[derive(Serialized)]
661/// struct Export(Vec<u8>);
662///
663/// #[subscriber("orders", publish("orders-wire"))]
664/// async fn encode(order: &Order) -> Export { /* your wire format */ }
665///
666/// // What a generator's config puts on every message it emits.
667/// #[derive(Clone, PartialEq, prost::Message, Serialized)]
668/// #[wire(prost)]
669/// struct OrderDone { #[prost(uint64, tag = "1")] id: u64 }
670/// ```
671#[proc_macro_derive(Serialized, attributes(wire))]
672pub fn derive_serialized(item: TokenStream) -> TokenStream {
673 let input = parse_macro_input!(item as DeriveInput);
674 lanes::derive_serialized(&input)
675 .unwrap_or_else(syn::Error::into_compile_error)
676 .into()
677}
678
679/// The set enum's models, one per variant: each variant wraps exactly one type, and a
680/// duplicate model is rejected (its membership index inference would be ambiguous).
681fn out_messages_models(data: &syn::DataEnum) -> syn::Result<Vec<&Type>> {
682 let mut models: Vec<&Type> = Vec::new();
683 for variant in &data.variants {
684 let syn::Fields::Unnamed(fields) = &variant.fields else {
685 return Err(syn::Error::new_spanned(
686 variant,
687 "an OutMessages variant wraps exactly one message model: `Variant(Model)`",
688 ));
689 };
690 if fields.unnamed.len() != 1 {
691 return Err(syn::Error::new_spanned(
692 fields,
693 "an OutMessages variant wraps exactly one message model: `Variant(Model)`",
694 ));
695 }
696 let model = &fields.unnamed[0].ty;
697 let rendered = model.to_token_stream().to_string();
698 if models
699 .iter()
700 .any(|existing| existing.to_token_stream().to_string() == rendered)
701 {
702 return Err(syn::Error::new_spanned(
703 model,
704 "this model is already in the set: a duplicate would make the membership \
705 index inference ambiguous",
706 ));
707 }
708 models.push(model);
709 }
710 Ok(models)
711}
712
713/// Parses every `#[publishes(Type, ..)]` attribute into one list, rejecting a type listed twice
714/// (a second entry would say nothing the first does not).
715fn publishes_dictionary(attrs: &[Attribute]) -> syn::Result<Vec<Type>> {
716 let mut dictionary: Vec<Type> = Vec::new();
717 for attr in attrs
718 .iter()
719 .filter(|attr| attr.path().is_ident("publishes"))
720 {
721 let entries = attr.parse_args_with(Punctuated::<Type, Token![,]>::parse_terminated)?;
722 for ty in entries {
723 let name = ty.to_token_stream().to_string();
724 if dictionary
725 .iter()
726 .any(|existing| existing.to_token_stream().to_string() == name)
727 {
728 return Err(syn::Error::new_spanned(
729 &ty,
730 "this type is already listed on the slot: a type appears once",
731 ));
732 }
733 dictionary.push(ty);
734 }
735 }
736 Ok(dictionary)
737}