Skip to main content

rust_microservice_macros/
lib.rs

1//! # πŸš€ Rust Microservice Macros
2//!
3//! A procedural macro crate designed to power the
4//! [`rust_microservice`](https://crates.io/crates/rust_microservice)
5//! ecosystem with compile-time server generation, automatic controller
6//! discovery, OpenAPI integration, authentication enforcement, and
7//! database injection.
8//!
9//! This crate eliminates runtime registration patterns by generating
10//! deterministic, compile-time server bootstrap logic.
11//!
12//! ---
13//!
14//! # 🎯 Design Goals
15//!
16//! - βœ… Zero runtime reflection
17//! - βœ… Compile-time controller discovery
18//! - βœ… Deterministic OpenAPI generation
19//! - βœ… Integrated JWT security middleware
20//! - βœ… Declarative database injection
21//! - βœ… Strict compile-time validation
22//!
23//! All routing, OpenAPI metadata, middleware wrapping, and database
24//! bindings are generated at compile time using Rust’s procedural macro
25//! system.
26//!
27//! ---
28//!
29//! # πŸ—οΈ Architecture Overview
30//!
31//! This crate is implemented using:
32//!
33//! - `proc_macro`
34//! - `proc_macro2`
35//! - `syn` (AST parsing)
36//! - `quote` (token generation)
37//! - `walkdir` (controller discovery)
38//!
39//! ## Macro Expansion Pipeline
40//!
41//! 1. Parse attribute arguments (`key = value` pairs)
42//! 2. Parse annotated Rust items (`ItemFn`, modules, etc.)
43//! 3. Load and inspect controller files
44//! 4. Extract Actix-Web handlers
45//! 5. Generate:
46//!    - Server bootstrap
47//!    - Route registration
48//!    - Swagger/OpenAPI specification
49//!    - JWT middleware wrappers
50//!    - Database injection logic
51//!
52//! No runtime route aggregation occurs β€” all handlers are resolved
53//! during compilation.
54//!
55//! ---
56//!
57//! # 🧩 Provided Macros
58//!
59//! This crate exposes three primary procedural attribute macros:
60//!
61//! - `#[api_server]`
62//! - `#[secured]`
63//! - `#[database]`
64//!
65//! ---
66//!
67//! # 🌐 `#[api_server]`
68//!
69//! Generates the full HTTP server bootstrap and controller registration
70//! logic for an `actix-web` application.
71//!
72//! ## Responsibilities
73//!
74//! - Recursively scans controller directories
75//! - Registers all HTTP handlers
76//! - Generates Swagger UI configuration
77//! - Generates OpenAPI documentation using `utoipa`
78//! - Optionally initializes database connections
79//! - Wraps the main function with `#[tokio::main]`
80//! - Initializes and runs the global `Server`
81//!
82//! ## Supported Attributes
83//!
84//! | Attribute | Type | Description |
85//! |------------|------|------------|
86//! | `controllers_path` | `&str` | Comma-separated directories containing controllers |
87//! | `openapi_title` | `&str` | OpenAPI title |
88//! | `openapi_api_name` | `&str` | OpenAPI tag name |
89//! | `openapi_api_description` | `&str` | OpenAPI tag description |
90//! | `openapi_auth_server` | `&str` | OAuth2 token URL fallback |
91//! | `database` | `"true" / "false"` | Enables SeaORM database initialization |
92//! | `banner` | `&str` | Startup banner printed during server initialization |
93//!
94//! ## Example
95//!
96//! ```rust,ignore
97//! use rust_microservice::ServerApi; // api_server was renamed to ServerApi for better ergonomics
98//!
99//! #[ServerApi(
100//!     controllers_path = "src/controllers",
101//!     openapi_title = "🌍 My API",
102//!     openapi_api_description = "Example API",
103//!     database = "true"
104//! )]
105//! async fn start() -> rust_microservice::Result<(), String> {}
106//! ```
107//!
108//! ---
109//!
110//! ⚠️ `IMPORTANT`: The `server_api` (*ServerApi in rust_microservice*), `database` and `secured`
111//! macros has been renamed or re-exported to improve ergonomics in the rust_microservice crate.
112//! This crate provides only the macro implementation. The public API is re-exported by the
113//! rust_microservice crate. Therefore, when using the macro in your project, prefer ServerApi
114//! instead of api_server.
115//!
116//! ---
117//!
118//! ## Generated Behavior
119//!
120//! - Wraps your function with `#[tokio::main]`
121//! - Discovers all Actix-Web handlers
122//! - Generates:
123//!   - `register_endpoints`
124//!   - `ApiDoc` (`utoipa::OpenApi`)
125//!   - Swagger UI endpoint `/swagger-ui/*`
126//!
127//! ---
128//!
129//! # πŸ” `#[secured]`
130//!
131//! Protects an Actix-Web endpoint with JWT authentication and
132//! role-based authorization.
133//!
134//! Internally generates:
135//!
136//! - A middleware module
137//! - A wrapper using `actix_web::middleware::from_fn`
138//! - Automatic role validation via `Server::validate_jwt`
139//!
140//! ## Supported Attributes
141//!
142//! | Attribute | Description |
143//! |------------|-------------|
144//! | `method` | HTTP method (`get`, `post`, etc.) |
145//! | `path` | Route path |
146//! | `authorize` | Role expression |
147//!
148//! ## Authorization Formats
149//!
150//! ### Single Role
151//!
152//! ```text
153//! authorize = "ROLE_ADMIN"
154//! ```
155//!
156//! ### Any Role
157//!
158//! ```text
159//! authorize = "hasAnyRole(ROLE_ADMIN, ROLE_USER)"
160//! ```
161//!
162//! ### All Roles
163//!
164//! ```text
165//! authorize = "hasAllRoles(ROLE_ADMIN, ROLE_AUDITOR)"
166//! ```
167//!
168//! ## Example
169//!
170//! ```rust,ignore
171//! use rust_microservice::secured;
172//! use actix_web::HttpResponse;
173//!
174//! #[secured(
175//!     method = "get",
176//!     path = "/v1/users",
177//!     authorize = "hasAnyRole(ROLE_ADMIN, ROLE_USER)"
178//! )]
179//! pub async fn list_users() -> HttpResponse {
180//!     HttpResponse::Ok().finish()
181//! }
182//! ```
183//!
184//! ## Security Validation
185//!
186//! The middleware validates:
187//!
188//! - JWT presence
189//! - Signature
190//! - Expiration (`exp`)
191//! - Issuer (`iss`)
192//! - Required roles
193//!
194//! If validation fails β†’ `401 Unauthorized`.
195//!
196//! ---
197//!
198//! # πŸ›’οΈ `#[database]`
199//!
200//! Injects a SeaORM `DatabaseConnection` into a repository function.
201//!
202//! ## Required Attributes
203//!
204//! | Attribute | Description |
205//! |------------|-------------|
206//! | `name` | Database configuration name |
207//! | `error` | Error variant returned if connection is unavailable |
208//!
209//! The macro injects:
210//!
211//! ```rust,ignore
212//! let db = Server::global()
213//!     .database_with_name("name")?;
214//! ```
215//!
216//! ## Example
217//!
218//! ```rust,ignore
219//! use rust_microservice::database;
220//!
221//! #[database(name = "api", error = "UserError::DatabaseNotConfigured")]
222//! pub async fn find_user(id: i32) -> Result<()> {
223//!     // `db` is available here
224//!     Ok(())
225//! }
226//! ```
227//!
228//! ---
229//!
230//! # πŸ”Ž Controller Discovery
231//!
232//! The `api_server` macro:
233//!
234//! - Traverses `controllers_path`
235//! - Parses each `.rs` file using `syn`
236//! - Extracts functions annotated with:
237//!
238//! ```text
239//! #[get]
240//! #[post]
241//! #[put]
242//! #[delete]
243//! #[patch]
244//! #[head]
245//! #[options]
246//! #[trace]
247//! #[connect]
248//! #[secured]
249//! ```
250//!
251//! These handlers are automatically registered into
252//! `actix_web::web::ServiceConfig`.
253//!
254//! ---
255//!
256//! # πŸ“„ OpenAPI Generation
257//!
258//! Uses `utoipa` to generate:
259//!
260//! - `#[derive(OpenApi)]`
261//! - Swagger UI configuration
262//! - OAuth2 security scheme
263//! - Global security requirements
264//!
265//! The security scheme is dynamically configured from:
266//!
267//! ```rust,ignore
268//! Server::global()?.settings().get_auth2_token_url()
269//! ```
270//!
271//! ---
272//!
273//! # βš™οΈ Internal Utility Structures
274//!
275//! ### `KeyValue`
276//!
277//! Parses:
278//!
279//! ```text
280//! key = value
281//! ```
282//!
283//! ### `ArgList`
284//!
285//! Parses:
286//!
287//! ```text
288//! key1 = value1, key2 = value2
289//! ```
290//!
291//! These power all attribute parsing in this crate.
292//!
293//! ---
294//!
295//! # 🧠 Compile-Time Guarantees
296//!
297//! - Controllers must be valid Rust modules
298//! - Handlers must use supported HTTP attributes
299//! - Database names must exist at runtime
300//! - Invalid macro parameters cause compile errors
301//!
302//! ---
303//!
304//! # πŸ§ͺ Runtime Integration
305//!
306//! Although this crate generates compile-time code,
307//! runtime behavior depends on:
308//!
309//! - `actix-web`
310//! - `tokio`
311//! - `utoipa`
312//! - `sea-orm`
313//! - `rust_microservice::Server`
314//!
315//! ---
316//!
317//! # πŸ“Œ Summary
318//!
319//! This macro crate transforms a modular Rust project into a fully
320//! initialized HTTP API server with:
321//!
322//! - Automatic route wiring
323//! - JWT security enforcement
324//! - OpenAPI documentation
325//! - Swagger UI
326//! - Database injection
327//!
328//! All achieved with minimal boilerplate and strict compile-time guarantees.
329//!
330//! ---
331//!
332//! πŸ¦€ Built for high-performance Rust microservices.
333//! Deterministic. Secure. Compile-time powered.
334//!
335#![allow(clippy::expect_fun_call)]
336#![allow(clippy::bind_instead_of_map)]
337#![allow(clippy::cmp_owned)]
338
339use proc_macro::TokenStream;
340use proc_macro2::Span;
341use quote::{ToTokens, format_ident, quote};
342use std::{fs::File, io::Read, path::Path};
343use syn::{
344    Attribute, Expr, Ident, ItemFn, Result, Token,
345    parse::{Parse, ParseStream},
346    parse_file, parse_macro_input, parse_str,
347    punctuated::Punctuated,
348};
349use walkdir::DirEntry;
350
351/// Represents a single `key = value` pair parsed from a macro input.
352///
353/// This structure is used when implementing procedural macros
354/// that accept configuration-style arguments. The `KeyValue` struct
355/// captures:
356///
357/// - A `key` as a [`syn::Ident`], representing the identifier on the left side.
358/// - An equals sign token (`=`), represented by [`Token![=]`].
359/// - A `value` expression, stored as a [`syn::Expr`].
360///
361/// # Parsing Behavior
362///
363/// The `Parse` implementation consumes three consecutive elements from
364/// the input stream:
365///
366/// ```text
367/// <ident> = <expr>
368/// ```
369///
370/// If any of these elements are missing or malformed, a parsing error
371/// is returned.
372///
373/// #  Example of how a single key-value pair might appear in a macro:
374///
375/// ```text
376/// timeout = 30
377/// ```
378struct KeyValue {
379    pub key: Ident,
380    pub _eq: Token![=],
381    pub value: Expr,
382}
383
384impl Parse for KeyValue {
385    fn parse(input: ParseStream) -> Result<Self> {
386        Ok(Self {
387            key: input.parse()?,
388            _eq: input.parse()?,
389            value: input.parse()?,
390        })
391    }
392}
393
394/// The ArgList struct represents a comma-separated list of `key = value`
395/// pairs.
396///
397/// `ArgList` is a generic container used in procedural macros to accept
398/// lists of configuration arguments in the form:
399///
400/// ```text
401/// key1 = value1, key2 = value2, key3 = value3
402/// ```
403///
404/// Internally it stores the items in a [`syn::punctuated::Punctuated`]
405/// structure, which keeps track of both the elements and their punctuation.
406///
407/// # Parsing Behavior
408///
409/// The `Parse` implementation uses [`Punctuated::parse_terminated`] to
410/// parse zero or more `KeyValue` entries separated by commas. Trailing
411/// commas are allowed.
412///
413/// # Example use inside a macro attribute:
414///
415/// ```rust
416/// #[my_macro(a = 1, b = "text", c = true)]
417/// ```
418///
419/// This structure facilitates ergonomic parsing of argument lists in
420/// procedural macros.
421struct ArgList {
422    items: Punctuated<KeyValue, Token![,]>,
423}
424
425impl Parse for ArgList {
426    fn parse(input: ParseStream) -> Result<Self> {
427        Ok(Self {
428            items: Punctuated::parse_terminated(input)?,
429        })
430    }
431}
432
433/// # πŸ”— API Server Macro
434///
435/// The `api_server` macro is a procedural macro that generates the code necessary to
436/// start an `actix-web` HTTP server with support for OpenAPI documentation and
437/// a health check endpoint.
438///
439/// The `api_server` macro takes the following attributes:
440///
441/// - `controllers_path`: A comma-separated list of paths to modules containing
442///   controllers. The macro will recursively traverse the directories and generate
443///   code to register the controllers with the HTTP server.
444///
445/// - `openapi_title`: A string used as the title of the OpenAPI documentation.
446///
447/// - `openapi_api_description`: A string used as the description of the OpenAPI
448///   documentation.
449///
450/// - `database`: A boolean indicating whether the microservice should enable database
451///   integration. If set to `true`, the macro will generate code to initialize the
452///   database connection pool using the `sea_orm` crate.
453///
454/// - `banner`: A string used as the banner of the microservice. The banner is displayed
455///   in the server logs during startup.
456///
457/// Example of a minimal server bootstrap using this crate:
458///
459/// ```rust,ignore
460/// use rust_microservice::ServerApi;
461///
462/// #[ServerApi(
463///     controllers_path = "src/module, src/controllers",
464///     openapi_title = "🌐 Rest API Server",
465///     openapi_api_description = "Rest API OpenApi Documentation built with Rust πŸ¦€.",
466///     database = "true",
467///     banner = r#"
468///             _~^~^~_         ___    ___   ____    ____
469///         \) /  o o  \ (/    / _ |  / _ \ /  _/   / __/___  ____ _  __ ___  ____
470///           '_   -   _'     / __ | / ___/_/ /    _\ \ / -_)/ __/| |/ //! -_)/ __/
471///           / '-----' \    /_/ |_|/_/   /___/   /___/ \__//!_/   |___/ \__//!_/
472///     "#
473/// )]
474/// async fn start_server() -> rust_microservice::Result<(), String> {}
475/// ```
476#[proc_macro_attribute]
477pub fn api_server(attrs: TokenStream, item: TokenStream) -> TokenStream {
478    let main_fn = parse_macro_input!(item as ItemFn);
479    let arg_list = parse_macro_input!(attrs as ArgList);
480
481    impl_main_fn(main_fn, arg_list)
482}
483
484/// Generates the expanded `main` function for the procedural macro.
485///
486/// This function extracts the body of the user-provided `main` function,
487/// processes the project's controllers to build endpoint registration code,
488/// and produces the final token stream that initializes and runs the server.
489///
490/// # Parameters
491/// - `main_fn`: The original `main` function captured by the macro.
492/// - `arg_list`: Parsed arguments used to locate and register controllers.
493///
494/// # Returns
495/// A [`TokenStream`] containing the generated `main` function and the
496/// controller registration code.
497fn impl_main_fn(main_fn: ItemFn, arg_list: ArgList) -> TokenStream {
498    let main_body = &main_fn.block.stmts;
499
500    let new_server_command = impl_generate_new_server(&arg_list);
501
502    // Search and process project controllers
503    let (register_token, openapi_handlers, import_modules) =
504        search_and_process_controllers(&arg_list);
505    let openapi_token = generate_openapi_token(openapi_handlers, &arg_list);
506    let database = impl_generate_database_intialization(&arg_list);
507    let fn_name = main_fn.sig.ident;
508    let fn_visibility = &main_fn.vis;
509
510    quote! {
511        #import_modules
512
513        use rust_microservice::Server;
514
515        #[tokio::main]
516        #fn_visibility async fn #fn_name() -> rust_microservice::Result<(), String> {
517            #( #main_body )*
518
519            let server = #new_server_command
520                .init().await.map_err(|e| e.to_string())?
521                #database
522                .configure(Some(register_endpoints));
523
524            Server::set_global(server);
525            let result = Server::global_server();
526            match result {
527                Some(server) => {
528                    server.run().await;
529                }
530                None => {
531                    return Err("Global server is not initialized".to_string());
532                }
533            }
534
535            Ok(())
536        }
537
538        #register_token
539
540        #openapi_token
541    }
542    .into()
543}
544
545/// Generates code to initialize the database connection based on the provided
546/// configuration parameters.
547///
548/// # Arguments
549///
550/// - `arg_list`: The list of arguments provided to the macro.
551///
552/// # Returns
553///
554/// A `TokenStream` containing the generated code to initialize the database
555/// connection. If the `database` parameter is set to `true`, the generated code
556/// will initialize the database connection. Otherwise, it will return an empty
557/// `TokenStream`.
558fn impl_generate_database_intialization(arg_list: &ArgList) -> proc_macro2::TokenStream {
559    let initialize_database: bool =
560        get_arg_string_value(arg_list, "database".to_string(), "false".to_string())
561            .parse()
562            .expect("Failed to parse Database value");
563
564    match initialize_database {
565        true => quote! {
566            .intialize_database().await.map_err(|e| e.to_string())?
567        },
568        false => quote! {},
569    }
570}
571
572fn impl_generate_new_server(arg_list: &ArgList) -> proc_macro2::TokenStream {
573    let banner = get_arg_string_value(arg_list, "banner".to_string(), "".to_string());
574    if !banner.is_empty() {
575        return quote! {
576            Server::new(env!("CARGO_PKG_VERSION").to_string(), Some(#banner.into()))
577        };
578    }
579    quote! {
580       Server::new(env!("CARGO_PKG_VERSION").to_string(), None)
581    }
582}
583
584/// Searches for controller files and generates endpoint registration code.
585///
586/// This function reads the `controllers_path` argument, recursively scans the
587/// specified directories for Rust (`.rs`) files, and processes each controller
588/// to extract route handlers and required module imports.
589///
590/// It returns:
591/// - A `TokenStream` containing the generated `register_endpoints` function,
592///   which registers all discovered handlers and configures Swagger UI.
593/// - A list of `TokenStream`s representing the collected handlers.
594/// - A `TokenStream` with the unique `use` statements (module imports)
595///   required by the discovered controllers.
596///
597/// # Parameters
598/// * `arg_list` - Macro arguments containing the `controllers_path` configuration.
599///
600/// # Returns
601/// A tuple with:
602/// 1. Generated endpoint registration code.
603/// 2. A vector of handler token streams.
604/// 3. Generated module import token streams.
605fn search_and_process_controllers(
606    arg_list: &ArgList,
607) -> (
608    proc_macro2::TokenStream,
609    Vec<proc_macro2::TokenStream>,
610    proc_macro2::TokenStream,
611) {
612    let controllers_path =
613        get_arg_string_value(arg_list, "controllers_path".to_string(), "".to_string());
614
615    let span = proc_macro::Span::call_site();
616    let main_file_syntax_tree = load_syntax_tree_from_file(span.file());
617
618    let mut handlers: Vec<proc_macro2::TokenStream> = Vec::new();
619    let mut openapi_handlers: Vec<proc_macro2::TokenStream> = Vec::new();
620    let mut import_modules: Vec<proc_macro2::TokenStream> = Vec::new();
621    if !controllers_path.is_empty() {
622        let paths = controllers_path.split(',').collect::<Vec<&str>>();
623        paths.iter().for_each(|root| {
624            //println!("Processing controller path: {root}");
625
626            let path = Path::new(root.trim_matches(|c| c == ' ' || c == '"'));
627            if path.exists() && path.is_dir() {
628                //println!("Processing controller DIR: {path:?}");
629                for entry in walkdir::WalkDir::new(path) {
630                    match entry {
631                        Ok(entry) => {
632                            let file_path = entry.path();
633                            if file_path.is_file()
634                                && file_path.extension().and_then(|s| s.to_str()) == Some("rs")
635                            {
636                                let module_path = convert_path_to_module(file_path);
637                                let module_token: proc_macro2::TokenStream =
638                                    parse_str(module_path.as_str()).unwrap();
639
640                                let (handler, openapi) = process_controller(&entry, &module_token);
641                                if !handler.is_empty() {
642                                    handlers.push(handler);
643                                    openapi_handlers.push(openapi);
644                                }
645
646                                let import_module =
647                                    find_imported_module(&main_file_syntax_tree, &module_token);
648                                if !import_module.is_empty() {
649                                    let match_found = import_modules
650                                        .iter()
651                                        .find(|m| *m.to_string() == import_module.to_string());
652                                    if match_found.is_none() {
653                                        import_modules.push(import_module);
654                                    }
655                                }
656                            }
657                        }
658                        Err(error) => println!("Error processing controller. Detail: {error}"),
659                    }
660                }
661            }
662            // else {
663            //     println!("Controller path does not exist or is not a directory: {path:?}. isDir: {}, exists: {}",
664            //         path.is_dir(),
665            //         path.exists()
666            //     );
667            // }
668        });
669    }
670
671    let quote = quote! {
672        use actix_web::web::ServiceConfig;
673
674        fn register_endpoints(cfg: &mut ServiceConfig) {
675            #(#handlers)*
676
677            // Register the swagger-ui handler
678            let openapi = ApiDoc::openapi();
679            cfg.service(
680                SwaggerUi::new("/swagger-ui/{_:.*}")
681                    .url("/api-docs/openapi.json", openapi.clone())
682                    .config(Config::default().validator_url("none"))
683            );
684        }
685    };
686
687    let import_mod = quote! {
688        #( #import_modules )*
689    };
690
691    (quote, openapi_handlers, import_mod)
692}
693
694/// Converts a file system path into a Rust module path.
695///
696/// This function normalizes a controller file path by stripping the `src`
697/// prefix, removing the extension, and converting directory separators
698/// into `::`, producing a valid Rust module path.
699///
700/// # Parameters
701/// - `path`: The file path to convert.
702///
703/// # Returns
704/// A `String` containing the normalized module path.
705fn convert_path_to_module(path: &Path) -> String {
706    let s = path
707        .with_extension("")
708        .strip_prefix("src")
709        .unwrap()
710        .to_string_lossy()
711        .to_string();
712    let normalized = s.replace('\\', "/").replace('/', "::");
713    //println!("NORMALIZED: {normalized}");
714    normalized
715        .replace("::mod::", "::")
716        .strip_suffix("::mod")
717        .unwrap_or(&normalized)
718        .to_string()
719}
720
721/// Generates the token stream needed to register all handler functions
722/// from a controller module.
723///
724/// This function reads the controller source file, parses its syntax tree,
725/// extracts handler functions, and produces the Actix-Web service
726/// registration code.
727///
728/// # Parameters
729/// - `file`: Directory entry of the controller file.
730/// - `module`: Parsed module path token.
731///
732/// # Returns
733/// A [`TokenStream`] containing the registration statements.
734fn process_controller(
735    file: &DirEntry,
736    module: &proc_macro2::TokenStream,
737) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
738    let filename = file.file_name().to_str().unwrap();
739    let mut f = File::open(file.path()).expect(format!("Unable to open file: {filename}").as_str());
740    let mut contents = String::new();
741    f.read_to_string(&mut contents)
742        .expect(format!("Unable to read file: {filename}").as_str());
743    let syntax_tree = parse_file(&contents).unwrap();
744    let handles = find_file_handles(&syntax_tree);
745
746    let services = quote! {
747        #( cfg.service(#module::#handles); )*
748    };
749
750    let openapi = quote! {
751        #(#module::#handles),*
752    };
753
754    (services, openapi)
755}
756
757/// Checks whether a module is already imported in the given syntax tree.
758///
759/// This function inspects the root items of a parsed Rust source file
760/// (`syn::File`) to determine if the root module of the provided module path
761/// is declared. If the module is not found, it generates a `mod <name>;`
762/// declaration.
763///
764/// # Parameters
765/// - `syntax_tree`: The parsed Rust file AST to search for module declarations.
766/// - `module`: A token stream representing a module path (e.g. `foo::bar`).
767///
768/// # Returns
769/// A [`TokenStream`] containing:
770/// - an empty token stream if the root module is already declared, or
771/// - a `mod <root_module>;` declaration if it is missing.
772fn find_imported_module(
773    file: &Option<syn::File>,
774    module: &proc_macro2::TokenStream,
775) -> proc_macro2::TokenStream {
776    let root_module: proc_macro2::TokenStream =
777        parse_str(module.to_string().split("::").next().unwrap()).unwrap();
778
779    if let Some(syntax_tree) = file {
780        for item in &syntax_tree.items {
781            if let syn::Item::Mod(item_mod) = item
782                && item_mod.ident.to_string() == root_module.to_string()
783            {
784                // println!("Module already imported: {}", root_module.to_string(),);
785                return quote! {};
786            }
787        }
788    }
789    quote! {
790        pub mod #root_module;
791    }
792}
793
794/// Loads and parses a Rust source file into a `syn::File` syntax tree.
795///
796/// This function opens the given file path, reads its entire contents,
797/// and parses it using `syn::parse_file`, returning the resulting
798/// abstract syntax tree (AST).
799///
800/// # Panics
801///
802/// Panics if the file cannot be opened, read, or if the source code
803/// is not valid Rust syntax.
804///
805/// # Arguments
806///
807/// * `file` - Path to the Rust source file to be parsed.
808///
809/// # Returns
810///
811/// A `syn::File` representing the parsed syntax tree of the source file.
812fn load_syntax_tree_from_file(file: String) -> Option<syn::File> {
813    let f = Path::new(&file);
814    if f.is_dir() || !f.exists() {
815        return None;
816    }
817    let mut f = File::open(file).expect("Unable to open file.");
818    let mut contents = String::new();
819    f.read_to_string(&mut contents)
820        .expect("Unable to read file: {file}");
821    let syntax_tree = parse_file(&contents).unwrap();
822    Some(syntax_tree)
823}
824
825/// Extracts all handler function identifiers from a parsed Rust file.
826///
827/// A handler function is any function annotated with an Actix-Web HTTP
828/// method macro (e.g., `#[get]`, `#[post]`). This function collects those
829/// identifiers for later endpoint registration.
830///
831/// # Parameters
832/// - `syntax_tree`: Parsed Rust file.
833///
834/// # Returns
835/// A vector of function identifiers representing handler endpoints.
836fn find_file_handles(syntax_tree: &syn::File) -> Vec<Ident> {
837    let mut handles: Vec<Ident> = Vec::new();
838    for item in &syntax_tree.items {
839        if let syn::Item::Fn(item_fn) = item
840            && is_handle_function(item_fn)
841        {
842            handles.push(item_fn.sig.ident.clone());
843        }
844    }
845    handles
846}
847
848/// Determines whether a function is an Actix-Web handler.
849///
850/// A function qualifies as a handler if it contains one of the supported
851/// HTTP method attributes (e.g., `#[get]`, `#[post]`, `#[put]`).
852///
853/// # Parameters
854/// - `item_fn`: The function to inspect.
855///
856/// # Returns
857/// `true` if the function is a handler, otherwise `false`.
858fn is_handle_function(item_fn: &ItemFn) -> bool {
859    const HTTP_METHODS: &[&str] = &[
860        "get", "post", "put", "delete", "head", "connect", "options", "trace", "patch", "secured",
861    ];
862
863    item_fn
864        .attrs
865        .iter()
866        .filter_map(|attr| attr.meta.path().get_ident())
867        .any(|ident| HTTP_METHODS.iter().any(|m| ident == m))
868}
869
870/// Generates an OpenAPI specification token stream.
871///
872/// This function builds a `TokenStream` containing the `#[derive(OpenApi)]`
873/// configuration, using values extracted from `arg_list` to define the
874/// OpenAPI title, API name, and description. It also injects the provided
875/// handler paths into the OpenAPI `paths` section.
876///
877/// # Parameters
878/// - `handlers`: A list of tokenized API handler paths.
879/// - `arg_list`: The arguments used to customize OpenAPI metadata.
880///
881/// # Returns
882/// A `TokenStream` representing the OpenAPI configuration for code generation.
883fn generate_openapi_token(
884    handlers: Vec<proc_macro2::TokenStream>,
885    arg_list: &ArgList,
886) -> proc_macro2::TokenStream {
887    let openapi_title = get_arg_string_value(
888        arg_list,
889        "openapi_title".to_string(),
890        "🌐 API Server".to_string(),
891    );
892    let api_name = get_arg_string_value(
893        arg_list,
894        "openapi_api_name".to_string(),
895        "βš™οΈ Rest API".to_string(),
896    );
897    let api_description = get_arg_string_value(
898        arg_list,
899        "openapi_api_description".to_string(),
900        "Rest API OpenApi Documentation.".to_string(),
901    );
902    let auth_server =
903        get_arg_string_value(arg_list, "openapi_auth_server".to_string(), "".to_string());
904
905    quote! {
906        use utoipa_swagger_ui::{SwaggerUi, Config};
907        use utoipa::{
908            Modify, OpenApi,
909            openapi::SecurityRequirement,
910            openapi::security::{
911                Flow,
912                Password,
913                OAuth2,
914                Scopes,
915                SecurityScheme,
916                Http,
917                HttpAuthScheme
918            },
919        };
920
921        #[derive(OpenApi)]
922        #[openapi(
923            info(
924                title = #openapi_title,
925            ),
926            paths(
927                #( #handlers, )*
928            ),
929            components(
930
931            ),
932            modifiers(&SecurityAddon),
933            tags(
934                (name = #api_name, description = #api_description)
935            ),
936        )]
937        struct ApiDoc;
938
939        struct SecurityAddon;
940
941        impl Modify for SecurityAddon {
942            fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
943                let token_url = match Server::global()
944                    .ok()
945                    .and_then(|s| s.settings().get_auth2_token_url())
946                {
947                    Some(url) => url,
948                    None => #auth_server.to_string(),
949                };
950
951                openapi.security = Some(vec![SecurityRequirement::new(
952                    "OAuth2 Authentication",
953                    ["openid", "profile", "email"],
954                )]);
955
956                if let Some(components) = openapi.components.as_mut() {
957                    components.add_security_scheme(
958                        "OAuth2 Authentication",
959                        SecurityScheme::OAuth2(OAuth2::with_description(
960                            [Flow::Password(Password::new(
961                                token_url, // authorization url
962                                Scopes::from_iter([
963                                    ("openid", "Standard OIDC scope"),
964                                    ("profile", "Access to user profile info"),
965                                    ("email", "Access to user email"),
966                                ]),
967                            ))],
968                            "OAuth2 Authentication",
969                        )),
970                    );
971                    // components.add_security_scheme(
972                    //     "bearerAuth",
973                    //     SecurityScheme::Http(Http::new(HttpAuthScheme::Bearer)),
974                    // );
975                }
976            }
977        }
978    }
979}
980
981/// Returns the string value of a given key from an `ArgList`.
982///
983/// This function searches the list for an item whose `key` matches the
984/// provided `key`. If found and the value is a string literal, that
985/// literal is returned. Otherwise, the provided `default` value is
986/// returned.
987///
988/// # Parameters
989/// - `arg_list`: The list of parsed arguments.
990/// - `key`: The key to search for.
991/// - `default`: The fallback value if the key is missing or not a string literal.
992///
993/// # Returns
994/// A `String` containing either the matched literal value or the default.
995fn get_arg_string_value(arg_list: &ArgList, key: String, default: String) -> String {
996    let value = arg_list
997        .items
998        .iter()
999        .find(|kv| kv.key == key)
1000        .and_then(|kv| match &kv.value {
1001            Expr::Lit(expr_lit) => match &expr_lit.lit {
1002                syn::Lit::Str(lit_str) => Some(lit_str.value()),
1003                _ => Some(default.clone()),
1004            },
1005            _ => Some(default.clone()),
1006        });
1007    value.unwrap_or(default)
1008}
1009
1010/// # πŸ” Secured Macro
1011///
1012/// The `Secured` macro protects `actix-web` endpoints by attaching an authentication middleware.
1013///
1014/// When applied to an endpoint, it validates:
1015///
1016/// - JWT presence in the request.
1017/// - JWT signature.
1018/// - JWT expiration time (`exp` claim).
1019/// - JWT issuer (`iss` claim).
1020/// - Required roles from the `authorize` expression.
1021///
1022/// ## Attribute Reference
1023//
1024/// Macro usage format:
1025//
1026/// ```no_run
1027/// #[secured(method = "...", path = "...", authorize = "...")]
1028/// ```
1029///
1030/// ### **`method`**
1031///
1032/// Defines the HTTP method used to map the endpoint in Actix-Web.
1033///
1034/// Supported values:
1035///
1036/// - `get`
1037/// - `post`
1038/// - `put`
1039/// - `delete`
1040/// - `head`
1041/// - `connect`
1042/// - `options`
1043/// - `trace`
1044/// - `patch`
1045///
1046/// ### **`path`**
1047///
1048/// Defines the endpoint path to be registered by Actix-Web.
1049///
1050/// Example:
1051///
1052/// `path = "/v1/user/{id}"`
1053///
1054/// ### **`authorize`**
1055///
1056/// Defines the required role rule that must be satisfied by roles present in the JWT.
1057///
1058/// Supported formats:
1059///
1060/// 1. `Single role`: validates one role in the token.
1061///
1062/// `authorize = "ROLE_ADMIN"`
1063///
1064/// 2. `hasAnyRole`: validates that at least one role in the list exists in the token.
1065///
1066/// `authorize = "hasAnyRole(ROLE_ADMIN, ROLE_USER)"`
1067///
1068/// 3. `hasAllRoles`: validates that all roles in the list exist in the token.
1069///
1070/// `authorize = "hasAllRoles(ROLE_ADMIN, ROLE_USER)"`
1071///
1072/// ## Examples
1073///
1074/// ### **`Single role`**:
1075///
1076/// ```rust,ignore
1077/// use rust_microservice::secured;
1078/// use actix_web::{HttpResponse, delete, get, http::StatusCode, post, put, web};
1079///
1080/// #[secured(method = "post", path = "/v1/user", authorize = "ROLE_ADMIN")]
1081/// pub async fn create_user_endpoint() -> HttpResponse {
1082///     // handler body
1083///     HttpResponse::Ok().finish()
1084/// }
1085/// ```
1086///
1087/// ### **`Any role`**:
1088///
1089/// ```rust,ignore
1090/// use rust_microservice::secured;
1091/// use actix_web::{HttpResponse, delete, get, http::StatusCode, post, put, web};
1092///
1093/// #[secured(
1094///     method = "get",
1095///     path = "/v1/user/{id}",
1096///     authorize = "hasAnyRole(ROLE_ADMIN, ROLE_USER)"
1097/// )]
1098/// pub async fn get_user_endpoint() -> HttpResponse {
1099///     // handler body
1100///     HttpResponse::Ok().finish()
1101/// }
1102/// ```
1103///
1104/// ### **`All roles`**:
1105///
1106/// ```rust,ignore
1107/// use rust_microservice::secured;
1108/// use actix_web::{HttpResponse, delete, get, http::StatusCode, post, put, web};
1109///
1110/// #[secured(
1111///     method = "delete",
1112///     path = "/v1/user/{id}",
1113///     authorize = "hasAllRoles(ROLE_ADMIN, ROLE_AUDITOR)"
1114/// )]
1115/// pub async fn delete_user_endpoint() -> HttpResponse {
1116///     // handler body
1117///    HttpResponse::Ok().finish()
1118/// }
1119/// ```
1120#[proc_macro_attribute]
1121pub fn secured(attrs: TokenStream, item: TokenStream) -> TokenStream {
1122    let secure_fn = parse_macro_input!(item as ItemFn);
1123    let arg_list = parse_macro_input!(attrs as ArgList);
1124
1125    impl_secured_fn(secure_fn, arg_list)
1126}
1127
1128/// Generates a secured version of a function.
1129///
1130/// This function takes an original function (ItemFn) and an argument list (ArgList)
1131/// and returns a new function definition with the same signature and body,
1132/// but with the authentication and authorization checks applied.
1133///
1134/// The returned function checks if the authenticated user has at least one of the roles
1135/// specified in the `roles` parameter. If the user is authorized, the function body is executed.
1136///
1137/// # Parameters
1138/// - `secured_fn`: The original function to secure.
1139/// - `arg_list`: The list of arguments containing the `roles` configuration parameter.
1140///
1141/// # Returns
1142/// A `TokenStream` containing the secured function definition.
1143fn impl_secured_fn(secured_fn: ItemFn, arg_list: ArgList) -> TokenStream {
1144    let fn_body = &secured_fn.block.stmts;
1145    let sig = &secured_fn.sig.to_token_stream();
1146    let fn_name = &secured_fn.sig.ident;
1147    let _fn_output = &secured_fn.sig.output;
1148    let _fn_params = &secured_fn.sig.inputs;
1149    let _fn_modifiers = &secured_fn.sig.asyncness;
1150    let fn_visibility = &secured_fn.vis;
1151
1152    let method = Ident::new(
1153        get_arg_string_value(&arg_list, "method".to_string(), "".to_string()).as_str(),
1154        Span::call_site(),
1155    );
1156    let path = get_arg_string_value(&arg_list, "path".to_string(), "".to_string()).to_lowercase();
1157    let authorize = get_arg_string_value(&arg_list, "authorize".to_string(), "".to_string());
1158    let _actix_web_attr = update_actix_web_attr(&secured_fn.attrs);
1159    let auth_module_name = format_ident!("auth_{}", fn_name);
1160    let wrap_fn = format!(
1161        "::actix_web::middleware::from_fn({}::auth_middleware)",
1162        auth_module_name
1163    )
1164    .to_string();
1165
1166    quote! {
1167        mod #auth_module_name {
1168            use ::actix_web::{
1169                HttpMessage,
1170                http::header::{self, HeaderValue}
1171            };
1172            use rust_microservice::Server;
1173            use tracing::warn;
1174
1175            pub async fn auth_middleware(
1176                req: ::actix_web::dev::ServiceRequest,
1177                next: ::actix_web::middleware::Next<impl ::actix_web::body::MessageBody>,
1178            ) -> Result<
1179                ::actix_web::dev::ServiceResponse<impl ::actix_web::body::MessageBody>,
1180                ::actix_web::Error,
1181            > {
1182                Server::global()
1183                    .map_err(|e| ::actix_web::error::ErrorInternalServerError(e.to_string()))?
1184                    .validate_jwt(&req, #authorize.to_string())
1185                    .map_err(|e| {
1186                        //warn!("Unauthorized: {}", e);
1187                        ::actix_web::error::ErrorUnauthorized("Unauthorized user.")
1188                    })?;
1189                next.call(req).await
1190            }
1191        }
1192
1193        #[#method(#path, wrap = #wrap_fn)]
1194        #fn_visibility #sig {
1195            #( #fn_body )*
1196        }
1197    }
1198    .into()
1199}
1200
1201/// Updates an Actix-Web attribute by extracting the HTTP method and path.
1202///
1203/// It takes a vector of `syn::Attribute`s, finds the first attribute that matches
1204/// one of the supported HTTP methods, and returns a `proc_macro2::TokenStream`
1205/// containing the updated attribute.
1206///
1207/// Supported HTTP methods are:
1208/// - `get`
1209/// - `post`
1210/// - `put`
1211/// - `delete`
1212/// - `head`
1213/// - `connect`
1214/// - `options`
1215/// - `trace`
1216/// - `patch`
1217///
1218/// If no matching attribute is found, an empty `TokenStream` is returned.
1219fn update_actix_web_attr(attrs: &[Attribute]) -> proc_macro2::TokenStream {
1220    use syn::Expr;
1221
1222    const HTTP_METHODS: &[&str] = &[
1223        "get", "post", "put", "delete", "head", "connect", "options", "trace", "patch",
1224    ];
1225
1226    let attr = match attrs
1227        .iter()
1228        .find(|attr| HTTP_METHODS.iter().any(|m| attr.path().is_ident(m)))
1229    {
1230        Some(attr) => attr,
1231        None => return quote! {},
1232    };
1233
1234    let method = match attr.path().get_ident() {
1235        Some(ident) => ident.to_string(),
1236        None => return quote! {},
1237    };
1238
1239    let value: Expr = match attr.parse_args() {
1240        Ok(v) => v,
1241        Err(_) => return quote! {},
1242    };
1243
1244    let wrapper = quote! {
1245        wrap = "::actix_web::middleware::from_fn(auth::auth_middleware)"
1246    };
1247
1248    match method.as_str() {
1249        "post" => {
1250            quote! { #[post(#value, #wrapper)] }
1251        }
1252        "get" => quote! { #[get(#value)] },
1253        "put" => quote! { #[put(#value)] },
1254        "delete" => quote! { #[delete(#value)] },
1255        "head" => quote! { #[head(#value)] },
1256        "connect" => quote! { #[connect(#value)] },
1257        "options" => quote! { #[options(#value)] },
1258        "trace" => quote! { #[trace(#value)] },
1259        "patch" => quote! { #[patch(#value)] },
1260        _ => quote! {},
1261    }
1262}
1263
1264/// Extracts the list of roles from the `secured` macro attribute.
1265///
1266/// If the `roles` parameter is specified, it is split by commas into a vector of strings.
1267/// If no roles are specified, an empty vector is returned.
1268///
1269/// # Parameters
1270/// - `arg_list`: The list of arguments containing the `roles` parameter.
1271///
1272/// # Returns
1273/// A vector of strings representing the roles specified in the `secured` macro attribute.
1274fn _get_security_roles(arg_list: &ArgList) -> proc_macro2::TokenStream {
1275    let roles_arg = get_arg_string_value(arg_list, "roles".to_string(), "".to_string());
1276    if !roles_arg.is_empty() {
1277        let roles = roles_arg.split(',').collect::<Vec<&str>>();
1278        quote! {
1279            const ROLES: &[&'static str] = &[#(#roles),*];
1280        }
1281    } else {
1282        quote! {
1283            let roles = vec![];
1284        }
1285    }
1286}
1287
1288/// # πŸ›’οΈ Database Macro
1289///
1290/// The `database` macro is a procedural macro that injects a database connection
1291/// into repository methods.
1292///
1293/// It expects two mandatory attributes:
1294/// - `name`: selects which configured database connection will be used.
1295/// - `error`: defines the error variant returned when the database is not configured or
1296///   database connection cannot be found.
1297///
1298/// The macro injects a variable named `db` with type `&DatabaseConnection` (seaorm),
1299/// so the function body can execute queries directly.
1300///
1301/// Example:
1302///
1303/// ```rust,ignore
1304/// use rust_microservice::{Server, database};
1305/// use thiserror::Error;
1306///
1307/// #[derive(Debug, Error)]
1308/// pub enum UserError {
1309///     #[error("Database is not configured")]
1310///     DatabaseNotConfigured,
1311///
1312///     #[error("User not found")]
1313///     NotFound,
1314/// }
1315///
1316/// pub type Result<T, E = UserError> = std::result::Result<T, E>;
1317///
1318/// #[database(name = "api", error = "UserError::DatabaseNotConfigured")]
1319/// pub async fn get_user_by_id(user_id: i32) -> Result<()> {
1320///
1321///     // Database will be injected here as `db`
1322///
1323///     //user::Entity::find_by_id(user_id)
1324///     //    .one(&db)
1325///     //    .await
1326///     //    .map_err(|_| UserError::NotFound)?
1327///     //    .ok_or(UserError::NotFound)
1328///     //    .map(Into::into)
1329///
1330///     Ok(())
1331/// }
1332/// ```
1333#[proc_macro_attribute]
1334pub fn database(attrs: TokenStream, item: TokenStream) -> TokenStream {
1335    let item_fn = parse_macro_input!(item as ItemFn);
1336    let arg_list = parse_macro_input!(attrs as ArgList);
1337
1338    impl_database_fn(item_fn, arg_list)
1339}
1340
1341/// Wraps a function with a database connection retrieval call.
1342///
1343/// This function takes an `ItemFn` and an `ArgList` as parameters. It extracts the
1344/// function body, signature, and visibility from the `ItemFn`, and extracts the database
1345/// name and error message from the `ArgList`. It then generates a token stream that wraps the
1346/// function body with a call to retrieve a database connection from the server's global
1347/// state, using the extracted database name and error message.
1348///
1349/// The generated token stream will contain a function with the same signature and visibility
1350/// as the original function, but with a wrapped body that first retrieves a database connection
1351/// and then calls the original function body with the connection as an argument.
1352///
1353/// # Parameters
1354/// - `item_fn`: The function to wrap with a database connection retrieval call.
1355/// - `arg_list`: The arguments containing the database name and error message.
1356///
1357/// # Returns
1358/// A token stream representing the wrapped function.
1359fn impl_database_fn(item_fn: ItemFn, arg_list: ArgList) -> TokenStream {
1360    let fn_body = &item_fn.block.stmts;
1361    let sig = &item_fn.sig.to_token_stream();
1362    let fn_visibility = &item_fn.vis;
1363    let db_name = get_arg_string_value(&arg_list, "name".to_string(), "".to_string());
1364    let error_str = get_arg_string_value(&arg_list, "error".to_string(), "".to_string());
1365    let error: proc_macro2::TokenStream = parse_str(error_str.as_str()).unwrap();
1366
1367    quote! {
1368        #fn_visibility #sig {
1369            let db = Server::global()
1370                .map_err(|_| #error)?
1371                .database_with_name(#db_name)
1372                .map_err(|_| #error)?;
1373
1374            #( #fn_body )*
1375        }
1376
1377    }
1378    .into()
1379}