Giter Site home page Giter Site logo

entrait's People

Contributors

audunhalland avatar mrjerb avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

mrjerb

entrait's Issues

Traits with multiple methods

I'm really intrigued by this implementation of dependency injection in Rust! One issue I was running into was how to create a trait with multiple methods.

I tried this

#[entrait]
pub trait Environment {
    fn read_var(&self, var: &str) -> Result<String> {
        todo!()
    }

    fn read_var_maybe(&self, var: &str) -> Option<String> {
        todo!()
    }
}

but then I get an error about entrait::Impl not implementing Environment, when I try to use the app in my code.

This is the relevant generated code:

pub trait Environment {
    fn read_var(&self, var: &str) -> Result<String>;
    fn read_var_maybe(&self, var: &str) -> Option<String>;
}
impl<EntraitT: Sync> Environment for ::entrait::Impl<EntraitT>
where
    EntraitT: Environment + Sync,
{
    #[inline]
    fn read_var(&self, var: &str) -> Result<String> {
        self.as_ref().read_var(var)
    }
    #[inline]
    fn read_var_maybe(&self, var: &str) -> Option<String> {
        self.as_ref().read_var_maybe(var)
    }
}

I can get it working if I remove the entrait macro and modify it to this:

pub trait Environment {
    fn read_var(&self, var: &str) -> Result<String>;
    fn read_var_maybe(&self, var: &str) -> Option<String>;
}

fn read_var(_deps: &impl std::any::Any, var: &str) -> Result<String> {
    unimplemented!()
}

fn read_var_maybe(_deps: &impl std::any::Any, var: &str) -> Option<String> {
    unimplemented!()
}

impl<EntraitT: Sync> Environment for ::entrait::Impl<EntraitT>
where
    EntraitT: Sync,
{
    #[inline]
    fn read_var(&self, var: &str) -> Result<String> {
        read_var(self, var)
    }
    #[inline]
    fn read_var_maybe(&self, var: &str) -> Option<String> {
        read_var_maybe(self, var)
    }
}

I have to remove the Environment trait bound and change how read_var and read_var_maybe are called. I could definitely be doing something wrong, but it seems like the generated output doesn't compile for traits with multiple methods. Do you have any insights here? If it doesn't work yet, is this a use-case you'd be willing to support?

Axum example doesn't compile

Cloned main and tried to compile the axum example but was met with (r1.65.0):

error[E0433]: failed to resolve: could not find `__async_trait` in `entrait`
  --> examples/axum/src/main.rs:14:36
   |
14 |     #[entrait(pub GetFoo, no_deps, box_future, mock_api=GetFooMock)]
   |                                    ^^^^^^^^^^ could not find `__async_trait` in `entrait`

error[E0706]: functions in traits cannot be declared `async`
  --> examples/axum/src/main.rs:14:5
   |
14 |     #[entrait(pub GetFoo, no_deps, box_future, mock_api=GetFooMock)]
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
15 |     async fn get_foo() -> Foo {
   |     ----- `async` because of this
   |
   = note: `async` trait functions are not currently supported
   = note: consider using the `async-trait` crate: https://crates.io/crates/async-trait
   = note: see issue #91611 <https://github.com/rust-lang/rust/issues/91611> for more information
   = note: this error originates in the attribute macro `entrait` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0706]: functions in traits cannot be declared `async`
  --> examples/axum/src/main.rs:15:5
   |
15 |     async fn get_foo() -> Foo {
   |     -----^^^^^^^^^^^^^^^^^^^^
   |     |
   |     `async` because of this
   |
   = note: `async` trait functions are not currently supported
   = note: consider using the `async-trait` crate: https://crates.io/crates/async-trait
   = note: see issue #91611 <https://github.com/rust-lang/rust/issues/91611> for more information

error[E0277]: the trait bound `fn(Extension<A>) -> impl Future<Output = Json<Foo>> {Routes::<A>::get_foo}: Handler<_, _>` is not satisfied
   --> examples/axum/src/main.rs:36:51
    |
36  |             axum::Router::new().route("/foo", get(Self::get_foo))
    |                                               --- ^^^^^^^^^^^^^ the trait `Handler<_, _>` is not implemented for fn item `fn(Extension<A>) -> impl Future<Output = Json<Foo>> {Routes::<A>::get_foo}`
    |                                               |
    |                                               required by a bound introduced by this call
    |
    = help: the trait `Handler<T, ReqBody>` is implemented for `Layered<S, T>`
note: required by a bound in `axum::routing::get`
   --> /home/dkolsoi/.cargo/registry/src/github.com-1ecc6299db9ec823/axum-0.5.17/src/routing/method_routing.rs:397:1
    |
397 | top_level_handler_fn!(get, GET);
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `axum::routing::get`
    = note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)

