arcature 2026.2.0

Arcature application framework: a high-level Application facade over the certified Arcature subsystems, with the low-level Axum/Tower escape hatch preserved.
Documentation
//! The zero-plumbing bootstrap factory — `arcature::run()` (AP2.1-2).
//!
//! The golden path for an Arcature application should not require the
//! developer to repeat the same boilerplate every app writes: read the
//! bind port from the environment, fall back to the default, start from a
//! fresh `ApplicationBuilder<()>`. [`run`] does exactly that and returns
//! the partially-configured builder, so the application's bootstrap
//! shrinks to the subsystems + state it actually owns:
//!
//! ```ignore
//! use arcature::prelude::*;
//!
//! #[arcature::main]
//! async fn main() -> arcature::Result<()> {
//!     arcature::run()
//!         .routes(routes::routes())
//!         .database(db_config)
//!         .build()
//!         .run_with_lifecycle(|resources| AppState::from_resources(resources))
//!         .await
//! }
//! ```
//!
//! The factory is *not* a global, a singleton, or a runtime reflection
//! container — it is a plain function returning a fresh owned
//! [`ApplicationBuilder`] (AGENTS.md §20). It reads exactly one documented
//! environment variable (`ARCATURE_BACKEND_PORT`), the established dev
//! convention; it never reads subsystem URLs or secrets (those stay
//! explicit per AGENTS.md §21 — configuration is explicit and resolved).
//!
//! Expert users who want a different port source, bind address, or no
//! env read at all simply call [`Application::new`](crate::Application::new)
//! and set `.port()` / `.bind()` themselves — the builder remains the
//! full-configuration seam.

use crate::application::ty::{DEFAULT_BIND_ADDR, DEFAULT_PORT};

/// The environment variable consulted by [`run`] for the bind port.
/// This is the established dev convention (`arc dev` sets it); production
/// deployments set it explicitly or fall back to the default.
pub(crate) const PORT_ENV: &str = "ARCATURE_BACKEND_PORT";

/// The zero-plumbing bootstrap factory.
///
/// Returns a fresh [`crate::ApplicationBuilder<()>`] configured with the
/// default bind address (`127.0.0.1`) and a port read from the
/// `ARCATURE_BACKEND_PORT` environment variable (default `3000`). The
/// application then adds its routes, subsystem configs, and state
/// closure and calls `.build().run_with_lifecycle(state_fn).await`.
///
/// This is additive — [`Application::new`](crate::Application::new)
/// remains the full-configuration seam for callers that want different
/// defaults. Reading the port env here is the single documented
/// convention; no other env variable is read, and no global state is
/// created (AGENTS.md §20/§21).
#[must_use]
pub fn run() -> crate::ApplicationBuilder<()> {
    crate::Application::new()
        .bind(DEFAULT_BIND_ADDR)
        .port(port_from_env())
}

/// Resolves the bind port from `ARCATURE_BACKEND_PORT`, defaulting to
/// [`DEFAULT_PORT`] when unset or unparseable. Never panics — a malformed
/// value silently falls back to the default (the dev convention is a
/// plain integer; an operator who sets garbage gets the default, not a
/// crash on startup).
fn port_from_env() -> u16 {
    std::env::var(PORT_ENV)
        .ok()
        .and_then(|value| value.parse::<u16>().ok())
        .unwrap_or(DEFAULT_PORT)
}

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

    #[test]
    fn port_from_env_defaults_when_unset() {
        // The env var may be unset in the test environment; if it is set,
        // clear it for this test's scope is not possible without process-
        // global mutation, so this asserts the default *only when unset*.
        // (If `ARCATURE_BACKEND_PORT` is set in CI it asserts the parsed
        // value instead — both are correct behavior.)
        let resolved = port_from_env();
        assert!(
            resolved == DEFAULT_PORT || std::env::var(PORT_ENV).is_ok(),
            "expected the default port when the env var is unset"
        );
    }

    #[test]
    fn run_returns_builder_with_default_bind() {
        let builder = run();
        // The factory wires the documented default bind address.
        assert_eq!(builder.bind_address, DEFAULT_BIND_ADDR);
        // The port is either the env override or the default — both valid.
        let expected = std::env::var(PORT_ENV)
            .ok()
            .and_then(|v| v.parse::<u16>().ok())
            .unwrap_or(DEFAULT_PORT);
        assert_eq!(builder.port, expected);
    }
}