├── .gitignore ├── toni-macros ├── src │ ├── enhancer │ │ ├── mod.rs │ │ └── enhancer.rs │ ├── module_macro │ │ ├── mod.rs │ │ └── module_struct.rs │ ├── shared │ │ ├── mod.rs │ │ ├── metadata_info.rs │ │ ├── dependency_info.rs │ │ └── generate_make_instances.rs │ ├── markers_params │ │ ├── mod.rs │ │ ├── remove_marker_controller_fn.rs │ │ ├── extracts_marker_params.rs │ │ └── get_marker_params.rs │ ├── provider_macro │ │ ├── mod.rs │ │ ├── output.rs │ │ ├── impl_functions.rs │ │ ├── provider_struct.rs │ │ ├── manager.rs │ │ └── provider.rs │ ├── controller_macro │ │ ├── mod.rs │ │ ├── output.rs │ │ ├── controller_struct.rs │ │ ├── impl_functions.rs │ │ ├── manager.rs │ │ └── controller.rs │ ├── utils │ │ ├── mod.rs │ │ ├── create_struct_name.rs │ │ ├── snake_to_upper.rs │ │ ├── controller_utils.rs │ │ ├── modify_return_body.rs │ │ ├── extracts.rs │ │ ├── types.rs │ │ └── modify_impl_function_body.rs │ └── lib.rs ├── README.md └── Cargo.toml ├── toni ├── src │ ├── traits_helpers │ │ ├── validator │ │ │ ├── mod.rs │ │ │ └── validate.rs │ │ ├── pipe.rs │ │ ├── guard.rs │ │ ├── interceptor.rs │ │ ├── module_metadata.rs │ │ ├── mod.rs │ │ ├── provider.rs │ │ └── controller.rs │ ├── structs_helpers │ │ ├── mod.rs │ │ └── enhancer.rs │ ├── router │ │ ├── mod.rs │ │ └── routes_resolve.rs │ ├── module_helpers │ │ ├── mod.rs │ │ └── module_enum.rs │ ├── http_helpers │ │ ├── body.enum.rs │ │ ├── http_request.struct.rs │ │ ├── mod.rs │ │ ├── http_response.enum.rs │ │ ├── http_method.enum.rs │ │ └── into_response.rs │ ├── adapter │ │ ├── mod.rs │ │ ├── http_adapter.rs │ │ ├── route_adapter.rs │ │ ├── axum_adapter.rs │ │ └── axum_route_adapter.rs │ ├── injector │ │ ├── mod.rs │ │ ├── context.rs │ │ ├── instance_wrapper.rs │ │ ├── dependency_graph.rs │ │ ├── module.rs │ │ ├── instance_loader.rs │ │ └── container.rs │ ├── toni_application.rs │ ├── toni_factory.rs │ ├── lib.rs │ └── scanner.rs ├── Cargo.toml └── README.md ├── toni-cli ├── src │ ├── commands │ │ ├── mod.rs │ │ ├── new.rs │ │ └── generate.rs │ ├── templates │ │ ├── new │ │ │ ├── src │ │ │ │ ├── app │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── app.module.rs │ │ │ │ │ ├── app.service.rs │ │ │ │ │ └── app.controller.rs │ │ │ │ └── main.rs │ │ │ └── Cargo.txt │ │ └── generate │ │ │ ├── mod.rs │ │ │ ├── resource.module.rs │ │ │ ├── resource.service.rs │ │ │ └── resource.controller.rs │ └── main.rs ├── README.md └── Cargo.toml ├── Cargo.toml ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | src/app 3 | Cargo.lock -------------------------------------------------------------------------------- /toni-macros/src/enhancer/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod enhancer; -------------------------------------------------------------------------------- /toni-macros/src/module_macro/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod module_struct; -------------------------------------------------------------------------------- /toni/src/traits_helpers/validator/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod validate; -------------------------------------------------------------------------------- /toni-cli/src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod generate; 2 | pub mod new; -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["toni", "toni-macros", "toni-cli"] -------------------------------------------------------------------------------- /toni/src/structs_helpers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod enhancer; 2 | pub use enhancer::EnhancerMetadata; -------------------------------------------------------------------------------- /toni/src/router/mod.rs: -------------------------------------------------------------------------------- 1 | mod routes_resolve; 2 | pub use self::routes_resolve::RoutesResolver; -------------------------------------------------------------------------------- /toni/src/module_helpers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod module_enum; 2 | // pub use self::module_definition::ModuleDefinition; -------------------------------------------------------------------------------- /toni-macros/src/shared/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod generate_make_instances; 2 | pub mod dependency_info; 3 | pub mod metadata_info; -------------------------------------------------------------------------------- /toni-macros/README.md: -------------------------------------------------------------------------------- 1 | # toni-macros 2 | 3 | Macros for `toni` 4 | 5 | --- 6 | 7 | ## License 8 | - **License**: MIT. 9 | 10 | --- -------------------------------------------------------------------------------- /toni-macros/src/markers_params/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod remove_marker_controller_fn; 2 | pub mod get_marker_params; 3 | pub mod extracts_marker_params; -------------------------------------------------------------------------------- /toni-macros/src/provider_macro/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod provider_struct; 2 | pub mod impl_functions; 3 | pub mod provider; 4 | pub mod manager; 5 | pub mod output; -------------------------------------------------------------------------------- /toni-macros/src/controller_macro/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod manager; 2 | pub mod controller; 3 | pub mod controller_struct; 4 | pub mod impl_functions; 5 | pub mod output; -------------------------------------------------------------------------------- /toni/src/traits_helpers/pipe.rs: -------------------------------------------------------------------------------- 1 | use crate::injector::Context; 2 | 3 | pub trait Pipe: Send + Sync { 4 | fn process(&self, data: &mut Context); 5 | } 6 | -------------------------------------------------------------------------------- /toni/src/traits_helpers/guard.rs: -------------------------------------------------------------------------------- 1 | use crate::injector::Context; 2 | 3 | pub trait Guard: Send + Sync { 4 | fn can_activate(&self, context: &Context) -> bool; 5 | } -------------------------------------------------------------------------------- /toni/src/module_helpers/module_enum.rs: -------------------------------------------------------------------------------- 1 | use crate::traits_helpers::ModuleMetadata; 2 | 3 | pub enum ModuleDefinition { 4 | DefaultModule(Box), 5 | } 6 | -------------------------------------------------------------------------------- /toni/src/http_helpers/body.enum.rs: -------------------------------------------------------------------------------- 1 | use serde_json::Value; 2 | 3 | #[derive(Debug, Clone)] 4 | pub enum Body { 5 | Text(String), 6 | Json(Value), 7 | // Binary(Vec), 8 | } -------------------------------------------------------------------------------- /toni-macros/src/shared/metadata_info.rs: -------------------------------------------------------------------------------- 1 | use syn::Ident; 2 | 3 | #[derive(Debug)] 4 | pub struct MetadataInfo { 5 | pub struct_name: Ident, 6 | pub dependencies: Vec<(Ident, String)>, 7 | } -------------------------------------------------------------------------------- /toni-cli/src/templates/new/src/app/mod.rs: -------------------------------------------------------------------------------- 1 | #[path = "app.module.rs"] 2 | pub mod app_module; 3 | 4 | #[path = "app.controller.rs"] 5 | pub mod app_controller; 6 | 7 | #[path = "app.service.rs"] 8 | pub mod app_service; -------------------------------------------------------------------------------- /toni-macros/src/shared/dependency_info.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use syn::Ident; 4 | 5 | pub struct DependencyInfo { 6 | pub fields: Vec<(Ident, Ident)>, 7 | pub unique_types: HashSet, 8 | } -------------------------------------------------------------------------------- /toni-macros/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod modify_impl_function_body; 2 | pub mod create_struct_name; 3 | pub mod snake_to_upper; 4 | pub mod extracts; 5 | pub mod modify_return_body; 6 | pub mod controller_utils; 7 | pub mod types; -------------------------------------------------------------------------------- /toni/src/adapter/mod.rs: -------------------------------------------------------------------------------- 1 | mod axum_adapter; 2 | mod axum_route_adapter; 3 | mod route_adapter; 4 | 5 | pub use axum_adapter::AxumAdapter; 6 | pub use axum_route_adapter::AxumRouteAdapter; 7 | pub use route_adapter::RouteAdapter; 8 | -------------------------------------------------------------------------------- /toni/src/traits_helpers/interceptor.rs: -------------------------------------------------------------------------------- 1 | use crate::injector::Context; 2 | 3 | pub trait Interceptor: Send + Sync { 4 | fn before_execute(&self, context: &mut Context); 5 | fn after_execute(&self, context: &mut Context); 6 | } -------------------------------------------------------------------------------- /toni/src/traits_helpers/validator/validate.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use validator::ValidationErrors; 4 | 5 | pub trait Validatable: Send + Sync + Debug { 6 | fn validate_dto(&self) -> Result<(), ValidationErrors>; 7 | } -------------------------------------------------------------------------------- /toni-cli/src/templates/generate/mod.rs: -------------------------------------------------------------------------------- 1 | #[path = "path_module"] 2 | pub mod resource_name_module; 3 | 4 | #[path = "path_controller"] 5 | pub mod resource_name_controller; 6 | 7 | #[path = "path_service"] 8 | pub mod resource_name_service; -------------------------------------------------------------------------------- /toni/src/structs_helpers/enhancer.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::traits_helpers::{Guard, Interceptor, Pipe}; 4 | 5 | pub struct EnhancerMetadata { 6 | pub guards: Vec>, 7 | pub pipes: Vec>, 8 | pub interceptors: Vec>, 9 | } -------------------------------------------------------------------------------- /toni-cli/README.md: -------------------------------------------------------------------------------- 1 | # toni-macros 2 | 3 | CLI for `toni` 4 | 5 | ## Create a New Project 6 | ```bash 7 | toni new my_app 8 | ``` 9 | 10 | ## Generate a Resource 11 | ```bash 12 | toni generate resource user 13 | ``` 14 | 15 | --- 16 | 17 | ## License 18 | - **License**: MIT. 19 | 20 | --- -------------------------------------------------------------------------------- /toni-cli/src/templates/new/src/app/app.module.rs: -------------------------------------------------------------------------------- 1 | use toni_macros::module; 2 | 3 | use super::app_controller::*; 4 | use super::app_service::*; 5 | 6 | #[module( 7 | imports: [], 8 | controllers: [_AppController], 9 | providers: [_AppService], 10 | exports: [] 11 | )] 12 | impl AppModule {} -------------------------------------------------------------------------------- /toni-cli/src/templates/generate/resource.module.rs: -------------------------------------------------------------------------------- 1 | use toni_macros::module; 2 | 3 | use super::resource_name_controller::*; 4 | use super::resource_name_service::*; 5 | 6 | #[module( 7 | imports: [], 8 | controllers: [_RESOURCE_NAME_CONTROLLER], 9 | providers: [_RESOURCE_NAME_SERVICE], 10 | exports: [] 11 | )] 12 | impl RESOURCE_NAME_MODULE {} -------------------------------------------------------------------------------- /toni-cli/src/templates/new/Cargo.txt: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "{{project_name}}" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | toni-macros = "0.1.3" 8 | toni = "0.1.1" 9 | rustc-hash = "2.1" 10 | serde = { version = "1.0.216", features = ["derive"] } 11 | serde_json = "1.0.134" 12 | tokio = { version = "1.42.0", features = ["full"] } 13 | async-trait = "0.1.86" -------------------------------------------------------------------------------- /toni/src/http_helpers/http_request.struct.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use super::Body; 4 | 5 | #[derive(Clone, Debug)] 6 | pub struct HttpRequest { 7 | pub body: Body, 8 | pub headers: Vec<(String, String)>, 9 | pub method: String, 10 | pub uri: String, 11 | pub query_params: HashMap, 12 | pub path_params: HashMap, 13 | } 14 | -------------------------------------------------------------------------------- /toni/src/injector/mod.rs: -------------------------------------------------------------------------------- 1 | mod container; 2 | pub use self::container::ToniContainer; 3 | 4 | mod instance_loader; 5 | pub use self::instance_loader::ToniInstanceLoader; 6 | mod module; 7 | 8 | mod dependency_graph; 9 | pub use self::dependency_graph::DependencyGraph; 10 | 11 | mod instance_wrapper; 12 | pub use self::instance_wrapper::InstanceWrapper; 13 | 14 | mod context; 15 | pub use self::context::Context; -------------------------------------------------------------------------------- /toni/src/traits_helpers/module_metadata.rs: -------------------------------------------------------------------------------- 1 | use super::{Controller, Provider}; 2 | pub trait ModuleMetadata { 3 | fn get_id(&self) -> String; 4 | fn get_name(&self) -> String; 5 | fn imports(&self) -> Option>>; 6 | fn controllers(&self) -> Option>>; 7 | fn providers(&self) -> Option>>; 8 | fn exports(&self) -> Option>; 9 | } -------------------------------------------------------------------------------- /toni-cli/src/templates/new/src/main.rs: -------------------------------------------------------------------------------- 1 | use app::app_module::AppModule; 2 | use toni::{adapter::AxumAdapter, http_adapter::HttpAdapter, toni_factory::ToniFactory}; 3 | 4 | mod app; 5 | 6 | #[tokio::main] 7 | async fn main() { 8 | let axum_adapter = AxumAdapter::new(); 9 | let factory = ToniFactory::new(); 10 | let app = factory.create(AppModule::module_definition(), axum_adapter); 11 | app.listen(3000, "127.0.0.1").await; 12 | } -------------------------------------------------------------------------------- /toni/src/adapter/http_adapter.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use anyhow::Result; 4 | 5 | use crate::http_helpers::HttpMethod; 6 | use crate::injector::InstanceWrapper; 7 | 8 | pub trait HttpAdapter: Clone + Send + Sync { 9 | fn new() -> Self; 10 | fn add_route(&mut self, path: &str, method: HttpMethod, handler: Arc); 11 | fn listen(self, port: u16, hostname: &str) -> impl Future> + Send; 12 | } 13 | -------------------------------------------------------------------------------- /toni-cli/src/templates/new/src/app/app.service.rs: -------------------------------------------------------------------------------- 1 | use toni_macros::provider_struct; 2 | 3 | #[provider_struct( 4 | pub struct _AppService; 5 | )] 6 | impl _AppService { 7 | pub fn find_all(&self) -> String { 8 | "find_all".to_string() 9 | } 10 | 11 | pub fn create(&self) -> String { 12 | "create".to_string() 13 | } 14 | 15 | pub fn update(&self) -> String { 16 | "update".to_string() 17 | } 18 | 19 | pub fn delete(&self) -> String { 20 | "delete".to_string() 21 | } 22 | } -------------------------------------------------------------------------------- /toni/src/traits_helpers/mod.rs: -------------------------------------------------------------------------------- 1 | mod module_metadata; 2 | pub use self::module_metadata::ModuleMetadata; 3 | 4 | mod provider; 5 | pub use self::provider::{Provider, ProviderTrait}; 6 | 7 | mod controller; 8 | pub use self::controller::{Controller, ControllerTrait}; 9 | 10 | mod interceptor; 11 | pub use self::interceptor::Interceptor; 12 | 13 | mod guard; 14 | pub use self::guard::Guard; 15 | 16 | mod pipe; 17 | pub use self::pipe::Pipe; 18 | 19 | mod validator; 20 | pub use self::validator::validate; -------------------------------------------------------------------------------- /toni-macros/src/provider_macro/output.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::{ItemImpl, ItemStruct}; 4 | 5 | pub fn generate_output( 6 | struct_attrs: ItemStruct, 7 | impl_block: ItemImpl, 8 | providers: Vec, 9 | manager: TokenStream, 10 | ) -> TokenStream { 11 | quote! { 12 | #[allow(dead_code)] 13 | #struct_attrs 14 | #[allow(dead_code)] 15 | #impl_block 16 | #(#providers)* 17 | #manager 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /toni-macros/src/controller_macro/output.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::{ItemImpl, ItemStruct}; 4 | 5 | pub fn generate_output( 6 | struct_attrs: ItemStruct, 7 | impl_block: ItemImpl, 8 | controllers: Vec, 9 | manager: TokenStream, 10 | ) -> TokenStream { 11 | quote! { 12 | #[allow(dead_code)] 13 | #struct_attrs 14 | #[allow(dead_code)] 15 | #impl_block 16 | #(#controllers)* 17 | #manager 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /toni/src/http_helpers/mod.rs: -------------------------------------------------------------------------------- 1 | #[path ="body.enum.rs"] 2 | mod body; 3 | pub use self::body::Body; 4 | 5 | #[path ="http_response.enum.rs"] 6 | mod http_response; 7 | pub use self::http_response::{HttpResponse, HttpResponseDefault}; 8 | 9 | #[path ="http_request.struct.rs"] 10 | mod http_request; 11 | pub use self::http_request::HttpRequest; 12 | 13 | #[path ="http_method.enum.rs"] 14 | mod http_method; 15 | pub use self::http_method::HttpMethod; 16 | 17 | #[path ="into_response.rs"] 18 | mod into_response; 19 | pub use self::into_response::IntoResponse; -------------------------------------------------------------------------------- /toni-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "toni-macros" 3 | version = "0.1.3" 4 | edition = "2024" 5 | description = "Macros for Toni" 6 | license = "MIT" 7 | repository = "https://github.com/monterxto/toni-rs" 8 | homepage = "https://github.com/monterxto/toni-rs" 9 | categories = ["asynchronous", "network-programming", "web-programming"] 10 | 11 | [lib] 12 | name = "toni_macros" 13 | path = "src/lib.rs" 14 | proc-macro = true 15 | 16 | [dependencies] 17 | proc-macro2 = "1.0.92" 18 | quote = "1.0.38" 19 | syn = { version = "2.0.94", features = ["visit-mut", "default"] } 20 | toni = { path = "../toni" } -------------------------------------------------------------------------------- /toni-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "toni-cli" 3 | version = "0.1.2" 4 | edition = "2024" 5 | description = "CLI for Toni" 6 | license = "MIT" 7 | repository = "https://github.com/monterxto/toni-rs" 8 | homepage = "https://github.com/monterxto/toni-rs" 9 | 10 | [[bin]] 11 | name = "toni" 12 | path = "src/main.rs" 13 | 14 | [dependencies] 15 | anyhow = "1.0.95" 16 | clap = { version = "4.5.27", features = ["derive"] } 17 | colored = "3.0.0" 18 | regex = "1.11.1" 19 | serde = { version = "1.0.217", features = ["derive"] } 20 | serde_json = "1.0.137" 21 | tokio = { version = "1.43.0", features = ["full"] } 22 | -------------------------------------------------------------------------------- /toni-cli/src/templates/generate/resource.service.rs: -------------------------------------------------------------------------------- 1 | use toni_macros::provider_struct; 2 | 3 | #[provider_struct( 4 | pub struct _RESOURCE_NAME_SERVICE; 5 | )] 6 | impl _RESOURCE_NAME_SERVICE { 7 | pub fn find_all(&self) -> String { 8 | "find_all".to_string() 9 | } 10 | 11 | pub fn find_by_id(&self, id: i32) -> String { 12 | format!("find_by_id {}", id) 13 | } 14 | 15 | pub fn create(&self) -> String { 16 | "create".to_string() 17 | } 18 | 19 | pub fn update(&self) -> String { 20 | "update".to_string() 21 | } 22 | 23 | pub fn delete(&self) -> String { 24 | "delete".to_string() 25 | } 26 | } -------------------------------------------------------------------------------- /toni-macros/src/markers_params/remove_marker_controller_fn.rs: -------------------------------------------------------------------------------- 1 | use syn::{Ident, ImplItemFn}; 2 | 3 | pub fn is_marker(segment: &Ident) -> bool { 4 | matches!(segment.to_string().as_str(), "body" | "param" | "query") 5 | } 6 | 7 | pub fn remove_marker_in_controller_fn_args(method: &mut ImplItemFn) { 8 | for input in method.sig.inputs.iter_mut() { 9 | if let syn::FnArg::Typed(pat_type) = input { 10 | pat_type.attrs.retain(|attr| { 11 | if let Some(ident) = attr.path().get_ident() { 12 | return !is_marker(ident); 13 | } 14 | true 15 | }); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /toni/src/http_helpers/http_response.enum.rs: -------------------------------------------------------------------------------- 1 | use super::Body; 2 | 3 | #[derive(Debug)] 4 | pub struct HttpResponseDefault { 5 | pub body: Option, 6 | pub status: Option 7 | } 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct HttpResponse { 11 | pub body: Option, 12 | pub status: u16, 13 | pub headers: Vec<(String, String)>, 14 | } 15 | impl HttpResponse { 16 | pub fn new() -> Self { 17 | Self { 18 | body: None, 19 | status: 200, 20 | headers: vec![], 21 | } 22 | } 23 | } 24 | 25 | impl Default for HttpResponse { 26 | fn default() -> Self { 27 | Self::new() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /toni/src/http_helpers/http_method.enum.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum HttpMethod { 3 | GET, 4 | POST, 5 | PUT, 6 | DELETE, 7 | HEAD, 8 | PATCH, 9 | OPTIONS, 10 | } 11 | 12 | impl HttpMethod { 13 | pub fn from_string(method: &str) -> Option { 14 | match method.to_lowercase().as_str() { 15 | "get" => Some(HttpMethod::GET), 16 | "post" => Some(HttpMethod::POST), 17 | "put" => Some(HttpMethod::PUT), 18 | "delete" => Some(HttpMethod::DELETE), 19 | "patch" => Some(HttpMethod::PATCH), 20 | "options" => Some(HttpMethod::OPTIONS), 21 | "head" => Some(HttpMethod::HEAD), 22 | _ => None, 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /toni/src/traits_helpers/provider.rs: -------------------------------------------------------------------------------- 1 | use std::{any::Any, sync::Arc}; 2 | 3 | use async_trait::async_trait; 4 | use rustc_hash::FxHashMap; 5 | 6 | #[async_trait] 7 | pub trait ProviderTrait: Send + Sync { 8 | fn get_token(&self) -> String; 9 | async fn execute(&self, params: Vec>) -> Box; 10 | fn get_token_manager(&self) -> String; 11 | } 12 | 13 | pub trait Provider { 14 | fn get_all_providers( 15 | &self, 16 | dependencies: &FxHashMap>> 17 | ) -> FxHashMap>>; 18 | fn get_name(&self) -> String; 19 | fn get_token(&self) -> String; 20 | fn get_dependencies(&self) -> Vec; 21 | } 22 | -------------------------------------------------------------------------------- /toni-cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | mod commands; 3 | 4 | #[derive(Parser)] 5 | #[command(name = "toni")] 6 | #[command(version = "0.0.1")] 7 | #[command(about = "Toni Framework CLI", long_about = None)] 8 | struct Cli { 9 | #[command(subcommand)] 10 | command: Commands, 11 | } 12 | 13 | #[derive(Subcommand)] 14 | enum Commands { 15 | New(commands::new::NewArgs), 16 | Generate(commands::generate::GenerateArgs), 17 | } 18 | 19 | #[tokio::main] 20 | async fn main() -> anyhow::Result<()> { 21 | let cli = Cli::parse(); 22 | match cli.command { 23 | Commands::New(args) => commands::new::execute(args).await, 24 | Commands::Generate(args) => commands::generate::execute(args).await, 25 | } 26 | } -------------------------------------------------------------------------------- /toni/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "toni" 3 | version = "0.1.1" 4 | edition = "2024" 5 | keywords = ["http", "web", "framework"] 6 | categories = ["asynchronous", "network-programming", "web-programming"] 7 | description = "Fast and modular web framework for scalable applications" 8 | license = "MIT" 9 | repository = "https://github.com/monterxto/toni-rs" 10 | homepage = "https://github.com/monterxto/toni-rs" 11 | readme = "README.md" 12 | 13 | [dependencies] 14 | axum = "0.8.1" 15 | tokio = { version = "1.42.0", features = ["full"] } 16 | serde = { version = "1.0.216", features = ["derive"] } 17 | serde_json = "1.0.134" 18 | rustc-hash = "2.1" 19 | anyhow = "1.0.95" 20 | async-trait = "0.1.86" 21 | 22 | [dev-dependencies] 23 | reqwest = { version = "0.12.11", features = ["json"] } 24 | 25 | -------------------------------------------------------------------------------- /toni/src/adapter/route_adapter.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use anyhow::Result; 4 | 5 | use crate::http_helpers::{HttpRequest, HttpResponse, IntoResponse}; 6 | use crate::injector::InstanceWrapper; 7 | 8 | pub trait RouteAdapter { 9 | type Request; 10 | type Response; 11 | 12 | fn adapt_request(request: Self::Request) -> impl Future>; 13 | 14 | fn adapt_response( 15 | response: Box>, 16 | ) -> Result; 17 | 18 | fn handle_request( 19 | request: Self::Request, 20 | controller: Arc, 21 | ) -> impl Future> { 22 | async move { 23 | let http_request = Self::adapt_request(request).await?; 24 | let http_response = controller.handle_request(http_request).await; 25 | Self::adapt_response(http_response) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /toni-macros/src/provider_macro/impl_functions.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use syn::{Ident, ImplItem, ItemImpl, Result}; 3 | 4 | use crate::shared::dependency_info::DependencyInfo; 5 | use crate::shared::metadata_info::MetadataInfo; 6 | 7 | use super::provider::generate_provider_and_metadata; 8 | 9 | pub fn process_impl_functions( 10 | impl_block: &ItemImpl, 11 | dependencies: &mut DependencyInfo, 12 | struct_name: &syn::Ident, 13 | trait_name: &Ident, 14 | ) -> Result<(Vec, Vec)> { 15 | let mut providers = Vec::new(); 16 | let mut metadata = Vec::new(); 17 | for item in &impl_block.items { 18 | if let ImplItem::Fn(method) = item { 19 | let (provider, meta) = 20 | generate_provider_and_metadata(method, struct_name, dependencies, trait_name)?; 21 | 22 | providers.push(provider); 23 | metadata.push(meta); 24 | } 25 | } 26 | 27 | Ok((providers, metadata)) 28 | } 29 | -------------------------------------------------------------------------------- /toni-cli/src/templates/new/src/app/app.controller.rs: -------------------------------------------------------------------------------- 1 | use toni_macros::{controller, controller_struct, get, post, put, delete}; 2 | use toni::http_helpers::{HttpRequest, Body}; 3 | use super::app_service::_AppService; 4 | 5 | #[controller_struct( 6 | pub struct _AppController { 7 | app_service: _AppService, 8 | } 9 | )] 10 | #[controller("/app")] 11 | impl _AppController { 12 | #[post("")] 13 | fn _create(&self, _req: HttpRequest) -> Body { 14 | let create: String = self.app_service.create(); 15 | Body::Text(create) 16 | } 17 | 18 | #[get("")] 19 | fn _find_all(&self, _req: HttpRequest) -> Body { 20 | let find_all: String = self.app_service.find_all(); 21 | Body::Text(find_all) 22 | } 23 | 24 | #[put("")] 25 | fn _update(&self, _req: HttpRequest) -> Body { 26 | let update: String = self.app_service.update(); 27 | Body::Text(update) 28 | } 29 | 30 | #[delete("")] 31 | fn _delete(&self, _req: HttpRequest) -> Body { 32 | let delete: String = self.app_service.delete(); 33 | Body::Text(delete) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /toni/src/toni_application.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use anyhow::Result; 4 | 5 | use crate::{ 6 | http_adapter::HttpAdapter, 7 | injector::ToniContainer, router::RoutesResolver, 8 | }; 9 | 10 | pub struct ToniApplication { 11 | http_adapter: H, 12 | routes_resolver: RoutesResolver, 13 | } 14 | 15 | impl ToniApplication { 16 | pub fn new(http_adapter: H, container: Rc>) -> Self { 17 | Self { 18 | http_adapter, 19 | routes_resolver: RoutesResolver::new(container.clone()), 20 | } 21 | } 22 | 23 | pub fn init(&mut self) -> Result<()> { 24 | self.routes_resolver.resolve(&mut self.http_adapter)?; 25 | Ok(()) 26 | } 27 | pub async fn listen(self, port: u16, hostname: &str) { 28 | if let Err(e) = self.http_adapter.listen(port, hostname).await { 29 | eprintln!("🚨 Failed to start server: {}", e); 30 | std::process::exit(1); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /toni-macros/src/provider_macro/provider_struct.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use syn::{Ident, ItemImpl, ItemStruct, Result, parse2}; 3 | 4 | use crate::utils::extracts::extract_struct_dependencies; 5 | 6 | use super::{ 7 | impl_functions::process_impl_functions, manager::generate_manager, output::generate_output, 8 | }; 9 | 10 | pub fn handle_provider_struct( 11 | attr: TokenStream, 12 | item: TokenStream, 13 | trait_name: Ident, 14 | ) -> Result { 15 | let struct_attrs = parse2::(attr)?; 16 | let impl_block = parse2::(item)?; 17 | 18 | let mut dependencies = extract_struct_dependencies(&struct_attrs)?; 19 | 20 | let (controllers, metadata) = process_impl_functions( 21 | &impl_block, 22 | &mut dependencies, 23 | &struct_attrs.ident, 24 | &trait_name, 25 | )?; 26 | 27 | let manager = generate_manager(&struct_attrs.ident, metadata, dependencies.unique_types); 28 | let expanded = generate_output(struct_attrs, impl_block, controllers, manager); 29 | 30 | Ok(expanded) 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Antonio Carlos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /toni/src/traits_helpers/controller.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use async_trait::async_trait; 4 | use rustc_hash::FxHashMap; 5 | 6 | use crate::http_helpers::{HttpMethod, HttpRequest, HttpResponse, IntoResponse}; 7 | 8 | use super::{provider::ProviderTrait, validate::Validatable, Guard, Interceptor, Pipe}; 9 | 10 | #[async_trait] 11 | pub trait ControllerTrait: Send + Sync{ 12 | fn get_token(&self) -> String; 13 | async fn execute(&self, req: HttpRequest) -> Box + Send>; 14 | fn get_path(&self) -> String; 15 | fn get_method(&self) -> HttpMethod; 16 | fn get_guards(&self) -> Vec>; 17 | fn get_pipes(&self) -> Vec>; 18 | fn get_interceptors(&self) -> Vec>; 19 | fn get_body_dto(&self, req: &HttpRequest) -> Option>; 20 | } 21 | pub trait Controller { 22 | fn get_all_controllers( 23 | &self, 24 | dependencies: &FxHashMap>>, 25 | ) -> FxHashMap>>; 26 | fn get_name(&self) -> String; 27 | fn get_token(&self) -> String; 28 | fn get_dependencies(&self) -> Vec; 29 | } 30 | -------------------------------------------------------------------------------- /toni-macros/src/controller_macro/controller_struct.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use syn::{Ident, ItemImpl, ItemStruct, Result, parse2}; 3 | 4 | use crate::utils::extracts::{extract_controller_prefix, extract_struct_dependencies}; 5 | 6 | use super::{ 7 | impl_functions::process_impl_functions, manager::generate_manager, output::generate_output, 8 | }; 9 | 10 | pub fn handle_controller_struct( 11 | attr: TokenStream, 12 | item: TokenStream, 13 | trait_name: Ident, 14 | ) -> Result { 15 | let struct_attrs = parse2::(attr)?; 16 | let mut impl_block = parse2::(item)?; 17 | 18 | let prefix_path = extract_controller_prefix(&impl_block)?; 19 | let mut dependencies = extract_struct_dependencies(&struct_attrs)?; 20 | 21 | let (controllers, metadata) = process_impl_functions( 22 | &mut impl_block, 23 | &mut dependencies, 24 | &struct_attrs.ident, 25 | &trait_name, 26 | &prefix_path, 27 | )?; 28 | 29 | let manager = generate_manager(&struct_attrs.ident, metadata, dependencies.unique_types); 30 | let expanded = generate_output(struct_attrs, impl_block, controllers, manager); 31 | 32 | Ok(expanded) 33 | } 34 | -------------------------------------------------------------------------------- /toni/src/router/routes_resolve.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use anyhow::Result; 4 | 5 | use crate::{ 6 | http_adapter::HttpAdapter, 7 | injector::ToniContainer, 8 | }; 9 | 10 | pub struct RoutesResolver { 11 | container: Rc> 12 | } 13 | 14 | impl RoutesResolver { 15 | pub fn new(container: Rc>) -> Self { 16 | Self { 17 | container 18 | } 19 | } 20 | 21 | pub fn resolve(&mut self, http_adapter: &mut impl HttpAdapter) -> Result<()> { 22 | let modules_token = self.container.borrow().get_modules_token(); 23 | 24 | for module_token in modules_token { 25 | self.register_routes(module_token, http_adapter)?; 26 | } 27 | Ok(()) 28 | } 29 | 30 | fn register_routes(&mut self, module_token: String, http_adapter: &mut impl HttpAdapter) -> Result<()> { 31 | let mut container = self.container.borrow_mut(); 32 | let controllers = container.get_controllers_instance(&module_token)?; 33 | for (_, controller) in controllers { 34 | http_adapter 35 | .add_route(&controller.get_path(), controller.get_method(), controller); 36 | } 37 | Ok(()) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /toni-macros/src/utils/create_struct_name.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, Span}; 2 | use syn::Result; 3 | 4 | use super::snake_to_upper::{snake_to_upper, upper_to_snake}; 5 | 6 | pub fn create_struct_name(ref_name: &str, method_name: &Ident) -> Result { 7 | let method_name_upper = snake_to_upper(method_name)?; 8 | let struct_name_created = format!("{}{}", method_name_upper, ref_name); 9 | let clean_name = match struct_name_created.strip_prefix("_") { 10 | Some(name) => name, 11 | None => struct_name_created.as_str(), 12 | }; 13 | Ok(Ident::new(clean_name, Span::call_site())) 14 | } 15 | 16 | pub fn create_field_struct_name(ref_name: &str, field_name: &Ident) -> Result { 17 | let method_name_snake = upper_to_snake(ref_name)?; 18 | let struct_name_created = format!("{}{}", field_name, method_name_snake); 19 | Ok(Ident::new(&struct_name_created, Span::call_site())) 20 | } 21 | 22 | 23 | pub fn create_provider_name_by_fn_and_struct_ident(function_name: &Ident, struct_ident: &Ident) -> Result { 24 | let function_name_upper = snake_to_upper(function_name)?; 25 | let provider_name = format!("{}{}", function_name_upper, struct_ident); 26 | let clean_name = match provider_name.strip_prefix("_") { 27 | Some(name) => name.to_string(), 28 | None => provider_name, 29 | }; 30 | Ok(clean_name) 31 | } -------------------------------------------------------------------------------- /toni-cli/src/templates/generate/resource.controller.rs: -------------------------------------------------------------------------------- 1 | use toni_macros::{controller, controller_struct, get, post, put, delete}; 2 | use toni::http_helpers::{HttpRequest, Body}; 3 | use super::resource_name_service::_RESOURCE_NAME_SERVICE; 4 | 5 | #[controller_struct( 6 | pub struct _RESOURCE_NAME_CONTROLLER { 7 | resource_name_service: _RESOURCE_NAME_SERVICE, 8 | } 9 | )] 10 | #[controller("/resource_name")] 11 | impl _RESOURCE_NAME_CONTROLLER { 12 | #[post("")] 13 | fn _create(&self, _req: HttpRequest) -> Body { 14 | let create: String = self.resource_name_service.create(); 15 | Body::Text(create) 16 | } 17 | 18 | #[get("")] 19 | fn _find_all(&self, _req: HttpRequest) -> Body { 20 | let find_all: String = self.resource_name_service.find_all(); 21 | Body::Text(find_all) 22 | } 23 | 24 | #[get("/{id}")] 25 | fn _find_by_id(&self, req: HttpRequest) -> Body { 26 | let id = req.path_params.get("id").unwrap().parse::().unwrap(); 27 | let find_by_id: String = self.resource_name_service.find_by_id(id); 28 | Body::Text(find_by_id) 29 | } 30 | 31 | #[put("")] 32 | fn _update(&self, _req: HttpRequest) -> Body { 33 | let update: String = self.resource_name_service.update(); 34 | Body::Text(update) 35 | } 36 | 37 | #[delete("")] 38 | fn _delete(&self, _req: HttpRequest) -> Body { 39 | let delete: String = self.resource_name_service.delete(); 40 | Body::Text(delete) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /toni-macros/src/markers_params/extracts_marker_params.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::Result; 4 | 5 | use super::get_marker_params::MarkerParam; 6 | 7 | 8 | pub fn extract_body_from_param(marker_param: &MarkerParam) -> Result { 9 | let param_name = &marker_param.param_name; 10 | let type_ident = &marker_param.type_ident; 11 | let extract_token_stream = quote! { 12 | let body = match req.body { 13 | Body::Json(json) => json.clone(), 14 | _ => ::serde_json::json!({}), 15 | }; 16 | let #param_name: #type_ident = ::serde_json::from_value(body).unwrap(); 17 | }; 18 | Ok(extract_token_stream) 19 | } 20 | 21 | pub fn extract_query_from_param(marker_param: &MarkerParam) -> Result { 22 | let param_name = &marker_param.param_name; 23 | let type_ident = &marker_param.type_ident; 24 | let marker_arg = &marker_param.marker_arg; 25 | let extract_token_stream = quote! { 26 | let #param_name: #type_ident = req.query_params.get(#marker_arg).unwrap().parse().unwrap(); 27 | }; 28 | Ok(extract_token_stream) 29 | } 30 | 31 | pub fn extract_path_param_from_param(marker_param: &MarkerParam) -> Result { 32 | let param_name = &marker_param.param_name; 33 | let type_ident = &marker_param.type_ident; 34 | let marker_arg = &marker_param.marker_arg; 35 | let extract_token_stream = quote! { 36 | let #param_name: #type_ident = req.path_params.get(#marker_arg).unwrap().parse().unwrap(); 37 | }; 38 | Ok(extract_token_stream) 39 | } -------------------------------------------------------------------------------- /toni-macros/src/utils/snake_to_upper.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, Span}; 2 | use syn::{Error, Result}; 3 | 4 | pub fn snake_to_upper(snake: &Ident) -> Result { 5 | let mut snake_str = snake.to_string(); 6 | let mut result = String::new(); 7 | if snake_str.starts_with('_') { 8 | snake_str = snake_str.strip_prefix('_') 9 | .ok_or_else(|| Error::new(snake.span(), "Failed to strip prefix"))?.to_owned(); 10 | } 11 | for segment in snake_str.split('_') { 12 | if segment.is_empty() { 13 | return Err(Error::new(snake.span(), "Empty segment found in snake case identifier")); 14 | } 15 | let mut chars = segment.chars(); 16 | let first_char = chars 17 | .next() 18 | .ok_or_else(|| Error::new(snake.span(), "Segment has no characters"))?; 19 | let upper_first: String = first_char.to_uppercase().collect(); 20 | result.push_str(&upper_first); 21 | result.push_str(chars.as_str()); 22 | } 23 | Ok(result) 24 | } 25 | 26 | pub fn upper_to_snake(upper: &str) -> Result { 27 | let mut result = String::new(); 28 | for c in upper.chars() { 29 | if c.is_uppercase() { 30 | result.push('_'); 31 | let lower = c.to_lowercase().next().ok_or_else(|| { 32 | Error::new(Span::call_site(), "Failed to convert uppercase to lowercase") 33 | })?; 34 | result.push(lower); 35 | } else { 36 | result.push(c); 37 | } 38 | } 39 | Ok(result) 40 | } -------------------------------------------------------------------------------- /toni-macros/src/utils/controller_utils.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | use syn::{Attribute, Ident}; 3 | 4 | fn is_http_method(segment: &Ident) -> bool { 5 | matches!( 6 | segment.to_string().as_str(), 7 | "get" | "post" | "put" | "delete" | "patch" | "options" | "head" 8 | ) 9 | } 10 | 11 | fn has_http_method_attribute(attr: &Attribute) -> bool { 12 | attr.path() 13 | .segments 14 | .iter() 15 | .any(|segment| is_http_method(&segment.ident)) 16 | } 17 | 18 | pub fn find_macro_attribute(attrs: &[Attribute], type_macro: String) -> Option<&Attribute> { 19 | if type_macro == "http_method" { 20 | return attrs.iter().find(|attr| has_http_method_attribute(attr)); 21 | } 22 | None 23 | } 24 | 25 | pub fn attr_to_string(attr: &Attribute) -> Result { 26 | let atribute_string = attr 27 | .path() 28 | .segments 29 | .first() 30 | .map(|segment| segment.ident.to_string()); 31 | match atribute_string { 32 | Some(s) => Ok(s), 33 | None => Err(()), 34 | } 35 | } 36 | 37 | pub fn create_extract_body_dto_token_stream( 38 | body_dto: &Ident, 39 | ) -> syn::Result { 40 | let get_body_dto_block = quote! { 41 | let body = match req.body { 42 | Body::Json(ref json) => json.clone(), 43 | _ => ::serde_json::json!({}), 44 | }; 45 | let body_json: #body_dto = ::serde_json::from_value(body).unwrap(); 46 | Some(Box::new(body_json)) 47 | }; 48 | Ok(get_body_dto_block) 49 | } 50 | -------------------------------------------------------------------------------- /toni/src/injector/context.rs: -------------------------------------------------------------------------------- 1 | use crate::{http_helpers::{HttpRequest, HttpResponse, IntoResponse}, traits_helpers::validate::Validatable}; 2 | 3 | #[derive(Debug)] 4 | pub struct Context { 5 | original_request: HttpRequest, 6 | response: Option + Send>>, 7 | should_abort: bool, 8 | dto: Option>, 9 | } 10 | 11 | impl Context { 12 | pub fn from_request(req: HttpRequest) -> Self { 13 | Self { 14 | original_request: req, 15 | response: None, 16 | should_abort: false, 17 | dto: None, 18 | } 19 | } 20 | 21 | pub fn take_request(&self) -> &HttpRequest { 22 | &self.original_request 23 | } 24 | 25 | pub fn set_response(&mut self, response: Box + Send>) { 26 | self.response = Some(response); 27 | } 28 | 29 | pub fn get_response(self) -> Box + Send> { 30 | if let Some(response) = self.response { 31 | return response 32 | } 33 | 34 | panic!("Response not set in context"); 35 | 36 | // else { 37 | // HttpResponse::InternalServerError().body("Internal Server Error") 38 | // } 39 | } 40 | 41 | pub fn abort(&mut self) { 42 | self.should_abort = true; 43 | } 44 | 45 | pub fn should_abort(&self) -> bool { 46 | self.should_abort 47 | } 48 | 49 | pub fn set_dto(&mut self, dto: Box) { 50 | self.dto = Some(dto); 51 | } 52 | 53 | pub fn get_dto(&self) -> Option<&dyn Validatable> { 54 | self.dto.as_deref() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /toni/src/toni_factory.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | 4 | use anyhow::Result; 5 | 6 | use crate::module_helpers::module_enum::ModuleDefinition; 7 | use crate::toni_application::ToniApplication; 8 | use crate::{ 9 | http_adapter::HttpAdapter, 10 | injector::{ToniContainer, ToniInstanceLoader}, 11 | scanner::ToniDependenciesScanner, 12 | }; 13 | 14 | #[derive(Default)] 15 | pub struct ToniFactory; 16 | 17 | impl ToniFactory { 18 | #[inline] 19 | pub fn new() -> Self { 20 | Self 21 | } 22 | 23 | pub fn create( 24 | &self, 25 | module: ModuleDefinition, 26 | http_adapter: impl HttpAdapter 27 | ) -> ToniApplication 28 | { 29 | let container = Rc::new(RefCell::new(ToniContainer::new())); 30 | 31 | match self.initialize(module, container.clone()) { 32 | Ok(_) => (), 33 | Err(e) => { 34 | eprintln!("Falha crítica na inicialização do módulo: {}", e); 35 | std::process::exit(1); 36 | } 37 | }; 38 | 39 | let mut app = ToniApplication::new(http_adapter, container); 40 | match app.init() { 41 | Ok(_) => (), 42 | Err(e) => { 43 | eprintln!("Falha na inicialização da aplicação: {}", e); 44 | std::process::exit(1); 45 | } 46 | } 47 | 48 | app 49 | } 50 | 51 | fn initialize( 52 | &self, 53 | module: ModuleDefinition, 54 | container: Rc> 55 | ) -> Result<()> { 56 | let mut scanner = ToniDependenciesScanner::new(container.clone()); 57 | scanner.scan(module)?; 58 | 59 | ToniInstanceLoader::new(container.clone()) 60 | .create_instances_of_dependencies()?; 61 | 62 | Ok(()) 63 | } 64 | } -------------------------------------------------------------------------------- /toni-macros/src/controller_macro/impl_functions.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use syn::{Ident, ImplItem, ItemImpl, Result}; 3 | 4 | use crate::{ 5 | controller_macro::controller::generate_controller_and_metadata, 6 | enhancer::enhancer::get_enhancers_attr, 7 | markers_params::{ 8 | get_marker_params::get_marker_params, 9 | remove_marker_controller_fn::remove_marker_in_controller_fn_args, 10 | }, 11 | shared::{dependency_info::DependencyInfo, metadata_info::MetadataInfo}, 12 | utils::controller_utils::find_macro_attribute, 13 | }; 14 | 15 | pub fn process_impl_functions( 16 | impl_block: &mut ItemImpl, 17 | dependencies: &mut DependencyInfo, 18 | struct_name: &syn::Ident, 19 | trait_name: &Ident, 20 | prefix_path: &str, 21 | ) -> Result<(Vec, Vec)> { 22 | let mut controllers = Vec::new(); 23 | let mut metadata = Vec::new(); 24 | for item in &mut impl_block.items { 25 | if let ImplItem::Fn(method) = item { 26 | if let Some(attr) = find_macro_attribute(&method.attrs, "http_method".to_owned()) { 27 | let enhancers_attr = get_enhancers_attr(&method.attrs)?; 28 | println!("enhancers_attr: {:?}", enhancers_attr); 29 | 30 | let marker_params = get_marker_params(method)?; 31 | 32 | let (controller, meta) = generate_controller_and_metadata( 33 | method, 34 | struct_name, 35 | dependencies, 36 | trait_name, 37 | &prefix_path.to_string(), 38 | attr, 39 | enhancers_attr, 40 | marker_params, 41 | )?; 42 | 43 | controllers.push(controller); 44 | metadata.push(meta); 45 | 46 | remove_marker_in_controller_fn_args(method); 47 | } 48 | } 49 | } 50 | Ok((controllers, metadata)) 51 | } 52 | -------------------------------------------------------------------------------- /toni-macros/src/provider_macro/manager.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::quote; 5 | use syn::Ident; 6 | 7 | use crate::shared::{ 8 | generate_make_instances::generate_make_instances, metadata_info::MetadataInfo, 9 | }; 10 | 11 | pub fn generate_manager( 12 | struct_name: &Ident, 13 | controllers_metadata: Vec, 14 | unique_dependencies: HashSet, 15 | ) -> TokenStream { 16 | let manager_struct_name = Ident::new(&format!("{}Manager", struct_name), struct_name.span()); 17 | let manager_name = struct_name.to_string(); 18 | let dependencies_name = unique_dependencies.iter().map(|dependency| { 19 | quote! { #dependency.to_string() } 20 | }); 21 | let providers_instances = 22 | generate_make_instances(controllers_metadata, &manager_name, true); 23 | quote! { 24 | pub struct #manager_struct_name; 25 | 26 | impl ::toni::traits_helpers::Provider for #manager_struct_name { 27 | fn get_all_providers(&self, dependencies: &::rustc_hash::FxHashMap>>) -> ::rustc_hash::FxHashMap>> { 28 | 29 | let mut providers = ::rustc_hash::FxHashMap::default(); 30 | #( 31 | let (key, value): (String, ::std::sync::Arc>) = #providers_instances; 32 | providers.insert(key, value); 33 | )* 34 | providers 35 | } 36 | fn get_name(&self) -> String { 37 | #manager_name.to_string() 38 | } 39 | fn get_token(&self) -> String { 40 | #manager_name.to_string() 41 | } 42 | fn get_dependencies(&self) -> Vec { 43 | vec![#(#dependencies_name),*] 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /toni-macros/src/controller_macro/manager.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::quote; 5 | use syn::Ident; 6 | 7 | use crate::shared::{ 8 | generate_make_instances::generate_make_instances, metadata_info::MetadataInfo, 9 | }; 10 | 11 | pub fn generate_manager( 12 | struct_name: &Ident, 13 | controllers_metadata: Vec, 14 | unique_dependencies: HashSet, 15 | ) -> TokenStream { 16 | let manager_struct_name = Ident::new(&format!("{}Manager", struct_name), struct_name.span()); 17 | let manager_name = struct_name.to_string(); 18 | let dependencies_name = unique_dependencies.iter().map(|dependency| { 19 | quote! { #dependency.to_string() } 20 | }); 21 | let controllers_instances = generate_make_instances(controllers_metadata, &manager_name, false); 22 | quote! { 23 | pub struct #manager_struct_name; 24 | 25 | impl ::toni::traits_helpers::Controller for #manager_struct_name { 26 | fn get_all_controllers(&self, dependencies: &::rustc_hash::FxHashMap>>) -> ::rustc_hash::FxHashMap>> { 27 | let mut controllers = ::rustc_hash::FxHashMap::default(); 28 | #( 29 | let (key, value): (String, ::std::sync::Arc>) = #controllers_instances; 30 | controllers.insert(key, value); 31 | )* 32 | controllers 33 | } 34 | fn get_name(&self) -> String { 35 | #manager_name.to_string() 36 | } 37 | fn get_token(&self) -> String { 38 | #manager_name.to_string() 39 | } 40 | fn get_dependencies(&self) -> Vec { 41 | vec![#(#dependencies_name),*] 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /toni-macros/src/markers_params/get_marker_params.rs: -------------------------------------------------------------------------------- 1 | use syn::{ImplItemFn, LitStr, Pat, Result, Type}; 2 | 3 | use crate::markers_params::remove_marker_controller_fn::is_marker; 4 | 5 | #[derive(Debug)] 6 | pub struct MarkerParam { 7 | pub param_name: syn::Ident, 8 | pub type_ident: syn::Ident, 9 | pub marker_name: String, 10 | pub marker_arg: Option, 11 | } 12 | 13 | pub fn get_marker_params(method: &ImplItemFn) -> Result> { 14 | let mut marked_params = Vec::new(); 15 | for input in method.sig.inputs.iter() { 16 | if let syn::FnArg::Typed(pat_type) = input { 17 | if !pat_type.attrs.is_empty() { 18 | if let Some(marker_ident) = pat_type.attrs[0].path().get_ident() { 19 | if is_marker(marker_ident) { 20 | let mut marker_arg = None; 21 | if marker_ident.to_string() == "query" 22 | || marker_ident.to_string() == "param" 23 | { 24 | marker_arg = Some(pat_type.attrs[0].parse_args::()?.value()); 25 | } 26 | if let Pat::Ident(pat_ident) = &*pat_type.pat { 27 | if let Type::Path(type_path) = &*pat_type.ty { 28 | if let Some(last_segment) = type_path.path.segments.last() { 29 | marked_params.push(MarkerParam { 30 | param_name: pat_ident.ident.clone(), 31 | type_ident: last_segment.ident.clone(), 32 | marker_name: marker_ident.to_string(), 33 | marker_arg, 34 | }); 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | } 43 | Ok(marked_params) 44 | } 45 | -------------------------------------------------------------------------------- /toni-macros/src/enhancer/enhancer.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::quote; 5 | use syn::{Attribute, Error, Ident, Result, spanned::Spanned}; 6 | 7 | fn is_enhancer(segment: &Ident) -> bool { 8 | matches!( 9 | segment.to_string().as_str(), 10 | "use_guard" | "interceptor" | "pipe" 11 | ) 12 | } 13 | 14 | pub fn has_enhancer_attribute(attr: &Attribute) -> bool { 15 | attr.path() 16 | .segments 17 | .iter() 18 | .any(|segment| is_enhancer(&segment.ident)) 19 | } 20 | 21 | pub fn create_enchancers_token_stream( 22 | enhancers_attr: HashMap<&Ident, &Attribute>, 23 | ) -> Result>> { 24 | if enhancers_attr.is_empty() { 25 | return Ok(HashMap::new()); 26 | } 27 | let mut enhancers: HashMap> = HashMap::new(); 28 | for (ident, attr) in enhancers_attr { 29 | let arg_ident = attr 30 | .parse_args::() 31 | .map_err(|_| Error::new(attr.span(), "Invalid attribute format"))?; 32 | match enhancers.get_mut(ident.to_string().as_str()) { 33 | Some(enhancer_mut) => { 34 | enhancer_mut.push(quote! {::std::sync::Arc::new(#arg_ident)}); 35 | } 36 | None => { 37 | enhancers.insert(ident.to_string(), vec![ 38 | quote! {::std::sync::Arc::new(#arg_ident)}, 39 | ]); 40 | } 41 | }; 42 | } 43 | Ok(enhancers) 44 | } 45 | 46 | pub fn get_enhancers_attr(attrs: &[Attribute]) -> Result> { 47 | let mut enhancers_attr = HashMap::new(); 48 | attrs.iter().for_each(|attr| { 49 | if has_enhancer_attribute(attr) { 50 | let ident = match attr.meta.path().get_ident() { 51 | Some(ident) => ident, 52 | None => return, 53 | }; 54 | enhancers_attr.insert(ident, attr); 55 | } 56 | }); 57 | Ok(enhancers_attr) 58 | } 59 | -------------------------------------------------------------------------------- /toni/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[path = "adapter/http_adapter.rs"] 2 | pub mod http_adapter; 3 | #[path = "adapter/mod.rs"] 4 | pub mod adapter; 5 | 6 | pub mod http_helpers; 7 | pub mod traits_helpers; 8 | pub mod types_helpers; 9 | pub mod module_helpers; 10 | pub mod toni_factory; 11 | pub mod pipes; 12 | pub mod injector; 13 | mod toni_application; 14 | mod scanner; 15 | mod router; 16 | mod structs_helpers; 17 | mod adapters; 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use std::time::Duration; 22 | use tokio::task::JoinHandle; 23 | 24 | #[tokio::test] 25 | async fn test_server() { 26 | let server_handle: JoinHandle<()> = tokio::spawn(async { 27 | // let factory = ToniFactory::new(); 28 | // let mut axum_adapter = AxumAdapter::new(); 29 | // let app = factory.create(app_module, axum_adapter).unwrap(); 30 | // app.listen(3000, "127.0.0.1").await; 31 | // let app = match app { 32 | // Ok(app) => { 33 | // app 34 | // } 35 | // Err(e) => panic!("sda") 36 | // }; 37 | // let axum_adapter2 = AxumAdapter::new(); 38 | // axum_adapter.add_route(&"/ta".to_string(), HttpMethod::GET, Box::new(GetUserNameController)); 39 | // axum_adapter.listen(3000, "127.0.0.1").await; 40 | // app.listen(3000, "127.0.0.1"); 41 | // servera.get("/ta", |req| Box::pin(route_adapter(req, &Handler))); 42 | // servera.post("/hello2", |req| Box::pin(route_adapter(req, &Handler2))); 43 | // servera.listen(3000, "127.0.0.1").await 44 | }); 45 | tokio::time::sleep(Duration::from_secs(1)).await; 46 | let client = reqwest::Client::new(); 47 | let response = client.get("http://localhost:3000/names").send().await; 48 | let res = match response { 49 | Ok(res) => { 50 | res 51 | } 52 | Err(e) => panic!("{}", e), 53 | }; 54 | 55 | let body = match res.json::().await { 56 | Ok(json) => json, 57 | Err(e) => panic!("{}", e), 58 | }; 59 | 60 | assert_eq!(body["message"].as_str().unwrap(), "John Doe"); 61 | server_handle.abort(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /toni-macros/src/utils/modify_return_body.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | use syn::{Block, Expr, Stmt, visit_mut::VisitMut}; 3 | struct ReturnBoxer; 4 | 5 | impl VisitMut for ReturnBoxer { 6 | fn visit_expr_return_mut(&mut self, expr_return: &mut syn::ExprReturn) { 7 | let expr = expr_return 8 | .expr 9 | .take() 10 | .map(|e| syn::parse2::(quote! { Box::new(#e) }).unwrap()) 11 | .unwrap_or_else(|| syn::parse2::(quote! { Box::new(()) }).unwrap()); 12 | 13 | expr_return.expr = Some(Box::new(expr)); 14 | 15 | syn::visit_mut::visit_expr_return_mut(self, expr_return); 16 | } 17 | } 18 | 19 | pub fn modify_return_method_body(method_body: &mut Block) { 20 | let mut visitor = ReturnBoxer; 21 | visitor.visit_block_mut(method_body); 22 | 23 | if let Some(last_stmt) = method_body.stmts.last_mut() { 24 | if let Stmt::Expr(expr, None) = last_stmt { 25 | let new_return_expr: Expr = syn::parse2(quote! { 26 | Box::new(#expr) 27 | }) 28 | .unwrap(); 29 | 30 | *expr = new_return_expr; 31 | } else { 32 | let new_return_expr: Expr = syn::parse2(quote! { 33 | Box::new(()) 34 | }) 35 | .unwrap(); 36 | 37 | method_body.stmts.push(Stmt::Expr(new_return_expr, None)); 38 | } 39 | } 40 | 41 | //if find return statement, replace it with Box::new(returned_expr) 42 | method_body.stmts.iter_mut().for_each(|stmt| { 43 | if let Stmt::Expr(Expr::Return(_), None) = stmt { 44 | println!("ReturnReturnReturn: {:?}", stmt); 45 | if let Stmt::Expr(Expr::Return(expr_return), None) = stmt { 46 | let returned_expr = &expr_return.expr; 47 | let new_return_expr: Expr = syn::parse2(quote! { 48 | Box::new(#returned_expr) 49 | }) 50 | .unwrap(); 51 | println!("new_return_expr: {:?}", &expr_return); 52 | *expr_return = syn::ExprReturn { 53 | attrs: expr_return.attrs.clone(), 54 | return_token: expr_return.return_token, 55 | expr: Some(Box::new(new_return_expr)), 56 | }; 57 | } 58 | } 59 | }); 60 | } 61 | -------------------------------------------------------------------------------- /toni/src/adapter/axum_adapter.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use std::sync::Arc; 3 | use tokio::net::TcpListener; 4 | 5 | use crate::{http_adapter::HttpAdapter, http_helpers::HttpMethod, injector::InstanceWrapper}; 6 | use axum::{ 7 | Router, 8 | body::Body, 9 | http::Request, 10 | routing::{delete, get, head, options, patch, post, put}, 11 | }; 12 | 13 | use super::{AxumRouteAdapter, RouteAdapter}; 14 | 15 | #[derive(Clone)] 16 | pub struct AxumAdapter { 17 | instance: Router, 18 | } 19 | 20 | impl HttpAdapter for AxumAdapter { 21 | fn new() -> Self { 22 | Self { 23 | instance: Router::new(), 24 | } 25 | } 26 | 27 | fn add_route( 28 | &mut self, 29 | path: &str, 30 | method: HttpMethod, 31 | handler: Arc, 32 | ) { 33 | let route_handler = move |req: Request| { 34 | let handler: Arc = handler.clone(); 35 | Box::pin(async move { 36 | AxumRouteAdapter::handle_request(req, handler) 37 | .await 38 | .unwrap() 39 | }) 40 | }; 41 | println!("Adding route: {} {:?}", path, method); 42 | 43 | self.instance = match method { 44 | HttpMethod::GET => self.instance.clone().route(path, get(route_handler)), 45 | HttpMethod::POST => self.instance.clone().route(path, post(route_handler)), 46 | HttpMethod::PUT => self.instance.clone().route(path, put(route_handler)), 47 | HttpMethod::DELETE => self.instance.clone().route(path, delete(route_handler)), 48 | HttpMethod::HEAD => self.instance.clone().route(path, head(route_handler)), 49 | HttpMethod::PATCH => self.instance.clone().route(path, patch(route_handler)), 50 | HttpMethod::OPTIONS => self.instance.clone().route(path, options(route_handler)), 51 | }; 52 | } 53 | 54 | async fn listen(self, port: u16, hostname: &str) -> Result<()> { 55 | let addr = format!("{}:{}", hostname, port); 56 | let listener: TcpListener = TcpListener::bind(&addr) 57 | .await?; 58 | 59 | println!("Listening on {}", addr); 60 | 61 | axum::serve(listener, self.instance) 62 | .await 63 | .with_context(|| "Axum server encountered an error")?; 64 | Ok(()) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /toni/src/injector/instance_wrapper.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::{ 4 | http_helpers::{HttpMethod, HttpRequest, HttpResponse, IntoResponse}, 5 | structs_helpers::EnhancerMetadata, 6 | traits_helpers::{ControllerTrait, Guard, Interceptor, Pipe} 7 | }; 8 | 9 | use super::Context; 10 | 11 | pub struct InstanceWrapper { 12 | instance: Arc>, 13 | guards: Vec>, 14 | interceptors: Vec>, 15 | pipes: Vec>, 16 | } 17 | 18 | impl InstanceWrapper { 19 | pub fn new( 20 | instance: Arc>, 21 | enhancer_metadata: EnhancerMetadata, 22 | ) -> Self { 23 | Self { 24 | instance, 25 | guards: enhancer_metadata.guards, 26 | interceptors: enhancer_metadata.interceptors, 27 | pipes: enhancer_metadata.pipes, 28 | } 29 | } 30 | 31 | pub fn get_path(&self) -> String { 32 | self.instance.get_path() 33 | } 34 | 35 | pub fn get_method(&self) -> HttpMethod { 36 | self.instance.get_method() 37 | } 38 | 39 | pub async fn handle_request( 40 | &self, 41 | req: HttpRequest, 42 | ) -> Box + Send> { 43 | let mut context = Context::from_request(req); 44 | 45 | 46 | for guard in &self.guards { 47 | if !guard.can_activate(&context) { 48 | return context.get_response(); 49 | } 50 | } 51 | 52 | for interceptor in &self.interceptors { 53 | interceptor.before_execute(&mut context); 54 | } 55 | 56 | let dto = self.instance.get_body_dto(context.take_request()); 57 | if let Some(dto) = dto { 58 | context.set_dto(dto); 59 | } 60 | 61 | for pipe in &self.pipes { 62 | pipe.process(&mut context); 63 | if context.should_abort() { 64 | return context.get_response(); 65 | } 66 | } 67 | 68 | let req = context.take_request().clone(); 69 | 70 | let controller_response = self.instance.execute(req).await; 71 | 72 | context.set_response(controller_response); 73 | 74 | for interceptor in &self.interceptors { 75 | interceptor.after_execute(&mut context); 76 | } 77 | 78 | context.get_response() 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /toni-macros/src/utils/extracts.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use syn::{ 4 | Error, FnArg, Ident, ImplItemFn, ItemImpl, ItemStruct, LitStr, Pat, Result, Type, TypePath, 5 | TypeReference, spanned::Spanned, 6 | }; 7 | 8 | use crate::shared::dependency_info::DependencyInfo; 9 | 10 | pub fn extract_controller_prefix(impl_block: &ItemImpl) -> Result { 11 | impl_block 12 | .attrs 13 | .iter() 14 | .find(|attr| attr.path().is_ident("controller")) 15 | .map(|attr| attr.parse_args::().map(|lit| lit.value())) 16 | .transpose() 17 | .map(|opt| opt.unwrap_or_default()) 18 | } 19 | 20 | pub fn extract_struct_dependencies(struct_attrs: &ItemStruct) -> Result { 21 | let unique_types = HashSet::new(); 22 | let mut fields = Vec::new(); 23 | 24 | for field in &struct_attrs.fields { 25 | let field_ident = field 26 | .ident 27 | .as_ref() 28 | .ok_or_else(|| syn::Error::new_spanned(field, "Unnamed struct fields not supported"))?; 29 | 30 | let type_ident = extract_ident_from_type(&field.ty)?; 31 | fields.push((field_ident.clone(), type_ident.clone())); 32 | } 33 | 34 | Ok(DependencyInfo { 35 | fields, 36 | unique_types, 37 | }) 38 | } 39 | 40 | pub fn extract_ident_from_type(ty: &Type) -> Result<&Ident> { 41 | if let Type::Reference(TypeReference { elem, .. }) = ty { 42 | if let Type::Path(TypePath { path, .. }) = &**elem { 43 | if let Some(segment) = path.segments.last() { 44 | return Ok(&segment.ident); 45 | } 46 | } 47 | } 48 | if let Type::Path(TypePath { path, .. }) = ty { 49 | if let Some(segment) = path.segments.last() { 50 | return Ok(&segment.ident); 51 | } 52 | } 53 | Err(Error::new(ty.span(), "Invalid type")) 54 | } 55 | 56 | pub fn extract_params_from_impl_fn(func: &ImplItemFn) -> Vec<(Ident, Type)> { 57 | let mut params = Vec::new(); 58 | 59 | for input in &func.sig.inputs { 60 | if let FnArg::Typed(pat_type) = input { 61 | let param_name = match &*pat_type.pat { 62 | Pat::Ident(pat_ident) => pat_ident.ident.clone(), 63 | _ => continue, 64 | }; 65 | 66 | let param_type = (*pat_type.ty).clone(); 67 | 68 | params.push((param_name, param_type)); 69 | } 70 | } 71 | 72 | params 73 | } 74 | -------------------------------------------------------------------------------- /toni-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro2; 2 | 3 | use controller_macro::controller_struct::handle_controller_struct; 4 | use proc_macro::TokenStream; 5 | use proc_macro2::Span; 6 | use provider_macro::provider_struct::handle_provider_struct; 7 | use syn::Ident; 8 | 9 | mod utils; 10 | mod module_macro; 11 | mod shared; 12 | mod provider_macro; 13 | mod controller_macro; 14 | mod enhancer; 15 | mod markers_params; 16 | 17 | #[proc_macro_attribute] 18 | pub fn module(attr: TokenStream, item: TokenStream) -> TokenStream { 19 | module_macro::module_struct::module(attr, item) 20 | } 21 | 22 | #[proc_macro_attribute] 23 | pub fn controller_struct(attr: TokenStream, item: TokenStream) -> TokenStream { 24 | let attr = proc_macro2::TokenStream::from(attr); 25 | let item = proc_macro2::TokenStream::from(item); 26 | let trait_name = Ident::new("ControllerTrait", Span::call_site()); 27 | let output = handle_controller_struct(attr, item, trait_name); 28 | proc_macro::TokenStream::from(output.unwrap_or_else(|e| e.to_compile_error())) 29 | } 30 | 31 | #[proc_macro_attribute] 32 | pub fn provider_struct(attr: TokenStream, item: TokenStream) -> TokenStream { 33 | let attr = proc_macro2::TokenStream::from(attr); 34 | let item = proc_macro2::TokenStream::from(item); 35 | let trait_name = Ident::new("ProviderTrait", Span::call_site()); 36 | let output = handle_provider_struct(attr, item, trait_name); 37 | proc_macro::TokenStream::from(output.unwrap_or_else(|e| e.to_compile_error())) 38 | } 39 | 40 | #[proc_macro_attribute] 41 | pub fn controller(_attr: TokenStream, item: TokenStream) -> TokenStream { 42 | item 43 | } 44 | 45 | #[proc_macro_attribute] 46 | pub fn get(_attr: TokenStream, item: TokenStream) -> TokenStream { 47 | item 48 | } 49 | #[proc_macro_attribute] 50 | pub fn post(_attr: TokenStream, item: TokenStream) -> TokenStream { 51 | item 52 | } 53 | #[proc_macro_attribute] 54 | pub fn put(_attr: TokenStream, item: TokenStream) -> TokenStream { 55 | item 56 | } 57 | #[proc_macro_attribute] 58 | pub fn delete(_attr: TokenStream, item: TokenStream) -> TokenStream { 59 | item 60 | } 61 | 62 | #[proc_macro_attribute] 63 | pub fn use_guard(_attr: TokenStream, item: TokenStream) -> TokenStream { 64 | item 65 | } 66 | 67 | #[proc_macro_attribute] 68 | pub fn interceptor(_attr: TokenStream, item: TokenStream) -> TokenStream { 69 | item 70 | } 71 | 72 | #[proc_macro_attribute] 73 | pub fn pipe(_attr: TokenStream, item: TokenStream) -> TokenStream { 74 | item 75 | } -------------------------------------------------------------------------------- /toni/src/injector/dependency_graph.rs: -------------------------------------------------------------------------------- 1 | use super::ToniContainer; 2 | use anyhow::{anyhow, Result}; 3 | use rustc_hash::FxHashMap; 4 | use std::{cell::RefCell, rc::Rc}; 5 | 6 | pub struct DependencyGraph { 7 | container: Rc>, 8 | module_token: String, 9 | visited: FxHashMap, 10 | temp_mark: FxHashMap, 11 | ordered: Vec, 12 | } 13 | 14 | impl DependencyGraph { 15 | pub fn new(container: Rc>, module_token: String) -> Self { 16 | Self { 17 | container, 18 | module_token, 19 | visited: FxHashMap::default(), 20 | temp_mark: FxHashMap::default(), 21 | ordered: Vec::new(), 22 | } 23 | } 24 | 25 | pub fn get_ordered_providers_token(mut self) -> Result> { 26 | let providers = { 27 | let container = self.container.borrow(); 28 | let providers_map = container.get_providers_manager(&self.module_token)?; 29 | providers_map 30 | .iter() 31 | .map(|(token, provider)| (token.clone(), provider.get_dependencies())) 32 | .collect::)>>() 33 | }; 34 | let clone_providers = providers.clone(); 35 | for (token, dependencies) in providers { 36 | if !self.visited.contains_key(&token) { 37 | self.visit_node(token, dependencies, &clone_providers)?; 38 | } 39 | } 40 | Ok(self.ordered) 41 | } 42 | 43 | fn visit_node(&mut self, token: String, dependencies: Vec, providers: &Vec<(String, Vec)>) -> Result<()> { 44 | if self.temp_mark.contains_key(&token) { 45 | return Err(anyhow!("Circular dependency detected for provider: {}", token)); 46 | } 47 | 48 | if self.visited.contains_key(&token) { 49 | return Ok(()); 50 | } 51 | 52 | self.temp_mark.insert(token.clone(), true); 53 | 54 | for dep_token in dependencies { 55 | if let Some((dep_token, dependencies)) = providers 56 | .iter() 57 | .find(|(token, _dependencies)| dep_token.contains(token)) 58 | { 59 | self.visit_node(dep_token.clone(), dependencies.clone(), providers)?; 60 | } 61 | } 62 | 63 | self.temp_mark.remove(&token); 64 | self.visited.insert(token.clone(), true); 65 | self.ordered.push(token); 66 | Ok(()) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /toni-macros/src/shared/generate_make_instances.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | use super::metadata_info::MetadataInfo; 5 | 6 | pub fn generate_make_instances( 7 | structs_metadata: Vec, 8 | manager_name: &String, 9 | self_dependency: bool, 10 | ) -> Vec { 11 | let (independent_structs, dependent_structs): (Vec<_>, Vec<_>) = { 12 | let has_manager_dependency = |instance: &&MetadataInfo| { 13 | instance 14 | .dependencies 15 | .iter() 16 | .any(|(_, dep_key)| dep_key.contains(manager_name)) 17 | }; 18 | 19 | structs_metadata 20 | .iter() 21 | .partition(|instance| !has_manager_dependency(instance)) 22 | }; 23 | 24 | let ordered_structs = independent_structs 25 | .into_iter() 26 | .chain(dependent_structs.into_iter()); 27 | 28 | ordered_structs 29 | .map(|instance_metadata| { 30 | let struct_ident = &instance_metadata.struct_name; 31 | let struct_name_string = struct_ident.to_string(); 32 | let dependencies = &instance_metadata.dependencies; 33 | 34 | let field_injections = dependencies 35 | .iter() 36 | .map(|(field_name, dependency_key)| { 37 | let error_message = format!( 38 | "Missing dependency '{}' for field '{}' in '{}'", 39 | dependency_key, field_name, &struct_name_string 40 | ); 41 | let error_handling = if self_dependency { 42 | quote! { 43 | unwrap_or_else(|| { 44 | providers.get(#dependency_key).expect(#error_message) 45 | }) 46 | } 47 | } else { 48 | quote! { expect(#error_message) } 49 | }; 50 | quote! { 51 | #field_name: dependencies 52 | .get(#dependency_key) 53 | .#error_handling 54 | .clone() 55 | } 56 | }) 57 | .collect::>(); 58 | 59 | quote! { 60 | ( 61 | #struct_name_string.to_string(), 62 | ::std::sync::Arc::new( 63 | Box::new(#struct_ident { 64 | #(#field_injections),* 65 | }) 66 | ) 67 | ) 68 | } 69 | }) 70 | .collect::>() 71 | } 72 | -------------------------------------------------------------------------------- /toni/src/http_helpers/into_response.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use serde_json::Value; 4 | 5 | use super::{Body, HttpResponse}; 6 | 7 | pub trait IntoResponse: Debug { 8 | type Response; 9 | 10 | fn to_response(&self) -> Self::Response; 11 | } 12 | 13 | impl IntoResponse for HttpResponse { 14 | type Response = Self; 15 | 16 | fn to_response(&self) -> Self { 17 | self.clone() 18 | } 19 | } 20 | 21 | impl IntoResponse for Body { 22 | type Response = HttpResponse; 23 | 24 | fn to_response(&self) -> Self::Response { 25 | HttpResponse { 26 | body: Some(self.clone()), 27 | ..HttpResponse::new() 28 | } 29 | } 30 | } 31 | 32 | impl IntoResponse for u16 { 33 | type Response = HttpResponse; 34 | 35 | fn to_response(&self) -> Self::Response { 36 | HttpResponse { 37 | status: *self, 38 | ..HttpResponse::new() 39 | } 40 | } 41 | } 42 | 43 | impl IntoResponse for Vec<(String, String)> { 44 | type Response = HttpResponse; 45 | 46 | fn to_response(&self) -> Self::Response { 47 | HttpResponse { 48 | headers: self.clone(), 49 | ..HttpResponse::new() 50 | } 51 | } 52 | } 53 | 54 | impl IntoResponse for (u16, Body) { 55 | type Response = HttpResponse; 56 | 57 | fn to_response(&self) -> Self::Response { 58 | HttpResponse { 59 | body: Some(self.1.clone()), 60 | status: self.0, 61 | ..HttpResponse::new() 62 | } 63 | } 64 | } 65 | 66 | // impl IntoResponse for (T1, T2) 67 | // where 68 | // T1: IntoResponse, 69 | // T2: IntoResponse, 70 | // { 71 | // type Response = HttpResponse; 72 | 73 | // fn to_response(&self) -> HttpResponse { 74 | // let mut response = self.0.to_response(); 75 | // let part = self.1.to_response(); 76 | 77 | // response.status = part.status; 78 | // response.headers.extend(part.headers); 79 | // response.body = part.body; 80 | 81 | // response 82 | // } 83 | // } 84 | 85 | impl IntoResponse for Value { 86 | type Response = HttpResponse; 87 | 88 | fn to_response(&self) -> Self::Response { 89 | HttpResponse { 90 | body: Some(Body::Json(self.clone())), 91 | headers: vec![("Content-Type".to_string(), "application/json".to_string())], 92 | ..HttpResponse::new() 93 | } 94 | } 95 | } 96 | 97 | impl IntoResponse for String { 98 | type Response = HttpResponse; 99 | 100 | fn to_response(&self) -> Self::Response { 101 | HttpResponse { 102 | body: Some(Body::Text(self.clone())), 103 | ..HttpResponse::new() 104 | } 105 | } 106 | } 107 | 108 | impl IntoResponse for &'static str { 109 | type Response = HttpResponse; 110 | 111 | fn to_response(&self) -> Self::Response { 112 | HttpResponse { 113 | body: Some(Body::Text(self.to_string())), 114 | ..HttpResponse::new() 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /toni-macros/src/utils/types.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Span; 2 | use syn::{parse_quote, punctuated::Punctuated, Ident, Path, PathArguments, PathSegment, Type}; 3 | 4 | pub fn create_type_reference(name: &str, arc_type: bool, box_type: bool, dyn_type: bool) -> Type { 5 | let mut base_type = create_base_type(name); 6 | 7 | if dyn_type { 8 | base_type = wrap_in_dyn(base_type); 9 | } 10 | 11 | if box_type { 12 | base_type = wrap_in_box(base_type); 13 | } 14 | 15 | if arc_type { 16 | base_type = wrap_in_arc(base_type); 17 | } 18 | 19 | base_type 20 | } 21 | 22 | fn create_base_type(name: &str) -> Type { 23 | Type::Path(syn::TypePath { 24 | qself: None, 25 | path: Path { 26 | leading_colon: Some(Default::default()), 27 | segments: Punctuated::from_iter(vec![ 28 | PathSegment::from(Ident::new("toni", Span::mixed_site())), 29 | PathSegment::from(Ident::new("traits_helpers", Span::mixed_site())), 30 | PathSegment::from(Ident::new(name, Span::mixed_site())), 31 | ]), 32 | }, 33 | }) 34 | } 35 | 36 | fn wrap_in_dyn(base_type: Type) -> Type { 37 | Type::TraitObject(syn::TypeTraitObject { 38 | dyn_token: Some(parse_quote! {dyn}), 39 | bounds: Punctuated::from_iter(vec![syn::TypeParamBound::Trait(syn::TraitBound { 40 | paren_token: None, 41 | modifier: syn::TraitBoundModifier::None, 42 | lifetimes: None, 43 | path: match base_type { 44 | Type::Path(type_path) => type_path.path, 45 | _ => panic!("Expected Type::Path for base_type"), 46 | }, 47 | })]), 48 | }) 49 | } 50 | 51 | fn wrap_in_box(base_type: Type) -> Type { 52 | Type::Path(syn::TypePath { 53 | qself: None, 54 | path: Path { 55 | leading_colon: None, 56 | segments: Punctuated::from_iter(vec![PathSegment { 57 | ident: syn::Ident::new("Box", proc_macro2::Span::mixed_site()), 58 | arguments: PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { 59 | colon2_token: None, 60 | lt_token: parse_quote! {<}, 61 | args: Punctuated::from_iter(vec![syn::GenericArgument::Type(base_type)]), 62 | gt_token: parse_quote! {>}, 63 | }), 64 | }]), 65 | }, 66 | }) 67 | } 68 | 69 | fn wrap_in_arc(base_type: Type) -> Type { 70 | Type::Path(syn::TypePath { 71 | qself: None, 72 | path: Path { 73 | leading_colon: Some(Default::default()), 74 | segments: Punctuated::from_iter(vec![ 75 | PathSegment::from(Ident::new("std", Span::mixed_site())), 76 | PathSegment::from(Ident::new("sync", Span::mixed_site())), 77 | PathSegment { 78 | ident: syn::Ident::new("Arc", proc_macro2::Span::mixed_site()), 79 | arguments: PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { 80 | colon2_token: None, 81 | lt_token: parse_quote! {<}, 82 | args: Punctuated::from_iter(vec![syn::GenericArgument::Type(base_type)]), 83 | gt_token: parse_quote! {>}, 84 | }), 85 | }, 86 | ]), 87 | }, 88 | }) 89 | } 90 | -------------------------------------------------------------------------------- /toni/README.md: -------------------------------------------------------------------------------- 1 | # Toni Framework 2 | 3 | **Toni** is a Rust backend framework designed for building modular and scalable applications inspired by the Nest.js architecture. It provides a structured approach to organizing your code with controllers, services, and modules, while remaining decoupled from the HTTP server (Axum adapted and used by default). 4 | 5 | --- 6 | 7 | ## Features 8 | 9 | - **Modular Architecture**: Organize your application into reusable modules. 10 | - **HTTP Server Flexibility**: Use Axum or integrate your preferred server. 11 | - **Dependency Injection**: Manage dependencies cleanly with module providers. 12 | - **Macro-Driven Syntax**: Reduce boilerplate with intuitive procedural macros. 13 | 14 | --- 15 | 16 | ## Installation 17 | 18 | ### Prerequisites 19 | 20 | - **[Rust & Cargo](https://www.rust-lang.org/tools/install)**: Ensure Rust is installed. 21 | - **Toni CLI**: Install the CLI tool globally: 22 | ```bash 23 | cargo install toni-cli 24 | ``` 25 | 26 | --- 27 | 28 | ## Quickstart: Build a CRUD App 29 | 30 | Use the Toni CLI to create a new project: 31 | ```bash 32 | toni new my_app 33 | ``` 34 | 35 | ## Project Structure 36 | ``` 37 | src/ 38 | ├── app/ 39 | │ ├── app.controller.rs 40 | │ ├── app.module.rs 41 | │ ├── app.service.rs 42 | │ └── mod.rs 43 | └── main.rs 44 | ``` 45 | 46 | ## Run the Server 47 | ```bash 48 | cargo run 49 | ``` 50 | Test your endpoints at `http://localhost:3000/app`. 51 | 52 | --- 53 | 54 | ## Key Concepts 55 | 56 | ### Project Structure 57 | | File | Role | 58 | |---------------------|-------------------------------------------| 59 | | **`app.controller.rs`** | Defines routes and handles HTTP requests. | 60 | | **`app.module.rs`** | Configures dependencies and module setup. | 61 | | **`app.service.rs`** | Implements core business logic. | 62 | 63 | ### Decoupled HTTP Server 64 | Toni decouples your application from the HTTP server, and by default we use Axum. In the future we plan to integrate other HTTP adapters. 65 | 66 | ## Code Example 67 | 68 | **`main.rs`** 69 | ```rust 70 | use toni::{ToniFactory, AxumAdapter}; 71 | 72 | #[tokio::main] 73 | async fn main() { 74 | let axum_adapter = AxumAdapter::new(); 75 | let factory = ToniFactory::new(); 76 | let app = factory.create(AppModule::module_definition(), axum_adapter); 77 | app.listen(3000, "127.0.0.1").await; 78 | } 79 | ``` 80 | 81 | **`app/app.module.rs`** (Root Module) 82 | ```rust 83 | #[module( 84 | imports: [], 85 | controllers: [_AppController], 86 | providers: [_AppService], 87 | exports: [] 88 | )] 89 | pub struct AppModule; 90 | ``` 91 | 92 | **`app/app.controller.rs`** (HTTP Routes) 93 | ```rust 94 | #[controller_struct(pub struct _AppController { app_service: _AppService })] 95 | #[controller("/app")] 96 | impl _AppController { 97 | #[post("")] 98 | fn create(&self, _req: HttpRequest) -> Body { 99 | Body::Text(self.app_service.create()) 100 | } 101 | 102 | #[get("")] 103 | fn find_all(&self, _req: HttpRequest) -> Body { 104 | Body::Text(self.app_service.find_all()) 105 | } 106 | } 107 | ``` 108 | 109 | **`app/app.service.rs`** (Business Logic) 110 | ```rust 111 | #[provider_struct(pub struct _AppService;)] 112 | impl _AppService { 113 | pub fn create(&self) -> String { 114 | "Item created!".into() 115 | } 116 | 117 | pub fn find_all(&self) -> String { 118 | "All items!".into() 119 | } 120 | } 121 | ``` 122 | 123 | --- 124 | 125 | ## License 126 | - **License**: MIT. 127 | 128 | --- -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

