alux_ext_macros/lib.rs
1//! Procedural macros for declaring and defunctionalizing DD programs.
2//!
3//! This crate is the shared syntax layer for DD extension, HTTP, and JSON-RPC programs.
4//! Its public surface stays small: parsing, validation, and code generation
5//! live in private modules, while this facade contains only procedural-macro
6//! entry points. Product crates re-export the macros they own.
7//!
8//! Every example below is illustrative rather than tested. Expansion names `::alux_ext`,
9//! `::alux_http`, and `::alux_jsonrpc`, and a procedural-macro crate cannot depend on the crates its
10//! own expansion targets. The executed examples live in those crates' documentation.
11
12mod ext;
13mod http_program;
14mod jsonrpc_program;
15mod lower;
16mod syntax;
17
18use ext::ext_internal;
19use http_program::http_program_defunc_internal;
20use jsonrpc_program::jsonrpc_program_defunc_internal;
21use proc_macro::TokenStream;
22
23/// Declares extension methods and optionally gives each method a first-order operation type.
24///
25/// Arguments unrelated to defunctionalization are forwarded to `extend::ext`.
26/// Plain `defunc` produces a hidden `*Operation<Context>` type implementing
27/// `OperationAlg` and `ApplyAlg`. When every method already builds first-order
28/// route syntax from `routes()`, plain `defunc` names those inferred programs
29/// directly. A backend such as `defunc(via = http)` or `defunc(via = jsonrpc)`
30/// additionally lifts convenient method references while producing named programs
31/// and their interpreter evidence.
32///
33/// ```ignore
34/// use alux_ext::ext;
35///
36/// #[ext(name = CounterExt, defunc)]
37/// impl<This> This
38/// where
39/// This: CounterAlg,
40/// {
41/// async fn incremented(&self, by: u32) -> u32 {
42/// self.increment(by).await
43/// }
44/// }
45///
46/// // The expansion also defines `IncrementedOperation<This>`.
47/// ```
48///
49/// ```ignore
50/// use alux_ext::ext;
51/// use alux_http::http;
52///
53/// #[ext(name = StatusRoutesExt, defunc(via = http))]
54/// impl<This> This
55/// where
56/// This: HttpApiAlg + JsonOutAlg,
57/// {
58/// /// Declares the status surface.
59/// fn status_routes<Alg>(&self)
60/// where
61/// Alg: StatusAlg,
62/// {
63/// self.routes().get("/status", self.op(Alg::status_current).json())
64/// }
65/// }
66///
67/// // The expansion also defines `StatusRoutesProgram<Alg>`.
68/// ```
69#[proc_macro_attribute]
70pub fn ext(attr: TokenStream, item: TokenStream) -> TokenStream {
71 ext_internal(attr.into(), item.into()).unwrap_or_else(syn::Error::into_compile_error).into()
72}
73
74/// Lowers extension methods into named, composable HTTP programs.
75///
76/// This is the HTTP backend that `alux_ext::ext(..., defunc(via = http))` selects. `via = http`
77/// resolves the name in the authoring scope, so a declaration imports it as `use alux_http::http`.
78/// Applying the attribute directly means the same thing.
79///
80/// Each method becomes a first-order HTTP program. A route handler written with `op(...)` is replaced
81/// by the operation type generated by `alux_ext::ext(defunc)`, the declared input roles and output
82/// kind become bounds on the interpreter, and a call to another method of the same extension becomes
83/// a nested program.
84///
85/// ```ignore
86/// use alux_ext::ext;
87/// use alux_http::{HttpApiAlg, JsonOutAlg, http};
88///
89/// #[ext(name = StatusApiExt, defunc(via = http))]
90/// impl<This> This
91/// where
92/// This: HttpApiAlg + JsonOutAlg,
93/// {
94/// /// Declares the status surface.
95/// fn status_api<Alg>(&self)
96/// where
97/// Alg: StatusAlg,
98/// {
99/// self.routes().get("/status", self.op(Alg::status_current).json())
100/// }
101/// }
102///
103/// // The expansion also defines `StatusApiProgram<Alg>`.
104/// ```
105#[proc_macro_attribute]
106pub fn http(attr: TokenStream, item: TokenStream) -> TokenStream {
107 http_program_defunc_internal(attr.into(), item.into()).unwrap_or_else(syn::Error::into_compile_error).into()
108}
109
110/// Lowers extension methods into named, composable JSON-RPC programs.
111///
112/// This is the JSON-RPC backend that `alux_ext::ext(..., defunc(via = jsonrpc))` selects. `via = jsonrpc`
113/// resolves the name in the authoring scope, so a declaration imports it as `use alux_jsonrpc::jsonrpc`.
114/// Applying the attribute directly means the same thing.
115///
116/// Each method becomes a first-order JSON-RPC program. A method handler written with `op(...)` is
117/// replaced by the operation type generated by `alux_ext::ext(defunc)`, the operation's argument
118/// product and output become bounds on the interpreter, `.named()` selects object decoding using the
119/// authored argument names, and a call to another method of the same extension becomes a nested
120/// program.
121///
122/// A `fallible` argument converts every declared method's error into a JSON-RPC protocol error, so
123/// each declaration that does not say otherwise is read as `.fallible()`. This argument belongs to
124/// the JSON-RPC backend; every other argument is forwarded to `extend::ext`.
125///
126/// ```ignore
127/// use alux_ext::ext;
128/// use alux_jsonrpc::{JsonRpcApiAlg, jsonrpc};
129///
130/// #[ext(name = StatusRpcExt, defunc(via = jsonrpc))]
131/// impl<This> This
132/// where
133/// This: JsonRpcApiAlg,
134/// {
135/// /// Declares the status surface.
136/// fn status_rpc<Alg>(&self)
137/// where
138/// Alg: StatusAlg,
139/// {
140/// self.methods().method("status_current", self.op(Alg::status_current))
141/// }
142/// }
143///
144/// // The expansion also defines `StatusRpcProgram<Alg>`.
145/// ```
146#[proc_macro_attribute]
147pub fn jsonrpc(attr: TokenStream, item: TokenStream) -> TokenStream {
148 jsonrpc_program_defunc_internal(attr.into(), item.into()).unwrap_or_else(syn::Error::into_compile_error).into()
149}