1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
#![doc(html_root_url = "https://docs.rs/chassis/0.1.0")] #![cfg_attr(nightly_diagnostics, feature(proc_macro_diagnostic, proc_macro_span))] //! Compile-time dependency injector. //! //! *Let the compiler generate your dependency injection code.* //! //! ## Goals //! * Detect errors at compile time like missing dependencies or cyclic dependencies //! * No need to annotate your classes (support for third-party classes) //! * No required usage of `std::sync::Arc` //! * Zero overhead: Fast as hand-written code //! * No use of runtime type information ([`Any`]) //! //! [`Any`]: std::any::Any //! //! ## Use //! //! Add `chassis` to your crate dependencies //! ```toml //! chassis = "^0.1.0" //! ``` //! //! Create a module for your dependency injection logic and annotate it with //! `#[chassis::integration]`. The code in this module will be inspected by this attribute. //! ```rust,no_run //! #[chassis::integration] //! mod integration { //! use super::*; //! //! // ... //! } //! ``` //! //! Structs will be modules that can provide dependencies with functions //! and that itself can have dependencies. //! *Note: Currently only associated functions are supported!* //! ```rust,no_run //! # pub struct Dependency1; //! # pub struct Dependency2; //! # pub struct Dependency3; //! # impl Dependency3 { fn new(dep1: Dependency1, dep2: Dependency2) -> Self { Self } } //! pub struct Module; //! impl Module { //! pub fn provide_something(dep1: Dependency1, dep2: Dependency2) -> Dependency3 { //! Dependency3::new(dep1, dep2) //! } //! // ... //! } //! ``` //! //! Traits will be components. For each trait a implemented component will be created. The generated //! implementation will have a `Impl` suffix, for example `ComponentImpl`. Also a //! `ComponentImpl::new` function is created. //! ```rust,no_run //! # pub struct MainClass; //! pub trait Component { //! fn resolve_main_class(&self) -> MainClass; //! } //! ``` //! //! ## Example //! ```rust,no_run //! use std::rc::Rc; //! //! // define your business logic //! //! /// printer trait //! pub trait Printer { //! fn print(&self, input: &str); //! } //! //! /// a printer implementation //! pub struct StdoutPrinter; //! impl Printer for StdoutPrinter { //! fn print(&self, input: &str) { //! println!("{}", input); //! } //! } //! //! /// greeter for messages //! pub struct Greeter { //! message: String, //! printer: Rc<dyn Printer>, //! } //! impl Greeter { //! /// constructor with dependencies //! pub fn new(message: String, printer: Rc<dyn Printer>) -> Self { //! Self { message, printer } //! } //! //! /// your business logic //! pub fn say_hello(&self) { //! self.printer.print(&self.message); //! } //! } //! //! /// module that is parsed to create the dependency injection code //! #[chassis::integration] //! mod integration { //! use super::*; //! //! pub struct DemoModule; //! //! // use strong types when in need to distinguish //! pub struct Message(String); //! //! /// Define how to create your dependencies //! impl DemoModule { //! pub fn provide_printer() -> Rc<dyn Printer> { //! Rc::new(StdoutPrinter) //! } //! //! pub fn provide_message() -> Message { //! Message("Hello World".to_string()) //! } //! //! pub fn provide_greeter( //! message: Message, //! printer: Rc<dyn Printer> //! ) -> Greeter { //! Greeter::new(message.0, printer) //! } //! } //! //! /// Define which dependencies you need. //! /// //! /// A struct `DemoComponentImpl` will be created for //! /// you which implements `DemoComponent`. //! pub trait DemoComponent { //! /// request the to create injection code for our main class `Greeter` //! fn resolve_greeter(&self) -> Greeter; //! } //! } //! //! fn main() { //! // import component trait //! use crate::integration::DemoComponent; //! //! // use generated component implementation //! let injector = integration::DemoComponentImpl::new(); //! //! // Resolve main dependency //! // Note: it can not fail at runtime! //! let greeter = injector.resolve_greeter(); //! //! // enjoy! //! greeter.say_hello(); //! } //! ``` //! //! The generated implementation will roughly look like this //! (*Tip: use [cargo-expand] to inspect the code*): //! ```rust,no_run //! # struct Greeter; //! # struct Message; //! # struct Printer; //! # struct DemoModule; //! # trait DemoComponent { //! # fn resolve_greeter(&self) -> Greeter; //! # } //! # impl DemoModule { //! # pub fn provide_printer() -> Printer { //! # Printer //! # } //! # pub fn provide_message() -> Message { //! # Message //! # } //! # pub fn provide_greeter( //! # message: Message, //! # printer: Printer //! # ) -> Greeter { //! # Greeter //! # } //! # } //! pub struct DemoComponentImpl{} //! //! impl DemoComponentImpl { //! pub fn new() -> Self { Self {} } //! } //! //! impl DemoComponent for DemoComponentImpl { //! fn resolve_greeter(&self) -> Greeter { //! DemoModule::provide_greeter( //! DemoModule::provide_message(), //! DemoModule::provide_printer()) //! } //! } //! ``` //! //! [cargo-expand]: https://crates.io/crates/cargo-expand //! //! ## Limitations //! * Dependencies are looked up through the syntax token //! * `Rc<Dep>` and `Rc< Dep >` are the same //! * but `Rc<Dep>` and `Rc<crate::Dep>` never //! * Also types aliases with `type` result in different type keys //! * Currently lifetimes in the types are not supported (also `'static`) //! * Currently generics are not handeled correctly //! * Currently only the first error is show at compile time //! * Currently modules can not have `&self`-methods, so inner data is useless //! * Currently it is not possible to request a reference to a registered non-reference type //! * Like `&MyType` when `MyType` is provided by a module //! //! ## Singletons //! //! Normaly for every needed dependency the provider function on the module is called. This results //! in types created multiple times. This is maybe not intended. The solution is to use a //! `singleton` attribute. The provide method will than only called once at build time of the //! component (call to `ComponentImpl::new`). The requirement is that the type implements the //! [`Clone`] trait. It is recommendable to use a shared reference type like [`Rc`] or [`Arc`] for //! singletons so that really only one instance is created. //! //! ### Example //! ```rust,no_run //! # #[chassis::integration] //! # mod integration { //! # use std::rc::Rc; //! # trait Printer {} //! # struct StdoutPrinter; //! # impl Printer for StdoutPrinter {} //! # struct Module; //! impl Module { //! #[singleton] //! pub fn provide_printer() -> Rc<dyn Printer> { //! Rc::new(StdoutPrinter) //! } //! } //! # } //! ``` //! //! [`Clone`]: std::clone::Clone //! [`Copy`]: std::marker::Copy //! [`Rc`]: std::rc::Rc //! [`Arc`]: std::sync::Arc #[macro_use] extern crate quote; #[macro_use] extern crate syn; use proc_macro::TokenStream; use syn::export::TokenStream2; use syn::spanned::Spanned; use crate::codegen::codegen_component_impl; use crate::container::IocContainer; use crate::errors::{codegen_errors, ChassisError, ChassisResult}; use crate::parse::parse_block; mod codegen; mod container; mod diagnostic; mod errors; mod key; mod model; mod parse; mod syn_ext; mod utils; /// Attribute for modules #[proc_macro_attribute] pub fn integration(_args: TokenStream, input: TokenStream) -> TokenStream { let mod_block: syn::ItemMod = parse_macro_input!(input); match parse_integration(_args, mod_block) { Ok(tokens) => tokens.into(), Err(err) => codegen_errors(err).into(), } } fn parse_integration( _args: TokenStream, mut mod_block: syn::ItemMod, ) -> ChassisResult<TokenStream2> { let mut mod_impl = match &mut mod_block.content { Some((_, items)) => items, None => { return Err(ChassisError::IllegalInput( "Expected module implementation when using integration attribute".to_string(), mod_block.span(), )) } }; // Parse components and modules let block = parse_block(&mut mod_impl)?; // analyse let modules = block.modules; let mut container = IocContainer::new(); for module in modules { container.add_module(module)?; } // generate let component_impls = block .components .into_iter() .map(|comp| codegen_component_impl(comp, &container)) .collect::<ChassisResult<Vec<TokenStream2>>>()?; // generate result let mod_name = &mod_block.ident; let mod_vis = &mod_block.vis; Ok(quote! { #mod_vis mod #mod_name { #(#mod_impl)* #(#component_impls)* } }) }