`use entrait::*` often clashes with `use unimock`, since there is a `unimock` module in entrait

should reconsider the idea of different "entrait scopes" for mocks.

Alternatives:

  1. always require attribute options for generating mocks
  2. control mock generation via cargo features

the second option could be interesting, because then libraries could more easily support entrait annotations without any cost: i.e. no features would generate no extra code; everything is opt-in. The applications selects the mocking framework it wants via a feature selection. An important thing to consider in that case is that features must have an additive design, features will be the same in every usage of entrait, regardless of which crate invoked it.

An important point to remember about entrait is that (I think) some entrait patterns will be uniform within a single crate, some patterns will be uniform within a whole binary:

feature "width"
mock library application/binary wide
mock cfg(test) crate wide

cargo-rdme does not render code examples under feature flags

i.e. #[cfg_attr(feature = "?", doc = "some code example")]

Possible solutions:

  1. Find a way to make doctests unconditional, exclude doctests from cargo hack runs, then only run doctests with specific features turned on. But there seems to be no way to exclude doctests from a test run.
  2. Fix cargo rdme so that it can interpret feature flags
  3. Write a new readme generator (a lot of work?)
  4. Don't autogenerate readme file (also a lot of manual sync work)
  5. Make README a much smaller, standalone document, with links to docs.rs for specific features

Entrait 0.5 plans

Change default behaviour for mockability

In entrait 0.5, I want more focus on dependency inversion and less focus on mocking out every single function.

The main advantages of the entrait pattern for functions are:

  1. Abstracting away the application, all business logic is available as extension methods on Impl<T>
  2. Each function only specifies its direct dependencies. The trait engine automatically takes care of verifying that transitive dependencies are fulfilled.

Support for mocking every little function when unimock is enabled is potentially generating a lot more code than necessary.
Instead, by default, we could try to generate a very simple impl<T> Trait for T where T: Dependencies that could cover both Impl<T> and mocked environments. Mocking a simple function will become opt-in, with something like #[entrait(Foo, mock)]).

Mock support should be enabled by default on inverted dependencies - the ones that have no local implementation.

Rename async-trait to boxed-futures.

async-trait is just an implementation detail on the way to get boxed futures, and this does not need to be exposed.

## Investigate trait redesign
#11

Generics in app state

I am continuing to work with your crate, it's really useful.

One thing where I am stuck: I have an object from an external crate that comes in as an &mut imp SomeTrait. It is a storage interface. Is there any way I can store this in the application state? I tried, but I get into trouble with Send and Sync traits.

Support for unimocking modular application by projecting implementations

Entrait apps have to be wrapped in ::implementation::Impl because of specialization issues. Traits get implemented for Impl<T> and Unimock.

Some applications need to be modular/composed of different crates, and linked together at the main crate. Imagine an app consisting of a library and a "final" app in the main crate:

// some lib crate
struct AppModule;
// main crate
struct App {
    app_module: Impl<AppModule>
}

In the main crate, we have some functions which want to call into entraited functions from the lib crate. But traits are not implemented for Impl<App>, but for Impl<AppModule>. So we need a way to get there, without naming the App. We only want to mention traits. We need access to all the traits implemented by Impl<AppModule> while still supporting unimock.

So we can imagine a trait for accessing the app module:

// main crate
trait GetAppModule {
    type Target: lib::Trait1 + lib::Trait2 + Send + Sync + 'static;
    
    fn get_app_module(&self) -> &Self::Target;
}

(this cannot be a generic trait because of custom bounds on Target)

This trait gets implemented for Impl<App> and Unimock:

impl GetAppModule for Impl<App> {
    type Target = Impl<AppModule>;
    
    fn get_app_module(&self) -> &Self::Target {
        &self.app_module
    }
}

impl GetAppModule for Unimock {
    type Target = Unimock;
    
    fn get_app_module(&self) -> &Self::Target {
        self
    }
}

This would work fine and we can then write things like:

fn do_with_module(deps: &impl GetAppModule) {
    deps.get_app_module().some_lib_function();
}