A Rust framework for building efficient and scalable server-side applications.

2 |

3 | 4 | ## Description 5 | 6 | Toni is a framework for building efficient and scalable server-side Rust applications. It was inspired by NestJS architecture, offering a clean architecture and a developer-friendly experience. 7 | 8 | Under the hood, Toni uses Axum, but it is built to be easily integrated with other HTTP servers. 9 | 10 | ## Features 11 | 12 | - **Modular Architecture**: Organize your application into reusable modules. 13 | - **HTTP Server Flexibility**: Use Axum or integrate your preferred server. 14 | - **Dependency Injection**: Manage dependencies cleanly with module providers. 15 | - **Macro-Driven Syntax**: Reduce boilerplate with intuitive procedural macros. 16 | 17 | --- 18 | 19 | ## Installation 20 | 21 | ### Prerequisites 22 | 23 | - **[Rust & Cargo](https://www.rust-lang.org/tools/install)**: Ensure Rust is installed. 24 | - **Toni CLI**: Install the CLI tool globally: 25 | ```bash 26 | cargo install toni-cli 27 | ``` 28 | 29 | --- 30 | 31 | ## Quickstart: Build a CRUD App 32 | 33 | Use the Toni CLI to create a new project: 34 | ```bash 35 | toni new my_app 36 | ``` 37 | 38 | ## Project Structure 39 | ``` 40 | src/ 41 | ├── app/ 42 | │ ├── app.controller.rs 43 | │ ├── app.module.rs 44 | │ ├── app.service.rs 45 | │ └── mod.rs 46 | └── main.rs 47 | ``` 48 | 49 | ## Run the Server 50 | ```bash 51 | cargo run 52 | ``` 53 | Test your endpoints at `http://localhost:3000/app`. 54 | 55 | --- 56 | 57 | ## Key Concepts 58 | 59 | ### Project Structure 60 | | File | Role | 61 | |---------------------|-------------------------------------------| 62 | | **`app.controller.rs`** | Defines routes and handles HTTP requests. | 63 | | **`app.module.rs`** | Configures dependencies and module setup. | 64 | | **`app.service.rs`** | Implements core business logic. | 65 | 66 | ### Decoupled HTTP Server 67 | Toni decouples your application from the HTTP server, and by default we use Axum. In the future we plan to integrate other HTTP adapters. 68 | 69 | ## Code Example 70 | 71 | **`main.rs`** 72 | ```rust 73 | use toni::{ToniFactory, AxumAdapter}; 74 | 75 | #[tokio::main] 76 | async fn main() { 77 | let axum_adapter = AxumAdapter::new(); 78 | let factory = ToniFactory::new(); 79 | let app = factory.create(AppModule::module_definition(), axum_adapter); 80 | app.listen(3000, "127.0.0.1").await; 81 | } 82 | ``` 83 | 84 | **`app/app.module.rs`** (Root Module) 85 | ```rust 86 | #[module( 87 | imports: [], 88 | controllers: [_AppController], 89 | providers: [_AppService], 90 | exports: [] 91 | )] 92 | pub struct AppModule; 93 | ``` 94 | 95 | **`app/app.controller.rs`** (HTTP Routes) 96 | ```rust 97 | #[controller_struct( 98 | pub struct _AppController { 99 | app_service: _AppService 100 | } 101 | )] 102 | #[controller("/app")] 103 | impl _AppController { 104 | #[post("")] 105 | fn create(&self, _req: HttpRequest) -> Body { 106 | Body::Text(self.app_service.create()) 107 | } 108 | 109 | #[get("")] 110 | fn find_all(&self, _req: HttpRequest) -> Body { 111 | Body::Text(self.app_service.find_all()) 112 | } 113 | } 114 | ``` 115 | 116 | **`app/app.service.rs`** (Business Logic) 117 | ```rust 118 | #[provider_struct( 119 | pub struct _AppService; 120 | )] 121 | impl _AppService { 122 | pub fn create(&self) -> String { 123 | "Item created!".into() 124 | } 125 | 126 | pub fn find_all(&self) -> String { 127 | "All items!".into() 128 | } 129 | } 130 | ``` 131 | 132 | ## License 133 | 134 | Toni is [MIT licensed](LICENSE). -------------------------------------------------------------------------------- /toni/src/adapter/axum_route_adapter.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, str::FromStr}; 2 | 3 | use anyhow::{Result, anyhow}; 4 | use axum::{ 5 | RequestPartsExt, 6 | body::to_bytes, 7 | extract::{Path, Query}, 8 | http::{HeaderMap, HeaderName, HeaderValue, Request, Response, StatusCode} 9 | }; 10 | use serde_json::Value; 11 | 12 | use crate::http_helpers::{self, Body, HttpRequest, HttpResponse}; 13 | 14 | use super::RouteAdapter; 15 | 16 | pub struct AxumRouteAdapter; 17 | 18 | impl RouteAdapter for AxumRouteAdapter { 19 | type Request = Request; 20 | type Response = Response; 21 | 22 | async fn adapt_request(request: Self::Request) -> Result { 23 | let (mut parts, body) = request.into_parts(); 24 | let body_bytes = to_bytes(body, usize::MAX).await?; 25 | let bytes = body_bytes.to_vec(); 26 | 27 | let body = if let Ok(body_str) = String::from_utf8(bytes) { 28 | if let Ok(json) = serde_json::from_str::(&body_str) { 29 | Body::Json(json) 30 | } else { 31 | Body::Text(body_str) 32 | } 33 | } else { 34 | Body::Text(String::from_utf8_lossy(&body_bytes).to_string()) 35 | }; 36 | 37 | let Path(path_params) = parts 38 | .extract::>>() 39 | .await 40 | .map_err(|e| anyhow!("Failed to extract path parameters: {:?}", e))?; 41 | 42 | let Query(query_params) = parts 43 | .extract::>>() 44 | .await 45 | .map_err(|e| anyhow!("Failed to extract query parameters: {:?}", e))?; 46 | 47 | let headers = parts 48 | .headers 49 | .iter() 50 | .map(|(name, value)| (name.to_string(), value.to_str().unwrap_or("").to_string())) 51 | .collect(); 52 | 53 | Ok(HttpRequest { 54 | body, 55 | headers, 56 | method: parts.method.to_string(), 57 | uri: parts.uri.to_string(), 58 | query_params, 59 | path_params, 60 | }) 61 | } 62 | 63 | fn adapt_response( 64 | response: Box>, 65 | ) -> Result { 66 | let response = response.to_response(); 67 | 68 | let status = 69 | StatusCode::from_u16(response.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR); 70 | 71 | let mut body_is_json = false; 72 | 73 | let body = match response.body { 74 | Some(Body::Text(text)) => axum::body::Body::from(text), 75 | Some(Body::Json(json)) => { 76 | body_is_json = true; 77 | let vec = serde_json::to_vec(&json) 78 | .map_err(|e| anyhow::anyhow!("Failed to serialize JSON: {}", e))?; 79 | axum::body::Body::from(vec) 80 | } 81 | _ => axum::body::Body::empty(), 82 | }; 83 | 84 | let mut headers = HeaderMap::new(); 85 | for (k, v) in &response.headers { 86 | if let Ok(header_name) = HeaderName::from_bytes(k.as_bytes()) { 87 | if let Ok(header_value) = HeaderValue::from_str(v) { 88 | headers.insert(header_name, header_value); 89 | } 90 | } 91 | } 92 | 93 | let content_type = if body_is_json { 94 | "application/json" 95 | } else { 96 | "text/plain" 97 | }; 98 | headers.insert( 99 | HeaderName::from_str("Content-Type") 100 | .map_err(|e| anyhow::anyhow!("Failed to parse header name Content-Type: {}", e))?, 101 | HeaderValue::from_static(content_type), 102 | ); 103 | 104 | let mut res = Response::builder() 105 | .status(status) 106 | .body(body) 107 | .map_err(|e| anyhow::anyhow!("Failed to build response: {}", e))?; 108 | 109 | res.headers_mut().extend(headers); 110 | 111 | Ok(res) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /toni-macros/src/provider_macro/provider.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::{Error, Ident, ImplItemFn}; 4 | 5 | use crate::{ 6 | shared::{dependency_info::DependencyInfo, metadata_info::MetadataInfo}, 7 | utils::{ 8 | create_struct_name::{create_provider_name_by_fn_and_struct_ident, create_struct_name}, 9 | extracts::extract_params_from_impl_fn, 10 | modify_impl_function_body::modify_method_body, 11 | modify_return_body::modify_return_method_body, 12 | types::create_type_reference, 13 | }, 14 | }; 15 | 16 | pub fn generate_provider_and_metadata( 17 | implementation_fn: &ImplItemFn, 18 | original_struct_ident: &Ident, 19 | dependency_info: &mut DependencyInfo, 20 | trait_reference_name: &Ident, 21 | ) -> Result<(TokenStream, MetadataInfo), Error> { 22 | let impl_fn_params: Vec<(Ident, syn::Type)> = extract_params_from_impl_fn(implementation_fn); 23 | 24 | let original_struct_name = original_struct_ident.to_string(); 25 | 26 | let function_name = &implementation_fn.sig.ident; 27 | let provider_name = create_struct_name(&original_struct_name, function_name)?; 28 | let provider_token = provider_name.to_string(); 29 | 30 | let mut modified_block = implementation_fn.block.clone(); 31 | modify_return_method_body(&mut modified_block); 32 | let injections = modify_method_body( 33 | &mut modified_block, 34 | dependency_info.fields.clone(), 35 | original_struct_ident.clone(), 36 | ); 37 | 38 | let mut dependencies = Vec::with_capacity(injections.len()); 39 | let mut field_definitions = Vec::with_capacity(injections.len()); 40 | 41 | for injection in injections { 42 | let (provider_manager, function_name, field_name) = injection; 43 | 44 | let provider_name = 45 | create_provider_name_by_fn_and_struct_ident(&function_name, &provider_manager)?; 46 | 47 | if !provider_name.contains(&original_struct_name) && !dependency_info.unique_types.insert(provider_name.clone()) { 48 | return Err(Error::new( 49 | original_struct_ident.span(), 50 | format!("Conflict in dependency: {}", provider_name), 51 | )); 52 | } 53 | 54 | dependencies.push((field_name.clone(), provider_name)); 55 | 56 | let provider_trait = create_type_reference("ProviderTrait", true, true, true); 57 | field_definitions.push(quote! { #field_name: #provider_trait }); 58 | } 59 | 60 | let generated_code = generate_provider_code( 61 | &provider_name, 62 | &field_definitions, 63 | &modified_block, 64 | &provider_token, 65 | trait_reference_name, 66 | impl_fn_params, 67 | original_struct_name, 68 | ); 69 | Ok((generated_code, MetadataInfo { 70 | struct_name: provider_name, 71 | dependencies, 72 | })) 73 | } 74 | 75 | fn generate_provider_code( 76 | provider_name: &Ident, 77 | field_defs: &[TokenStream], 78 | method_body: &syn::Block, 79 | provider_token: &str, 80 | trait_name: &Ident, 81 | impl_fn_params: Vec<(Ident, syn::Type)>, 82 | original_struct_name: String, 83 | ) -> TokenStream { 84 | let params_token_stream = impl_fn_params 85 | .iter() 86 | .map(|(name, ty)| { 87 | quote! { 88 | let #name = *iter.next().unwrap().downcast::<#ty>().unwrap(); 89 | } 90 | }) 91 | .collect::>(); 92 | 93 | quote! { 94 | struct #provider_name { 95 | #(#field_defs),* 96 | } 97 | 98 | #[::async_trait::async_trait] 99 | impl ::toni::traits_helpers::#trait_name for #provider_name { 100 | #[inline] 101 | async fn execute(&self, params: Vec>) -> Box { 102 | let mut iter = params.into_iter(); 103 | #(#params_token_stream)* 104 | #method_body 105 | } 106 | 107 | #[inline] 108 | fn get_token(&self) -> String { 109 | #provider_token.to_string() 110 | } 111 | 112 | #[inline] 113 | fn get_token_manager(&self) -> String { 114 | #original_struct_name.to_string() 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /toni-cli/src/commands/new.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Context, Result}; 2 | use std::path::PathBuf; 3 | use tokio::fs::{self, File}; 4 | use tokio::io::AsyncWriteExt; 5 | use colored::*; 6 | 7 | #[derive(clap::Args)] 8 | pub struct NewArgs { 9 | project_name: String, 10 | } 11 | 12 | pub async fn execute(args: NewArgs) -> anyhow::Result<()> { 13 | validate_project_name(&args.project_name)?; 14 | 15 | let root_path = PathBuf::from(&args.project_name); 16 | create_project_structure(&root_path).await?; 17 | copy_template_files(&root_path).await?; 18 | post_process_files(&root_path, &args.project_name).await?; 19 | 20 | println!( 21 | "\n{}", 22 | format!( 23 | "✅ Successfully created project '{}'!\n\nNext steps:\n cd {}\n cargo build", 24 | args.project_name, args.project_name 25 | ) 26 | .green() 27 | ); 28 | 29 | Ok(()) 30 | } 31 | 32 | fn validate_project_name(name: &str) -> Result<()> { 33 | if name.is_empty() { 34 | return Err(anyhow!("Project name cannot be empty")); 35 | } 36 | 37 | if name.chars().any(|c| !c.is_ascii_alphanumeric() && c != '-' && c != '_') { 38 | return Err(anyhow!("Project name contains invalid characters. Use only alphanumerics, '-' or '_'")); 39 | } 40 | 41 | if PathBuf::from(name).exists() { 42 | return Err(anyhow!("Directory '{}' already exists", name)); 43 | } 44 | 45 | Ok(()) 46 | } 47 | 48 | async fn create_project_structure(root: &PathBuf) -> Result<()> { 49 | let dirs = [ 50 | "src", 51 | "src/app" 52 | ]; 53 | 54 | for dir in dirs { 55 | let path = root.join(dir); 56 | fs::create_dir_all(&path) 57 | .await 58 | .with_context(|| format!("Failed to create directory {}", path.display()))?; 59 | } 60 | 61 | Ok(()) 62 | } 63 | 64 | async fn copy_template_files(root: &PathBuf) -> Result<()> { 65 | let cargo_toml = root.join("Cargo.toml"); 66 | let mut file = File::create(&cargo_toml) 67 | .await 68 | .context("Failed to create Cargo.toml")?; 69 | 70 | file.write_all(include_str!("../templates/new/Cargo.txt").as_bytes()) 71 | .await 72 | .context("Failed to write Cargo.toml")?; 73 | 74 | write_template_file( 75 | root, 76 | "src/main.rs", 77 | include_str!("../templates/new/src/main.rs"), 78 | ).await?; 79 | 80 | write_template_file( 81 | root, 82 | "src/app/app.module.rs", 83 | include_str!("../templates/new/src/app/app.module.rs"), 84 | ).await?; 85 | 86 | write_template_file( 87 | root, 88 | "src/app/app.controller.rs", 89 | include_str!("../templates/new/src/app/app.controller.rs"), 90 | ).await?; 91 | 92 | write_template_file( 93 | root, 94 | "src/app/app.service.rs", 95 | include_str!("../templates/new/src/app/app.service.rs"), 96 | ).await?; 97 | 98 | write_template_file( 99 | root, 100 | "src/app/mod.rs", 101 | include_str!("../templates/new/src/app/mod.rs"), 102 | ).await?; 103 | 104 | Ok(()) 105 | } 106 | 107 | async fn write_template_file(root: &PathBuf, path: &str, content: &str) -> Result<()> { 108 | let full_path = root.join(path); 109 | let mut file = File::create(&full_path) 110 | .await 111 | .with_context(|| format!("Failed to create {}", full_path.display()))?; 112 | 113 | file.write_all(content.as_bytes()) 114 | .await 115 | .with_context(|| format!("Failed to write to {}", full_path.display()))?; 116 | 117 | Ok(()) 118 | } 119 | 120 | async fn post_process_files(root: &PathBuf, project_name: &str) -> Result<()> { 121 | let files_to_process = [ 122 | "Cargo.toml", 123 | "src/main.rs", 124 | "src/app/app.module.rs", 125 | ]; 126 | 127 | for file in files_to_process { 128 | let path = root.join(file); 129 | let content = fs::read_to_string(&path) 130 | .await 131 | .with_context(|| format!("Failed to read {}", path.display()))?; 132 | 133 | let processed = content 134 | .replace("{{project_name}}", project_name) 135 | .replace("{{version}}", env!("CARGO_PKG_VERSION")); 136 | 137 | fs::write(&path, processed) 138 | .await 139 | .with_context(|| format!("Failed to write processed {}", path.display()))?; 140 | } 141 | 142 | Ok(()) 143 | } -------------------------------------------------------------------------------- /toni/src/injector/module.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::hash_map::Drain, sync::Arc}; 2 | 3 | use rustc_hash::{FxHashMap, FxHashSet}; 4 | 5 | use super::InstanceWrapper; 6 | 7 | use crate::{ 8 | structs_helpers::EnhancerMetadata, 9 | traits_helpers::{Controller, ControllerTrait, ModuleMetadata, Provider, ProviderTrait}, 10 | }; 11 | pub struct Module { 12 | _token: String, 13 | _name: String, 14 | controllers: FxHashMap>, 15 | providers: FxHashMap>, 16 | imports: FxHashSet, 17 | exports: FxHashSet, 18 | controllers_instances: FxHashMap>, 19 | providers_instances: FxHashMap>>, 20 | exports_instances: FxHashSet, 21 | metadata: Box, 22 | } 23 | 24 | impl Module { 25 | pub fn new(token: &str, name: &str, metadata: Box) -> Self { 26 | Self { 27 | _token: token.to_owned(), 28 | _name: name.to_string(), 29 | controllers: FxHashMap::default(), 30 | providers: FxHashMap::default(), 31 | imports: FxHashSet::default(), 32 | exports: FxHashSet::default(), 33 | controllers_instances: FxHashMap::default(), 34 | providers_instances: FxHashMap::default(), 35 | exports_instances: FxHashSet::default(), 36 | metadata, 37 | } 38 | } 39 | } 40 | impl Module { 41 | pub fn add_controller(&mut self, controller: Box) { 42 | self.controllers.insert(controller.get_name(), controller); 43 | } 44 | 45 | pub fn add_provider(&mut self, provider: Box) { 46 | self.providers.insert(provider.get_name(), provider); 47 | } 48 | 49 | pub fn add_import(&mut self, module_token: String) { 50 | self.imports.insert(module_token); 51 | } 52 | 53 | pub fn add_export(&mut self, provider_token: String) { 54 | self.exports.insert(provider_token); 55 | } 56 | 57 | pub fn add_controller_instance( 58 | &mut self, 59 | controller: Arc>, 60 | enhancer_metadata: EnhancerMetadata, 61 | ) { 62 | let token = controller.get_token(); 63 | let instance_wrapper = InstanceWrapper::new(controller, enhancer_metadata); 64 | self.controllers_instances 65 | .insert(token, Arc::new(instance_wrapper)); 66 | } 67 | 68 | pub fn add_provider_instance(&mut self, provider: Arc>) { 69 | self.providers_instances 70 | .insert(provider.get_token(), provider); 71 | } 72 | pub fn add_export_instance(&mut self, provider_token: String) { 73 | self.exports_instances.insert(provider_token); 74 | } 75 | 76 | pub fn get_providers_manager(&self) -> &FxHashMap> { 77 | &self.providers 78 | } 79 | 80 | pub fn get_providers_instances(&self) -> &FxHashMap>> { 81 | &self.providers_instances 82 | } 83 | 84 | pub fn get_provider_by_token(&self, provider_token: &String) -> Option<&dyn Provider> { 85 | self.providers 86 | .get(provider_token) 87 | .map(|provider| provider.as_ref()) 88 | } 89 | 90 | pub fn get_provider_instance_by_token( 91 | &self, 92 | provider_token: &String, 93 | ) -> Option<&Arc>> { 94 | self.providers_instances.get(provider_token) 95 | } 96 | 97 | pub fn get_controllers_manager(&self) -> &FxHashMap> { 98 | &self.controllers 99 | } 100 | 101 | pub fn drain_controllers_instances( 102 | &mut self, 103 | ) -> Drain<'_, String, Arc> { 104 | self.controllers_instances.drain() 105 | } 106 | 107 | pub fn get_imported_modules(&self) -> &FxHashSet { 108 | &self.imports 109 | } 110 | 111 | pub fn get_exports_instances_tokens(&self) -> &FxHashSet { 112 | &self.exports_instances 113 | } 114 | 115 | pub fn get_exports_tokens(&self) -> &FxHashSet { 116 | &self.exports 117 | } 118 | 119 | pub fn get_metadata(&self) -> &dyn ModuleMetadata { 120 | &*self.metadata 121 | } 122 | 123 | pub fn _get_name(&self) -> &String { 124 | &self._name 125 | } 126 | 127 | pub fn _get_token(&self) -> &String { 128 | &self._token 129 | } 130 | 131 | pub fn _get_controller_by_token(&self, controller_token: &String) -> Option<&dyn Controller> { 132 | self.controllers 133 | .get(controller_token) 134 | .map(|controller| controller.as_ref()) 135 | } 136 | 137 | pub fn _get_controllers_instances(&self) -> &FxHashMap> { 138 | &self.controllers_instances 139 | } 140 | 141 | pub fn _take_controllers_instances( 142 | &mut self, 143 | ) -> FxHashMap> { 144 | std::mem::take(&mut self.controllers_instances) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /toni/src/scanner.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use anyhow::{Result, anyhow}; 4 | 5 | use crate::{ 6 | injector::ToniContainer, module_helpers::module_enum::ModuleDefinition, 7 | traits_helpers::ModuleMetadata, 8 | }; 9 | 10 | pub struct ToniDependenciesScanner { 11 | container: Rc>, 12 | } 13 | 14 | impl ToniDependenciesScanner { 15 | pub fn new(container: Rc>) -> Self { 16 | Self { container } 17 | } 18 | pub fn scan(&mut self, module: ModuleDefinition) -> Result<()> { 19 | self.scan_for_modules_with_imports(module)?; 20 | self.scan_modules_for_dependencies()?; 21 | Ok(()) 22 | } 23 | fn scan_for_modules_with_imports(&mut self, module: ModuleDefinition) -> Result<()> { 24 | let mut ctx_registry: Vec = vec![]; 25 | 26 | let mut stack: Vec = vec![module]; 27 | 28 | while let Some(current_module_definition) = stack.pop() { 29 | let ModuleDefinition::DefaultModule(default_module) = current_module_definition; 30 | 31 | ctx_registry.push(default_module.get_name()); 32 | 33 | let modules_imported = default_module.imports().unwrap_or_default(); 34 | 35 | let mut modules_imported_tokens = vec![]; 36 | 37 | for module_imported in modules_imported { 38 | modules_imported_tokens.push(module_imported.get_id()); 39 | 40 | if ctx_registry 41 | .iter() 42 | .any(|module_imported_id| module_imported_id == &module_imported.get_name()) 43 | { 44 | continue; 45 | } 46 | 47 | stack.push(ModuleDefinition::DefaultModule(module_imported)); 48 | } 49 | let default_module_id = default_module.get_id(); 50 | self.insert_module(default_module); 51 | self.insert_imports(default_module_id, modules_imported_tokens)?; 52 | } 53 | Ok(()) 54 | } 55 | 56 | pub fn scan_modules_for_dependencies(&mut self) -> Result<()> { 57 | let modules_token = self.container.borrow().get_modules_token(); 58 | for module_token in modules_token { 59 | self.insert_providers(module_token.clone())?; 60 | self.insert_controllers(module_token.clone())?; 61 | self.insert_exports(module_token.clone())?; 62 | } 63 | Ok(()) 64 | } 65 | 66 | fn insert_module(&mut self, module: Box) { 67 | let mut container = self.container.borrow_mut(); 68 | container.add_module(module); 69 | } 70 | 71 | pub fn insert_imports(&mut self, module_token: String, imports: Vec) -> Result<()> { 72 | let mut container = self.container.borrow_mut(); 73 | 74 | for import in imports { 75 | container.add_import(&module_token, import)?; 76 | } 77 | 78 | Ok(()) 79 | } 80 | 81 | pub fn insert_controllers(&mut self, module_token: String) -> Result<()> { 82 | let mut container = self.container.borrow_mut(); 83 | let module_ref = container.get_module_by_token(&module_token); 84 | let resolved_module_ref = match module_ref { 85 | Some(module_ref) => module_ref, 86 | None => return Err(anyhow!("Module not found")), 87 | }; 88 | 89 | let controllers = resolved_module_ref.get_metadata().controllers(); 90 | 91 | if let Some(controllers) = controllers { 92 | for controller in controllers { 93 | container.add_controller(&module_token, controller)?; 94 | } 95 | }; 96 | 97 | Ok(()) 98 | } 99 | 100 | pub fn insert_providers(&mut self, module_token: String) -> Result<()> { 101 | let mut container = self.container.borrow_mut(); 102 | let module_ref = container.get_module_by_token(&module_token); 103 | let resolved_module_ref = match module_ref { 104 | Some(module_ref) => module_ref, 105 | None => return Err(anyhow!("Module not found")), 106 | }; 107 | 108 | let providers = resolved_module_ref.get_metadata().providers(); 109 | 110 | if let Some(providers) = providers { 111 | for provider in providers { 112 | container.add_provider(&module_token, provider)?; 113 | } 114 | }; 115 | 116 | Ok(()) 117 | } 118 | 119 | pub fn insert_exports(&mut self, module_token: String) -> Result<()> { 120 | let mut container = self.container.borrow_mut(); 121 | let module_ref = container.get_module_by_token(&module_token); 122 | let resolved_module_ref = match module_ref { 123 | Some(module_ref) => module_ref, 124 | None => return Err(anyhow!("Module not found")), 125 | }; 126 | 127 | let exports = resolved_module_ref.get_metadata().exports(); 128 | if let Some(exports) = exports { 129 | for export in exports { 130 | container.add_export(&module_token, export)?; 131 | } 132 | }; 133 | 134 | Ok(()) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /toni-macros/src/module_macro/module_struct.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use proc_macro2::Span; 3 | use quote::quote; 4 | use syn::{ 5 | Ident, ItemImpl, Token, Type, TypePath, bracketed, 6 | parse::{Parse, ParseStream}, 7 | parse_macro_input, 8 | punctuated::Punctuated, 9 | }; 10 | 11 | #[derive(Debug, Default)] 12 | struct ModuleConfig { 13 | imports: Vec, 14 | controllers: Vec, 15 | providers: Vec, 16 | exports: Vec, 17 | } 18 | 19 | struct ConfigParser { 20 | imports: Vec, 21 | controllers: Vec, 22 | providers: Vec, 23 | exports: Vec, 24 | } 25 | 26 | impl Parse for ConfigParser { 27 | fn parse(input: ParseStream) -> syn::Result { 28 | let mut config = ConfigParser { 29 | imports: Vec::new(), 30 | controllers: Vec::new(), 31 | providers: Vec::new(), 32 | exports: Vec::new(), 33 | }; 34 | 35 | while !input.is_empty() { 36 | let key: Ident = input.parse()?; 37 | input.parse::()?; 38 | let content; 39 | bracketed!(content in input); 40 | 41 | let fields = Punctuated::::parse_terminated(&content)?; 42 | 43 | match key.to_string().as_str() { 44 | "imports" => config.imports = fields.into_iter().collect(), 45 | "controllers" => { 46 | config.controllers = fields 47 | .into_iter() 48 | .map(|field| { 49 | Ident::new(&format!("{}Manager", field), field.span()) 50 | }) 51 | .collect() 52 | } 53 | "providers" => { 54 | config.providers = fields 55 | .into_iter() 56 | .map(|field| { 57 | Ident::new(&format!("{}Manager", field), field.span()) 58 | }) 59 | .collect() 60 | } 61 | "exports" => config.exports = fields.into_iter().collect(), 62 | _ => return Err(syn::Error::new(key.span(), "Unknown field")), 63 | } 64 | 65 | if !input.is_empty() { 66 | input.parse::()?; 67 | } 68 | } 69 | 70 | Ok(config) 71 | } 72 | } 73 | 74 | impl TryFrom for ModuleConfig { 75 | type Error = syn::Error; 76 | fn try_from(attr: TokenStream) -> syn::Result { 77 | let parser = syn::parse::(attr)?; 78 | Ok(ModuleConfig { 79 | imports: parser.imports, 80 | controllers: parser.controllers, 81 | providers: parser.providers, 82 | exports: parser.exports, 83 | }) 84 | } 85 | } 86 | 87 | pub fn module(attr: TokenStream, item: TokenStream) -> TokenStream { 88 | let config = match ModuleConfig::try_from(attr) { 89 | Ok(c) => c, 90 | Err(e) => return e.to_compile_error().into(), 91 | }; 92 | 93 | let input = parse_macro_input!(item as ItemImpl); 94 | let input_type = input.self_ty.as_ref(); 95 | let input_ident = match input_type { 96 | Type::Path(TypePath { path, .. }) => path.segments.last().unwrap().ident.clone(), 97 | _ => { 98 | return syn::Error::new(Span::call_site(), "Invalid input type") 99 | .to_compile_error() 100 | .into(); 101 | } 102 | }; 103 | let input_name = input_ident.to_string(); 104 | let imports: &Vec = &config.imports; 105 | let controllers = config.controllers; 106 | let providers = &config.providers; 107 | let exports = &config.exports; 108 | let exports_string: Vec = exports.iter().map(|e| e.to_string()).collect(); 109 | 110 | let generated = quote! { 111 | pub struct #input_ident; 112 | 113 | impl #input_ident { 114 | pub fn module_definition() -> ::toni::module_helpers::module_enum::ModuleDefinition { 115 | let app_module = Self; 116 | ::toni::module_helpers::module_enum::ModuleDefinition::DefaultModule(Box::new(app_module)) 117 | } 118 | pub fn new() -> Self { 119 | Self 120 | } 121 | } 122 | 123 | impl ::toni::traits_helpers::ModuleMetadata for #input_ident { 124 | fn get_id(&self) -> String { 125 | #input_name.to_string() 126 | } 127 | fn get_name(&self) -> String { 128 | #input_name.to_string() 129 | } 130 | fn imports(&self) -> Option>> { 131 | Some(vec![#(Box::new(#imports::new())),*]) 132 | } 133 | fn controllers(&self) -> Option>> { 134 | Some(vec![#(Box::new(#controllers)),*]) 135 | } 136 | fn providers(&self) -> Option>> { 137 | Some(vec![#(Box::new(#providers)),*]) 138 | } 139 | fn exports(&self) -> Option> { 140 | Some(vec![#(#exports_string.to_string()),*]) 141 | } 142 | } 143 | }; 144 | 145 | generated.into() 146 | } 147 | -------------------------------------------------------------------------------- /toni-macros/src/controller_macro/controller.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::quote; 5 | use syn::{spanned::Spanned, Attribute, Error, Ident, ImplItemFn, LitStr, Result}; 6 | 7 | use crate::{ 8 | enhancer::enhancer::create_enchancers_token_stream, 9 | markers_params::{ 10 | extracts_marker_params::{extract_body_from_param, extract_path_param_from_param, extract_query_from_param}, get_marker_params::MarkerParam, 11 | }, 12 | shared::{dependency_info::DependencyInfo, metadata_info::MetadataInfo}, 13 | utils::{ 14 | controller_utils::{attr_to_string, create_extract_body_dto_token_stream}, 15 | create_struct_name::{create_provider_name_by_fn_and_struct_ident, create_struct_name}, 16 | modify_impl_function_body::modify_method_body, 17 | modify_return_body::modify_return_method_body, 18 | types::create_type_reference, 19 | }, 20 | }; 21 | 22 | pub fn generate_controller_and_metadata( 23 | implementation_fn: &ImplItemFn, 24 | original_struct_name: &Ident, 25 | dependency_info: &mut DependencyInfo, 26 | trait_reference_name: &Ident, 27 | route_prefix: &String, 28 | http_method_attr: &Attribute, 29 | enhancers_attr: HashMap<&Ident, &Attribute>, 30 | marker_params: Vec, 31 | ) -> Result<(TokenStream, MetadataInfo)> { 32 | let http_method = attr_to_string(http_method_attr) 33 | .map_err(|_| Error::new(http_method_attr.span(), "Invalid attribute format"))?; 34 | 35 | let route_path = http_method_attr 36 | .parse_args::() 37 | .map_err(|_| Error::new(http_method_attr.span(), "Invalid attribute format"))? 38 | .value(); 39 | 40 | let enhancers = create_enchancers_token_stream(enhancers_attr)?; 41 | 42 | let function_name = &implementation_fn.sig.ident; 43 | let controller_name = create_struct_name("Controller", function_name)?; 44 | let controller_token = controller_name.to_string(); 45 | 46 | let mut modified_block = implementation_fn.block.clone(); 47 | modify_return_method_body(&mut modified_block); 48 | let injections = modify_method_body( 49 | &mut modified_block, 50 | dependency_info.fields.clone(), 51 | original_struct_name.clone(), 52 | ); 53 | 54 | let mut dependencies = Vec::with_capacity(injections.len()); 55 | let mut field_definitions = Vec::with_capacity(injections.len()); 56 | for injection in injections { 57 | let (provider_manager, function_name, field_name) = injection; 58 | 59 | let provider_name = 60 | create_provider_name_by_fn_and_struct_ident(&function_name, &provider_manager)?; 61 | 62 | if !dependency_info.unique_types.contains(&provider_name) { 63 | dependency_info.unique_types.insert(provider_name.clone()); 64 | } 65 | if !dependencies.contains(&(field_name.clone(), provider_name.clone())) { 66 | dependencies.push((field_name.clone(), provider_name)); 67 | let provider_trait = create_type_reference("ProviderTrait", true, true, true); 68 | field_definitions.push(quote! { #field_name: #provider_trait }); 69 | } 70 | } 71 | 72 | let full_route_path = format!("{}{}", route_prefix, route_path); 73 | let mut marker_params_token_stream = Vec::new(); 74 | let mut body_dto_token_stream = None; 75 | if !marker_params.is_empty() { 76 | marker_params_token_stream = marker_params 77 | .iter() 78 | .map(|marker_param| { 79 | if marker_param.marker_name == "body" { 80 | let dto_type_ident = marker_param.type_ident.clone(); 81 | body_dto_token_stream = Some(create_extract_body_dto_token_stream(&dto_type_ident)?); 82 | return extract_body_from_param(marker_param); 83 | } 84 | if marker_param.marker_name == "query" { 85 | return extract_query_from_param(marker_param); 86 | } 87 | if marker_param.marker_name == "param" { 88 | return extract_path_param_from_param(marker_param); 89 | } 90 | Ok(quote! { }) 91 | }) 92 | .collect::>>()?; 93 | } 94 | 95 | let generated_code = generate_controller_code( 96 | &controller_name, 97 | &field_definitions, 98 | &modified_block, 99 | &controller_token, 100 | full_route_path, 101 | &http_method, 102 | trait_reference_name, 103 | enhancers, 104 | marker_params_token_stream, 105 | body_dto_token_stream 106 | ); 107 | Ok((generated_code, MetadataInfo { 108 | struct_name: controller_name, 109 | dependencies, 110 | })) 111 | } 112 | 113 | fn generate_controller_code( 114 | controller_name: &Ident, 115 | field_defs: &[TokenStream], 116 | method_body: &syn::Block, 117 | controller_token: &str, 118 | full_route_path: String, 119 | http_method: &str, 120 | trait_name: &Ident, 121 | enhancers: HashMap>, 122 | marker_params_token_stream: Vec, 123 | body_dto_token_stream: Option 124 | ) -> TokenStream { 125 | let binding = Vec::new(); 126 | let use_guards = enhancers.get("use_guard").unwrap_or(&binding); 127 | let interceptors = enhancers.get("interceptor").unwrap_or(&binding); 128 | let pipes = enhancers.get("pipe").unwrap_or(&binding); 129 | let body_dto_stream = if let Some(token_stream) = body_dto_token_stream { 130 | token_stream 131 | } else { 132 | quote! { None } 133 | }; 134 | quote! { 135 | struct #controller_name { 136 | #(#field_defs),* 137 | } 138 | #[::async_trait::async_trait] 139 | impl ::toni::traits_helpers::#trait_name for #controller_name { 140 | #[inline] 141 | async fn execute( 142 | &self, 143 | req: ::toni::http_helpers::HttpRequest 144 | ) -> Box + Send> { 145 | #(#marker_params_token_stream)* 146 | #method_body 147 | } 148 | 149 | #[inline] 150 | fn get_token(&self) -> String { 151 | #controller_token.to_string() 152 | } 153 | 154 | #[inline] 155 | fn get_path(&self) -> String { 156 | #full_route_path.to_string() 157 | } 158 | 159 | #[inline] 160 | fn get_method(&self) -> ::toni::http_helpers::HttpMethod { 161 | ::toni::http_helpers::HttpMethod::from_string(#http_method).unwrap() 162 | } 163 | 164 | #[inline] 165 | fn get_guards(&self) -> Vec<::std::sync::Arc> { 166 | vec![#(#use_guards),*] 167 | } 168 | 169 | #[inline] 170 | fn get_pipes(&self) -> Vec<::std::sync::Arc> { 171 | vec![#(#pipes),*] 172 | } 173 | 174 | #[inline] 175 | fn get_interceptors(&self) -> Vec<::std::sync::Arc> { 176 | vec![#(#interceptors),*] 177 | } 178 | 179 | #[inline] 180 | fn get_body_dto(&self, req: &::toni::http_helpers::HttpRequest) -> Option> { 181 | #body_dto_stream 182 | } 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /toni/src/injector/instance_loader.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Result, anyhow}; 2 | use rustc_hash::FxHashMap; 3 | use std::{ 4 | cell::{RefCell, RefMut}, 5 | rc::Rc, 6 | sync::Arc, 7 | }; 8 | 9 | use super::{DependencyGraph, ToniContainer}; 10 | use crate::{ 11 | structs_helpers::EnhancerMetadata, 12 | traits_helpers::{ControllerTrait, ProviderTrait}, 13 | }; 14 | 15 | pub struct ToniInstanceLoader { 16 | container: Rc>, 17 | } 18 | 19 | impl ToniInstanceLoader { 20 | pub fn new(container: Rc>) -> Self { 21 | Self { container } 22 | } 23 | 24 | pub fn create_instances_of_dependencies(&self) -> Result<()> { 25 | let modules_token = self.container.borrow().get_ordered_modules_token(); 26 | 27 | for module_token in modules_token { 28 | self.create_module_instances(module_token)?; 29 | } 30 | Ok(()) 31 | } 32 | 33 | fn create_module_instances(&self, module_token: String) -> Result<()> { 34 | self.create_instances_of_providers(module_token.clone())?; 35 | self.create_instances_of_controllers(module_token.clone())?; 36 | Ok(()) 37 | } 38 | 39 | fn create_instances_of_providers(&self, module_token: String) -> Result<()> { 40 | let dependency_graph = DependencyGraph::new(self.container.clone(), module_token.clone()); 41 | let ordered_providers_token = dependency_graph.get_ordered_providers_token()?; 42 | let provider_instances = { 43 | let container = self.container.borrow(); 44 | let mut instances = FxHashMap::default(); 45 | 46 | for provider_token in ordered_providers_token { 47 | let provider_manager = container 48 | .get_provider_by_token(&module_token, &provider_token)? 49 | .ok_or_else(|| anyhow!("Provider not found: {}", provider_token))?; 50 | 51 | let dependencies = provider_manager.get_dependencies(); 52 | let resolved_dependencies = 53 | self.resolve_dependencies(&module_token, dependencies, Some(&instances))?; 54 | 55 | let provider_instances = provider_manager.get_all_providers(&resolved_dependencies); 56 | instances.extend(provider_instances); 57 | } 58 | instances 59 | }; 60 | self.add_providers_instances(&module_token, provider_instances)?; 61 | Ok(()) 62 | } 63 | 64 | fn add_providers_instances( 65 | &self, 66 | module_token: &String, 67 | providers_instances: FxHashMap>>, 68 | ) -> Result<()> { 69 | let mut container = self.container.borrow_mut(); 70 | let mut providers_tokens = Vec::new(); 71 | for (provider_instance_token, provider_instance) in providers_instances { 72 | let token_manager = provider_instance.get_token_manager().clone(); 73 | container.add_provider_instance(module_token, provider_instance)?; 74 | providers_tokens.push((token_manager, provider_instance_token)); 75 | } 76 | 77 | self.resolve_exports(module_token, providers_tokens, container)?; 78 | Ok(()) 79 | } 80 | 81 | fn resolve_exports( 82 | &self, 83 | module_token: &String, 84 | providers_tokens: Vec<(String, String)>, 85 | container: RefMut<'_, ToniContainer>, 86 | ) -> Result<()> { 87 | let exports = container.get_exports_tokens_vec(module_token)?; 88 | self.add_export_instances_tokens(module_token, providers_tokens, exports, container)?; 89 | Ok(()) 90 | } 91 | 92 | fn add_export_instances_tokens( 93 | &self, 94 | module_token: &String, 95 | providers_tokens: Vec<(String, String)>, 96 | exports: Vec, 97 | mut container: RefMut<'_, ToniContainer>, 98 | ) -> Result<()> { 99 | for (provider_manager_token, provider_instance_token) in providers_tokens { 100 | if exports.contains(&provider_manager_token) { 101 | container.add_export_instance(module_token, provider_instance_token)?; 102 | } 103 | } 104 | Ok(()) 105 | } 106 | 107 | fn create_instances_of_controllers(&self, module_token: String) -> Result<()> { 108 | let controllers_instances = { 109 | let container = self.container.borrow(); 110 | let mut instances = FxHashMap::default(); 111 | let controllers_manager = container.get_controllers_manager(&module_token)?; 112 | 113 | for controller_manager in controllers_manager.values() { 114 | let dependencies = controller_manager.get_dependencies(); 115 | let resolved_dependencies = 116 | self.resolve_dependencies(&module_token, dependencies, None)?; 117 | let controllers_instances = 118 | controller_manager.get_all_controllers(&resolved_dependencies); 119 | instances.extend(controllers_instances); 120 | } 121 | instances 122 | }; 123 | self.add_controllers_instances(module_token, controllers_instances)?; 124 | Ok(()) 125 | } 126 | 127 | fn add_controllers_instances( 128 | &self, 129 | module_token: String, 130 | controllers_instances: FxHashMap>>, 131 | ) -> Result<()> { 132 | let mut container_mut = self.container.borrow_mut(); 133 | for (_controller_instance_token, controller_instance) in controllers_instances { 134 | let enhancer_metadata = EnhancerMetadata { 135 | guards: controller_instance.get_guards(), 136 | pipes: controller_instance.get_pipes(), 137 | interceptors: controller_instance.get_interceptors(), 138 | }; 139 | container_mut.add_controller_instance( 140 | &module_token, 141 | controller_instance, 142 | enhancer_metadata, 143 | )?; 144 | } 145 | Ok(()) 146 | } 147 | 148 | fn resolve_dependencies( 149 | &self, 150 | module_token: &String, 151 | dependencies: Vec, 152 | providers_instances: Option<&FxHashMap>>>, 153 | ) -> Result>>> { 154 | let container = self.container.borrow(); 155 | let mut resolved_dependencies = FxHashMap::default(); 156 | 157 | for dependency in dependencies { 158 | let instances = match providers_instances { 159 | Some(providers_instances) => providers_instances, 160 | None => container.get_providers_instance(module_token)?, 161 | }; 162 | if let Some(instance) = instances.get(&dependency) { 163 | resolved_dependencies.insert(dependency, instance.clone()); 164 | } else if let Some(exported_instance) = 165 | self.resolve_from_imported_modules(module_token, &dependency)? 166 | { 167 | resolved_dependencies.insert(dependency, exported_instance.clone()); 168 | } else { 169 | return Err(anyhow!( 170 | "Dependency not found: {} in module {}", 171 | dependency, 172 | module_token 173 | )); 174 | } 175 | } 176 | 177 | Ok(resolved_dependencies) 178 | } 179 | 180 | fn resolve_from_imported_modules( 181 | &self, 182 | module_token: &String, 183 | dependency: &String, 184 | ) -> Result>>> { 185 | let container = self.container.borrow(); 186 | let imported_modules = container.get_imported_modules(module_token)?; 187 | for imported_module in imported_modules { 188 | let exported_instances_tokens = 189 | container.get_exports_instances_tokens(imported_module)?; 190 | if exported_instances_tokens.contains(dependency) { 191 | if let Ok(Some(exported_instance)) = 192 | container.get_provider_instance_by_token(imported_module, dependency) 193 | { 194 | return Ok(Some(exported_instance.clone())); 195 | } 196 | } 197 | } 198 | 199 | Ok(None) 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /toni-cli/src/commands/generate.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result, anyhow}; 2 | use colored::Colorize; 3 | use regex::Regex; 4 | use std::path::PathBuf; 5 | use tokio::fs::{self}; 6 | 7 | #[derive(clap::Args)] 8 | pub struct GenerateArgs { 9 | resource_type: String, 10 | name: String, 11 | } 12 | 13 | pub async fn execute(args: GenerateArgs) -> anyhow::Result<()> { 14 | match args.resource_type.to_lowercase().as_str() { 15 | "resource" => generate_resource(&args.name).await, 16 | _ => Err(anyhow!("Invalid resource type")), 17 | } 18 | } 19 | 20 | async fn generate_resource(name: &str) -> anyhow::Result<()> { 21 | let base_path = PathBuf::from("src").join("app").join(name); 22 | 23 | create_resource_structure(&base_path).await?; 24 | 25 | generate_resource_files(&base_path, name).await?; 26 | 27 | update_app_module(name).await?; 28 | 29 | update_mod_file(name).await?; 30 | 31 | println!( 32 | "{}", 33 | format!("✅ Successfully generated resource '{}'!", name).green() 34 | ); 35 | 36 | Ok(()) 37 | } 38 | 39 | async fn create_resource_structure(base_path: &PathBuf) -> Result<()> { 40 | fs::create_dir_all(base_path) 41 | .await 42 | .context("Failed to create resource directory")?; 43 | Ok(()) 44 | } 45 | 46 | async fn generate_resource_files(base_path: &PathBuf, name: &str) -> Result<()> { 47 | let snake_case = to_snake_case(name); 48 | let upper_case_first_letter = to_upper_case_first_letter(name); 49 | 50 | let service_name = format!("{}Service", &upper_case_first_letter); 51 | let controller_name = format!("{}Controller", &upper_case_first_letter); 52 | let module_name = format!("{}Module", &upper_case_first_letter); 53 | let service_name_snake_case = format!("{}_service", &snake_case); 54 | let controller_name_snake_case = format!("{}_controller", &snake_case); 55 | let module_name_snake_case = format!("{}_module", &snake_case); 56 | 57 | let path_controller = format!("{}.controller.rs", name); 58 | let path_service = format!("{}.service.rs", name); 59 | let path_module = format!("{}.module.rs", name); 60 | 61 | let replacements: &[(&str, &str); 10] = &[ 62 | ("resource_name", &snake_case), 63 | ("RESOURCE_NAME_SERVICE", &service_name), 64 | ("RESOURCE_NAME_CONTROLLER", &controller_name), 65 | ("RESOURCE_NAME_MODULE", &module_name), 66 | ("resource_name_service", &service_name_snake_case), 67 | ("resource_name_controller", &controller_name_snake_case), 68 | ("resource_name_module", &module_name_snake_case), 69 | ("path_module", &path_module), 70 | ("path_controller", &path_controller), 71 | ("path_service", &path_service), 72 | ]; 73 | 74 | write_template_file( 75 | base_path, 76 | &format!("{}.module.rs", name), 77 | include_str!("../templates/generate/resource.module.rs"), 78 | replacements, 79 | ) 80 | .await?; 81 | 82 | write_template_file( 83 | base_path, 84 | &format!("{}.controller.rs", name), 85 | include_str!("../templates/generate/resource.controller.rs"), 86 | replacements, 87 | ) 88 | .await?; 89 | 90 | write_template_file( 91 | base_path, 92 | &format!("{}.service.rs", name), 93 | include_str!("../templates/generate/resource.service.rs"), 94 | replacements, 95 | ) 96 | .await?; 97 | 98 | write_template_file( 99 | base_path, 100 | "mod.rs", 101 | include_str!("../templates/generate/mod.rs"), 102 | replacements, 103 | ) 104 | .await?; 105 | 106 | Ok(()) 107 | } 108 | 109 | async fn write_template_file( 110 | base_path: &PathBuf, 111 | filename: &str, 112 | template: &str, 113 | replacements: &[(&str, &str)], 114 | ) -> Result<()> { 115 | let path = base_path.join(filename); 116 | let mut content = template.to_string(); 117 | 118 | for (placeholder, value) in replacements { 119 | content = content.replace(placeholder, value); 120 | } 121 | 122 | if content.contains("{{") || content.contains("}}") { 123 | return Err(anyhow!( 124 | "Template placeholders not fully replaced in {}", 125 | filename 126 | )); 127 | } 128 | 129 | fs::write(&path, content) 130 | .await 131 | .context(format!("Failed to write {}", path.display()))?; 132 | 133 | Ok(()) 134 | } 135 | 136 | async fn update_app_module(resource_name: &str) -> Result<()> { 137 | let snake_case = to_snake_case(resource_name); 138 | let upper_case_first_letter = to_upper_case_first_letter(resource_name); 139 | let app_module_path = PathBuf::from("src").join("app").join("app.module.rs"); 140 | 141 | let mut content = fs::read_to_string(&app_module_path) 142 | .await 143 | .context("Failed to read app.module.rs")?; 144 | 145 | let module_import = format!("use super::{}::{}_module::*;", resource_name, snake_case); 146 | if !content.contains(&module_import) { 147 | content = content.replacen( 148 | "use toni_macros::module;", 149 | &format!("use toni_macros::module;\n{}", module_import), 150 | 1, 151 | ); 152 | } 153 | 154 | let module_insert = format!("{}Module", upper_case_first_letter); 155 | if !content.contains(&module_insert) { 156 | content = add_module_to_imports(&content, &module_insert); 157 | } 158 | 159 | fs::write(&app_module_path, content) 160 | .await 161 | .context("Failed to update app.module.rs")?; 162 | 163 | Ok(()) 164 | } 165 | 166 | async fn update_mod_file(resource_name: &str) -> Result<()> { 167 | let snake_case = to_snake_case(resource_name); 168 | let mod_path = PathBuf::from("src").join("app").join("mod.rs"); 169 | let mut content = fs::read_to_string(&mod_path) 170 | .await 171 | .context("Failed to read mod.rs")?; 172 | 173 | let mod_insert = format!(r#"#[path = "{}/mod.rs"]"#, snake_case); 174 | if !content.contains(&mod_insert) { 175 | let part1 = format!(r#"#[path = "{}/mod.rs"]"#, snake_case); 176 | let part2 = format!("pub mod {};", snake_case); 177 | let part3 = r#"#[path = "app.module.rs"]"#; 178 | let output = format!("{}\n{}\n\n{}", part1, part2, part3); 179 | content = content.replacen(r#"#[path = "app.module.rs"]"#, &output, 1); 180 | } 181 | 182 | fs::write(&mod_path, content) 183 | .await 184 | .context("Failed to update mod.rs")?; 185 | 186 | Ok(()) 187 | } 188 | 189 | fn add_module_to_imports(content: &str, module_name: &str) -> String { 190 | let regex_module = Regex::new(r"(?s)imports:\s*\[(.*?)\]").unwrap(); 191 | let imports_content = regex_module 192 | .captures(content) 193 | .and_then(|cap| cap.get(1)) 194 | .map(|m| m.as_str()) 195 | .unwrap(); 196 | 197 | let mut modules: Vec<&str> = imports_content 198 | .split(',') 199 | .map(|s| s.trim()) 200 | .filter(|s| !s.is_empty()) 201 | .collect(); 202 | 203 | modules.push(module_name); 204 | 205 | let new_imports = format!("imports: [\n {},\n ]", modules.join(",\n ")); 206 | let output = regex_module.replace(content, new_imports); 207 | output.to_string() 208 | } 209 | 210 | fn _to_pascal_case(s: &str) -> String { 211 | let mut result = String::new(); 212 | for part in s.split('_') { 213 | let mut chars = part.chars(); 214 | if let Some(first) = chars.next() { 215 | result.push(first.to_ascii_uppercase()); 216 | result.extend(chars.as_str().to_ascii_lowercase().chars()); 217 | } 218 | } 219 | result 220 | } 221 | 222 | fn to_upper_case_first_letter(s: &str) -> String { 223 | let mut chars = s.chars(); 224 | if let Some(first) = chars.next() { 225 | return format!("{}{}", first.to_ascii_uppercase(), chars.as_str()); 226 | } 227 | s.to_string() 228 | } 229 | 230 | fn to_snake_case(input: &str) -> String { 231 | let mut snake_case = String::new(); 232 | let mut chars = input.chars().peekable(); 233 | 234 | while let Some(c) = chars.next() { 235 | if c.is_uppercase() { 236 | if !snake_case.is_empty() { 237 | snake_case.push('_'); 238 | } 239 | snake_case.push(c.to_ascii_lowercase()); 240 | } else if c == ' ' { 241 | if !snake_case.is_empty() && snake_case.chars().last() != Some('_') { 242 | snake_case.push('_'); 243 | } 244 | } else { 245 | snake_case.push(c); 246 | } 247 | 248 | if let Some(&next_char) = chars.peek() { 249 | if next_char.is_uppercase() && c != ' ' && c != '_' { 250 | snake_case.push('_'); 251 | } 252 | } 253 | } 254 | 255 | snake_case 256 | } 257 | -------------------------------------------------------------------------------- /toni/src/injector/container.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::hash_map::Drain, sync::Arc}; 2 | 3 | use anyhow::{Result, anyhow}; 4 | use rustc_hash::{FxHashMap, FxHashSet}; 5 | 6 | use crate::{structs_helpers::EnhancerMetadata, traits_helpers::{Controller, ControllerTrait, ModuleMetadata, Provider, ProviderTrait}}; 7 | 8 | use super::{module::Module, InstanceWrapper}; 9 | 10 | pub struct ToniContainer { 11 | modules: FxHashMap, 12 | } 13 | 14 | impl Default for ToniContainer { 15 | fn default() -> Self { 16 | Self::new() 17 | } 18 | } 19 | 20 | impl ToniContainer { 21 | pub fn new() -> Self { 22 | Self { 23 | modules: FxHashMap::default(), 24 | } 25 | } 26 | 27 | pub fn add_module(&mut self, module_metadata: Box) { 28 | let token: String = module_metadata.get_id(); 29 | let name: String = module_metadata.get_name(); 30 | let module = Module::new(&token, &name, module_metadata); 31 | self.modules.insert(token, module); 32 | } 33 | 34 | pub fn add_import( 35 | &mut self, 36 | module_ref_token: &String, 37 | imported_module_token: String, 38 | ) -> Result<()> { 39 | let module_ref = self 40 | .modules 41 | .get_mut(module_ref_token) 42 | .ok_or_else(|| anyhow!("Module not found"))?; 43 | module_ref.add_import(imported_module_token); 44 | Ok(()) 45 | } 46 | 47 | pub fn add_controller( 48 | &mut self, 49 | module_ref_token: &String, 50 | controller: Box, 51 | ) -> Result<()> { 52 | let module_ref = self 53 | .modules 54 | .get_mut(module_ref_token) 55 | .ok_or_else(|| anyhow!("Module not found"))?; 56 | module_ref.add_controller(controller); 57 | Ok(()) 58 | } 59 | 60 | pub fn add_provider( 61 | &mut self, 62 | module_ref_token: &String, 63 | provider: Box, 64 | ) -> Result<()> { 65 | let module_ref = self 66 | .modules 67 | .get_mut(module_ref_token) 68 | .ok_or_else(|| anyhow!("Module not found"))?; 69 | module_ref.add_provider(provider); 70 | Ok(()) 71 | } 72 | 73 | pub fn add_provider_instance( 74 | &mut self, 75 | module_ref_token: &String, 76 | provider_instance: Arc>, 77 | ) -> Result<()> { 78 | let module_ref = self 79 | .modules 80 | .get_mut(module_ref_token) 81 | .ok_or_else(|| anyhow!("Module not found"))?; 82 | module_ref.add_provider_instance(provider_instance); 83 | Ok(()) 84 | } 85 | 86 | pub fn add_controller_instance( 87 | &mut self, 88 | module_ref_token: &String, 89 | controller_instance: Arc>, 90 | enhancer_metadata: EnhancerMetadata, 91 | ) -> Result<()> { 92 | let module_ref = self 93 | .modules 94 | .get_mut(module_ref_token) 95 | .ok_or_else(|| anyhow!("Module not found"))?; 96 | module_ref.add_controller_instance(controller_instance, enhancer_metadata); 97 | Ok(()) 98 | } 99 | 100 | pub fn add_export(&mut self, module_ref_token: &String, provider_token: String) -> Result<()> { 101 | let module_ref = self 102 | .modules 103 | .get_mut(module_ref_token) 104 | .ok_or_else(|| anyhow!("Module not found"))?; 105 | module_ref.add_export(provider_token); 106 | Ok(()) 107 | } 108 | 109 | pub fn add_export_instance( 110 | &mut self, 111 | module_ref_token: &String, 112 | provider_token: String, 113 | ) -> Result<()> { 114 | let module_ref = self 115 | .modules 116 | .get_mut(module_ref_token) 117 | .ok_or_else(|| anyhow!("Module not found"))?; 118 | module_ref.add_export_instance(provider_token); 119 | Ok(()) 120 | } 121 | 122 | pub fn get_providers_manager( 123 | &self, 124 | module_ref_token: &String, 125 | ) -> Result<&FxHashMap>> { 126 | let module_ref = self 127 | .modules 128 | .get(module_ref_token) 129 | .ok_or_else(|| anyhow!("Module not found"))?; 130 | Ok(module_ref.get_providers_manager()) 131 | } 132 | 133 | pub fn get_controllers_manager( 134 | &self, 135 | module_ref_token: &String, 136 | ) -> Result<&FxHashMap>> { 137 | let module_ref = self 138 | .modules 139 | .get(module_ref_token) 140 | .ok_or_else(|| anyhow!("Module not found"))?; 141 | Ok(module_ref.get_controllers_manager()) 142 | } 143 | 144 | pub fn get_providers_instance( 145 | &self, 146 | module_ref_token: &String, 147 | ) -> Result<&FxHashMap>>> { 148 | let module_ref = self 149 | .modules 150 | .get(module_ref_token) 151 | .ok_or_else(|| anyhow!("Module not found"))?; 152 | Ok(module_ref.get_providers_instances()) 153 | } 154 | 155 | pub fn get_provider_instance_by_token( 156 | &self, 157 | module_ref_token: &String, 158 | provider_token: &String, 159 | ) -> Result>>> { 160 | let module_ref = self 161 | .modules 162 | .get(module_ref_token) 163 | .ok_or_else(|| anyhow!("Module not found"))?; 164 | Ok(module_ref.get_provider_instance_by_token(provider_token)) 165 | } 166 | 167 | pub fn get_provider_by_token( 168 | &self, 169 | module_ref_token: &String, 170 | provider_token: &String, 171 | ) -> Result> { 172 | let module_ref = self 173 | .modules 174 | .get(module_ref_token) 175 | .ok_or_else(|| anyhow!("Module not found"))?; 176 | Ok(module_ref 177 | .get_provider_by_token(provider_token)) 178 | } 179 | 180 | pub fn get_controllers_instance( 181 | &mut self, 182 | module_ref_token: &String, 183 | ) -> Result>> { 184 | let module_ref = self 185 | .modules 186 | .get_mut(module_ref_token) 187 | .ok_or_else(|| anyhow!("Module not found"))?; 188 | Ok(module_ref.drain_controllers_instances()) 189 | } 190 | 191 | pub fn get_imported_modules(&self, module_ref_token: &String) -> Result<&FxHashSet> { 192 | let module_ref = self 193 | .modules 194 | .get(module_ref_token) 195 | .ok_or_else(|| anyhow!("Module not found"))?; 196 | Ok(module_ref.get_imported_modules()) 197 | } 198 | 199 | pub fn get_exports_instances_tokens( 200 | &self, 201 | module_ref_token: &String, 202 | ) -> Result<&FxHashSet> { 203 | let module_ref = self 204 | .modules 205 | .get(module_ref_token) 206 | .ok_or_else(|| anyhow!("Module not found: {:?}", module_ref_token))?; 207 | Ok(module_ref.get_exports_instances_tokens()) 208 | } 209 | 210 | pub fn get_exports_tokens_vec(&self, module_ref_token: &String) -> Result> { 211 | let module_ref = self 212 | .modules 213 | .get(module_ref_token) 214 | .ok_or_else(|| anyhow!("Module not found: {:?}", module_ref_token))?; 215 | Ok(module_ref.get_exports_tokens().iter().cloned().collect()) 216 | } 217 | 218 | pub fn get_modules_token(&self) -> Vec { 219 | self.modules.keys().cloned().collect::>() 220 | } 221 | 222 | pub fn get_ordered_modules_token(&self) -> Vec { 223 | let mut ordered_modules: Vec = Vec::new(); 224 | let mut visited: FxHashMap = FxHashMap::default(); 225 | 226 | for (token, module) in self.modules.iter() { 227 | if module.get_imported_modules().is_empty() { 228 | ordered_modules.push(token.clone()); 229 | visited.insert(token.clone(), true); 230 | } 231 | } 232 | while ordered_modules.len() < self.modules.len() { 233 | for (token, module) in self.modules.iter() { 234 | if visited.contains_key(token) { 235 | continue; 236 | } 237 | 238 | let imported_modules = module.get_imported_modules(); 239 | let all_imports_processed = imported_modules 240 | .iter() 241 | .all(|import_token| visited.contains_key(import_token)); 242 | 243 | if all_imports_processed { 244 | ordered_modules.push(token.clone()); 245 | visited.insert(token.clone(), true); 246 | } 247 | } 248 | } 249 | 250 | ordered_modules 251 | } 252 | 253 | pub fn get_module_by_token(&self, module_ref_token: &String) -> Option<&Module> { 254 | self.modules.get(module_ref_token) 255 | } 256 | 257 | // pub fn register_controller_enhancers( 258 | // &mut self, 259 | // module_ref_token: &String, 260 | // controller_token: &String, 261 | // controller_enhancers: &Vec>, 262 | // ) -> Result<()> { 263 | // let module_ref = self 264 | // .modules 265 | // .get_mut(module_ref_token) 266 | // .ok_or_else(|| anyhow!("Module not found"))?; 267 | // module_ref.register_controller_enhancers(controller_enhancers); 268 | // Ok(()) 269 | // } 270 | } 271 | -------------------------------------------------------------------------------- /toni-macros/src/utils/modify_impl_function_body.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::visit_mut::{self, VisitMut}; 4 | use syn::{Block, Expr, Ident, Local, Member, Pat, PatType, Type, parse_quote}; 5 | 6 | use super::create_struct_name::create_field_struct_name; 7 | struct ExprModifier { 8 | provider_names: Vec<(Ident, Ident)>, 9 | modified_exprs: Vec<(Ident, Ident, Ident)>, 10 | ty: Option, 11 | self_name: Ident, 12 | whitout_type: bool, 13 | } 14 | 15 | impl ExprModifier { 16 | fn new(provider_names: Vec<(Ident, Ident)>, self_name: Ident) -> Self { 17 | Self { 18 | provider_names, 19 | modified_exprs: Vec::new(), 20 | ty: None, 21 | self_name, 22 | whitout_type: true, 23 | } 24 | } 25 | 26 | fn get_modified_exprs(self) -> Vec<(Ident, Ident, Ident)> { 27 | self.modified_exprs 28 | } 29 | 30 | fn put_box_in_expr(&self, exprs: syn::punctuated::Iter<'_, Expr>) -> Vec { 31 | exprs 32 | .map(|expr| { 33 | quote! { 34 | Box::new(#expr) 35 | } 36 | }) 37 | .collect() 38 | } 39 | 40 | fn put_inject_type(&mut self, ty: Option) { 41 | self.ty = ty; 42 | } 43 | } 44 | 45 | impl VisitMut for ExprModifier { 46 | fn visit_expr_mut(&mut self, expr: &mut Expr) { 47 | if let Expr::Await(expr_await) = &mut *expr { 48 | if let Expr::MethodCall(method_call) = &mut *expr_await.base { 49 | if let Expr::Field(expr_field) = &mut *method_call.receiver { 50 | if let Member::Named(ident) = &expr_field.member { 51 | let ident_clone = ident.clone(); 52 | let method_args_clone = &method_call.args.clone(); 53 | let method_call_name = &method_call.method.clone(); 54 | for provide_name in &self.provider_names { 55 | if ident_clone == provide_name.0 { 56 | let method_name = method_call_name; 57 | let new_field_name = create_field_struct_name( 58 | &provide_name.1.to_string(), 59 | method_name, 60 | ) 61 | .unwrap(); 62 | let args = self.put_box_in_expr(method_args_clone.iter()); 63 | self.modified_exprs.push(( 64 | provide_name.1.clone(), 65 | method_name.clone(), 66 | new_field_name.clone(), 67 | )); 68 | let new_call_fn_expr: Expr = parse_quote! { 69 | self.#new_field_name.execute(vec![#(#args),*]) 70 | }; 71 | if self.whitout_type { 72 | *expr = new_call_fn_expr; 73 | } else { 74 | let type_inject = match &self.ty { 75 | Some(ty) => ty, 76 | None => panic!("Need type"), 77 | }; 78 | let with_downcast: Expr = parse_quote! { 79 | *#new_call_fn_expr.await.downcast::<#type_inject>().unwrap() 80 | }; 81 | *expr = with_downcast; 82 | } 83 | return; 84 | } 85 | } 86 | } 87 | } else if let Expr::Path(expr_path) = &*method_call.receiver { 88 | if let Some(segment) = expr_path.path.segments.last() { 89 | if segment.ident == "self" { 90 | let method_args_clone = method_call.args.clone(); 91 | let method_name = method_call.method.clone(); 92 | let new_method_name = 93 | create_field_struct_name(&self.self_name.to_string(), &method_name) 94 | .unwrap(); 95 | let args = self.put_box_in_expr(method_args_clone.iter()); 96 | self.modified_exprs.push(( 97 | self.self_name.clone(), 98 | method_name.clone(), 99 | new_method_name.clone(), 100 | )); 101 | let new_call_fn_expr: Expr = parse_quote! { 102 | self.#new_method_name.execute(vec![#(#args),*]) 103 | }; 104 | if self.whitout_type { 105 | *expr = new_call_fn_expr; 106 | return; 107 | } 108 | let type_inject = match &self.ty { 109 | Some(ty) => ty, 110 | None => panic!("Need type"), 111 | }; 112 | let downcast_token: Expr = parse_quote! { 113 | downcast::<#type_inject>().unwrap() 114 | }; 115 | *expr = parse_quote! {*#new_call_fn_expr.await.#downcast_token}; 116 | } 117 | } 118 | } 119 | } 120 | } 121 | 122 | if let Expr::MethodCall(method_call) = &mut *expr { 123 | if let Expr::Field(expr_field) = &mut *method_call.receiver { 124 | if let Member::Named(ident) = &expr_field.member { 125 | let ident_clone = ident.clone(); 126 | let method_args_clone = &method_call.args.clone(); 127 | let method_call_name = &method_call.method.clone(); 128 | for provide_name in &self.provider_names { 129 | if ident_clone == provide_name.0 { 130 | let method_name = method_call_name; 131 | let new_field_name = create_field_struct_name( 132 | &provide_name.1.to_string(), 133 | method_name, 134 | ) 135 | .unwrap(); 136 | let args = self.put_box_in_expr(method_args_clone.iter()); 137 | self.modified_exprs.push(( 138 | provide_name.1.clone(), 139 | method_name.clone(), 140 | new_field_name.clone(), 141 | )); 142 | let new_call_fn_expr: Expr = parse_quote! { 143 | self.#new_field_name.execute(vec![#(#args),*]) 144 | }; 145 | if self.whitout_type { 146 | *expr = new_call_fn_expr; 147 | } else { 148 | let type_inject = match &self.ty { 149 | Some(ty) => ty, 150 | None => panic!("Need type"), 151 | }; 152 | let with_downcast: Expr = parse_quote! { 153 | *#new_call_fn_expr.await.downcast::<#type_inject>().unwrap() 154 | }; 155 | *expr = with_downcast; 156 | } 157 | return; 158 | } 159 | } 160 | } 161 | } else if let Expr::Path(expr_path) = &*method_call.receiver { 162 | if let Some(segment) = expr_path.path.segments.last() { 163 | if segment.ident == "self" { 164 | let method_args_clone = method_call.args.clone(); 165 | let method_name = method_call.method.clone(); 166 | let new_method_name = 167 | create_field_struct_name(&self.self_name.to_string(), &method_name) 168 | .unwrap(); 169 | let args = self.put_box_in_expr(method_args_clone.iter()); 170 | self.modified_exprs.push(( 171 | self.self_name.clone(), 172 | method_name.clone(), 173 | new_method_name.clone(), 174 | )); 175 | let new_call_fn_expr: Expr = parse_quote! { 176 | self.#new_method_name.execute(vec![#(#args),*]) 177 | }; 178 | if self.whitout_type { 179 | *expr = new_call_fn_expr; 180 | return; 181 | } 182 | let type_inject = match &self.ty { 183 | Some(ty) => ty, 184 | None => panic!("Need type"), 185 | }; 186 | let downcast_token: Expr = parse_quote! { 187 | downcast::<#type_inject>().unwrap() 188 | }; 189 | *expr = parse_quote! {*#new_call_fn_expr.await.#downcast_token}; 190 | } 191 | } 192 | } 193 | } 194 | visit_mut::visit_expr_mut(self, expr); 195 | } 196 | } 197 | 198 | pub fn modify_method_body( 199 | method_body: &mut Block, 200 | provider_names: Vec<(Ident, Ident)>, 201 | self_name: Ident, 202 | ) -> Vec<(Ident, Ident, Ident)> { 203 | let mut modifier = ExprModifier::new(provider_names, self_name); 204 | for stmt in &mut method_body.stmts { 205 | if let syn::Stmt::Expr(expr, _) = stmt { 206 | modifier.visit_expr_mut(expr); 207 | } 208 | if let syn::Stmt::Local(Local { 209 | init: Some(init), 210 | pat, 211 | .. 212 | }) = stmt 213 | { 214 | if let Pat::Type(PatType { ty, .. }) = pat.clone() { 215 | modifier.whitout_type = false; 216 | let type_inject = Some(*ty); 217 | modifier.put_inject_type(type_inject); 218 | } 219 | modifier.visit_expr_mut(&mut init.expr); 220 | } 221 | } 222 | modifier.get_modified_exprs() 223 | } 224 | --------------------------------------------------------------------------------