Skip to main content

saluki_components/sources/dogstatsd/replay/
capture_api.rs

1//! HTTP API handler for the DogStatsD capture control surface.
2//!
3//! Exposes `POST /dogstatsd/capture/trigger` on the privileged API to start a capture session.
4
5use std::path::Path;
6
7use saluki_api::{
8    extract::State,
9    routing::{post, Router},
10    APIHandler, Json, StatusCode,
11};
12use saluki_config::parse_duration;
13use serde::{Deserialize, Serialize};
14
15use super::DogStatsDCaptureControl;
16
17/// Request body for `POST /dogstatsd/capture/trigger`.
18#[derive(Deserialize)]
19pub struct CaptureTriggerBody {
20    /// Duration of the capture, parsed by `parse_duration` (for example, `"10s"`, `"500ms"`).
21    pub duration: String,
22
23    /// Optional override for the capture output directory. When omitted, the source's
24    /// configured default directory is used.
25    #[serde(default)]
26    pub path: Option<String>,
27
28    /// Whether the capture file should be zstd-compressed.
29    #[serde(default)]
30    pub compressed: bool,
31}
32
33/// Response body for `POST /dogstatsd/capture/trigger`.
34#[derive(Serialize)]
35pub struct CaptureTriggerResponseBody {
36    /// Absolute path the capture is being written to.
37    pub path: String,
38}
39
40/// API handler for the DogStatsD capture control surface.
41#[derive(Clone)]
42pub struct DogStatsDCaptureAPIHandler {
43    capture_control: DogStatsDCaptureControl,
44}
45
46impl DogStatsDCaptureAPIHandler {
47    /// Creates a new handler bound to the given capture control.
48    pub fn new(capture_control: DogStatsDCaptureControl) -> Self {
49        Self { capture_control }
50    }
51
52    async fn trigger_handler(
53        State(capture_control): State<DogStatsDCaptureControl>, Json(body): Json<CaptureTriggerBody>,
54    ) -> Result<Json<CaptureTriggerResponseBody>, (StatusCode, String)> {
55        let duration = parse_duration(&body.duration).map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
56        let requested_dir = body.path.as_deref().map(Path::new);
57
58        let capture_path = capture_control
59            .start_capture(requested_dir, duration, body.compressed)
60            .map_err(|e| (StatusCode::PRECONDITION_FAILED, e.to_string()))?;
61
62        Ok(Json(CaptureTriggerResponseBody {
63            path: capture_path.display().to_string(),
64        }))
65    }
66}
67
68impl APIHandler for DogStatsDCaptureAPIHandler {
69    type State = DogStatsDCaptureControl;
70
71    fn generate_initial_state(&self) -> Self::State {
72        self.capture_control.clone()
73    }
74
75    fn generate_routes(&self) -> Router<Self::State> {
76        Router::new().route("/dogstatsd/capture/trigger", post(Self::trigger_handler))
77    }
78}