puerto 0.19.1

Puerto — Rust full-stack DDD framework CLI. Scaffolds Clean Architecture projects with auto-wired DI, SQLx support, and zero manual wiring. Formerly puerto-framework.
// AUTO-GENERATED by puerto. Edit puerto.toml, then run `puerto generate bootstrap`.
{% if entities or auth -%}
use std::sync::Arc;

{% endif -%}
{% if entities -%}
{% for entity in entities -%}
{% for uc in entity.use_cases -%}
use business::application::{{ entity.snake }}::{{ uc.action }}::{{ uc.uc_pascal }}UseCaseImpl;
{% endfor -%}
{% endfor %}
{% for entity in entities -%}
use business::domain::{{ entity.snake }}::repository::{{ entity.pascal }}RepositoryTrait;
{% if entity.uow -%}
use business::domain::{{ entity.snake }}::unit_of_work::{{ entity.pascal }}UnitOfWorkFactory;
use infrastructure::{{ entity.snake }}::unit_of_work::Pg{{ entity.pascal }}UnitOfWorkFactory;
{% endif -%}
use infrastructure::{{ entity.snake }}::repository::{% if entity.db %}Pg{% else %}InMemory{% endif %}{{ entity.pascal }}Repository;
{% endfor %}
{% endif -%}
{% if local -%}
use business::application::user::login::LoginUseCaseImpl;
use business::application::user::logout::LogoutUseCaseImpl;
use business::application::user::register_user::RegisterUserUseCaseImpl;
use business::domain::user::repository::UserRepositoryTrait;
use infrastructure::db::auth::{PgCredentials, PgSessions};
use infrastructure::user::repository::PgUserRepository;
use poem::EndpointExt;
{% elif auth -%}
use infrastructure::user::provisioning::ProvisioningAuthenticator;
use infrastructure::user::repository::{% if user_db %}Pg{% else %}InMemory{% endif %}UserRepository;
use poem::EndpointExt;
{% endif -%}
use poem::Endpoint;
use poem_openapi::OpenApiService;
{% if local %}use puerto_core::auth::{Authenticator, CredentialStore, PasswordHasher};
{% elif auth %}use puerto_core::auth::Authenticator;
{% endif %}
{% if local -%}
use crate::api::auth::routes::AuthApi;
{% endif -%}
{% if auth -%}
use crate::api::me::routes::MeApi;
{% endif -%}
{% for entity in entities -%}
use crate::api::{{ entity.snake }}::routes::{{ entity.pascal }}Api;
{% endfor %}
/// The routes, wired. An error is what stopped the startup: `main` prints it and exits 1.
pub async fn build_app(config: puerto_web::Config) -> Result<impl Endpoint, String> {
{%- if has_db %}
    let database_url = std::env::var("DATABASE_URL")
        .map_err(|_| "DATABASE_URL is not set — set it in .env (.env.example has one)".to_string())?;
    let pool = infrastructure::db::create_postgres_pool(&database_url)
        .await
        .map_err(|e| format!("{e} — is the database up? `make docker-compose/up`"))?;
    infrastructure::db::run_migrations(&pool, &infrastructure::db::MIGRATOR)
        .await
        .map_err(|e| e.to_string())?;
{% endif %}
{%- if local %}
    // Accounts in this database (ADR-0021 § 2): their sessions are what every `BearerAuth` asks.
    let users: Arc<dyn UserRepositoryTrait> = Arc::new(PgUserRepository::new(pool.clone()));
    let credentials: Arc<dyn CredentialStore> = Arc::new(PgCredentials::new(pool.clone()));
    let hasher: Arc<dyn PasswordHasher> = Arc::new(puerto_auth::local::Argon2Hasher::new());
    let sessions = Arc::new(PgSessions::new(
        pool.clone(),
        infrastructure::db::auth::session_ttl_from_env()?,
    ));
    let authenticator: Arc<dyn Authenticator> = sessions.clone();
    let auth_api = AuthApi {
        register_user: Arc::new(RegisterUserUseCaseImpl {
            users: Arc::clone(&users),
            credentials: Arc::clone(&credentials),
            hasher: Arc::clone(&hasher),
        }),
        login: Arc::new(LoginUseCaseImpl {
            users,
            credentials,
            hasher,
            sessions: sessions.clone(),
        }),
        logout: Arc::new(LogoutUseCaseImpl { sessions }),
    };
    let me_api = MeApi;
{% elif auth %}
    // The provider's tokens, and a `User` row for every caller (ADR-0021 § 3).
    let auth_config = puerto_auth::OidcConfig::from_env().map_err(|e| e.to_string())?;
    let issuer = auth_config.issuer.clone();
    let provider = puerto_auth::OidcAuthenticator::discover(auth_config)
        .await
        .map_err(|e| format!("{e} — is the auth provider up? `make docker-compose/up`"))?;
    let authenticator: Arc<dyn Authenticator> = Arc::new(ProvisioningAuthenticator::new(
        Arc::new(provider),
        Arc::new({% if user_db %}PgUserRepository::new(pool.clone()){% else %}InMemoryUserRepository::new(){% endif %}),
        issuer,
    ));
    let me_api = MeApi;
{% endif %}
{%- for entity in entities %}
{%- if entity.db %}{% set repo = "Pg" ~ entity.pascal ~ "Repository::new(pool.clone())" %}{% else %}{% set repo = "InMemory" ~ entity.pascal ~ "Repository::new()" %}{% endif %}
{%- if entity.uow %}
    let {{ entity.snake }}_repo: Arc<dyn {{ entity.pascal }}RepositoryTrait> = Arc::new({{ repo }});
    let {{ entity.snake }}_uow: Arc<dyn {{ entity.pascal }}UnitOfWorkFactory> = Arc::new(Pg{{ entity.pascal }}UnitOfWorkFactory::new(pool.clone()));
{%- for uc in entity.use_cases %}
    let {{ uc.action }} = Arc::new({{ uc.uc_pascal }}UseCaseImpl { {% if uc.repository %}repository: Arc::clone(&{{ entity.snake }}_repo){% endif %}{% if uc.repository and uc.uow %}, {% endif %}{% if uc.uow %}uow_factory: Arc::clone(&{{ entity.snake }}_uow){% endif %} });
{%- endfor %}
{%- else %}
{%- if entity.use_cases | length > 1 %}
    let {{ entity.snake }}_repo: Arc<dyn {{ entity.pascal }}RepositoryTrait> = Arc::new({{ repo }});
{%- endif %}
{%- for uc in entity.use_cases %}
    let {{ uc.action }} = Arc::new({{ uc.uc_pascal }}UseCaseImpl { repository: {% if entity.use_cases | length == 1 %}Arc::new({{ repo }}) as Arc<dyn {{ entity.pascal }}RepositoryTrait>{% elif loop.last %}{{ entity.snake }}_repo{% else %}Arc::clone(&{{ entity.snake }}_repo){% endif %} });
{%- endfor %}
{%- endif %}
    let {{ entity.snake }}_api = {{ entity.pascal }}Api {% if entity.use_cases %}{ {% for uc in entity.use_cases %}{{ uc.action }}{% if not loop.last %}, {% endif %}{% endfor %} }{% else %}{}{% endif %};
{% endfor %}
{%- if has_db %}
    let ready = puerto_web::health::probe(move || {
        let pool = pool.clone();
        async move { infrastructure::db::ping(&pool).await }
    });
{%- else %}
    let ready = puerto_web::health::always_ready();
{%- endif %}

    // `app.rs` is the project's own: middleware and routes added there wrap everything here.
    Ok(crate::app::configure(puerto_web::routes(
        openapi_service({{ apis }}),
        &config,
        ready,
    )){% if auth %}
    // What every `BearerAuth` asks.
    .data(authenticator){% endif %})
}