But it's a lot of boilerplate to generate these impls, and I'd like to make it easier to express this relationship using a macro.

A key point is that we need to repeat all the lib traits as bounds for GetAppModule, but it would be better if the lib exported its "public API" traits. It could do that by exporting a "meta trait" that inherits from all its public traits:

// lib
pub trait AppModuleMeta: Trait1, Trait2, Trait2 {}

impl AppModuleMeta for Impl<AppModule> {}
impl AppModuleMeta for Unimock {}

Find a working design pattern for modelling transaction-like things

Database transactions require some kind of context value representing a session with the database. The transaction either succeeds or is cancelled in case of an error. In SQLx the transaction object requires a call to .commit() at the end.

Designing a DB abstraction this way requires an associated type representing the transaction object:

async fn some_db_op(deps: &impl Repository) {
     let mut tx = deps.transaction();
     deps.insert_something(some_value, &mut tx)?;
     tx.commit().await?;
     Ok(())
}

(the transaction object is likely different for each implementation of Repository).

I'm not completely sold on the need to explicitly call commit. What about taking advantage of the Result each db call returns? If it returns some kind of Result<Transaction<T>, Error>. That Ok context could be used somehow in subsequent calls. At the end of the operation chain, the T needs to be dug out from the transaction, and that's where the commit happens.

This issue represent exploring the design space around transaction modelling.

REAL dependency inversion: Support internal dispatch by using a pair of traits

TL;DR: The crate that defines an interface should not need to be the crate that implements it. The way entrait is designed, using delegating blanket implementations, requires some more delegation "magic".

The entrait-for-trait feature #[entrait(delegate_by = Borrow)] only works for leaf dependencies.

Here is an idea for how to do something similar, but without exiting the Impl layer.

We have some API that we want to potentially implement (downstream) in different dynamic ways:

pub trait Facade {
    fn foo(&self) -> i32;
}

We want to implement this trait for Impl<T> so it can be used as a dependency. But that, by definition, is the only implementation. We want to have a delegation through dynamic dispatch to reach the final destination. Since that trait is already implemented for Impl<T>, we need another trait! We can use the entrait syntax to generate a trait from a trait ๐Ÿ˜ฌ Let's call the new trait Backend:

#[entrait(pub Backend)]
pub trait Facade {
    fn foo(&self) -> i32;
}

The generated trait is generic, and has two receivers:

pub trait Backend<T>: 'static {
    fn foo(&self, _impl: &entrait::Impl<T>) -> i32;
}

The generated delegation looks like this:

impl<T: 'static> Facade for Impl<T>
where
    T: core::borrow::Borrow<dyn Backend<T>>,
{
    fn foo(&self) -> i32 {
        self.borrow().foo(self)
    }
}

Now the application has to implement Borrow<dyn Backend<Self>>. This is the manual part.

To define an implementation of Backend<T>, we can write the following module:

pub struct MyImpl;

#[entrait_impl(some_crate::Backend for MyImpl)] <--- proposed syntax
#[entrait_impl(dyn some_crate::Backend for MyImpl)] <--- dyn version
mod my_impl {
    fn foo(deps: &impl Whatever) -> i32 {
         42
    }
}

The functions inside this module need to match the backend interface. I think the compile errors will be good enough. The implementation is of course auto-generated:

pub struct MyImpl;

impl<T> Backend<T> for MyImpl
where
    Impl<T>: Whatever,
{
    fn foo(&self, _impl: &Impl<T>) -> i32 {
        foo(_impl)
    }
}

The Borrow part of the application could either be a Box or some enum. Avoiding allocation could look like this:

impl Borrow<dyn facade::Backend<Self>> for App {
    fn borrow(&self) -> &dyn Backend<Self> {
        match &self.facade_impl {
            AppFacadeImpl::My(my_impl) => my_impl,
            AppFacadeImpl::Other(other_impl) => other_impl,
        }
    }
}

`#[entrait_impl] mod {}` should just be replaced by an impl block

Instead of

#[entrait_impl]
mod some_mod {
    #[derive_impl(super::SomeTrait)]
    pub struct SomeType;
}

The macro should instead work with:

pub struct SomeType;

#[entrait_impl] // ?
impl SomeTrait for SomeType {
    fn foo(deps: &impl SomethingElse) {}
}

which would expand to:

