cellos-server 0.5.3

HTTP control plane for CellOS — admission, projection over JetStream, WebSocket fan-out of CloudEvents. Pure event-sourced architecture.
Documentation
//! `/v1/version` — server build metadata.
//!
//! This is the canonical day-1 reachability probe for cellctl. A fresh
//! operator types `cellctl version` to confirm the server is alive
//! before doing anything else; the route MUST exist (E2E report
//! SRV-001) and MUST require Bearer auth like every other route.
//!
//! ## Response shape (stable)
//!
//! ```json
//! {
//!   "server": {
//!     "version":      "0.5.0",      // env!("CARGO_PKG_VERSION")
//!     "gitSha":       "abc1234",     // optional; only present when CELLOS_GIT_SHA was set at build time
//!     "buildProfile": "release"     // "release" or "debug"
//!   },
//!   "api": { "version": "v1" }
//! }
//! ```
//!
//! `gitSha` is best-effort: the field is *omitted* (via
//! `#[serde(skip_serializing_if = "Option::is_none")]`) when the
//! `CELLOS_GIT_SHA` env var is unset at build time. This keeps test
//! output stable regardless of git state and avoids forcing a
//! `build.rs` that would re-run on every commit.
//!
//! ## Why a typed response instead of `serde_json::json!`
//!
//! The response shape is a stable contract — cellctl pins on
//! `server.version` today and future tooling will pin on more fields.
//! A struct catches typos at compile time and gives us a place to hang
//! doc comments that explain each field.

use axum::extract::State;
use axum::http::HeaderMap;
use axum::Json;
use serde::Serialize;

use crate::auth::require_bearer;
use crate::error::AppError;
use crate::state::AppState;

/// Compile-time build profile string. `cfg!(debug_assertions)` is
/// evaluated at the call site, so this constant materialises to a
/// single `&'static str` per build.
const BUILD_PROFILE: &str = if cfg!(debug_assertions) {
    "debug"
} else {
    "release"
};

/// Top-level wire shape returned by `GET /v1/version`.
#[derive(Debug, Serialize)]
pub struct VersionResponse {
    pub server: ServerInfo,
    pub api: ApiInfo,
}

#[derive(Debug, Serialize)]
pub struct ServerInfo {
    /// `env!("CARGO_PKG_VERSION")` — the cellos-server crate version.
    pub version: &'static str,
    /// Short git SHA captured at build time via the optional
    /// `CELLOS_GIT_SHA` env var. Omitted from the JSON when unset so
    /// downstream consumers can rely on "present means real".
    #[serde(rename = "gitSha", skip_serializing_if = "Option::is_none")]
    pub git_sha: Option<&'static str>,
    /// `"release"` for non-debug builds, `"debug"` otherwise.
    #[serde(rename = "buildProfile")]
    pub build_profile: &'static str,
}

#[derive(Debug, Serialize)]
pub struct ApiInfo {
    /// API surface version. Stable string — bump only when the URL
    /// prefix bumps (e.g. `/v2`).
    pub version: &'static str,
}

/// `GET /v1/version` handler. Bearer auth required.
pub async fn get_version(
    State(state): State<AppState>,
    headers: HeaderMap,
) -> Result<Json<VersionResponse>, AppError> {
    require_bearer(&headers, &state.api_token)?;
    Ok(Json(VersionResponse {
        server: ServerInfo {
            version: env!("CARGO_PKG_VERSION"),
            git_sha: option_env!("CELLOS_GIT_SHA"),
            build_profile: BUILD_PROFILE,
        },
        api: ApiInfo { version: "v1" },
    }))
}