/// The API service, built in one place: whatever else reads its spec (the export test of a project
/// with `web/`) sees the API the server answers.
fn openapi_service<T: poem_openapi::OpenApi>(api: T) -> OpenApiService<T, ()> {
    OpenApiService::new(api, env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"))
}
{%- if web %}

#[cfg(test)]
mod tests {
    use super::*;
{% for entity in entities -%}
{% for uc in entity.use_cases %}
    use business::domain::{{ entity.snake }}::use_cases::{{ uc.action }}::mocks::Mock{{ uc.uc_pascal }}UseCase;
{%- endfor %}
{%- endfor %}
{%- if local %}
    use business::domain::user::use_cases::login::mocks::MockLoginUseCase;
    use business::domain::user::use_cases::logout::mocks::MockLogoutUseCase;
    use business::domain::user::use_cases::register_user::mocks::MockRegisterUserUseCase;
{%- endif %}

    /// The API contract the frontend's types are generated from (`make web/sync`).
    const OPENAPI_JSON: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../docs/api/openapi.json");

    /// Built from mocked use cases: no server, no database. A missing file is written; a different
    /// one fails — run `make web/sync` (it sets `UPDATE_OPENAPI=1`) and commit the diff.
    #[test]
    fn openapi_spec_matches_docs_api_openapi_json() {
{%- for entity in entities %}
        let {{ entity.snake }}_api = {{ entity.pascal }}Api {% if entity.use_cases %}{ {% for uc in entity.use_cases %}{{ uc.action }}: Arc::new(Mock{{ uc.uc_pascal }}UseCase::new()){% if not loop.last %}, {% endif %}{% endfor %} }{% else %}{}{% endif %};
{%- endfor %}
{%- if auth %}
        let me_api = MeApi;
{%- endif %}
{%- if local %}
        let auth_api = AuthApi {
            register_user: Arc::new(MockRegisterUserUseCase::new()),
            login: Arc::new(MockLoginUseCase::new()),
            logout: Arc::new(MockLogoutUseCase::new()),
        };
{%- endif %}
        let spec = openapi_service({{ apis }}).spec();

        let path = std::path::Path::new(OPENAPI_JSON);
        if std::env::var_os("UPDATE_OPENAPI").is_some() || !path.exists() {
            let dir = path.parent().expect("docs/api/openapi.json has a parent");
            std::fs::create_dir_all(dir).expect("create docs/api");
            std::fs::write(path, &spec).expect("write docs/api/openapi.json");
            return;
        }
        let committed = std::fs::read_to_string(path).expect("read docs/api/openapi.json");
        assert!(
            committed == spec,
            "docs/api/openapi.json is not the API this code serves: run `make web/sync`"
        );
    }
}
{%- endif %}