use anyhow::Result;
use clap::Parser;
use crate::cli::datadog::format::{output_as, OutputFormat};
use crate::cli::datadog::slo::{render_slo_table, SloRow};
use crate::datadog::client::DatadogClient;
use crate::datadog::slo_api::SloApi;
#[derive(Parser)]
pub struct GetCommand {
pub id: String,
#[arg(short = 'o', long, value_enum, default_value_t = OutputFormat::Table)]
pub output: OutputFormat,
}
impl GetCommand {
pub async fn execute(self, client: &DatadogClient) -> Result<()> {
run_get(client, &self.id, &self.output).await
}
}
async fn run_get(client: &DatadogClient, id: &str, output: &OutputFormat) -> Result<()> {
let slo = SloApi::new(client).get(id).await?;
if output_as(&slo, output)? {
return Ok(());
}
let stdout = std::io::stdout();
let mut handle = stdout.lock();
let row = SloRow {
id: slo.id.as_str(),
name: slo.name.as_str(),
slo_type: slo.slo_type.as_str(),
tags: &slo.tags,
};
render_slo_table(std::slice::from_ref(&row), &mut handle)?;
if let Some(desc) = slo.description.as_deref() {
if !desc.is_empty() {
use std::io::Write;
writeln!(handle, "DESCRIPTION: {desc}").ok();
}
}
Ok(())
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
fn slo_json() -> serde_json::Value {
serde_json::json!({
"id": "abc-def",
"name": "Latency",
"type": "metric",
"tags": ["team:sre"],
"monitor_ids": [],
"description": "Latency under 200ms"
})
}
#[tokio::test]
async fn run_get_table_path_writes_to_stdout() {
let server = wiremock::MockServer::start().await;
wiremock::Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/api/v1/slo/abc-def"))
.respond_with(
wiremock::ResponseTemplate::new(200).set_body_json(serde_json::json!({
"data": slo_json()
})),
)
.expect(1)
.mount(&server)
.await;
let client = DatadogClient::new(&server.uri(), "api", "app").unwrap();
run_get(&client, "abc-def", &OutputFormat::Table)
.await
.unwrap();
}
#[tokio::test]
async fn run_get_json_path_returns_ok() {
let server = wiremock::MockServer::start().await;
wiremock::Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/api/v1/slo/x"))
.respond_with(
wiremock::ResponseTemplate::new(200).set_body_json(serde_json::json!({
"data": slo_json()
})),
)
.mount(&server)
.await;
let client = DatadogClient::new(&server.uri(), "api", "app").unwrap();
run_get(&client, "x", &OutputFormat::Json).await.unwrap();
}
#[tokio::test]
async fn run_get_propagates_404() {
let server = wiremock::MockServer::start().await;
wiremock::Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/api/v1/slo/missing"))
.respond_with(wiremock::ResponseTemplate::new(404).set_body_string("not found"))
.mount(&server)
.await;
let client = DatadogClient::new(&server.uri(), "api", "app").unwrap();
let err = run_get(&client, "missing", &OutputFormat::Table)
.await
.unwrap_err();
assert!(err.to_string().contains("404"));
}
#[tokio::test]
async fn run_get_table_path_succeeds_when_description_missing() {
let server = wiremock::MockServer::start().await;
wiremock::Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/api/v1/slo/x"))
.respond_with(
wiremock::ResponseTemplate::new(200).set_body_json(serde_json::json!({
"data": {
"id": "x", "name": "y", "type": "metric",
"tags": [], "monitor_ids": []
}
})),
)
.expect(1)
.mount(&server)
.await;
let client = DatadogClient::new(&server.uri(), "api", "app").unwrap();
run_get(&client, "x", &OutputFormat::Table).await.unwrap();
}
#[tokio::test]
async fn run_get_table_path_succeeds_when_description_is_empty_string() {
let server = wiremock::MockServer::start().await;
wiremock::Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/api/v1/slo/x"))
.respond_with(
wiremock::ResponseTemplate::new(200).set_body_json(serde_json::json!({
"data": {
"id": "x", "name": "y", "type": "metric",
"tags": [], "monitor_ids": [],
"description": ""
}
})),
)
.expect(1)
.mount(&server)
.await;
let client = DatadogClient::new(&server.uri(), "api", "app").unwrap();
run_get(&client, "x", &OutputFormat::Table).await.unwrap();
}
#[tokio::test]
async fn execute_threads_id_and_output_through() {
let server = wiremock::MockServer::start().await;
wiremock::Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/api/v1/slo/abc"))
.respond_with(
wiremock::ResponseTemplate::new(200).set_body_json(serde_json::json!({
"data": slo_json()
})),
)
.expect(1)
.mount(&server)
.await;
let client = DatadogClient::new(&server.uri(), "api", "app").unwrap();
let cmd = GetCommand {
id: "abc".into(),
output: OutputFormat::Json,
};
cmd.execute(&client).await.unwrap();
}
}