Skip to main content

arcly_stream_macros/
lib.rs

1//! Procedural macros for `arcly-stream`.
2//!
3//! These are ergonomics, not load-bearing — everything they generate can be
4//! written by hand. They are gated behind the `macros` feature of the parent
5//! crate and expand against the stable `arcly_stream::__macro_support` surface.
6
7use proc_macro::TokenStream;
8use quote::quote;
9use syn::{parse_macro_input, Data, DeriveInput, Fields, Index, LitStr};
10
11/// Derive [`MediaSink`] for a wrapper struct by **delegating** to one inner
12/// field that already implements it. The delegate is the field marked
13/// `#[sink]`, or the first field if none is marked.
14///
15/// ```ignore
16/// use arcly_stream::prelude::*;
17///
18/// #[derive(MediaSinkDerive)]
19/// struct LoggingSink {
20///     #[sink]
21///     inner: NullSink,
22///     label: String,
23/// }
24/// ```
25#[proc_macro_derive(MediaSink, attributes(sink))]
26pub fn derive_media_sink(input: TokenStream) -> TokenStream {
27    let input = parse_macro_input!(input as DeriveInput);
28    let name = &input.ident;
29    let (impl_g, ty_g, where_c) = input.generics.split_for_impl();
30
31    let fields = match &input.data {
32        Data::Struct(s) => &s.fields,
33        _ => {
34            return syn::Error::new_spanned(name, "MediaSink can only be derived for structs")
35                .to_compile_error()
36                .into()
37        }
38    };
39
40    let accessor = match pick_delegate(fields) {
41        Ok(tokens) => tokens,
42        Err(e) => return e.to_compile_error().into(),
43    };
44
45    quote! {
46        #[::arcly_stream::__macro_support::async_trait]
47        impl #impl_g ::arcly_stream::traits::MediaSink for #name #ty_g #where_c {
48            async fn send_frame(
49                &mut self,
50                frame: ::arcly_stream::MediaFrame,
51            ) -> ::arcly_stream::Result<()> {
52                ::arcly_stream::traits::MediaSink::send_frame(&mut self.#accessor, frame).await
53            }
54            async fn flush(&mut self) -> ::arcly_stream::Result<()> {
55                ::arcly_stream::traits::MediaSink::flush(&mut self.#accessor).await
56            }
57        }
58    }
59    .into()
60}
61
62/// Picks the delegate field: the one tagged `#[sink]`, else the first field.
63fn pick_delegate(fields: &Fields) -> syn::Result<proc_macro2::TokenStream> {
64    let marked = |attrs: &[syn::Attribute]| attrs.iter().any(|a| a.path().is_ident("sink"));
65    match fields {
66        Fields::Named(named) => {
67            let chosen = named
68                .named
69                .iter()
70                .find(|f| marked(&f.attrs))
71                .or_else(|| named.named.first());
72            match chosen {
73                Some(f) => {
74                    let ident = f.ident.as_ref().expect("named field has ident");
75                    Ok(quote!(#ident))
76                }
77                None => Err(syn::Error::new_spanned(
78                    &named.named,
79                    "MediaSink derive needs at least one field to delegate to",
80                )),
81            }
82        }
83        Fields::Unnamed(unnamed) => {
84            let idx = unnamed
85                .unnamed
86                .iter()
87                .position(|f| marked(&f.attrs))
88                .unwrap_or(0);
89            if unnamed.unnamed.is_empty() {
90                return Err(syn::Error::new_spanned(
91                    &unnamed.unnamed,
92                    "MediaSink derive needs at least one field to delegate to",
93                ));
94            }
95            let index = Index::from(idx);
96            Ok(quote!(#index))
97        }
98        Fields::Unit => Err(syn::Error::new_spanned(
99            fields,
100            "MediaSink cannot be derived for a unit struct (nothing to delegate to)",
101        )),
102    }
103}
104
105/// Attach a protocol name to a handler type. Generates an associated
106/// `PROTOCOL_NAME` constant the `ProtocolHandler::name` impl can return.
107///
108/// ```ignore
109/// #[protocol("rtmp")]
110/// struct RtmpHandler { addr: std::net::SocketAddr }
111///
112/// #[async_trait]
113/// impl ProtocolHandler for RtmpHandler {
114///     fn name(&self) -> &'static str { Self::PROTOCOL_NAME }
115///     // ...
116/// }
117/// ```
118#[proc_macro_attribute]
119pub fn protocol(attr: TokenStream, item: TokenStream) -> TokenStream {
120    let lit = parse_macro_input!(attr as LitStr);
121    let input = parse_macro_input!(item as DeriveInput);
122    let name = &input.ident;
123    let (impl_g, ty_g, where_c) = input.generics.split_for_impl();
124    let value = lit.value();
125
126    quote! {
127        #input
128
129        impl #impl_g #name #ty_g #where_c {
130            /// The protocol name supplied via `#[protocol("…")]`.
131            pub const PROTOCOL_NAME: &'static str = #value;
132        }
133    }
134    .into()
135}