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}