nicolascotton / nject

Simple zero cost dependency injection library made for rust
MIT License
42 stars 3 forks source link

How to make sqlx with nject work? #36

Closed numfin closed 8 months ago

numfin commented 8 months ago

Could you please help me figure out What i want:

pub struct DBProvider<T: sqlx::Database> {
    pub pool: sqlx::Pool<T>,
}
pub struct UserService<T: sqlx::Database> {
    db: DBProvider<T>,
}

What i get:

error[E0277]: the trait bound `DBProvider<Postgres>: Injectable<'_, DBProvider<Postgres>, DBProvider<Postgres>>` is not satisfied
  --> packages/minutin/src/db/service/postgres.rs:72:39
   |
72 |     let _f: UserService<Postgres> = p.provide();
   |                                       ^^^^^^^ the trait `Injectable<'_, DBProvider<Postgres>, DBProvider<Postgres>>` is not implemented for `DBProvider<Postgres>`, which is required by `DBProvider<Postgres>: nject::Provider<'_, UserService<Postgres>>`
   |
   = help: the following other types implement trait `Injectable<'prov, Injecty, Provider>`:
             <PostgresService as Injectable<'prov, PostgresService, NjectProvider>>
             <UserService<T> as Injectable<'prov, UserService<T>, NjectProvider>>
note: required for `DBProvider<Postgres>` to implement `nject::Provider<'_, DBProvider<Postgres>>`
  --> packages/minutin/src/db/service/postgres.rs:12:1

image

nicolascotton commented 8 months ago

The issue comes from the fact that the provider does not know how to inject sqlx::Pool<Postgres>. You can solve your issue like so (I used Sqlite instead of Postgres, but it should be similar):

use nject::{injectable, provider};
use sqlx::Sqlite;

#[injectable]
pub struct DBProvider<T: sqlx::Database> {
    pub pool: sqlx::Pool<T>,
}

#[injectable]
pub struct UserService<T: sqlx::Database> {
    db: DBProvider<T>,
}

#[provider]
#[provide(sqlx::pool::Pool::<Sqlite>, sqlx::pool::Pool::<Sqlite>::connect_lazy("addr").expect("Invalid db addr"))]
struct Provider;

fn main() {
    let _f: UserService<Sqlite> = Provider.provide();
}

You can also refer to the actix example for a use case using sqlx.

nicolascotton commented 8 months ago

If you wanted the type to be generic up to the provider, you can do:

use nject::{injectable, provider};
use sqlx::Sqlite;

#[injectable]
pub struct DBProvider<T: sqlx::Database> {
    pub pool: sqlx::Pool<T>,
}

#[injectable]
pub struct UserService<T: sqlx::Database> {
    db: DBProvider<T>,
}

#[provider]
#[provide(sqlx::pool::Pool::<T>, sqlx::pool::Pool::<T>::connect_lazy("addr").expect("Invalid db addr"))]
struct Provider<T: sqlx::Database>(PhantomData<T>);

fn main() {
    let provider = Provider(PhantomData);
    let _f: UserService<Sqlite> = provider.provide();
}
numfin commented 8 months ago

@nicolascotton thank you for answer. Your advice helped me!

hm, is it possible to implement provider by hand so we don't have to rely on macro?

I imagine api like:

struct Provider;
impl DependencyProvider<Dep1> for Provider {
  type Error = ();
  async fn create_async(&self) -> Result<Dep1, Self::Error>;
  fn create(&self) -> Result<Dep1, Self::Error>;
}
nicolascotton commented 8 months ago

I don't know for the API you are trying to make, but you can expand the provider macro and see what it is doing under the hood. For the previous example, the provider macro can be substituted by:

// From the provider attribute
impl<'prov, T: sqlx::Database, Value> nject::Provider<'prov, Value> for Provider<T>
where
    Value: nject::Injectable<'prov, Value, Provider<T>>,
{
    fn provide(&'prov self) -> Value {
        Value::inject(self)
    }
}

// From the provide attribute
impl<'prov, T: sqlx::Database> nject::Provider<'prov, sqlx::pool::Pool<T>> for Provider<T> {
    fn provide(&'prov self) -> sqlx::pool::Pool<T> {
        sqlx::pool::Pool::<T>::connect_lazy("addr").expect("Invalid db addr")
    }
}