arcature-macros 0.1.0

Proc-macro crate for Arcature: #[model], #[request], #[controller], #[derive(Job)], #[derive(Event)].
Documentation
//! `redirect!(...)` -- turns a path-producing expression into an Arcature
//! redirect response.
//!
//! ```ignore
//! redirect!(route::links::show(link.id))
//! // expands to: ::arcature::redirect().to(route::links::show(link.id))
//! ```
//!
//! The argument is any expression producing a path (typically a call to a
//! `route::...` helper generated by `routes!`). The expansion returns a
//! [`RedirectResponse`](arcature::RedirectResponse) builder, so the caller
//! may keep chaining the response vocabulary:
//!
//! ```ignore
//! redirect!(route::links::index()).with("status", "Link created")
//! ```
//!
//! The macro does not wrap the result in `Ok(..)`; a controller returning
//! `Result<Response>` writes `Ok(redirect!(..).into_response())`, and one
//! returning `Result<RedirectResponse>` writes `Ok(redirect!(..))`.

use proc_macro2::TokenStream;
use quote::quote;

use crate::diagnostic::{MacroError, MacroErrorCode, MacroResult};

/// The implementation of `redirect!`. Called by the thin `lib.rs`
/// entrypoint. Returns a [`MacroError`] (converted to `compile_error!` by
/// the entrypoint) on failure -- never panics.
pub fn redirect(input: TokenStream) -> MacroResult {
    if input.is_empty() {
        return Err(MacroError::new(
            MacroErrorCode::ArcM001,
            proc_macro2::Span::call_site(),
            "redirect! requires a route expression, e.g. `redirect!(route::dashboard())`",
        ));
    }

    Ok(quote! {
        ::arcature::redirect().to(#input)
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn wraps_route_helper_call() {
        let expanded = redirect(quote! { route::links::show(link.id) }).unwrap();
        let s = expanded.to_string();
        assert!(s.contains(":: arcature :: redirect ()"), "got: {s}");
        assert!(s.contains("route :: links :: show"), "got: {s}");
    }

    #[test]
    fn wraps_simple_path() {
        let expanded = redirect(quote! { route::dashboard() }).unwrap();
        assert!(expanded.to_string().contains("dashboard"));
    }

    #[test]
    fn rejects_empty_input() {
        let err = redirect(TokenStream::new()).unwrap_err();
        assert_eq!(err.code(), MacroErrorCode::ArcM001);
    }
}