caelix 0.0.35

Public Caelix framework crate.
Documentation
#![cfg(feature = "actix")]

use std::sync::Arc;

use caelix::{
    Container, Controller, Injectable, Module, ModuleMetadata, Response, StatusCode,
    TestApplication,
};
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};

struct NamedRepository {
    name: &'static str,
}

impl Injectable for NamedRepository {
    fn dependencies() -> Vec<caelix::ProviderDependency> {
        caelix::provider_dependencies![]
    }

    fn create(_container: &Container) -> caelix::BoxFuture<'_, caelix::Result<Self>> {
        Box::pin(async move { Ok(Self { name: "production" }) })
    }
}

struct UsersService {
    repo: Arc<NamedRepository>,
}

impl Injectable for UsersService {
    fn dependencies() -> Vec<caelix::ProviderDependency> {
        caelix::provider_dependencies![NamedRepository]
    }

    fn create(container: &Container) -> caelix::BoxFuture<'_, caelix::Result<Self>> {
        Box::pin(async move {
            Ok(Self {
                repo: container.resolve::<NamedRepository>()?,
            })
        })
    }
}

struct UsersController {
    service: Arc<UsersService>,
}

impl Injectable for UsersController {
    fn dependencies() -> Vec<caelix::ProviderDependency> {
        caelix::provider_dependencies![UsersService]
    }

    fn create(container: &Container) -> caelix::BoxFuture<'_, caelix::Result<Self>> {
        Box::pin(async move {
            Ok(Self {
                service: container.resolve::<UsersService>()?,
            })
        })
    }
}

#[derive(Deserialize, Serialize)]
struct CreateUserDto {
    name: String,
    email: String,
}

#[derive(Deserialize, Serialize)]
struct UserDto {
    name: String,
    email: String,
    backend: String,
}

impl UsersController {
    async fn create(
        container: actix_web::web::Data<Container>,
        body: actix_web::web::Json<CreateUserDto>,
    ) -> actix_web::HttpResponse {
        let controller = container.resolve::<UsersController>().unwrap();
        let body = body.into_inner();
        let dto = UserDto {
            name: body.name,
            email: body.email,
            backend: controller.service.repo.name.to_string(),
        };
        caelix::to_actix_response(caelix::IntoCaelixResponse::into_response(
            Response::WithStatus(StatusCode::CREATED, dto),
        ))
    }

    async fn backend(container: actix_web::web::Data<Container>) -> actix_web::HttpResponse {
        let controller = container.resolve::<UsersController>().unwrap();
        caelix::to_actix_response(caelix::IntoCaelixResponse::into_response(Response::Body(
            json!({ "backend": controller.service.repo.name }),
        )))
    }
}

impl Controller for UsersController {
    fn base_path() -> &'static str {
        "/users"
    }

    fn register_routes(cfg_any: &mut dyn std::any::Any) {
        let cfg = cfg_any
            .downcast_mut::<actix_web::web::ServiceConfig>()
            .expect("expected actix ServiceConfig");

        cfg.route("/users", actix_web::web::post().to(UsersController::create));
        cfg.route(
            "/users/backend",
            actix_web::web::get().to(UsersController::backend),
        );
    }
}

struct UsersModule;
impl Module for UsersModule {
    fn register() -> ModuleMetadata {
        ModuleMetadata::new()
            .provider::<NamedRepository>()
            .provider::<UsersService>()
            .controller::<UsersController>()
    }
}

struct AppModule;
impl Module for AppModule {
    fn register() -> ModuleMetadata {
        ModuleMetadata::new().import::<UsersModule>()
    }
}

#[caelix::test]
async fn should_create_user() {
    let app = TestApplication::new::<AppModule>().await.unwrap();

    let response = app
        .post("/users")
        .json(CreateUserDto {
            name: "Ronnie".into(),
            email: "ronnie@example.com".into(),
        })
        .send()
        .await
        .unwrap()
        .assert_status(StatusCode::CREATED);

    let body: UserDto = response.json().await;
    assert_eq!(body.name, "Ronnie");
    assert_eq!(body.email, "ronnie@example.com");
    assert_eq!(body.backend, "production");
}

#[caelix::test]
async fn should_override_nested_provider() {
    let app = TestApplication::new::<AppModule>()
        .override_provider(NamedRepository { name: "in-memory" })
        .await
        .unwrap();

    let body: Value = app
        .get("/users/backend")
        .send()
        .await
        .unwrap()
        .assert_status(StatusCode::OK)
        .json()
        .await;

    assert_eq!(body, json!({ "backend": "in-memory" }));
    assert_eq!(app.resolve::<NamedRepository>().unwrap().name, "in-memory");
}