├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── calling.rs ├── calling_with_kwargs.rs ├── default.rs ├── provide_reference.rs └── resolve_dependency.rs ├── inject-macro ├── Cargo.toml └── src │ ├── bool_to_option.rs │ ├── call │ ├── kwargs.rs │ └── mod.rs │ ├── container.rs │ ├── get.rs │ ├── inject │ ├── arguments │ │ ├── argument │ │ │ ├── default.rs │ │ │ ├── mergable.rs │ │ │ ├── mod.rs │ │ │ └── no_inject.rs │ │ ├── error.rs │ │ └── mod.rs │ ├── input │ │ ├── constructor.rs │ │ ├── free_function.rs │ │ ├── injectable_signature.rs │ │ └── mod.rs │ └── mod.rs │ └── lib.rs ├── src ├── error.rs ├── inject.rs ├── lib.rs ├── module.rs ├── provider.rs └── providers.rs └── tests ├── fixtures.rs ├── test_call.rs ├── test_container.rs ├── test_inject.rs └── test_providers.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Build 17 | run: cargo build --verbose 18 | - name: Run tests 19 | run: cargo test --verbose 20 | - name: Run macro tests 21 | run: cargo test --manifest-path ./inject-macro/Cargo.toml 22 | - name: Run examples 23 | run: cargo test --examples --verbose 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.idea 2 | **/target 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "inject" 3 | version = "0.1.3" 4 | authors = ["Tobias Nilsson "] 5 | description = "Experimental IOC library for Rust" 6 | edition = "2018" 7 | readme = "README.md" 8 | repository = "https://github.com/tobni/inject-rs" 9 | license-file = "LICENSE" 10 | keywords = ["injection", "ioc", "resolve", "control", "dependency"] 11 | categories = ["config"] 12 | 13 | 14 | [dependencies] 15 | inject-macro = { path = "./inject-macro", version = "0.1.1" } 16 | 17 | [dev-dependencies] 18 | rstest = "0.6.4" 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tobias Nilsson 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/tobni/inject-rs/Rust)](https://github.com/tobni/inject-rs/actions?query=workflow%3ARust) 2 | [![Crates.io](https://img.shields.io/crates/v/inject)](https://crates.io/crates/inject) 3 | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/tobni/inject-rs)](https://github.com/tobni/inject-rs/releases) 4 | [![API](https://docs.rs/inject/badge.svg)](https://docs.rs/inject) 5 | 6 | Experimental IOC library inspired by [injector](https://github.com/alecthomas/injector) for Rust. Goals: IOC + ergonomics. 7 | 8 | See [test suite](https://github.com/tobni/inject-rs/tree/master/tests) for all supported usages. 9 | 10 | **Examples** 11 | 12 | using `#[inject]`, `call!`, `get!` and `container!`. 13 | 14 | 1. Configure a container, add some provider, e.g an `Arc` 15 | ```rust 16 | use std::sync::Arc; 17 | 18 | use ::inject::{container, get, inject}; 19 | 20 | struct Instance(pub isize); 21 | 22 | impl Instance { 23 | #[inject] 24 | fn new(a: isize) -> Self { 25 | Instance(a) 26 | } 27 | } 28 | 29 | fn main() { 30 | let provider = Arc::new(Instance(3)); 31 | 32 | // Install the Arc as a reference provider, anytime using get! 33 | // will resolve to a reference of this Arc. 34 | let container = container![ref provider]; 35 | 36 | let instance: &Instance = get!(&container, &Instance).unwrap(); 37 | 38 | assert_eq!(3, instance.0) 39 | } 40 | ``` 41 | 42 | 2. Let the container resolve a dependency, using a closure as provider 43 | 44 | ```rust 45 | use ::inject::{Container, container, get, inject}; 46 | 47 | struct Instance(pub isize); 48 | 49 | impl Instance { 50 | #[inject] 51 | fn new(a: isize) -> Self { 52 | Instance(a) 53 | } 54 | } 55 | 56 | struct Service { 57 | a: Instance, 58 | } 59 | 60 | impl Service { 61 | #[inject] 62 | fn new(instance: Instance) -> Self { 63 | Self { a: instance } 64 | } 65 | } 66 | 67 | fn main() { 68 | // Install a provider, this time a closure returning a value 69 | let container = container![|container: &Container| Ok(Instance(2))]; 70 | 71 | let service: Service = get!(&container, Service).unwrap(); 72 | 73 | assert_eq!(service.a.0, 2) 74 | } 75 | ``` 76 | 77 | 3. Sometimes, calling a function with injection is useful, 78 | ```rust 79 | use ::inject::{call, Container, container, inject}; 80 | 81 | struct Service(isize); 82 | 83 | impl Service { 84 | #[inject] 85 | fn new() -> Self { 86 | Self(0) 87 | } 88 | } 89 | 90 | #[inject] 91 | fn acts_on_service(service: Service) -> isize { 92 | 2 + service.0 93 | } 94 | 95 | fn main() { 96 | let container = container![|container: &Container| Ok(Service(3))]; 97 | 98 | let result = call!(&container, acts_on_service).unwrap(); 99 | 100 | assert_eq!(result, 5) 101 | } 102 | ``` 103 | 4. `call!` supports a kwarg-flavored syntax 104 | ```rust 105 | use ::inject::{call, container, inject}; 106 | 107 | struct Service(isize); 108 | 109 | impl Service { 110 | #[inject] 111 | fn new() -> Self { 112 | Self(0) 113 | } 114 | } 115 | 116 | #[inject] 117 | fn acts_on_service(service: Service) -> isize { 118 | 2 + service.0 119 | } 120 | 121 | fn main() { 122 | let container = container![]; 123 | 124 | let result = call!(&container, acts_on_service, kwargs = { service: Service(2) }).unwrap(); 125 | 126 | assert_eq!(result, 4) 127 | } 128 | ``` 129 | 130 | 5. Dependency resolution can rely upon a type implementing the `Default` trait 131 | ```rust 132 | use inject::{container, get}; 133 | 134 | #[derive(Default)] 135 | struct Service(isize); 136 | 137 | fn main() { 138 | let container = container![]; 139 | 140 | let service = get!(&container, Service).unwrap(); 141 | 142 | assert_eq!(service.0, 0) 143 | } 144 | ``` 145 | 146 | 147 | **Details** 148 | 149 | The `get!` macro with a `container` resolves a type in order of: installed provider (1), calling the associated `inject` function (often generated with `#[inject]`) function on a type (2), and lastly the `Default` trait (3). 150 | 151 | (2) & (3) can be opt-out by attribute `#[inject(no_inject(arg))]`, (name tbd) in which case only container held provider will be used for resolution of the type. Method specific defaults are annotated as `#[inject(defualt(arg = expression))]` where expression will lazy evaluate on failing attempt at (1) and (2). 152 | 153 | Todo: 154 | 1. Support kwargs for "constructors" with a `create_object!` flavored macro. 155 | 2. Make `#[inject]` support Struct attribute notation with `#[inject(..)]` for individual struct fields. 156 | 3. Make `default` and `no_inject` story less annoying. 157 | -------------------------------------------------------------------------------- /examples/calling.rs: -------------------------------------------------------------------------------- 1 | use ::inject::{call, container, inject, Container}; 2 | 3 | struct Service(isize); 4 | 5 | impl Service { 6 | #[inject] 7 | fn new() -> Self { 8 | Self(0) 9 | } 10 | } 11 | 12 | #[inject] 13 | fn acts_on_service(service: Service) -> isize { 14 | 2 + service.0 15 | } 16 | 17 | fn main() { 18 | let container = container![|container: &Container| Ok(Service(3))]; 19 | 20 | let result = call!(&container, acts_on_service).unwrap(); 21 | 22 | assert_eq!(result, 5) 23 | } 24 | -------------------------------------------------------------------------------- /examples/calling_with_kwargs.rs: -------------------------------------------------------------------------------- 1 | use ::inject::{call, container, inject}; 2 | 3 | struct Service(isize); 4 | 5 | impl Service { 6 | #[inject] 7 | fn new() -> Self { 8 | Self(0) 9 | } 10 | } 11 | 12 | #[inject] 13 | fn acts_on_service(service: Service) -> isize { 14 | 2 + service.0 15 | } 16 | 17 | fn main() { 18 | let container = container![]; 19 | 20 | let result = call!(&container, acts_on_service, kwargs = { service: Service(2) }).unwrap(); 21 | 22 | assert_eq!(result, 4) 23 | } 24 | -------------------------------------------------------------------------------- /examples/default.rs: -------------------------------------------------------------------------------- 1 | use inject::{container, get}; 2 | 3 | #[derive(Default)] 4 | struct Service(isize); 5 | 6 | fn main() { 7 | let container = container![]; 8 | 9 | let service = get!(&container, Service).unwrap(); 10 | 11 | assert_eq!(service.0, 0) 12 | } 13 | -------------------------------------------------------------------------------- /examples/provide_reference.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ::inject::{container, get, inject}; 4 | 5 | struct Instance(pub isize); 6 | 7 | impl Instance { 8 | #[inject] 9 | fn new(a: isize) -> Self { 10 | Instance(a) 11 | } 12 | } 13 | 14 | fn main() { 15 | let provider = Arc::new(Instance(3)); 16 | 17 | // Install the Arc as a reference provider, anytime using get! 18 | // will resolve to a reference of this Arc. 19 | let container = container![ref provider]; 20 | 21 | let instance: &Instance = get!(&container, &Instance).unwrap(); 22 | 23 | assert_eq!(3, instance.0) 24 | } 25 | -------------------------------------------------------------------------------- /examples/resolve_dependency.rs: -------------------------------------------------------------------------------- 1 | use ::inject::{container, get, inject, Container}; 2 | 3 | struct Instance(pub isize); 4 | 5 | impl Instance { 6 | #[inject] 7 | fn new(a: isize) -> Self { 8 | Instance(a) 9 | } 10 | } 11 | 12 | struct Service { 13 | a: Instance, 14 | } 15 | 16 | impl Service { 17 | #[inject] 18 | fn new(instance: Instance) -> Self { 19 | Self { a: instance } 20 | } 21 | } 22 | 23 | fn main() { 24 | // Install a provider, this time a closure returning a value 25 | let container = container![|container: &Container| Ok(Instance(2))]; 26 | 27 | let service: Service = get!(&container, Service).unwrap(); 28 | 29 | assert_eq!(service.a.0, 2) 30 | } 31 | -------------------------------------------------------------------------------- /inject-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "inject-macro" 3 | version = "0.1.1" 4 | authors = ["Tobias Nilsson "] 5 | edition = "2018" 6 | description = "Experimental IOC library for Rust, procedural macros" 7 | readme = "../README.md" 8 | repository = "https://github.com/tobni/inject-rs" 9 | license-file = "../LICENSE" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | syn = { version = "1.0.23", features = ["full"] } 16 | quote = "1.0.6" 17 | proc-macro2 = "1.0.17" -------------------------------------------------------------------------------- /inject-macro/src/bool_to_option.rs: -------------------------------------------------------------------------------- 1 | pub(crate) trait BoolToOption: Copy + std::ops::Not { 2 | fn and(self, t: T) -> Option; 3 | 4 | fn and_then(self, f: impl FnOnce() -> T) -> Option; 5 | 6 | #[inline] 7 | fn or(self, t: T) -> Option { 8 | (!self).and(t) 9 | } 10 | 11 | #[inline] 12 | fn or_then(self, f: impl FnOnce() -> T) -> Option { 13 | (!self).and_then(f) 14 | } 15 | } 16 | 17 | impl BoolToOption for bool { 18 | #[inline] 19 | fn and(self, t: T) -> Option { 20 | if self { 21 | Some(t) 22 | } else { 23 | None 24 | } 25 | } 26 | 27 | #[inline] 28 | fn and_then(self, f: impl FnOnce() -> T) -> Option { 29 | if self { 30 | Some(f()) 31 | } else { 32 | None 33 | } 34 | } 35 | } 36 | 37 | #[cfg(test)] 38 | mod tests { 39 | use super::BoolToOption; 40 | #[test] 41 | fn test_and() { 42 | let a = true.and(1); 43 | let b = false.and(0); 44 | assert_eq!(a, Some(1)); 45 | assert_eq!(b, None); 46 | } 47 | 48 | #[test] 49 | fn test_and_then() { 50 | let a = true.and_then(|| 1); 51 | let b = false.and_then(|| 0); 52 | assert_eq!(a, Some(1)); 53 | assert_eq!(b, None); 54 | } 55 | 56 | #[test] 57 | fn test_or() { 58 | let a = true.or(1); 59 | let b = false.or(0); 60 | assert_eq!(a, None); 61 | assert_eq!(b, Some(0)); 62 | } 63 | 64 | #[test] 65 | fn test_or_then() { 66 | let a = true.or_then(|| 1); 67 | let b = false.or_then(|| 0); 68 | assert_eq!(a, None); 69 | assert_eq!(b, Some(0)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /inject-macro/src/call/kwargs.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::ToTokens; 3 | use syn::parse::{Parse, ParseStream}; 4 | use syn::punctuated::Punctuated; 5 | use syn::token::Brace; 6 | use syn::{braced, Expr, Ident, Result, Token}; 7 | 8 | mod kw { 9 | syn::custom_keyword!(kwargs); 10 | } 11 | 12 | pub struct Kwargs { 13 | pub keyword: kw::kwargs, 14 | pub equals: Token![=], 15 | pub brace: Brace, 16 | pub fields: Punctuated, 17 | } 18 | 19 | impl Parse for Kwargs { 20 | fn parse(input: ParseStream) -> Result { 21 | let content; 22 | Ok(Self { 23 | keyword: input.parse()?, 24 | equals: input.parse()?, 25 | brace: braced!(content in input), 26 | fields: Punctuated::parse_terminated(&content)?, 27 | }) 28 | } 29 | } 30 | 31 | pub struct Kwarg { 32 | member: Ident, 33 | colon: Token![:], 34 | expr: Expr, 35 | } 36 | 37 | impl Parse for Kwarg { 38 | fn parse(input: ParseStream) -> Result { 39 | Ok(Self { 40 | member: input.parse()?, 41 | colon: input.parse()?, 42 | expr: input.parse()?, 43 | }) 44 | } 45 | } 46 | 47 | impl ToTokens for Kwarg { 48 | fn to_tokens(&self, tokens: &mut TokenStream) { 49 | self.member.to_tokens(tokens); 50 | self.colon.to_tokens(tokens); 51 | self.expr.to_tokens(tokens); 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use super::{Kwarg, Kwargs}; 58 | use quote::quote; 59 | use syn::parse2; 60 | 61 | #[test] 62 | fn test_parsing_args() { 63 | let tree = quote! { 64 | kwargs = { a: "Hi", b: A::new(), c: P } 65 | }; 66 | 67 | let kwargs: Kwargs = parse2(tree).unwrap(); 68 | let members = vec!["a", "b", "c"]; 69 | 70 | assert_eq!(kwargs.fields.len(), members.len()); 71 | 72 | for kwarg in kwargs.fields { 73 | assert!(members.contains(&kwarg.member.to_string().as_str())); 74 | } 75 | } 76 | 77 | #[test] 78 | fn test_to_tokens_for_kwarg() { 79 | let tree = quote! { 80 | a: "Hi" 81 | }; 82 | 83 | let kwarg: Kwarg = parse2(tree).unwrap(); 84 | let expected = quote! { 85 | a: "Hi" 86 | }; 87 | 88 | let expanded = quote! { 89 | #kwarg 90 | }; 91 | 92 | assert_eq!(expected.to_string(), expanded.to_string()) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /inject-macro/src/call/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{format_ident, quote}; 3 | use syn::parse::{Parse, ParseStream}; 4 | use syn::{Expr, Ident, Result, Token}; 5 | 6 | use crate::BoolToOption; 7 | 8 | mod kwargs; 9 | 10 | use kwargs::Kwargs; 11 | 12 | pub struct Call { 13 | pub ident: Expr, 14 | pub comma1: Token![,], 15 | pub func: Ident, 16 | pub comma2: Option, 17 | pub kwargs: Option, 18 | } 19 | 20 | impl Call { 21 | pub fn expand(self) -> TokenStream { 22 | let Call { 23 | ident, 24 | func, 25 | kwargs, 26 | .. 27 | } = self; 28 | let macro_name = format_ident!("__inject_{}", func); 29 | let fields = kwargs.as_ref().map(|kwargs| { 30 | let kwargs = kwargs.fields.iter(); 31 | quote! { #(, #kwargs )* } 32 | }); 33 | 34 | quote! { #macro_name ! (#ident #fields) } 35 | } 36 | } 37 | 38 | impl Parse for Call { 39 | fn parse(input: ParseStream) -> Result { 40 | Ok(Self { 41 | ident: input.parse()?, 42 | comma1: input.parse()?, 43 | func: input.parse()?, 44 | comma2: input.parse()?, 45 | kwargs: (!input.is_empty()).and_then(|| input.parse()).transpose()?, 46 | }) 47 | } 48 | } 49 | 50 | #[cfg(test)] 51 | mod tests { 52 | use super::Call; 53 | use quote::quote; 54 | use syn::parse2; 55 | 56 | #[test] 57 | fn test_parsing_args() { 58 | let tree = quote! { 59 | &container, a_func 60 | }; 61 | 62 | let call: Call = parse2(tree).unwrap(); 63 | 64 | assert!(call.func == "a_func"); 65 | } 66 | 67 | #[test] 68 | fn test_parsing_args_with_kwargs() { 69 | let tree = quote! { 70 | &container, a_func, kwargs = { a: 1 } 71 | }; 72 | 73 | let call: Call = parse2(tree).unwrap(); 74 | 75 | assert!(call.func == "a_func"); 76 | } 77 | 78 | #[test] 79 | fn test_expansion() { 80 | let tree = quote! { 81 | &container, a_func 82 | }; 83 | 84 | let call = parse2::(tree).unwrap().expand(); 85 | 86 | let expected = quote! { 87 | __inject_a_func!(&container) 88 | }; 89 | 90 | assert_eq!(call.to_string(), expected.to_string()) 91 | } 92 | 93 | #[test] 94 | fn test_expansion_with_kwargs() { 95 | let tree = quote! { 96 | &container, a_func, kwargs = { a: 1 } 97 | }; 98 | 99 | let call = parse2::(tree).unwrap().expand(); 100 | 101 | let expected = quote! { 102 | __inject_a_func!(&container, a: 1) 103 | }; 104 | 105 | assert_eq!(call.to_string(), expected.to_string()) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /inject-macro/src/container.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::parse::{Parse, ParseStream}; 4 | use syn::punctuated::Punctuated; 5 | use syn::{Expr, Result, Token}; 6 | 7 | pub struct Container { 8 | providers: Punctuated, 9 | } 10 | 11 | impl Container { 12 | pub fn expand(self) -> TokenStream { 13 | let provider_calls = self.providers.into_iter().map( 14 | |Provider { 15 | ref_token, 16 | provider, 17 | }| match ref_token { 18 | Some(_) => quote! { container.install_ref(#[allow(unused_variables)] #provider) }, 19 | None => quote! { container.install(#[allow(unused_variables)] #provider) }, 20 | }, 21 | ); 22 | 23 | quote! { 24 | { 25 | let mut container = ::inject::Container::new(); 26 | #(#provider_calls; )* 27 | container 28 | 29 | } 30 | } 31 | } 32 | } 33 | 34 | impl Parse for Container { 35 | fn parse(input: ParseStream) -> Result { 36 | Ok(Self { 37 | providers: Punctuated::parse_terminated(&input)?, 38 | }) 39 | } 40 | } 41 | 42 | struct Provider { 43 | ref_token: Option, 44 | provider: Expr, 45 | } 46 | 47 | impl Parse for Provider { 48 | fn parse(input: ParseStream) -> Result { 49 | Ok(Self { 50 | ref_token: input.parse()?, 51 | provider: input.parse()?, 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /inject-macro/src/get.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::parse::{Parse, ParseStream}; 4 | use syn::{Expr, LitBool, Path, Result, Token}; 5 | 6 | use crate::bool_to_option::BoolToOption; 7 | 8 | mod kw { 9 | syn::custom_keyword!(create); 10 | } 11 | 12 | pub struct Create { 13 | pub keyword: kw::create, 14 | pub colon: Token![:], 15 | pub boolean: LitBool, 16 | } 17 | 18 | impl Parse for Create { 19 | fn parse(input: ParseStream) -> Result { 20 | Ok(Self { 21 | keyword: input.parse()?, 22 | colon: input.parse()?, 23 | boolean: input.parse()?, 24 | }) 25 | } 26 | } 27 | 28 | pub struct Get { 29 | pub expr: Expr, 30 | pub comma: Token![,], 31 | pub ampersand: Option, 32 | pub ident: Path, 33 | pub comma2: Option, 34 | pub create: Option, 35 | } 36 | 37 | impl Get { 38 | pub fn expand(self) -> TokenStream { 39 | let Get { 40 | ident, 41 | expr, 42 | ampersand, 43 | create, 44 | .. 45 | } = self; 46 | let can_create = if let Some(create) = create { 47 | create.boolean.value 48 | } else { 49 | true 50 | }; 51 | 52 | let can_fallback = if let Some(segment) = ident.segments.last() { 53 | (segment.ident != "Arc") && can_create 54 | } else { 55 | false 56 | }; 57 | 58 | let fallback = can_fallback.and_then(|| { 59 | quote! {.or_else(|_| <#ident>::inject(#expr))} 60 | }); 61 | 62 | if ampersand.is_none() { 63 | quote! { 64 | { 65 | use ::inject::{Inject, InjectExt}; 66 | (#expr) 67 | .get::<#ident>() 68 | #fallback 69 | 70 | } 71 | } 72 | } else { 73 | quote! { 74 | { 75 | use ::inject::{Inject, InjectExt}; 76 | (#expr) 77 | .get_ref::<#ident>() 78 | } 79 | } 80 | } 81 | } 82 | } 83 | 84 | impl Parse for Get { 85 | fn parse(input: ParseStream) -> Result { 86 | let expr = input.parse()?; 87 | let comma = input.parse()?; 88 | let ampersand = input.parse()?; 89 | let ident: Path = input.parse()?; 90 | let comma2 = input.parse()?; 91 | let create = (!input.is_empty()).and_then(|| input.parse()).transpose()?; 92 | 93 | Ok(Self { 94 | expr, 95 | comma, 96 | ampersand, 97 | ident, 98 | comma2, 99 | create, 100 | }) 101 | } 102 | } 103 | 104 | #[cfg(test)] 105 | mod tests { 106 | use proc_macro2::TokenStream; 107 | use quote::{quote, ToTokens}; 108 | use syn::parse2; 109 | 110 | use super::Get; 111 | 112 | #[test] 113 | fn test_parsing_args() { 114 | let tree = quote! { 115 | &container, A 116 | }; 117 | 118 | let get: Get = parse2(tree).unwrap(); 119 | 120 | assert_eq!(get.ident.to_token_stream().to_string(), "A < isize >"); 121 | assert_eq!(get.comma.to_token_stream().to_string(), ","); 122 | assert_eq!(get.expr.to_token_stream().to_string(), "& container"); 123 | } 124 | 125 | #[test] 126 | fn test_expansion() { 127 | let tree = quote! { 128 | &container, A 129 | }; 130 | 131 | let expected = quote! { 132 | { 133 | use ::inject::{Inject, InjectExt}; 134 | (&container) 135 | .get:: >() 136 | .or_else(|_| < A < isize > >::inject(& container ) ) 137 | } 138 | }; 139 | 140 | let get: TokenStream = parse2::(tree).unwrap().expand(); 141 | 142 | assert_eq!(get.to_string(), expected.to_string()) 143 | } 144 | 145 | #[test] 146 | fn test_arc_expansion() { 147 | let tree = quote! { 148 | &container, Arc> 149 | }; 150 | 151 | let expected = quote! { 152 | { 153 | use ::inject::{Inject, InjectExt}; 154 | (&container).get:: > >() 155 | } 156 | }; 157 | 158 | let get: TokenStream = parse2::(tree).unwrap().expand(); 159 | 160 | assert_eq!(get.to_string(), expected.to_string()) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /inject-macro/src/inject/arguments/argument/default.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::Entry; 2 | use std::collections::{HashMap, HashSet}; 3 | use syn::parse::Parse; 4 | use syn::punctuated::Punctuated; 5 | use syn::{Error, Expr, Ident, Result, Token}; 6 | 7 | use crate::inject::arguments::argument::mergable::Mergable; 8 | use crate::inject::arguments::error::duplicate_field_error; 9 | 10 | pub struct DefaultArgs(HashMap); 11 | 12 | impl DefaultArgs { 13 | pub fn remove(&mut self, field: &Ident) -> Option { 14 | self.0.remove(field) 15 | } 16 | 17 | pub fn field_set(&self) -> HashSet { 18 | self.fields().cloned().collect() 19 | } 20 | pub fn fields(&self) -> impl Iterator { 21 | self.0.keys() 22 | } 23 | } 24 | 25 | impl Parse for DefaultArgs { 26 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 27 | let mut arg_map = HashMap::default(); 28 | let args: Punctuated = input.parse_terminated(DefaultArg::parse)?; 29 | for arg in args { 30 | match arg_map.entry(arg.field.clone()) { 31 | Entry::Vacant(entry) => entry.insert( 32 | arg.value 33 | .unwrap_or_else(|| syn::parse_str("Default::default()").unwrap()), 34 | ), 35 | _ => { 36 | return Err(Error::new( 37 | arg.field.span(), 38 | format!("duplicate identifier '{}'", arg.field), 39 | )) 40 | } 41 | }; 42 | } 43 | Ok(DefaultArgs(arg_map)) 44 | } 45 | } 46 | 47 | impl Mergable for DefaultArgs { 48 | fn merge(mut self, other: Self) -> Result { 49 | if let Some(same) = self.field_set().intersection(&other.field_set()).next() { 50 | return Err(duplicate_field_error(same)); 51 | } 52 | self.0.extend(other.0.into_iter()); 53 | Ok(self) 54 | } 55 | } 56 | 57 | struct DefaultArg { 58 | pub field: Ident, 59 | pub eq: Option, 60 | pub value: Option, 61 | } 62 | 63 | impl Parse for DefaultArg { 64 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 65 | let field = input.parse()?; 66 | let eq: Option = input.parse()?; 67 | let value = if eq.is_some() { 68 | Some(input.parse()?) 69 | } else { 70 | None 71 | }; 72 | Ok(DefaultArg { field, eq, value }) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /inject-macro/src/inject/arguments/argument/mergable.rs: -------------------------------------------------------------------------------- 1 | use syn::Result; 2 | pub trait Mergable: Sized { 3 | fn merge(self, other: Self) -> Result; 4 | 5 | fn merge_many(mergables: impl IntoIterator) -> Result> { 6 | let mut mergables = mergables.into_iter(); 7 | let first = mergables.next(); 8 | Ok(if let Some(mut first) = first { 9 | for mergable in mergables { 10 | first = mergable.merge(first)?; 11 | } 12 | Some(first) 13 | } else { 14 | None 15 | }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /inject-macro/src/inject/arguments/argument/mod.rs: -------------------------------------------------------------------------------- 1 | use syn::parse::{Parse, ParseStream}; 2 | use syn::token::Paren; 3 | use syn::{parenthesized, Result}; 4 | 5 | mod default; 6 | mod mergable; 7 | mod no_inject; 8 | 9 | pub use default::DefaultArgs; 10 | pub use mergable::Mergable; 11 | pub use no_inject::NoInjectArgs; 12 | 13 | mod kw { 14 | syn::custom_keyword!(default); 15 | syn::custom_keyword!(no_inject); 16 | } 17 | 18 | #[allow(dead_code)] 19 | pub enum InjectArgument { 20 | Default { 21 | keyword: kw::default, 22 | paren: Paren, 23 | args: DefaultArgs, 24 | }, 25 | NoInject { 26 | keyword: kw::no_inject, 27 | paren: Paren, 28 | args: NoInjectArgs, 29 | }, 30 | } 31 | 32 | impl Parse for InjectArgument { 33 | fn parse(input: ParseStream) -> Result { 34 | let content; 35 | let lookahead = input.lookahead1(); 36 | Ok(if lookahead.peek(kw::default) { 37 | Self::Default { 38 | keyword: input.parse()?, 39 | paren: parenthesized!(content in input), 40 | args: content.parse()?, 41 | } 42 | } else if lookahead.peek(kw::no_inject) { 43 | Self::NoInject { 44 | keyword: input.parse()?, 45 | paren: parenthesized!(content in input), 46 | args: content.parse()?, 47 | } 48 | } else { 49 | return Err(lookahead.error()); 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /inject-macro/src/inject/arguments/argument/no_inject.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use syn::parse::Parse; 4 | use syn::punctuated::Punctuated; 5 | use syn::{Error, Ident, Result, Token}; 6 | 7 | use crate::inject::arguments::argument::mergable::Mergable; 8 | use crate::inject::arguments::error::duplicate_field_error; 9 | 10 | pub struct NoInjectArgs(HashSet); 11 | 12 | impl NoInjectArgs { 13 | pub fn fields(&self) -> impl Iterator { 14 | self.0.iter() 15 | } 16 | 17 | pub fn remove(&mut self, field: &Ident) -> bool { 18 | self.0.remove(field) 19 | } 20 | } 21 | 22 | impl Mergable for NoInjectArgs { 23 | fn merge(mut self, other: Self) -> Result { 24 | if let Some(same) = self.0.intersection(&other.0).next() { 25 | return Err(duplicate_field_error(same)); 26 | } 27 | self.0.extend(other.0.into_iter()); 28 | Ok(self) 29 | } 30 | } 31 | 32 | impl Parse for NoInjectArgs { 33 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 34 | let fields: Punctuated<_, Token![,]> = input.parse_terminated(syn::Ident::parse)?; 35 | let mut arg_set = HashSet::default(); 36 | for field in fields { 37 | if arg_set.contains(&field) { 38 | return Err(Error::new( 39 | field.span(), 40 | format!("duplicate identifier '{}'", field), 41 | )); 42 | } else { 43 | arg_set.insert(field); 44 | } 45 | } 46 | Ok(NoInjectArgs(arg_set)) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /inject-macro/src/inject/arguments/error.rs: -------------------------------------------------------------------------------- 1 | use syn::{Error, Ident}; 2 | 3 | pub fn duplicate_field_error(field: &Ident) -> Error { 4 | Error::new(field.span(), format!("duplicate identifier '{}'", field)) 5 | } 6 | -------------------------------------------------------------------------------- /inject-macro/src/inject/arguments/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use argument::{DefaultArgs, InjectArgument, Mergable, NoInjectArgs}; 4 | use proc_macro2::TokenStream; 5 | use quote::quote; 6 | use syn::parse::{Parse, ParseStream}; 7 | use syn::punctuated::Punctuated; 8 | use syn::spanned::Spanned; 9 | use syn::{Error, Expr, Ident, Result, Token}; 10 | 11 | use crate::inject::input::InjectableSignature; 12 | 13 | mod argument; 14 | pub mod error; 15 | 16 | pub(crate) struct InjectArgs { 17 | default_args: Option, 18 | no_inject_args: Option, 19 | } 20 | 21 | impl InjectArgs { 22 | pub fn expand_signature( 23 | mut self, 24 | sig: &dyn InjectableSignature, 25 | ) -> Result>> { 26 | let mut args = vec![]; 27 | let mut fields = vec![]; 28 | for argument in sig.inputs() { 29 | match argument { 30 | syn::FnArg::Typed(syn::PatType { pat, ty, .. }) => { 31 | if let syn::Pat::Ident(syn::PatIdent { ident, .. }) = pat.as_ref() { 32 | let default_arg = self.get_default(&ident); 33 | let should_inject = !self.is_no_inject(&ident); 34 | fields.push(format!("'{}'", ident)); 35 | let injected_arg = 36 | Some(quote! { ::inject::get!(container, #ty, create: #should_inject) }) 37 | .map(|injection| { 38 | if default_arg.is_some() { 39 | quote! { #injection.or_else(|_| Ok(#default_arg) ) } 40 | } else { 41 | injection 42 | } 43 | }) 44 | .map(|injection| quote! { #injection? }); 45 | 46 | args.push(injected_arg) 47 | } 48 | } 49 | syn::FnArg::Receiver(receiver) => { 50 | return Err(Error::new( 51 | receiver.self_token.span(), 52 | "not allowed to reference 'self'", 53 | )) 54 | } 55 | } 56 | } 57 | 58 | if let Some(&extra_field) = self.remaining().iter().next() { 59 | return Err(Error::new( 60 | extra_field.span(), 61 | format!("unknown identifier, expected {}", fields.join(", ")), 62 | )); 63 | } 64 | 65 | Ok(args) 66 | } 67 | 68 | fn get_default(&mut self, field: &Ident) -> Option { 69 | self.default_args 70 | .as_mut() 71 | .map(|args| args.remove(&field)) 72 | .flatten() 73 | } 74 | 75 | fn is_no_inject(&mut self, field: &Ident) -> bool { 76 | self.no_inject_args 77 | .as_mut() 78 | .map(|args| args.remove(&field)) 79 | .unwrap_or(false) 80 | } 81 | 82 | fn remaining(&self) -> HashSet<&Ident> { 83 | &self.remaining_defaults() | &self.remaining_no_injects() 84 | } 85 | 86 | fn remaining_defaults(&self) -> HashSet<&Ident> { 87 | self.default_args 88 | .as_ref() 89 | .map(|args| args.fields().collect()) 90 | .unwrap_or_default() 91 | } 92 | 93 | fn remaining_no_injects(&self) -> HashSet<&Ident> { 94 | self.no_inject_args 95 | .as_ref() 96 | .map(|args| args.fields().collect()) 97 | .unwrap_or_default() 98 | } 99 | } 100 | 101 | impl Parse for InjectArgs { 102 | fn parse(input: ParseStream) -> Result { 103 | let mut default_args = vec![]; 104 | let mut no_inject_args = vec![]; 105 | 106 | let parsed_arguments: Punctuated = 107 | input.parse_terminated(InjectArgument::parse)?; 108 | 109 | for arg in parsed_arguments { 110 | match arg { 111 | InjectArgument::Default { args, .. } => default_args.push(args), 112 | InjectArgument::NoInject { args, .. } => no_inject_args.push(args), 113 | } 114 | } 115 | 116 | let default_args = Mergable::merge_many(default_args)?; 117 | let no_inject_args = Mergable::merge_many(no_inject_args)?; 118 | 119 | Ok(InjectArgs { 120 | default_args, 121 | no_inject_args, 122 | }) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /inject-macro/src/inject/input/constructor.rs: -------------------------------------------------------------------------------- 1 | use syn::parse::{Parse, ParseStream}; 2 | use syn::punctuated::Punctuated; 3 | use syn::token::Paren; 4 | use syn::{parenthesized, Attribute, Block, FnArg, Generics, Ident, Result, Token, Visibility}; 5 | 6 | use crate::inject::input::InjectableSignature; 7 | 8 | pub struct ConstructorImpl { 9 | pub attrs: Vec, 10 | pub vis: Visibility, 11 | pub defaultness: Option, 12 | pub sig: Constructor, 13 | pub block: Block, 14 | } 15 | 16 | impl Parse for ConstructorImpl { 17 | fn parse(input: ParseStream) -> Result { 18 | Ok(Self { 19 | attrs: Attribute::parse_outer(input)?, 20 | vis: input.parse()?, 21 | defaultness: input.parse()?, 22 | sig: input.parse()?, 23 | block: input.parse()?, 24 | }) 25 | } 26 | } 27 | 28 | impl InjectableSignature for Constructor { 29 | fn ident(&self) -> &Ident { 30 | &self.ident 31 | } 32 | 33 | fn generics(&self) -> &Generics { 34 | &self.generics 35 | } 36 | 37 | fn inputs(&self) -> &Punctuated { 38 | &self.inputs 39 | } 40 | } 41 | 42 | pub struct Constructor { 43 | pub fn_token: Token![fn], 44 | pub ident: Ident, 45 | pub generics: Generics, 46 | pub paren: Paren, 47 | pub inputs: Punctuated, 48 | pub arrow: Token![->], 49 | pub output: Token![Self], 50 | } 51 | 52 | impl Parse for Constructor { 53 | fn parse(input: ParseStream) -> Result { 54 | let content; 55 | Ok(Self { 56 | fn_token: input.parse()?, 57 | ident: input.parse()?, 58 | generics: input.parse()?, 59 | paren: parenthesized!(content in input), 60 | inputs: Punctuated::parse_terminated(&content)?, 61 | arrow: input.parse()?, 62 | output: input 63 | .parse() 64 | .or_else(|_| Err(input.error("expected 'Self'")))?, 65 | }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /inject-macro/src/inject/input/free_function.rs: -------------------------------------------------------------------------------- 1 | use syn::parse::{Parse, ParseStream}; 2 | use syn::punctuated::Punctuated; 3 | use syn::token::Paren; 4 | use syn::{ 5 | parenthesized, Attribute, Block, FnArg, Generics, Ident, Result, ReturnType, Token, Type, 6 | TypePath, Visibility, 7 | }; 8 | 9 | use crate::inject::input::InjectableSignature; 10 | 11 | pub struct FreeFunctionImpl { 12 | pub attrs: Vec, 13 | pub vis: Visibility, 14 | pub sig: FreeFunction, 15 | pub block: Block, 16 | } 17 | 18 | impl Parse for FreeFunctionImpl { 19 | fn parse(input: ParseStream) -> Result { 20 | Ok(Self { 21 | attrs: Attribute::parse_outer(input)?, 22 | vis: input.parse()?, 23 | sig: input.parse()?, 24 | block: input.parse()?, 25 | }) 26 | } 27 | } 28 | 29 | pub struct FreeFunction { 30 | pub unsafety: Option, 31 | pub asyncness: Option, 32 | pub fn_token: Token![fn], 33 | pub ident: Ident, 34 | pub generics: Generics, 35 | pub paren: Paren, 36 | pub inputs: Punctuated, 37 | pub output: ReturnType, 38 | } 39 | 40 | impl Parse for FreeFunction { 41 | fn parse(input: ParseStream) -> Result { 42 | let content; 43 | let unsafety = input.parse()?; 44 | let asyncness = input.parse()?; 45 | let fn_token = input.parse()?; 46 | let ident = input.parse()?; 47 | let generics = input.parse()?; 48 | let paren = parenthesized!(content in input); 49 | let inputs = Punctuated::parse_terminated(&content)?; 50 | let output = input.parse()?; 51 | if let ReturnType::Type(_, ty) = &output { 52 | if let Type::Path(TypePath { path, .. }) = ty.as_ref() { 53 | if let Some(segment) = path.segments.last() { 54 | if segment.ident == "Self" { 55 | return Err(input.error("'Self' not allowed in free function")); 56 | } 57 | } 58 | } 59 | }; 60 | Ok(Self { 61 | unsafety, 62 | asyncness, 63 | fn_token, 64 | ident, 65 | generics, 66 | paren, 67 | inputs, 68 | output, 69 | }) 70 | } 71 | } 72 | 73 | impl InjectableSignature for FreeFunction { 74 | fn ident(&self) -> &Ident { 75 | &self.ident 76 | } 77 | 78 | fn generics(&self) -> &Generics { 79 | &self.generics 80 | } 81 | 82 | fn inputs(&self) -> &Punctuated { 83 | &self.inputs 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /inject-macro/src/inject/input/injectable_signature.rs: -------------------------------------------------------------------------------- 1 | use syn::punctuated::Punctuated; 2 | use syn::{FnArg, Generics, Ident, Token}; 3 | 4 | pub trait InjectableSignature { 5 | fn ident(&self) -> &Ident; 6 | 7 | fn generics(&self) -> &Generics; 8 | 9 | fn inputs(&self) -> &Punctuated; 10 | 11 | fn input_idents(&self) -> Vec<&Ident> { 12 | self.inputs() 13 | .iter() 14 | .filter_map(|arg| match arg { 15 | syn::FnArg::Typed(syn::PatType { pat, .. }) => Some(pat), 16 | _ => None, 17 | }) 18 | .filter_map(|pat| match pat.as_ref() { 19 | syn::Pat::Ident(syn::PatIdent { ident, .. }) => Some(ident), 20 | _ => None, 21 | }) 22 | .collect() 23 | } 24 | } 25 | 26 | impl InjectableSignature for syn::Signature { 27 | fn ident(&self) -> &Ident { 28 | &self.ident 29 | } 30 | 31 | fn generics(&self) -> &Generics { 32 | &self.generics 33 | } 34 | 35 | fn inputs(&self) -> &Punctuated { 36 | &self.inputs 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /inject-macro/src/inject/input/mod.rs: -------------------------------------------------------------------------------- 1 | use syn::parse::{Parse, ParseStream}; 2 | use syn::{Ident, Result}; 3 | 4 | mod constructor; 5 | mod free_function; 6 | mod injectable_signature; 7 | 8 | use constructor::ConstructorImpl; 9 | use free_function::FreeFunctionImpl; 10 | pub use injectable_signature::InjectableSignature; 11 | 12 | pub enum InjectInput { 13 | Constructor(ConstructorImpl), 14 | FreeFunction(FreeFunctionImpl), 15 | } 16 | 17 | impl InjectInput { 18 | pub fn name(&self) -> &Ident { 19 | self.signature().ident() 20 | } 21 | 22 | pub fn inputs(&self) -> Vec<&Ident> { 23 | self.signature().input_idents() 24 | } 25 | 26 | pub fn signature(&self) -> &dyn InjectableSignature { 27 | match self { 28 | InjectInput::Constructor(method) => &method.sig, 29 | InjectInput::FreeFunction(method) => &method.sig, 30 | } 31 | } 32 | } 33 | 34 | impl Parse for InjectInput { 35 | fn parse(input: ParseStream) -> Result { 36 | if input.fork().parse::().is_ok() { 37 | input 38 | .parse::() 39 | .map(InjectInput::Constructor) 40 | } else { 41 | input.parse().map(InjectInput::FreeFunction) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /inject-macro/src/inject/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{format_ident, quote}; 3 | use syn::{parse, Ident, Result}; 4 | 5 | use arguments::InjectArgs; 6 | use input::InjectInput; 7 | 8 | pub mod arguments; 9 | mod input; 10 | 11 | pub struct Inject { 12 | origin: TokenStream, 13 | args: InjectArgs, 14 | method: InjectInput, 15 | } 16 | 17 | impl Inject { 18 | pub fn try_parse( 19 | arguments: proc_macro::TokenStream, 20 | method: proc_macro::TokenStream, 21 | ) -> Result { 22 | let origin = proc_macro2::TokenStream::from(method.clone()); 23 | let method = parse(method)?; 24 | let args = parse(arguments)?; 25 | 26 | Ok(Self { 27 | origin, 28 | args, 29 | method, 30 | }) 31 | } 32 | 33 | pub fn expand(self) -> TokenStream { 34 | let Self { 35 | origin, 36 | args, 37 | method, 38 | } = self; 39 | 40 | let name = method.name(); 41 | let inputs = method.inputs(); 42 | let args = match args.expand_signature(method.signature()) { 43 | Ok(parsed_args) => parsed_args, 44 | Err(compile_error) => return compile_error.to_compile_error(), 45 | }; 46 | 47 | let expansion = match method { 48 | InjectInput::Constructor(_) => Self::expand_constructor(name, args), 49 | InjectInput::FreeFunction(_) => Self::expand_free_function(name, inputs, args), 50 | }; 51 | 52 | quote! { 53 | #[allow(dead_code)] 54 | #origin 55 | #expansion 56 | } 57 | } 58 | 59 | fn expand_constructor(name: &Ident, args: Vec>) -> TokenStream { 60 | quote! { 61 | pub fn inject(container: &::inject::Container) -> Result { 62 | Ok( 63 | Self:: #name ( #(#args,)* ) 64 | ) 65 | } 66 | } 67 | } 68 | 69 | fn expand_free_function( 70 | name: &Ident, 71 | inputs: Vec<&Ident>, 72 | args: Vec>, 73 | ) -> TokenStream { 74 | let macro_name = format_ident!("__inject_{}", name); 75 | let macro_expansion = format_ident!("{}_expand", macro_name); 76 | 77 | let expand_macro = quote! { 78 | #[doc(hidden)] 79 | #[macro_export] 80 | macro_rules! #macro_expansion { 81 | ( ) => { }; 82 | #( ( $container:expr, #inputs #inputs : $arg:expr ) => { 83 | $arg 84 | }; 85 | ($container:expr, #inputs) => { 86 | { 87 | let container = $container; 88 | #args 89 | } 90 | 91 | }; 92 | )*} 93 | }; 94 | 95 | let inject_macro = quote! { 96 | #[doc(hidden)] 97 | #[macro_export] 98 | macro_rules! #macro_name { 99 | ($container:expr #( $(, #inputs : $#inputs:expr )? )* ) => { 100 | { 101 | let __helper = |__container: &::inject::Container| { 102 | Ok(#name ( #( #macro_expansion ! ( __container, #inputs $( #inputs : $#inputs )? ) ,)* )) 103 | }; 104 | let result: Result<_, ::inject::InjectError> = __helper($container); 105 | result 106 | } 107 | }; 108 | } 109 | }; 110 | 111 | quote! { 112 | #expand_macro 113 | #inject_macro 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /inject-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | use bool_to_option::BoolToOption; 2 | use call::Call; 3 | use container::Container; 4 | use get::Get; 5 | use inject::Inject; 6 | use syn::parse_macro_input; 7 | 8 | mod bool_to_option; 9 | mod call; 10 | mod container; 11 | mod get; 12 | mod inject; 13 | 14 | #[proc_macro_attribute] 15 | pub fn inject( 16 | arguments: proc_macro::TokenStream, 17 | method: proc_macro::TokenStream, 18 | ) -> proc_macro::TokenStream { 19 | let inject = match Inject::try_parse(arguments, method) { 20 | Ok(inject) => inject, 21 | Err(err) => return err.to_compile_error().into(), 22 | }; 23 | inject.expand().into() 24 | } 25 | 26 | #[proc_macro] 27 | pub fn get(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 28 | parse_macro_input!(input as Get).expand().into() 29 | } 30 | 31 | #[proc_macro] 32 | pub fn call(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 33 | parse_macro_input!(input as Call).expand().into() 34 | } 35 | 36 | #[proc_macro] 37 | pub fn container(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 38 | parse_macro_input!(input as Container).expand().into() 39 | } 40 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Errors due to injection resolution failure 2 | //! 3 | //! When injection fails due to a provider not being available, or a downcast has gone awry. 4 | //! 5 | //! Most probably, the encountered error will be `InjectError::MissingProvider`. 6 | use std::error::Error; 7 | use std::fmt::{Display, Formatter, Result}; 8 | 9 | #[derive(Debug, Eq, PartialEq, Copy, Clone)] 10 | pub enum InjectError { 11 | /// Returned when down-casting has failed for the installed provider. Very rare. 12 | FailedCast, 13 | /// Returned when a provider for the type is not present within 14 | /// the [`Container`](../struct.Container.html) 15 | MissingProvider, 16 | } 17 | 18 | impl Display for InjectError { 19 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 20 | match self { 21 | InjectError::FailedCast => write!(f, "failed cast"), 22 | InjectError::MissingProvider => write!(f, "no provider available"), 23 | } 24 | } 25 | } 26 | 27 | impl Error for InjectError {} 28 | -------------------------------------------------------------------------------- /src/inject.rs: -------------------------------------------------------------------------------- 1 | //! Injectable types 2 | //! 3 | //! Any type which is `'static` implements `Inject`, meaning it can be resolved by a 4 | //! [`Provider`](../provider/trait.Provider.html). 5 | //! 6 | //! Any type which implements `Inject` and `Default` trivially implements `InjectExt`, which means 7 | //! that when no provider is present, and [`get!`](../macro.get.html) is used to resolve a type, 8 | //! `InjectExt::inject(..)` will be invoked. 9 | //! 10 | use std::any::TypeId; 11 | 12 | /// Marker trait for an injectable type. 13 | pub trait Inject: 'static {} 14 | 15 | /// Trait to blanket implement an associated `inject` method for all types implementing `Default`, 16 | /// enabling ergonomic [`get!`](../macro.get.html) and [`call!`](../macro.call.html) usages. 17 | /// 18 | pub trait InjectExt: Inject + Default { 19 | fn inject(_container: &crate::Container) -> Result { 20 | Ok(Self::default()) 21 | } 22 | } 23 | 24 | pub fn id() -> TypeId { 25 | TypeId::of::() 26 | } 27 | 28 | impl Inject for T {} 29 | impl InjectExt for T {} 30 | 31 | #[cfg(test)] 32 | mod tests { 33 | use std::sync::Arc; 34 | 35 | use super::*; 36 | 37 | #[derive(Debug, PartialEq, Clone, Copy)] 38 | struct FakeImpl { 39 | val: isize, 40 | } 41 | 42 | #[derive(Debug, PartialEq, Clone, Copy)] 43 | struct FakeImpl2 { 44 | val: isize, 45 | } 46 | 47 | trait FakeTrait: Sized + Send {} 48 | 49 | impl FakeTrait for FakeImpl {} 50 | 51 | #[test] 52 | fn test_reference_of_type_does_not_share_type_id_with_type() { 53 | assert_ne!(id::(), id::>()) 54 | } 55 | 56 | #[test] 57 | fn test_references_of_different_types_do_not_share_type_id() { 58 | assert_ne!(id::>(), id::>()) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Library for dependency injection 2 | //! 3 | //! Inject implements macros for dependency resolution, attempting to promote the 4 | //! [Inversion of control](https://en.wikipedia.org/wiki/Inversion_of_control) (IoC) principle. 5 | //! 6 | //! # Quick Start 7 | //! 8 | //! To get you started quickly, there are 3 procedural macros and one attribute macro to keep track of: 9 | //! [`container!`](macro.container.html), [`get!`](macro.get.html), [`call!`](macro.call.html) and 10 | //! [`#[inject]`](attr.inject.html). 11 | //! 12 | //! [`container!`](macro.container.html) constructs a new container with [providers](provider/index.html). 13 | //! 14 | //! [`get!`](macro.get.html) resolves a type using a container. 15 | //! 16 | //! [`call!`](macro.call.html) invokes a function using a container. 17 | //! 18 | //! [`#[inject]`](attr.inject.html) generates code to enable the above two macros. 19 | //! 20 | //! # Example 21 | //! 22 | //! ``` 23 | //! use ::inject::{container, get, inject}; 24 | //! 25 | //! struct Connection(isize); 26 | //! 27 | //! impl Connection { 28 | //! #[inject] 29 | //! fn new(foo: isize) -> Self { 30 | //! Self(foo) 31 | //! } 32 | //! } 33 | //! 34 | //! struct Instance(isize); 35 | //! 36 | //! impl Instance { 37 | //! #[inject] 38 | //! fn new(conn: &Connection) -> Self { 39 | //! Self(conn.0) 40 | //! } 41 | //! } 42 | //! let conn = Box::new(Connection(2)); 43 | //! let container = container![ 44 | //! ref conn 45 | //! ]; 46 | //! 47 | //! let instance = get!(&container, Instance).unwrap(); 48 | //! 49 | //! assert_eq!(instance.0, 2) 50 | //! 51 | //! ``` 52 | //! 53 | //! The container resolves the dependencies of the `Instance` struct, using the installed provider to 54 | //! resolve the `&Connection` dependency. 55 | 56 | use std::any::Any; 57 | use std::any::TypeId; 58 | use std::collections::HashMap; 59 | use std::sync::Arc; 60 | 61 | /// Call a function with dependency resolution for its arguments 62 | /// 63 | /// `call!(..) accepts 2-3 arguments. 64 | /// 1. The first argument can be any expression, and should return a 65 | /// reference to a [`Container`](struct.Container.html) instance. 66 | /// 2. The second argument should be the name of a function that has been annotated using the 67 | /// [`#[inject]`](attr.inject.html) attribute. 68 | /// 3. Optionally, a sequence of keyword-value-arguments (kwargs) can be supplied on the form 69 | /// `kwargs = {arg1: expr1, arg2: expr2}`, for a method with arguments 70 | /// 71 | /// # Examples 72 | /// 73 | /// ``` 74 | /// use ::inject::{call, container, inject}; 75 | /// 76 | /// // A function that can be called with call!(..) 77 | /// #[inject] 78 | /// fn foo(a: isize) -> isize { 79 | /// a + 1 80 | /// } 81 | /// 82 | /// // An empty container. 83 | /// let container = container![]; 84 | /// 85 | /// // call "foo", argument(s) resolved using injection. 86 | /// // This time, it will be resolved to 0 (default). 87 | /// let result = call!(&container, foo).unwrap(); 88 | /// 89 | /// assert_eq!(result, 1); 90 | /// 91 | /// // call "foo", this time using the provided kwarg "a = 2" 92 | /// let result = call!(&container, foo, kwargs = { a: 2 }).unwrap(); 93 | /// 94 | /// assert_eq!(result, 3); 95 | /// ``` 96 | /// 97 | /// 98 | /// Kwargs are parsed recursively and can be supplied in any order. They take precedence over any 99 | /// installed provider for the invoking of the function. 100 | /// 101 | /// ``` 102 | /// use ::inject::{call, Container, container, inject}; 103 | /// 104 | /// // A struct which is allowed to be constructed 105 | /// // with a provided "isize" type. 106 | /// struct Injectable(isize); 107 | /// 108 | /// impl Injectable { 109 | /// #[inject] 110 | /// fn new(a: isize) -> Self { 111 | /// Self(a) 112 | /// } 113 | /// } 114 | /// 115 | /// #[inject] 116 | /// fn callable(injectable: Injectable) { 117 | /// println!("{} + {} = {}", injectable.0, 2, injectable.0 + 2); 118 | /// } 119 | /// 120 | /// let mut container = container![]; 121 | /// 122 | /// // prints 0 + 2 = 2 123 | /// call!(&container, callable); 124 | /// 125 | /// container.install(|container: &Container| Ok(Injectable::new(3))); 126 | /// 127 | /// // prints 3 + 2 = 5 128 | /// call!(&container, callable); 129 | /// 130 | /// // prints 1 + 2 = 3 131 | /// call!(&container, callable, kwargs = { injectable: Injectable::new(1) }); 132 | /// 133 | /// ``` 134 | /// 135 | /// Under the hood, if an arg is not provided a corresponding kwarg, the 136 | /// [`get!`](macro.get.html) macro is used to evaluate the argument. 137 | /// 138 | pub use inject_macro::call; 139 | 140 | /// Create a container with providers 141 | /// 142 | /// `container![..]` accepts any number of arguments, each which is expected to implement one of the 143 | /// [provider traits](provider.mod.html) 144 | /// 145 | pub use inject_macro::container; 146 | 147 | /// Resolve a dependency from a container 148 | /// 149 | /// `get!(..)` accepts 2-3 arguments. 150 | /// 1. The first argument can be any expression, and should return a 151 | /// reference to a [`Container`](struct.Container.html) instance. 152 | /// 2. The second argument should be 153 | /// a type which we want to resolve, optionally prepended by an '`&`' to indicate that we 154 | /// want a reference. 155 | /// 3. Lastly, the `create: (true|false)` key-value can be supplied to indicate 156 | /// that we only want to use a `Provider` for the type, NOT the associated `inject` method. 157 | /// 158 | /// # Example 159 | /// 160 | /// ``` 161 | /// use inject::{Container, get}; 162 | /// 163 | /// // Derive default for brevity, see #[inject] for more intricate usages. 164 | /// #[derive(Default)] 165 | /// struct Foo; 166 | /// 167 | /// let container = Container::new(); 168 | /// 169 | /// // 1. Attempt to resolve a reference 170 | /// let result = get!(&container, &Foo); 171 | /// 172 | /// // Fails because no reference-provider is installed into "container". 173 | /// assert!(result.is_err()); 174 | /// 175 | /// // 2. Attempt to resolve a value 176 | /// let result = get!(&container, Foo); 177 | /// 178 | /// // Succeeds because the type could be resolved with injection using Foo::inject(&container). 179 | /// assert!(result.is_ok()); 180 | /// 181 | /// // 3. Attempt to resolve a value, but ONLY using a Provider 182 | /// let result = get!(&container, Foo, create: false); 183 | /// 184 | /// // Fails because no value-provider is installed into "container". 185 | /// assert!(result.is_err()); 186 | /// 187 | /// ``` 188 | /// 189 | /// The `create: (true|false)` key-value only holds meaning for value types. New references cannot be 190 | /// created by the macro, as their corresponding instance is dropped on return. 191 | /// 192 | pub use inject_macro::get; 193 | 194 | /// Generate functionality for a function/constructor to be injectable 195 | /// 196 | /// [`#[inject]`](attr.inject.html) accepts two positions: 197 | /// in a "free" function, or a struct impl method that returns `Self`. 198 | /// 199 | /// # Examples 200 | /// 201 | /// When in struct impl position, a new associated method `inject` is generated, in which 202 | /// the `get!` macro is invoked for each argument. 203 | /// ``` 204 | /// use ::inject::{Container, inject}; 205 | /// 206 | /// #[derive(Debug, PartialEq)] 207 | /// struct A(String); 208 | /// 209 | /// impl A { 210 | /// #[inject] 211 | /// fn new(string: String) -> Self { 212 | /// Self(string) 213 | /// } 214 | /// } 215 | /// 216 | /// let container = Container::new(); 217 | /// assert_eq!(A::new("".into()), A::inject(&container).unwrap()) 218 | /// 219 | /// ``` 220 | /// 221 | /// When in free function position, a set of macro_rules macros are generated (and hidden), which 222 | /// enables injection and kwarg-style resolution of the function arguments. 223 | /// 224 | /// ``` 225 | /// use ::inject::{call, Container, container, inject}; 226 | /// 227 | /// #[inject] 228 | /// fn injectable(a: usize, b: usize) -> usize { a + b } 229 | /// 230 | /// // A container with a value provider for "usize" 231 | /// let container = container![ 232 | /// |container: &Container| Ok(2usize), 233 | /// ]; 234 | /// 235 | /// // Call the function, letting injection resolve the values 236 | /// let result = call!(&container, injectable).unwrap(); 237 | /// assert_eq!(result, 4); 238 | /// 239 | /// // Use kwargs to provide a value for one of the args of the function 240 | /// // By using macros generated by the #[inject] attribute. 241 | /// let result = call!(&container, injectable, kwargs = { b: 12 }).unwrap(); 242 | /// assert_eq!(result, 14); 243 | /// 244 | /// ``` 245 | pub use inject_macro::inject; 246 | 247 | pub use error::InjectError; 248 | pub use provider::{Provider, RefProvider}; 249 | 250 | pub use crate::inject::{Inject, InjectExt}; 251 | 252 | pub mod error; 253 | pub mod inject; 254 | pub mod module; 255 | pub mod provider; 256 | pub mod providers; 257 | 258 | /// Contains providers for resolvable types. 259 | /// 260 | /// The macro `container!` simplifies the construction of `Container`s calling 261 | /// [`container.install(..)`](struct.Container.html#method.install) and 262 | /// [`container.install_ref(..)`](struct.Container.html#method.install_ref) (if `ref` keyword is supplied) 263 | /// on the macro arguments provided. 264 | /// 265 | /// To resolve a dependecy by using the installed providers, methods 266 | /// [`container.get()`](struct.Container.html#method.get) and 267 | /// [`container.get_ref()`](struct.Container.html#method.get_ref) are called for values and references, 268 | /// respectively. 269 | /// 270 | /// 271 | /// # Example 272 | /// 273 | /// ``` 274 | /// use inject::{Container, container}; 275 | /// 276 | /// let reference_provider = Box::new(5usize); 277 | /// let container = container![ 278 | /// |container: &Container| Ok(2i32), 279 | /// ref reference_provider, 280 | /// ]; 281 | /// 282 | /// assert_eq!(5usize, *container.get_ref().unwrap()); 283 | /// assert_eq!(2i32, container.get().unwrap()); 284 | /// 285 | /// ``` 286 | #[derive(Clone, Debug, Default)] 287 | pub struct Container { 288 | providers: HashMap>, 289 | } 290 | 291 | impl Container { 292 | /// Create a new `Container`, used to store implementors of 293 | /// [`Provider`](provider/trait.Provider.html)s and [`RefProvider`](provider/trait.RefProvider.html)s. 294 | pub fn new() -> Container { 295 | Self::default() 296 | } 297 | 298 | /// Install a [`Provider`](provider/trait.Provider.html) into this `Container` 299 | pub fn install>(&mut self, provider: P) { 300 | self.providers 301 | .insert(provider.id(), Arc::new(Self::box_provider(provider))); 302 | } 303 | 304 | /// Install a [`RefProvider`](provider/trait.RefProvider.html) into this `Container` 305 | pub fn install_ref>( 306 | &mut self, 307 | provider: P, 308 | ) { 309 | self.providers 310 | .insert(provider.id(), Arc::new(Self::box_ref_provider(provider))); 311 | } 312 | 313 | /// Resolve a value-type from the installed [`Provider`](provider/trait.Provider.html)s. 314 | pub fn get(&self) -> Result { 315 | let provider = self 316 | .providers 317 | .get(&inject::id::()) 318 | .ok_or_else(|| InjectError::MissingProvider)? 319 | .downcast_ref::>>() 320 | .ok_or_else(|| InjectError::FailedCast)?; 321 | provider.provide(self) 322 | } 323 | 324 | /// Resolve a reference-type from the installed [`RefProvider`](provider/trait.RefProvider.html)s. 325 | pub fn get_ref(&self) -> Result<&T, InjectError> { 326 | let provider = self 327 | .providers 328 | .get(&inject::id::<&T>()) 329 | .ok_or_else(|| InjectError::MissingProvider)? 330 | .downcast_ref::>>() 331 | .ok_or_else(|| InjectError::FailedCast)?; 332 | provider.provide(self) 333 | } 334 | 335 | /// Clones the `Container`, returning a new container with the same providers. This is exactly 336 | /// equal to `Container::clone(&container)`. 337 | /// 338 | /// # Example 339 | /// ``` 340 | /// use inject::{Container, container}; 341 | /// 342 | /// let container = container![|container: &Container| Ok(2usize)]; 343 | /// let child_container = container.create_child(); 344 | /// 345 | /// assert_eq!(child_container.get::(), container.get()) 346 | /// ``` 347 | pub fn create_child(&self) -> Self { 348 | self.clone() 349 | } 350 | 351 | fn box_provider>( 352 | provider: P, 353 | ) -> Box> { 354 | Box::new(provider) 355 | } 356 | 357 | fn box_ref_provider>( 358 | provider: P, 359 | ) -> Box> { 360 | Box::new(provider) 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /src/module.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! singleton { 3 | ($injectable:ty) => {{ 4 | struct SingletonProvider { 5 | instance: std::sync::Mutex>>, 6 | } 7 | 8 | impl $crate::Provider for SingletonProvider { 9 | type ProvidedType = std::sync::Arc<$injectable>; 10 | fn provide( 11 | &self, 12 | c: &$crate::Container, 13 | ) -> Result { 14 | loop { 15 | let mut instance = self.instance.lock().unwrap(); 16 | 17 | if let Some(instance) = instance.as_ref() { 18 | return Ok(std::sync::Arc::clone(&instance)); 19 | } else { 20 | let maybe_instance = $crate::get!(&c, $injectable); 21 | match maybe_instance { 22 | Ok(maybe_instance) => { 23 | *instance = Some(std::sync::Arc::new(maybe_instance)); 24 | } 25 | Err(err) => return Err(err), 26 | } 27 | } 28 | } 29 | } 30 | } 31 | SingletonProvider { 32 | instance: std::sync::Mutex::default(), 33 | } 34 | }}; 35 | } 36 | -------------------------------------------------------------------------------- /src/provider.rs: -------------------------------------------------------------------------------- 1 | //! Provides types 2 | //! 3 | //! Providers are implementations that can resolve a types construction, 4 | //! given a [`Container`](struct.Container.html), using the `provide(_ref)` methods. 5 | //! 6 | //! When a provider provides a value, the provider implements [`Provider`](trait.Provider.html). 7 | //! 8 | //! When a provider provides a reference, it implements [`RefProvider`](trait.RefProvider.html). 9 | //! 10 | //! 11 | //! # Examples 12 | //! 13 | //! For convenience, `Box`, `Rc` and `Arc` types all implement `RefProvider`. 14 | //! ``` 15 | //! use inject::{container, get, RefProvider}; 16 | //! 17 | //! let container = container![]; 18 | //! 19 | //! let boxed_value = Box::new(5); 20 | //! 21 | //! assert_eq!(Ok(&5), boxed_value.provide(&container)); 22 | //! ``` 23 | //! 24 | //! `Arc` also implements `Provider` using `Arc::clone`. 25 | //! ``` 26 | //! use std::sync::Arc; 27 | //! 28 | //! use inject::{container, get, Provider}; 29 | //! 30 | //! let arc = Arc::new(5isize); 31 | //! let container = container![ 32 | //! arc 33 | //! ]; 34 | //! 35 | //! assert_eq!(&5, get!(&container, Arc).unwrap().as_ref()); 36 | //! ``` 37 | //! 38 | //! Closures that implement `Fn(&Container) -> Result` 39 | //! also serves as factory functions for providing values as they implement `Provider`. 40 | //! 41 | //! ``` 42 | //! use inject::{Container, container, get}; 43 | //! 44 | //! let container = container![ 45 | //! |container: &Container| Ok(5usize) 46 | //! ]; 47 | //! 48 | //! assert_eq!(Ok(5), get!(&container, usize)) 49 | //! ``` 50 | 51 | use std::any::TypeId; 52 | use std::rc::Rc; 53 | use std::sync::Arc; 54 | 55 | use crate::inject::Inject; 56 | use crate::Container; 57 | use crate::InjectError; 58 | 59 | /// Value provider. 60 | pub trait Provider { 61 | type ProvidedType: 'static; 62 | 63 | /// Provides the value using the `Container` 64 | fn provide(&self, container: &Container) -> Result; 65 | 66 | /// Returns the type id of the provided type. 67 | fn id(&self) -> TypeId { 68 | TypeId::of::() 69 | } 70 | } 71 | 72 | impl Provider for Arc { 73 | type ProvidedType = Self; 74 | 75 | fn provide(&self, _container: &Container) -> Result { 76 | Ok(Arc::clone(&self)) 77 | } 78 | } 79 | 80 | impl Provider for F 81 | where 82 | F: Fn(&Container) -> Result, 83 | { 84 | type ProvidedType = T; 85 | fn provide(&self, container: &Container) -> Result { 86 | self(container) 87 | } 88 | } 89 | 90 | /// Reference provider. 91 | pub trait RefProvider { 92 | type ProvidedRef: 'static; 93 | 94 | /// Provides the reference using the `Container` 95 | fn provide<'a>( 96 | &'a self, 97 | container: &'a Container, 98 | ) -> Result<&'a Self::ProvidedRef, InjectError>; 99 | 100 | /// Returns the type id of the provided reference. 101 | fn id(&self) -> TypeId { 102 | TypeId::of::<&Self::ProvidedRef>() 103 | } 104 | } 105 | 106 | impl RefProvider for Arc { 107 | type ProvidedRef = T; 108 | 109 | fn provide<'a>(&'a self, _: &'a Container) -> Result<&'a T, InjectError> { 110 | Ok(&self) 111 | } 112 | } 113 | 114 | impl RefProvider for Rc { 115 | type ProvidedRef = T; 116 | 117 | fn provide<'a>(&'a self, _: &'a Container) -> Result<&'a T, InjectError> { 118 | Ok(&self) 119 | } 120 | } 121 | 122 | impl RefProvider for Box { 123 | type ProvidedRef = T; 124 | 125 | fn provide<'a>(&'a self, _: &'a Container) -> Result<&'a T, InjectError> { 126 | Ok(&self) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/providers.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::inject::Inject; 4 | use crate::provider::Provider; 5 | use crate::Container; 6 | use crate::InjectError; 7 | use std::marker::PhantomData; 8 | 9 | pub struct InstanceProvider { 10 | pub instance: Arc, 11 | } 12 | 13 | impl Provider for InstanceProvider { 14 | type ProvidedType = Arc; 15 | 16 | fn provide(&self, _: &Container) -> Result { 17 | Ok(self.instance.clone()) 18 | } 19 | } 20 | 21 | impl InstanceProvider { 22 | pub fn new(instance: T) -> Self { 23 | Self { 24 | instance: Arc::from(instance), 25 | } 26 | } 27 | 28 | pub fn install_into(self, container: &mut Container) { 29 | let cloned = Arc::clone(&self.instance); 30 | container.install(self); 31 | container.install_ref(cloned); 32 | } 33 | } 34 | 35 | #[derive(Debug, Default)] 36 | pub struct DefaultProvider { 37 | type_: PhantomData, 38 | } 39 | 40 | impl DefaultProvider { 41 | pub fn new() -> Self { 42 | Self::default() 43 | } 44 | } 45 | 46 | impl Provider for DefaultProvider { 47 | type ProvidedType = T; 48 | 49 | fn provide(&self, _: &Container) -> Result { 50 | Ok(T::default()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/fixtures.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ::inject::*; 4 | use rstest::*; 5 | 6 | pub trait TestTrait: Inject { 7 | fn hello(&self) -> &'static str { 8 | "Hello" 9 | } 10 | } 11 | 12 | pub struct TestTraitProvider { 13 | pub data: Arc, 14 | } 15 | 16 | impl Provider for TestTraitProvider { 17 | type ProvidedType = Arc; 18 | 19 | fn provide(&self, _: &Container) -> Result { 20 | Ok(self.data.clone()) 21 | } 22 | } 23 | 24 | impl TestTrait for Data {} 25 | 26 | pub struct DataArcProvider { 27 | pub data: Arc, 28 | } 29 | 30 | impl Provider for DataArcProvider { 31 | type ProvidedType = Arc; 32 | 33 | fn provide(&self, _: &Container) -> Result { 34 | Ok(self.data.clone()) 35 | } 36 | } 37 | 38 | pub struct DependsOnDyn { 39 | pub test_trait: Arc, 40 | } 41 | 42 | impl DependsOnDyn { 43 | #[inject] 44 | pub fn new(test_trait: Arc) -> Self { 45 | Self { test_trait } 46 | } 47 | } 48 | 49 | #[derive(Debug, Clone, Copy, PartialEq)] 50 | pub struct Data { 51 | pub a: isize, 52 | } 53 | 54 | impl Data { 55 | #[inject(default(a = 1), no_inject(a))] 56 | pub fn new(a: isize) -> Self { 57 | Data { a } 58 | } 59 | } 60 | 61 | #[derive(Debug, Clone, Copy, PartialEq)] 62 | pub struct DependsOnData { 63 | pub data: Data, 64 | pub b: isize, 65 | } 66 | 67 | impl DependsOnData { 68 | #[inject] 69 | pub fn new(data: Data, b: isize) -> Self { 70 | Self { data, b } 71 | } 72 | } 73 | 74 | #[derive(Debug, Clone, PartialEq)] 75 | pub struct GenericData { 76 | pub c: T, 77 | } 78 | 79 | impl GenericData { 80 | #[inject] 81 | pub fn new(c: DependsOnData) -> Self { 82 | Self { c } 83 | } 84 | } 85 | 86 | #[derive(Debug, Clone, PartialEq)] 87 | pub struct ArcData { 88 | pub data: Arc, 89 | } 90 | 91 | impl ArcData { 92 | #[inject] 93 | pub fn new(data: Arc) -> Self { 94 | Self { data } 95 | } 96 | } 97 | 98 | #[fixture(a = 1)] 99 | pub fn data(a: isize) -> Data { 100 | Data::new(a) 101 | } 102 | 103 | #[fixture(b = 0)] 104 | pub fn depends_on_data(data: Data, b: isize) -> DependsOnData { 105 | DependsOnData::new(data, b) 106 | } 107 | 108 | #[fixture] 109 | pub fn generic_data(depends_on_data: DependsOnData) -> GenericData { 110 | GenericData::new(depends_on_data) 111 | } 112 | 113 | #[fixture] 114 | pub fn arc_data(data: Data) -> ArcData { 115 | ArcData::new(Arc::new(data)) 116 | } 117 | 118 | #[fixture(data = Data { a: 2 })] 119 | pub fn data_provider(data: Data) -> impl Provider { 120 | move |_: &_| Ok(data) 121 | } 122 | 123 | #[fixture] 124 | pub fn depends_on_data_provider() -> impl Provider { 125 | move |container: &Container| Ok(DependsOnData::new(container.get()?, 0)) 126 | } 127 | 128 | #[fixture] 129 | pub fn data_arc_provider(data: Data) -> impl Provider> { 130 | DataArcProvider { 131 | data: Arc::new(data), 132 | } 133 | } 134 | 135 | #[fixture] 136 | pub fn test_trait_provider(data: Data) -> TestTraitProvider { 137 | TestTraitProvider { 138 | data: Arc::new(data), 139 | } 140 | } 141 | 142 | #[fixture] 143 | pub fn depends_on_dyn(data: Data) -> DependsOnDyn { 144 | DependsOnDyn::new(Arc::new(data)) 145 | } 146 | -------------------------------------------------------------------------------- /tests/test_call.rs: -------------------------------------------------------------------------------- 1 | use ::inject::{call, container, inject}; 2 | use rstest::*; 3 | 4 | mod fixtures; 5 | use fixtures::*; 6 | 7 | #[inject] 8 | fn injected_func() {} 9 | 10 | #[inject] 11 | fn func_with_args(a: Data, b: Data) -> isize { 12 | a.a + b.a 13 | } 14 | 15 | #[rstest] 16 | fn test_call() { 17 | let container = container![]; 18 | call!(&container, injected_func).unwrap(); 19 | } 20 | 21 | #[rstest] 22 | fn test_call_with_injectable_args() { 23 | let container = container![]; 24 | let a = call!(&container, func_with_args).unwrap(); 25 | assert_eq!(a, 2) 26 | } 27 | 28 | #[rstest(data(2))] 29 | fn test_call_with_injectable_args_with_kwarg(data: Data) { 30 | let container = container![]; 31 | let expected_value = data.a + Data::inject(&container).unwrap().a; 32 | let return_value = call!(&container, func_with_args, kwargs = { b: data }).unwrap(); 33 | assert_eq!(return_value, expected_value) 34 | } 35 | 36 | #[rstest(data(3))] 37 | fn test_call_with_injectable_args_provider(data: Data) { 38 | let provider = move |_: &_| Ok(data); 39 | let container = container![provider]; 40 | let a = call!(&container, func_with_args, kwargs = { b: Data::new(1) }).unwrap(); 41 | assert_eq!(a, 4) 42 | } 43 | -------------------------------------------------------------------------------- /tests/test_container.rs: -------------------------------------------------------------------------------- 1 | use ::inject::{container, get, Container, InjectError, Provider}; 2 | use std::sync::Arc; 3 | 4 | use rstest::*; 5 | 6 | mod fixtures; 7 | use fixtures::*; 8 | 9 | #[rstest] 10 | fn test_get_without_provider_installed_errors() { 11 | let expected = Err(InjectError::MissingProvider); 12 | let container = Container::default(); 13 | 14 | let provided = container.get::(); 15 | 16 | assert_eq!(provided, expected) 17 | } 18 | 19 | #[rstest(data(2))] 20 | fn test_get_with_provider_installed_returns_implementation( 21 | data: Data, 22 | data_provider: impl Provider + 'static, 23 | ) { 24 | let mut container = Container::new(); 25 | container.install(data_provider); 26 | 27 | let provided = container.get::().unwrap(); 28 | 29 | assert_eq!(provided, data) 30 | } 31 | 32 | #[rstest] 33 | fn test_get_installed_trait_object(test_trait_provider: TestTraitProvider) { 34 | let mut container = Container::new(); 35 | container.install(test_trait_provider); 36 | let provided = container.get::>().unwrap(); 37 | 38 | assert_eq!(provided.hello(), "Hello") 39 | } 40 | 41 | #[rstest] 42 | fn test_closure_as_provider(data: Data) { 43 | let expected = data; 44 | let mut container = Container::new(); 45 | container.install(move |_: &_| Ok(data)); 46 | let provided = container.get::().unwrap(); 47 | 48 | assert_eq!(provided, expected) 49 | } 50 | 51 | #[rstest] 52 | fn test_get_macro_gives_default_without_provider_and_default_available(data: Data) { 53 | let expected = data; 54 | let container = Container::new(); 55 | let provided = get!(&container, Data).unwrap(); 56 | 57 | assert_eq!(expected, provided) 58 | } 59 | 60 | #[rstest] 61 | fn test_get_macro_resolves_default_in_nested_provider_when_provider_missing( 62 | depends_on_data: DependsOnData, 63 | ) { 64 | let expected = depends_on_data; 65 | let container = container![]; 66 | let provided: DependsOnData = get!(&container, DependsOnData).unwrap(); 67 | 68 | assert_eq!(expected, provided) 69 | } 70 | 71 | #[rstest] 72 | fn test_get_macro_resolves_nested_provider(data_provider: impl Provider + 'static) { 73 | let expected = DependsOnData { 74 | data: Data::new(2), 75 | b: 0, 76 | }; 77 | let container = container![data_provider]; 78 | let provided = get!(&container, DependsOnData).unwrap(); 79 | 80 | assert_eq!(expected, provided) 81 | } 82 | 83 | #[rstest] 84 | fn test_get_macro_resolves_nested_provider_with_nested_provider( 85 | data_provider: impl Provider + 'static, 86 | depends_on_data_provider: impl Provider + 'static, 87 | ) { 88 | let expected = GenericData::new(DependsOnData::new(Data::new(2), 0)); 89 | 90 | let container = container![data_provider, depends_on_data_provider]; 91 | 92 | let provided = get!(&container, GenericData).unwrap(); 93 | 94 | assert_eq!(expected, provided) 95 | } 96 | 97 | #[rstest] 98 | fn test_get_macro_resolves_dyn_provider(test_trait_provider: impl Provider + 'static) { 99 | let container = container![test_trait_provider]; 100 | 101 | let provided = get!(&container, DependsOnDyn).unwrap(); 102 | 103 | assert_eq!(provided.test_trait.hello(), "Hello") 104 | } 105 | 106 | #[rstest] 107 | fn test_get_macro_resolves_reference(data: Data) { 108 | let container = container![ref Box::new(data)]; 109 | 110 | let provided = get!(&container, &Data).unwrap(); 111 | 112 | assert_eq!(provided, &data) 113 | } 114 | -------------------------------------------------------------------------------- /tests/test_inject.rs: -------------------------------------------------------------------------------- 1 | use ::inject::{container, get, singleton, Provider}; 2 | 3 | mod fixtures; 4 | 5 | use fixtures::*; 6 | use rstest::*; 7 | 8 | #[rstest] 9 | fn test_construct(data: Data) { 10 | let container = container![]; 11 | let injected_struct = get!(&container, Data).unwrap(); 12 | 13 | assert_eq!(injected_struct, data); 14 | } 15 | 16 | #[rstest] 17 | fn test_construct_with_dependency_using_inject(depends_on_data: DependsOnData) { 18 | let container = container![]; 19 | let injected_struct = get!(&container, DependsOnData).unwrap(); 20 | 21 | assert_eq!(injected_struct, depends_on_data); 22 | } 23 | 24 | #[rstest] 25 | fn test_construct_with_dependency_using_provider(data_provider: impl Provider + 'static) { 26 | let expected_data = DependsOnData::new(Data::new(2), 0); 27 | let container = container![data_provider]; 28 | 29 | let injected_struct = get!(&container, DependsOnData).unwrap(); 30 | 31 | assert_eq!(expected_data, injected_struct); 32 | } 33 | 34 | #[rstest] 35 | fn test_construct_with_generic_dependency(generic_data: GenericData) { 36 | let container = container![]; 37 | 38 | let injected_struct = get!(&container, GenericData).unwrap(); 39 | 40 | assert_eq!(generic_data, injected_struct); 41 | } 42 | 43 | #[rstest] 44 | fn test_construct_with_arc_dependency( 45 | arc_data: ArcData, 46 | data_arc_provider: impl Provider + 'static, 47 | ) { 48 | let container = container![data_arc_provider]; 49 | 50 | let injected_struct = get!(&container, ArcData).unwrap(); 51 | 52 | assert_eq!(arc_data, injected_struct); 53 | } 54 | 55 | #[rstest] 56 | fn test_install_singleton_returns_same_instance() { 57 | let container = container![singleton!(Data), singleton!(ArcData),]; 58 | 59 | let injected_data_1 = get!(&container, std::sync::Arc).unwrap(); 60 | let injected_data_2 = get!(&container, std::sync::Arc).unwrap(); 61 | assert_eq!(injected_data_1, injected_data_2) 62 | } 63 | -------------------------------------------------------------------------------- /tests/test_providers.rs: -------------------------------------------------------------------------------- 1 | use ::inject::providers::DefaultProvider; 2 | use ::inject::*; 3 | 4 | #[derive(Debug, PartialEq, Clone, Copy, Default)] 5 | struct FakeImpl { 6 | val: isize, 7 | } 8 | 9 | impl FakeImpl { 10 | #[inject] 11 | fn new() -> Self { 12 | Self { 13 | ..Default::default() 14 | } 15 | } 16 | } 17 | 18 | struct RefProvide { 19 | pub fake_impl: FakeImpl, 20 | } 21 | 22 | impl RefProvider for RefProvide { 23 | type ProvidedRef = FakeImpl; 24 | 25 | fn provide<'a>( 26 | &'a self, 27 | _container: &'a Container, 28 | ) -> Result<&'a Self::ProvidedRef, InjectError> { 29 | Ok(&self.fake_impl) 30 | } 31 | } 32 | 33 | struct BoxProvider { 34 | pub a: Box, 35 | } 36 | 37 | impl RefProvider for BoxProvider { 38 | type ProvidedRef = FakeImpl; 39 | 40 | fn provide<'a>( 41 | &'a self, 42 | _container: &'a Container, 43 | ) -> Result<&'a Self::ProvidedRef, InjectError> { 44 | Ok(&self.a) 45 | } 46 | } 47 | 48 | #[test] 49 | fn test_default_provider() { 50 | let boxed_ref = FakeImpl { val: 1 }; 51 | let box_provider = Box::from(boxed_ref); 52 | 53 | let container = container![DefaultProvider::::new(), ref box_provider,]; 54 | let expected = Ok(FakeImpl { val: 0 }); 55 | let provided = get!(&container, FakeImpl); 56 | 57 | assert_eq!(provided, expected); 58 | 59 | let reference: &FakeImpl = get!(&container, &FakeImpl).unwrap(); 60 | assert_eq!(&boxed_ref, reference); 61 | } 62 | --------------------------------------------------------------------------------