Skip to main content

sark_gen/
lib.rs

1//! Borrow-preserving JSON and protobuf derives.
2//!
3//! JSON fields: scalar, `nested`, `seq`, and `seq + nested`.
4
5mod app;
6mod handler;
7mod json;
8mod macros;
9mod request;
10mod response;
11mod runtime;
12mod types;
13
14use macros::fibers;
15use runtime::paths;
16
17/// Derives borrowed protobuf encoding for one declared bytes field.
18#[proc_macro_derive(Message, attributes(prost))]
19pub fn message(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
20    let input = syn::parse_macro_input!(input as syn::DeriveInput);
21    use macros::message::Message;
22    match Message::new(input).and_then(Message::expand) {
23        Ok(tokens) => tokens.into(),
24        Err(error) => error.into_compile_error().into(),
25    }
26}
27
28/// Produces a statically validated direct response header name.
29#[proc_macro]
30pub fn header_name(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
31    let value = syn::parse_macro_input!(input as syn::LitStr);
32    emit(
33        |paths| Ok(response::name(paths, &value)),
34        runtime::Requirement::Http,
35    )
36}
37
38fn emit(
39    build: impl FnOnce(&paths::Paths) -> syn::Result<proc_macro2::TokenStream>,
40    need: runtime::Requirement,
41) -> proc_macro::TokenStream {
42    let result = paths::Paths::resolve(need)
43        .and_then(|paths| build(&paths).and_then(|tokens| paths.finish(tokens)));
44    match result {
45        Ok(tokens) => tokens.into(),
46        Err(error) => error.into_compile_error().into(),
47    }
48}
49
50/// Lowers an `async move` block through the consuming crate's `sark-fiber` facade.
51#[proc_macro]
52pub fn fiber(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
53    let input = syn::parse_macro_input!(input as fibers::Body);
54    emit(|paths| Ok(input.expand(paths)), runtime::Requirement::Fiber)
55}
56
57/// Defines statically dispatched routes at module scope; constructors infer state type and const parameters.
58/// `'_` selects the driver lifetime; scoped constructors retain state and route bounds.
59/// Capacity expressions and type/const arguments cannot contain macros, local bindings, or item declarations.
60/// The current pin-project expansion rejects const parameters named `core` (including `r#core`); use another name.
61#[proc_macro]
62pub fn define_route(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
63    use self::app::App;
64    let input = syn::parse_macro_input!(input as app::inputs::Input);
65    emit(
66        |paths| {
67            paths.h1.require()?;
68            App::new(paths, input).map(|app| app.expand(paths, runtime::H1::Available))
69        },
70        runtime::Requirement::Fiber,
71    )
72}
73
74/// Defines a protocol-neutral multiplexed task application at module scope.
75/// The input and const-parameter naming restrictions of [`define_route`] also apply here.
76#[proc_macro]
77pub fn define_task_route(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
78    use self::app::App;
79    let input = syn::parse_macro_input!(input as app::inputs::Input);
80    emit(
81        |paths| App::new(paths, input).map(|app| app.expand(paths, runtime::H1::Missing)),
82        runtime::Requirement::Fiber,
83    )
84}
85
86/// Defines a fiber-free, statically dispatched router at module scope.
87/// Omitting `: State` emits a zero-sized router backed by `sark::EmptyState`.
88/// The type and const argument restrictions of [`define_route`] also apply here.
89#[proc_macro]
90pub fn define_router(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
91    use self::app::App;
92    let input = syn::parse_macro_input!(input as app::inputs::Input);
93    emit(
94        |paths| App::new_router(paths, input).map(|app| app.router(paths, paths.h1())),
95        runtime::Requirement::Portable,
96    )
97}
98
99/// Leading visibility affects the marker; function visibility and state-family type/const bounds remain intact.
100/// Async state bounds use `'_` for the driver; responses may retain state-associated types. Signature `for<...>` binders cannot declare `'req` or `'d`.
101/// `request = Schema` disambiguates parsing/timer aliases; no-arg, `&State`, and async `Timer<'_>` handlers infer empty requests.
102#[proc_macro_attribute]
103pub fn handler(
104    attr: proc_macro::TokenStream,
105    item: proc_macro::TokenStream,
106) -> proc_macro::TokenStream {
107    use self::handler::{Entry, Handler, args};
108    let args = syn::parse_macro_input!(attr as args::Args);
109    let fun = syn::parse_macro_input!(item as syn::ItemFn);
110    let need = if fun.sig.asyncness.is_some() {
111        runtime::Requirement::Fiber
112    } else {
113        runtime::Requirement::Portable
114    };
115    emit(
116        |paths| {
117            Handler::new(args, fun, Entry::Handler)
118                .and_then(|handler| handler.expand(paths, paths.h1()))
119        },
120        need,
121    )
122}
123
124/// Defines a synchronous CONNECT session constructor using the same request
125/// projection as an HTTP handler while preserving state-borrowing sessions.
126/// Signature `for<...>` binders follow the lifetime naming requirement of [`handler`].
127#[proc_macro_attribute]
128pub fn duplex(
129    attr: proc_macro::TokenStream,
130    item: proc_macro::TokenStream,
131) -> proc_macro::TokenStream {
132    use syn::{Error, Item};
133
134    use self::handler::{Entry, Handler};
135
136    let args = syn::parse_macro_input!(attr as handler::args::Args);
137    let item = syn::parse_macro_input!(item as Item);
138    emit(
139        |paths| match item {
140            Item::Fn(fun) => Handler::new(args, fun, Entry::Duplex)
141                .and_then(|handler| handler.expand(paths, paths.h1())),
142            other => Err(Error::new_spanned(
143                other,
144                "#[sark_gen::duplex] supports only fn",
145            )),
146        },
147        runtime::Requirement::Fiber,
148    )
149}
150
151/// Derives JSON decoding and encoding for structs declared at module scope.
152/// Decoding requires nongeneric structs; `encode` and `try_encode` also support generics.
153/// Type and const arguments cannot contain macros, local bindings, or item declarations.
154#[proc_macro_attribute]
155pub fn json(
156    attr: proc_macro::TokenStream,
157    item: proc_macro::TokenStream,
158) -> proc_macro::TokenStream {
159    let st = syn::parse_macro_input!(item as syn::ItemStruct);
160    let mode = syn::parse_macro_input!(attr as json::mode::Mode);
161    emit(
162        |paths| json::build(paths, mode, st),
163        runtime::Requirement::Json,
164    )
165}
166
167/// Generates a request projection for a module-scope struct whose fields declare their wire source.
168/// `#[authority]` accepts retained bytes; H1 uses Host/absolute targets while
169/// H2 and H3 use `:authority` with Host fallback, retaining it only on demand.
170#[proc_macro_attribute]
171pub fn request(
172    attr: proc_macro::TokenStream,
173    item: proc_macro::TokenStream,
174) -> proc_macro::TokenStream {
175    let mode = syn::parse_macro_input!(attr as request::mode::Mode);
176    let st = syn::parse_macro_input!(item as syn::ItemStruct);
177    emit(
178        |paths| {
179            if st
180                .attrs
181                .iter()
182                .any(|attr| attr.path().is_ident("json_body"))
183            {
184                paths.json.require()?;
185            }
186            request::build(paths, mode, st)
187        },
188        runtime::Requirement::Portable,
189    )
190}
191
192#[proc_macro_attribute]
193pub fn response(
194    attr: proc_macro::TokenStream,
195    item: proc_macro::TokenStream,
196) -> proc_macro::TokenStream {
197    use self::response::code;
198    let mode = syn::parse_macro_input!(attr as response::Mode);
199    let st = syn::parse_macro_input!(item as syn::ItemStruct);
200    emit(
201        |paths| {
202            if matches!(mode, response::Mode::Json) {
203                paths.json.require()?;
204            }
205            code::build(paths, mode, st)
206        },
207        runtime::Requirement::Portable,
208    )
209}