impl SomeType { // <--- removed "SomeTrait for" from this location
    fn foo(deps: &impl SomethingElse) {}
}
impl<T> SomeTrait<T> for SomeType { // <--- move into this place
    fn foo(__impl: &Impl<T>) {
        Self::foo(__impl)
    }
}

Dependency issues when using unimock

Hi there, a couple of issues with dependencies I noticed. Thanks for this elegant library, I searched high and low for a solid dependency injection in Rust and this is the only one I think makes sense.

I use cargo build --tests to build.

Using the unimock option for the entrait macro

Given this cargo.toml

[package]
name = "entrait-issue"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
entrait = "0.5.3"

and this main.rs

use entrait::entrait;

#[entrait(Foo, unimock, mock_api=FooMock)]
fn foo<D>(_: &D) -> i32 {
    unimplemented!()
}

#[entrait(MyMod, unimock, mock_api=mock)]
mod my_mod {
    pub fn bar<D>(_: &D) -> i32 {
        unimplemented!()
    }
}

fn main(){}

I get the error error[E0433]: failed to resolve: could not find `__unimock` in `entrait

Using the unimock feature

When I switch to the unimock feature in cargo.toml things get better.

[package]
name = "entrait-issue"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
entrait = { version = "0.5.3", features = ["unimock"] }

[dev-dependencies]
unimock = "0.5.3"

Here is the main.rs for this case

use entrait::entrait;

#[entrait(Foo, mock_api=FooMock)]
fn foo<D>(_: &D) -> i32 {
    unimplemented!()
}
#[entrait(MyMod, mock_api=mock)]
mod my_mod {
    pub fn bar<D>(_: &D) -> i32 {
        unimplemented!()
    }
}

fn my_func(deps: &(impl Foo + MyMod)) -> i32 {
    deps.foo() + deps.bar()
}

fn main() {}

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

    #[test]
    fn test() {
        let mocked_deps = Unimock::new((
            FooMock.each_call(matching!()).returns(40),
            my_mod::mock::bar.each_call(matching!()).returns(2),
        ));

        assert_eq!(42, my_func(&mocked_deps));
    }
}

Still, the compiler complains no method named `each_call` found for struct `FooMock` in the current scope

What I did to work around this problem was to import entrait::__unimock::* instead of use unimock::*;.

Am I overlooking something (quite likely) or are these bugs?

Impl expansion incorrectly adding lifetime from method

Hi there,

Thanks for the library as well as the concept! I might have discovered a case of incorrect expansion in a certain scenario. I'll try to explain below:

I have a handwritten trait named Foo in crate A which I then want to implement as MyFoo in crate B.
The expansion in crate B is causing errors due to the lifetime specified in the method I prescribed in my handwritten trait.

The DoFoo method in my trait contains a lifetime parameter (my real implementation involves tokenizing some input string, and preferably I would like to avoid clones).

Crate A (no issues)

Crate A:

#[entrait(FooImpl, delegate_by = FooDelegate)]
trait Foo {
    fn DoFoo<'a>(&self, input: &'a str) -> &'a str;
}

Crate A expanded (no issues):

trait Foo {
    fn DoFoo<'a>(&self, input: &'a str) -> &'a str;
}

trait FooImpl<EntraitT>: 'static {
    fn DoFoo<'a>(__impl: &::entrait::Impl<EntraitT>, input: &'a str) -> &'a str;
}
pub trait FooDelegate<T> {
    type Target: FooImpl<T>;
}

impl<EntraitT: Sync> Foo for ::entrait::Impl<EntraitT>
where
    EntraitT: FooDelegate<EntraitT> + Sync + 'static,
{
    #[inline]
    fn DoFoo<'a>(&self, input: &'a str) -> &'a str {
        <EntraitT::Target as FooImpl<EntraitT>>::DoFoo(self, input)
    }
}
Crate B

Crate B

struct MyFoo;

#[entrait]
impl FooImpl for MyFoo {
    fn DoFoo<'a, D>(deps: &D, input: &'a str) -> &'a str {
        input
    }
}

Crate B expanded (issue is here)

impl MyFoo {
    fn DoFoo<'a, D>(deps: &D, input: &'a str) -> &'a str {
        input
    }
}

// Issue lies here: as the lifetime is added to the impl for the trait and in the wrong order (it is not needed at all)
impl<EntraitT: Sync, 'a> FooImpl<EntraitT, 'a> for MyFoo {
    #[inline]
    fn DoFoo<'a>(__impl: &::entrait::Impl<EntraitT>, input: &'a str) -> &'a str {
        Self::DoFoo(__impl, input)
    }
}

I am using version 0.5.3 of the crate.

Trait method cfgs aren't honored

Suppose I have something like:

#[cfg(feature = "baz")]
struct Baz;

#[entrait]
trait Foo {
    fn bar();
    #[cfg(feature = "baz")]
    fn baz() -> Baz;
}

You'll get an error saying Baz doesn't exist when you don't provide the baz feature flag - entrait doesn't seem to propagate cfgs to the output code.

Mocking traits with &impl and &mut impl parameters

Both functions give me compiler errrors for cargo build --tests. I use entrait with the unimock feature as dependency. Is there a way to make them work with entrait/unimock?

use entrait::entrait;

pub trait MyTrait {}

#[entrait(Foo, mock_api=FooMock)]
fn foo<D>(_: &D, _foo: &impl MyTrait) -> i32 {
    unimplemented!()
}

#[entrait(Bar, mock_api=BarMock)]
fn bar<D>(_: &D, _bar: &mut impl MyTrait) -> i32 {
    unimplemented!()
}

fn main() {}

Errors:

error[E0658]: `impl Trait` in type aliases is unstable
 --> src/main.rs:6:25
  |
6 | fn foo<D>(_: &D, _foo: &impl MyTrait) -> i32 {
  |                         ^^^^^^^^^^^^
  |
  = note: see issue #63063 <https://github.com/rust-lang/rust/issues/63063> for more information

error[E0658]: `impl Trait` in type aliases is unstable
  --> src/main.rs:11:29
   |
11 | fn bar<D>(_: &D, _bar: &mut impl MyTrait) -> i32 {
   |                             ^^^^^^^^^^^^
   |
   = note: see issue #63063 <https://github.com/rust-lang/rust/issues/63063> for more information

error: unconstrained opaque type
 --> src/main.rs:6:25
  |
6 | fn foo<D>(_: &D, _foo: &impl MyTrait) -> i32 {
  |                         ^^^^^^^^^^^^
  |
  = note: `Inputs` must be used in combination with a concrete type within the same impl

error[E0599]: the method `unimock_try_debug` exists for mutable reference `&mut _::<impl MockFn for BarMock>::Inputs<'_>::{opaque#0}`, but its trait bounds were not satisfied
  --> src/main.rs:10:1
   |
