Skip to main content

lightshuttle_control/
state.rs

1//! Shared application state injected into every axum handler.
2//!
3//! [`ControlState`] is generic over a [`LifecycleHandle`] implementation so
4//! the control plane remains free of runtime-specific types. Axum requires
5//! the state to implement `Clone`, so the handle must be cheaply cloneable
6//! (typically via an inner `Arc`).
7
8use std::sync::Arc;
9
10use lightshuttle_runtime::LifecycleHandle;
11
12use crate::metrics::Metrics;
13
14/// Shared state injected into every route of the control plane.
15///
16/// Constructed once and cloned into the axum router via
17/// [`axum::Router::with_state`]. All fields that route handlers need are
18/// either `pub` or exposed through the constructors below.
19///
20/// Use [`ControlState::new`] for tests and embedders that do not need
21/// Prometheus metrics, and [`ControlState::with_metrics`] for production
22/// use where `GET /metrics` must return live data.
23#[derive(Clone)]
24pub struct ControlState<H>
25where
26    H: LifecycleHandle + Clone,
27{
28    /// Project name as declared in the manifest.
29    ///
30    /// Shown in the dashboard title and returned by `GET /healthz`.
31    pub project: String,
32    /// Lifecycle handle backing the resource endpoints.
33    ///
34    /// Handlers call [`lightshuttle_runtime::LifecycleHandle::list`],
35    /// [`lightshuttle_runtime::LifecycleHandle::get`],
36    /// [`lightshuttle_runtime::LifecycleHandle::restart`],
37    /// [`lightshuttle_runtime::LifecycleHandle::logs`], and
38    /// [`lightshuttle_runtime::LifecycleHandle::subscribe_events`] through
39    /// this field.
40    pub handle: H,
41    /// Prometheus metrics renderer.
42    pub(crate) metrics: Arc<Metrics>,
43}
44
45impl<H> ControlState<H>
46where
47    H: LifecycleHandle + Clone,
48{
49    /// Build state with a non-installing [`crate::Metrics`] handle.
50    ///
51    /// The attached [`crate::Metrics`] does not install a global recorder, so
52    /// the `metrics!` macros write nowhere and `GET /metrics` renders an
53    /// empty snapshot. This constructor is intended for tests and for embedders
54    /// that do not need to serve metrics. In production, install the recorder
55    /// once with [`crate::Metrics::install`] and use [`Self::with_metrics`]
56    /// to pass the live handle.
57    pub fn new(project: impl Into<String>, handle: H) -> Self {
58        Self {
59            project: project.into(),
60            handle,
61            metrics: Arc::new(Metrics::for_test()),
62        }
63    }
64
65    /// Build state bound to an existing [`crate::Metrics`] renderer.
66    ///
67    /// Use this constructor in production when you have already called
68    /// [`crate::Metrics::install`] and wrapped the result in an `Arc`.
69    /// `GET /metrics` will then render live counters and gauges.
70    ///
71    /// The `Arc` is cheap to clone across axum route handlers.
72    pub fn with_metrics(project: impl Into<String>, handle: H, metrics: Arc<Metrics>) -> Self {
73        Self {
74            project: project.into(),
75            handle,
76            metrics,
77        }
78    }
79}