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(None)))
69        },
70        runtime::Requirement::Fiber,
71    )
72}
73
74#[proc_macro]
75pub fn define_cleartext_route(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
76    let input = syn::parse_macro_input!(input as app::inputs::Input);
77    emit(
78        |paths| {
79            paths.h1.require()?;
80            let h2 = runtime::require("sark-h2", "define_cleartext_route")?;
81            app::App::new(paths, input)
82                .map(|app| app.expand(paths, runtime::H1::Available(Some(h2))))
83        },
84        runtime::Requirement::Fiber,
85    )
86}
87
88/// Defines a protocol-neutral multiplexed task application at module scope.
89/// The input and const-parameter naming restrictions of [`define_route`] also apply here.
90#[proc_macro]
91pub fn define_task_route(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
92    use self::app::App;
93    let input = syn::parse_macro_input!(input as app::inputs::Input);
94    emit(
95        |paths| App::new(paths, input).map(|app| app.expand(paths, runtime::H1::Missing)),
96        runtime::Requirement::Fiber,
97    )
98}
99
100/// Defines a fiber-free, statically dispatched router at module scope.
101/// Omitting `: State` emits a zero-sized router backed by `sark::EmptyState`.
102/// The type and const argument restrictions of [`define_route`] also apply here.
103#[proc_macro]
104pub fn define_router(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
105    use self::app::App;
106    let input = syn::parse_macro_input!(input as app::inputs::Input);
107    emit(
108        |paths| App::new_router(paths, input).map(|app| app.router(paths, paths.h1())),
109        runtime::Requirement::Portable,
110    )
111}
112
113/// Leading visibility affects the marker; function visibility and state-family type/const bounds remain intact.
114/// Async state bounds use `'_` for the driver; responses may retain state-associated types. Signature `for<...>` binders cannot declare `'req` or `'d`.
115/// `request = Schema` disambiguates parsing/timer aliases; no-arg, `&State`, and async `Timer<'_>` handlers infer empty requests.
116#[proc_macro_attribute]
117pub fn handler(
118    attr: proc_macro::TokenStream,
119    item: proc_macro::TokenStream,
120) -> proc_macro::TokenStream {
121    use self::handler::{Entry, Handler, args};
122    let args = syn::parse_macro_input!(attr as args::Args);
123    let fun = syn::parse_macro_input!(item as syn::ItemFn);
124    let need = if fun.sig.asyncness.is_some() {
125        runtime::Requirement::Fiber
126    } else {
127        runtime::Requirement::Portable
128    };
129    emit(
130        |paths| {
131            Handler::new(args, fun, Entry::Handler)
132                .and_then(|handler| handler.expand(paths, paths.h1()))
133        },
134        need,
135    )
136}
137
138/// Defines a synchronous CONNECT session constructor using the same request
139/// projection as an HTTP handler while preserving state-borrowing sessions.
140/// Signature `for<...>` binders follow the lifetime naming requirement of [`handler`].
141#[proc_macro_attribute]
142pub fn duplex(
143    attr: proc_macro::TokenStream,
144    item: proc_macro::TokenStream,
145) -> proc_macro::TokenStream {
146    use syn::{Error, Item};
147
148    use self::handler::{Entry, Handler};
149
150    let args = syn::parse_macro_input!(attr as handler::args::Args);
151    let item = syn::parse_macro_input!(item as Item);
152    emit(
153        |paths| match item {
154            Item::Fn(fun) => Handler::new(args, fun, Entry::Duplex)
155                .and_then(|handler| handler.expand(paths, paths.h1())),
156            other => Err(Error::new_spanned(
157                other,
158                "#[sark_gen::duplex] supports only fn",
159            )),
160        },
161        runtime::Requirement::Fiber,
162    )
163}
164
165/// Derives JSON decoding and encoding for structs declared at module scope.
166/// Decoding requires nongeneric structs; `encode` and `try_encode` also support generics.
167/// Type and const arguments cannot contain macros, local bindings, or item declarations.
168#[proc_macro_attribute]
169pub fn json(
170    attr: proc_macro::TokenStream,
171    item: proc_macro::TokenStream,
172) -> proc_macro::TokenStream {
173    let st = syn::parse_macro_input!(item as syn::ItemStruct);
174    let mode = syn::parse_macro_input!(attr as json::mode::Mode);
175    emit(
176        |paths| json::build(paths, mode, st),
177        runtime::Requirement::Json,
178    )
179}
180
181/// Generates a request projection for a module-scope struct whose fields declare their wire source.
182/// `#[authority]` accepts retained bytes; H1 uses Host/absolute targets while
183/// H2 and H3 use `:authority` with Host fallback, retaining it only on demand.
184#[proc_macro_attribute]
185pub fn request(
186    attr: proc_macro::TokenStream,
187    item: proc_macro::TokenStream,
188) -> proc_macro::TokenStream {
189    let mode = syn::parse_macro_input!(attr as request::mode::Mode);
190    let st = syn::parse_macro_input!(item as syn::ItemStruct);
191    emit(
192        |paths| {
193            if st
194                .attrs
195                .iter()
196                .any(|attr| attr.path().is_ident("json_body"))
197            {
198                paths.json.require()?;
199            }
200            request::build(paths, mode, st)
201        },
202        runtime::Requirement::Portable,
203    )
204}
205
206#[proc_macro_attribute]
207pub fn response(
208    attr: proc_macro::TokenStream,
209    item: proc_macro::TokenStream,
210) -> proc_macro::TokenStream {
211    use self::response::code;
212    let mode = syn::parse_macro_input!(attr as response::Mode);
213    let st = syn::parse_macro_input!(item as syn::ItemStruct);
214    emit(
215        |paths| {
216            if matches!(mode, response::Mode::Json) {
217                paths.json.require()?;
218            }
219            code::build(paths, mode, st)
220        },
221        runtime::Requirement::Portable,
222    )
223}