10 | #[entrait(Bar, mock_api=BarMock)]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method cannot be called due to unsatisfied trait bounds
   |
   = note: the following trait bounds were not satisfied:
           `&mut _::<impl MockFn for BarMock>::Inputs<'_>::{opaque#0}: Debug`
           which is required by `&mut _::<impl MockFn for BarMock>::Inputs<'_>::{opaque#0}: ProperDebug`
           `_::<impl MockFn for BarMock>::Inputs<'_>::{opaque#0}: Debug`
           which is required by `_::<impl MockFn for BarMock>::Inputs<'_>::{opaque#0}: ProperDebug`
   = note: this error originates in the attribute macro `::entrait::__unimock::unimock` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0308]: mismatched types
   --> src/main.rs:6:18
    |
5   | #[entrait(Foo, mock_api=FooMock)]
    | --------------------------------- arguments to this function are incorrect
6   | fn foo<D>(_: &D, _foo: &impl MyTrait) -> i32 {
    |                  ^^^^   ------------
    |                  |      |
    |                  |      the expected opaque type
    |                  |      this type parameter
    |                  expected `&_::<impl MockFn for ...>::Inputs<'_>::{opaque#0}`, found `&impl MyTrait`
    |
    = note: expected reference `&_::<impl MockFn for FooMock>::Inputs<'_>::{opaque#0}`
               found reference `&impl MyTrait`
    = help: type parameters must be constrained to match other types
    = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters
note: function defined here
   --> /Users/ander/.cargo/registry/src/github.com-1ecc6299db9ec823/unimock-0.4.12/src/macro_api.rs:209:8
    |
209 | pub fn eval<'u, 'i, F>(unimock: &'u Unimock, inputs: F::Inputs<'i>) -> Evaluation<'u, 'i, F>
    |        ^^^^

error[E0308]: mismatched types
   --> src/main.rs:11:18
    |
10  | #[entrait(Bar, mock_api=BarMock)]
    | --------------------------------- arguments to this function are incorrect
11  | fn bar<D>(_: &D, _bar: &mut impl MyTrait) -> i32 {
    |                  ^^^^       ------------
    |                  |          |
    |                  |          the expected opaque type
    |                  |          this type parameter
    |                  expected `&mut _::<impl MockFn for ...>::Inputs<'_>::{opaque#0}`, found `&mut impl MyTrait`
    |
    = note: expected mutable reference `&mut _::<impl MockFn for BarMock>::Inputs<'_>::{opaque#0}`
               found mutable reference `&mut impl MyTrait`
    = help: type parameters must be constrained to match other types
    = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters
note: function defined here
   --> /Users/ander/.cargo/registry/src/github.com-1ecc6299db9ec823/unimock-0.4.12/src/macro_api.rs:209:8
    |
209 | pub fn eval<'u, 'i, F>(unimock: &'u Unimock, inputs: F::Inputs<'i>) -> Evaluation<'u, 'i, F>
    |        ^^^^

Some errors have detailed explanations: E0308, E0599, E0658.
For more information about an error, try `rustc --explain E0308`.

Improve documentation and examples

As the author, it's a little unclear to me whether the documentation of this crate is adequate and understandable. I fear that my imagination on how the crate can be used is somewhat limited, because I have to think about the technical details at the same time. I'd appreciate external contributions from a pure usability-perspective, high-level improvement tips, describing novel design patterns, etc.

Investigate always taking self by value

I don't know whether this will work, but consider:

impl<T> Trait for Impl<T> {
    fn foo(&self) {}
}

vs.

impl<T> Trait for &Impl<T> {
    fn foo(self) {}
}

If the traits always take self by value, it might not be necessary to duplicate the traits when doing dependency inversion, because then we can make a newtype without reborrowing:

struct NewType<'a, T>(&'a Impl<T>);

impl<'a, T> Trait for NewType<'a, T> {
    fn foo(self) { self.0.foo() }
}

The main reason it was necessary to duplicate the trait with the current design, was that it was impossible to make futures implement Send when "reborrowing" for a struct like NewType. NewType had to be passed as a reference into the delegation-target, referencing the value just created on the stack, and things referencing a value owned by the stack (vs. the heap) can't be sent to another thread. If NewType is passed by-value, this restriction is likely lifted.

Associated types fail to compile

Hey @audunhalland, another associated type issue here!

#[entrait::entrait]
pub trait Client {
    type DatabaseImpl;

    fn database(&self, name: &str) -> Self::DatabaseImpl;
}

The associated type disappears from the generated trait:

pub trait MongoClient {
    fn database(&self, name: &str) -> Self::DatabaseImpl;
}

It also needs to be bound in the entrait::Impl block as:

type DatabaseImpl = <EntraitT as MongoClient>::DatabaseImpl;

Revise how unimock types are exported

Currently, the naming of generated unimock types is very inconsistent:

entrait unimock top-level item MockFn path
#[entrait(Foo)] fn foo() {} new module foo foo::Fn
#[entrait(Foo)] mod foo { fn bar() {} } flattened inside foo foo::bar::Fn
#[entrait] trait Foo { fn bar() } top-level struct Foo__bar

I wish to make these more consistent. The way Mockall does it seems better: always make some Mock* struct that represents the mockable trait.

This is the suggestion:

entrait unimock top-level item MockFn path notes
#[entrait(Foo)] fn foo() {} MockFoo MockFoo() the MockFn struct is defined as an empty tuple struct
#[entrait(Foo)] mod foo { fn bar() {} } MockFoo MockFoo::bar()
#[entrait] trait Foo { fn bar(); } MockFoo MockFoo::bar()

This should be part of the next major version of unimock (0.4), where the defaults will be changed from Foo__bar to Foo::bar()

Multiple implementations of the same trait?

Hi,

I wonder if there's a way to have multiple implementations of the same trait?

Something like:

fn login(deps: &impl UserRepository, username: &str) {
   deps.user_exists(&username)
} 

fn postgres_user_exist(pool: &PgPool, username: &str) {
   // do pg stuff
}

fn inmemory_user_exist(data: &Map, username: &str) {
  // check in memory
}

And in the main.rs I would like to switch between postgres and in-memory based on, say, a config variable.

Thanks! ๐Ÿ™‚

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.