alux-ext-macros 0.1.0

Procedural macros for ALUX first-order extension and transport programs
Documentation
//! Procedural macros for declaring and defunctionalizing DD programs.
//!
//! This crate is the shared syntax layer for DD extension, HTTP, and JSON-RPC programs.
//! Its public surface stays small: parsing, validation, and code generation
//! live in private modules, while this facade contains only procedural-macro
//! entry points. Product crates re-export the macros they own.
//!
//! Every example below is illustrative rather than tested. Expansion names `::alux_ext`,
//! `::alux_http`, and `::alux_jsonrpc`, and a procedural-macro crate cannot depend on the crates its
//! own expansion targets. The executed examples live in those crates' documentation.

mod ext;
mod http_program;
mod jsonrpc_program;
mod lower;
mod syntax;

use ext::ext_internal;
use http_program::http_program_defunc_internal;
use jsonrpc_program::jsonrpc_program_defunc_internal;
use proc_macro::TokenStream;

/// Declares extension methods and optionally gives each method a first-order operation type.
///
/// Arguments unrelated to defunctionalization are forwarded to `extend::ext`.
/// Plain `defunc` produces a hidden `*Operation<Context>` type implementing
/// `OperationAlg` and `ApplyAlg`. When every method already builds first-order
/// route syntax from `routes()`, plain `defunc` names those inferred programs
/// directly. A backend such as `defunc(via = http)` or `defunc(via = jsonrpc)`
/// additionally lifts convenient method references while producing named programs
/// and their interpreter evidence.
///
/// ```ignore
/// use alux_ext::ext;
///
/// #[ext(name = CounterExt, defunc)]
/// impl<This> This
/// where
///     This: CounterAlg,
/// {
///     async fn incremented(&self, by: u32) -> u32 {
///         self.increment(by).await
///     }
/// }
///
/// // The expansion also defines `IncrementedOperation<This>`.
/// ```
///
/// ```ignore
/// use alux_ext::ext;
/// use alux_http::http;
///
/// #[ext(name = StatusRoutesExt, defunc(via = http))]
/// impl<This> This
/// where
///     This: HttpApiAlg + JsonOutAlg,
/// {
///     fn status_routes<Alg>(&self)
///     where
///         Alg: StatusAlg,
///     {
///         self.routes().get("/status", self.op(Alg::status_current).json())
///     }
/// }
///
/// // The expansion also defines `StatusRoutesProgram<Alg>`.
/// ```
#[proc_macro_attribute]
pub fn ext(attr: TokenStream, item: TokenStream) -> TokenStream {
    ext_internal(attr.into(), item.into()).unwrap_or_else(syn::Error::into_compile_error).into()
}

/// Lowers extension methods into named, composable HTTP programs.
///
/// This is the HTTP backend that `alux_ext::ext(..., defunc(via = http))` selects. `via = http`
/// resolves the name in the authoring scope, so a declaration imports it as `use alux_http::http`.
/// Applying the attribute directly means the same thing.
///
/// Each method becomes a first-order HTTP program. A route handler written with `op(...)` is replaced
/// by the operation type generated by `alux_ext::ext(defunc)`, the declared input roles and output
/// kind become bounds on the interpreter, and a call to another method of the same extension becomes
/// a nested program.
///
/// ```ignore
/// use alux_ext::ext;
/// use alux_http::{HttpApiAlg, JsonOutAlg, http};
///
/// #[ext(name = StatusApiExt, defunc(via = http))]
/// impl<This> This
/// where
///     This: HttpApiAlg + JsonOutAlg,
/// {
///     fn status_api<Alg>(&self)
///     where
///         Alg: StatusAlg,
///     {
///         self.routes().get("/status", self.op(Alg::status_current).json())
///     }
/// }
///
/// // The expansion also defines `StatusApiProgram<Alg>`.
/// ```
#[proc_macro_attribute]
pub fn http(attr: TokenStream, item: TokenStream) -> TokenStream {
    http_program_defunc_internal(attr.into(), item.into()).unwrap_or_else(syn::Error::into_compile_error).into()
}

/// Lowers extension methods into named, composable JSON-RPC programs.
///
/// This is the JSON-RPC backend that `alux_ext::ext(..., defunc(via = jsonrpc))` selects. `via = jsonrpc`
/// resolves the name in the authoring scope, so a declaration imports it as `use alux_jsonrpc::jsonrpc`.
/// Applying the attribute directly means the same thing.
///
/// Each method becomes a first-order JSON-RPC program. A method handler written with `op(...)` is
/// replaced by the operation type generated by `alux_ext::ext(defunc)`, the operation's argument
/// product and output become bounds on the interpreter, `.named()` selects object decoding using the
/// authored argument names, and a call to another method of the same extension becomes a nested
/// program.
///
/// ```ignore
/// use alux_ext::ext;
/// use alux_jsonrpc::{JsonRpcApiAlg, jsonrpc};
///
/// #[ext(name = StatusRpcExt, defunc(via = jsonrpc))]
/// impl<This> This
/// where
///     This: JsonRpcApiAlg,
/// {
///     fn status_rpc<Alg>(&self)
///     where
///         Alg: StatusAlg,
///     {
///         self.methods().method("status_current", self.op(Alg::status_current))
///     }
/// }
///
/// // The expansion also defines `StatusRpcProgram<Alg>`.
/// ```
#[proc_macro_attribute]
pub fn jsonrpc(attr: TokenStream, item: TokenStream) -> TokenStream {
    jsonrpc_program_defunc_internal(attr.into(), item.into()).unwrap_or_else(syn::Error::into_compile_error).into()
}