Adopt rustfmt and clean up clippy warnings

Formats the whole backend to rustfmt defaults and fixes all 23
outstanding clippy warnings (collapsible ifs, clone-to-slice-ref,
type-complexity, single-arm match, unnecessary is_none check), then
wires cargo fmt --check and -D warnings into make lint so both stay
enforced going forward.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TeyhRfMYjMditek6geDRoQ
This commit is contained in:
Hugh Kaznowski
2026-09-07 12:21:38 +01:00
co-authored by Claude Sonnet 5
parent 2cc8ac8138
commit c3108677ce
28 changed files with 4721 additions and 2851 deletions
+142 -44
View File
@@ -381,7 +381,9 @@ impl OttylMcp {
// ---- logs / timeline / stats ----
#[tool(description = "Count or read log lines matching a FilterBar query against the running ottyl instance.")]
#[tool(
description = "Count or read log lines matching a FilterBar query against the running ottyl instance."
)]
async fn query_logs(
&self,
Parameters(params): Parameters<QueryLogsParams>,
@@ -414,7 +416,9 @@ impl OttylMcp {
}
}
#[tool(description = "Read the log-count timeline (bucketed histogram) matching an optional FilterBar query — the same data backing the Log Timeline chart.")]
#[tool(
description = "Read the log-count timeline (bucketed histogram) matching an optional FilterBar query — the same data backing the Log Timeline chart."
)]
async fn get_timeline(
&self,
Parameters(params): Parameters<GetTimelineParams>,
@@ -435,14 +439,18 @@ impl OttylMcp {
json_result(self.get_json("/api/timeline", &query).await?)
}
#[tool(description = "Read overall ingest stats: log count, sequence/timestamp bounds, ingest lag.")]
#[tool(
description = "Read overall ingest stats: log count, sequence/timestamp bounds, ingest lag."
)]
async fn get_stats(&self) -> Result<CallToolResult, McpError> {
json_result(self.get_json("/api/stats", &[]).await?)
}
// ---- params ----
#[tool(description = "List all currently declared params (name, regex, kind, etc.), including auto-derived ones.")]
#[tool(
description = "List all currently declared params (name, regex, kind, etc.), including auto-derived ones."
)]
async fn list_params(&self) -> Result<CallToolResult, McpError> {
json_result(self.get_json("/api/params", &[]).await?)
}
@@ -452,13 +460,22 @@ impl OttylMcp {
&self,
Parameters(params): Parameters<DeclareParamParams>,
) -> Result<CallToolResult, McpError> {
let body = serde_json::to_value(&params).map_err(|err| mcp_error("failed to encode params", err))?;
json_result(self.send_json(reqwest::Method::POST, "/api/params", Some(&body)).await?)
let body = serde_json::to_value(&params)
.map_err(|err| mcp_error("failed to encode params", err))?;
json_result(
self.send_json(reqwest::Method::POST, "/api/params", Some(&body))
.await?,
)
}
#[tool(description = "Clear all declared params and re-derive auto params from retained log lines.")]
#[tool(
description = "Clear all declared params and re-derive auto params from retained log lines."
)]
async fn clear_params(&self) -> Result<CallToolResult, McpError> {
json_result(self.send_json(reqwest::Method::DELETE, "/api/params", None).await?)
json_result(
self.send_json(reqwest::Method::DELETE, "/api/params", None)
.await?,
)
}
// ---- metrics ----
@@ -468,13 +485,19 @@ impl OttylMcp {
json_result(self.get_json("/api/metrics", &[]).await?)
}
#[tool(description = "Declare a metric: named event counts/values bucketed by label, derived from matching log lines.")]
#[tool(
description = "Declare a metric: named event counts/values bucketed by label, derived from matching log lines."
)]
async fn declare_metric(
&self,
Parameters(params): Parameters<DeclareMetricParams>,
) -> Result<CallToolResult, McpError> {
let body = serde_json::to_value(&params).map_err(|err| mcp_error("failed to encode params", err))?;
json_result(self.send_json(reqwest::Method::POST, "/api/metrics", Some(&body)).await?)
let body = serde_json::to_value(&params)
.map_err(|err| mcp_error("failed to encode params", err))?;
json_result(
self.send_json(reqwest::Method::POST, "/api/metrics", Some(&body))
.await?,
)
}
#[tool(description = "Update an existing metric declaration in place by name.")]
@@ -509,7 +532,9 @@ impl OttylMcp {
)
}
#[tool(description = "Read a metric's raw bucket values (labels, sequence, timestamp, value), optionally filtered by label and time range.")]
#[tool(
description = "Read a metric's raw bucket values (labels, sequence, timestamp, value), optionally filtered by label and time range."
)]
async fn get_metric_bucket_values(
&self,
Parameters(params): Parameters<GetMetricBucketValuesParams>,
@@ -531,19 +556,27 @@ impl OttylMcp {
query.push(("limit", limit.to_string()));
}
json_result(
self.get_json(&format!("/api/metrics/{}", urlencoding_path(&params.name)), &query)
.await?,
self.get_json(
&format!("/api/metrics/{}", urlencoding_path(&params.name)),
&query,
)
.await?,
)
}
// ---- promql ----
#[tool(description = "Evaluate a PromQL-style query directly and get back raw series data (labels + timestamped points) — the same evaluation dashboards use to render charts.")]
#[tool(
description = "Evaluate a PromQL-style query directly and get back raw series data (labels + timestamped points) — the same evaluation dashboards use to render charts."
)]
async fn run_promql_query(
&self,
Parameters(params): Parameters<RunPromqlQueryParams>,
) -> Result<CallToolResult, McpError> {
json_result(self.run_promql(&params.promql, params.start_ms, params.end_ms).await?)
json_result(
self.run_promql(&params.promql, params.start_ms, params.end_ms)
.await?,
)
}
// ---- dashboards / panels ----
@@ -556,7 +589,10 @@ impl OttylMcp {
let profile = self.resolve_profile(params.profile).await?;
json_result(
self.get_json(
&format!("/api/settings/profiles/{}/dashboards", urlencoding_path(&profile)),
&format!(
"/api/settings/profiles/{}/dashboards",
urlencoding_path(&profile)
),
&[],
)
.await?,
@@ -572,14 +608,19 @@ impl OttylMcp {
json_result(
self.send_json(
reqwest::Method::POST,
&format!("/api/settings/profiles/{}/dashboards", urlencoding_path(&profile)),
&format!(
"/api/settings/profiles/{}/dashboards",
urlencoding_path(&profile)
),
Some(&serde_json::json!({ "name": params.name })),
)
.await?,
)
}
#[tool(description = "Read a dashboard's current panels (definitions only — use render_panel or render_dashboard for the data a panel would chart).")]
#[tool(
description = "Read a dashboard's current panels (definitions only — use render_panel or render_dashboard for the data a panel would chart)."
)]
async fn get_dashboard(
&self,
Parameters(params): Parameters<GetDashboardParams>,
@@ -631,12 +672,18 @@ impl OttylMcp {
.get_mut("panels")
.and_then(Value::as_array_mut)
.ok_or_else(|| mcp_error("unexpected dashboard shape", "missing panels array"))?;
match panels.iter().position(|panel| panel.get("id") == new_panel.get("id")) {
match panels
.iter()
.position(|panel| panel.get("id") == new_panel.get("id"))
{
Some(index) => panels[index] = new_panel,
None => panels.push(new_panel),
}
json_result(self.save_dashboard(&profile, &params.dashboard_id, dashboard).await?)
json_result(
self.save_dashboard(&profile, &params.dashboard_id, dashboard)
.await?,
)
}
#[tool(description = "Remove a panel from a dashboard by panel_id.")]
@@ -650,8 +697,13 @@ impl OttylMcp {
.get_mut("panels")
.and_then(Value::as_array_mut)
.ok_or_else(|| mcp_error("unexpected dashboard shape", "missing panels array"))?;
panels.retain(|panel| panel.get("id").and_then(Value::as_str) != Some(params.panel_id.as_str()));
json_result(self.save_dashboard(&profile, &params.dashboard_id, dashboard).await?)
panels.retain(|panel| {
panel.get("id").and_then(Value::as_str) != Some(params.panel_id.as_str())
});
json_result(
self.save_dashboard(&profile, &params.dashboard_id, dashboard)
.await?,
)
}
async fn save_dashboard(
@@ -672,7 +724,9 @@ impl OttylMcp {
.await
}
#[tool(description = "Render one panel: resolve its promql from the dashboard definition, evaluate it, and return the chart's underlying series data (labels + timestamped points) as JSON — this is how an agent should 'see' a chart without a browser.")]
#[tool(
description = "Render one panel: resolve its promql from the dashboard definition, evaluate it, and return the chart's underlying series data (labels + timestamped points) as JSON — this is how an agent should 'see' a chart without a browser."
)]
async fn render_panel(
&self,
Parameters(params): Parameters<RenderPanelParams>,
@@ -682,20 +736,30 @@ impl OttylMcp {
let panel = dashboard
.get("panels")
.and_then(Value::as_array)
.and_then(|panels| panels.iter().find(|panel| panel.get("id").and_then(Value::as_str) == Some(params.panel_id.as_str())))
.ok_or_else(|| McpError::invalid_params("no panel with that panel_id in the dashboard", None))?;
.and_then(|panels| {
panels.iter().find(|panel| {
panel.get("id").and_then(Value::as_str) == Some(params.panel_id.as_str())
})
})
.ok_or_else(|| {
McpError::invalid_params("no panel with that panel_id in the dashboard", None)
})?;
let promql = panel
.get("promql")
.and_then(Value::as_str)
.ok_or_else(|| mcp_error("unexpected panel shape", "missing promql"))?;
let series = self.run_promql(promql, params.start_ms, params.end_ms).await?;
let series = self
.run_promql(promql, params.start_ms, params.end_ms)
.await?;
json_result(serde_json::json!({
"panel": panel,
"series": series,
}))
}
#[tool(description = "Render every panel in a dashboard: evaluate each panel's promql and return all their chart data as JSON, so an agent can reason about the whole dashboard without a browser.")]
#[tool(
description = "Render every panel in a dashboard: evaluate each panel's promql and return all their chart data as JSON, so an agent can reason about the whole dashboard without a browser."
)]
async fn render_dashboard(
&self,
Parameters(params): Parameters<RenderDashboardParams>,
@@ -709,8 +773,13 @@ impl OttylMcp {
.unwrap_or_default();
let mut rendered = Vec::with_capacity(panels.len());
for panel in panels {
let promql = panel.get("promql").and_then(Value::as_str).unwrap_or_default();
let series = self.run_promql(promql, params.start_ms, params.end_ms).await?;
let promql = panel
.get("promql")
.and_then(Value::as_str)
.unwrap_or_default();
let series = self
.run_promql(promql, params.start_ms, params.end_ms)
.await?;
rendered.push(serde_json::json!({ "panel": panel, "series": series }));
}
json_result(serde_json::json!({
@@ -721,18 +790,26 @@ impl OttylMcp {
// ---- settings / theming ----
#[tool(description = "Read the running instance's global settings (theme, active profile, max_logs, ntfy config).")]
#[tool(
description = "Read the running instance's global settings (theme, active profile, max_logs, ntfy config)."
)]
async fn get_settings(&self) -> Result<CallToolResult, McpError> {
json_result(self.get_json("/api/settings", &[]).await?)
}
#[tool(description = "Update global settings. Only fields provided are changed; pass null for an Option field to clear it.")]
#[tool(
description = "Update global settings. Only fields provided are changed; pass null for an Option field to clear it."
)]
async fn update_settings(
&self,
Parameters(params): Parameters<UpdateSettingsParams>,
) -> Result<CallToolResult, McpError> {
let body = serde_json::to_value(&params).map_err(|err| mcp_error("failed to encode params", err))?;
json_result(self.send_json(reqwest::Method::PATCH, "/api/settings", Some(&body)).await?)
let body = serde_json::to_value(&params)
.map_err(|err| mcp_error("failed to encode params", err))?;
json_result(
self.send_json(reqwest::Method::PATCH, "/api/settings", Some(&body))
.await?,
)
}
#[tool(description = "List the names of the themes embedded in this build (see /api/themes).")]
@@ -779,7 +856,10 @@ impl OttylMcp {
"enabled": params.enabled,
"condition": params.condition,
});
json_result(self.send_json(reqwest::Method::POST, "/api/settings/alerts", Some(&body)).await?)
json_result(
self.send_json(reqwest::Method::POST, "/api/settings/alerts", Some(&body))
.await?,
)
}
#[tool(description = "Update an existing alert rule by id.")]
@@ -817,21 +897,30 @@ impl OttylMcp {
)
}
#[tool(description = "Send a test ntfy notification to verify alert delivery is configured correctly.")]
#[tool(
description = "Send a test ntfy notification to verify alert delivery is configured correctly."
)]
async fn test_alert_notification(
&self,
Parameters(params): Parameters<TestAlertNotificationParams>,
) -> Result<CallToolResult, McpError> {
let body = serde_json::to_value(&params).map_err(|err| mcp_error("failed to encode params", err))?;
let body = serde_json::to_value(&params)
.map_err(|err| mcp_error("failed to encode params", err))?;
json_result(
self.send_json(reqwest::Method::POST, "/api/settings/alerts/test", Some(&body))
.await?,
self.send_json(
reqwest::Method::POST,
"/api/settings/alerts/test",
Some(&body),
)
.await?,
)
}
// ---- profiles ----
#[tool(description = "List all saved profiles with summary counts (params, metrics, dashboards).")]
#[tool(
description = "List all saved profiles with summary counts (params, metrics, dashboards)."
)]
async fn list_profiles(&self) -> Result<CallToolResult, McpError> {
json_result(self.get_json("/api/settings/profiles", &[]).await?)
}
@@ -841,8 +930,12 @@ impl OttylMcp {
&self,
Parameters(params): Parameters<CreateProfileParams>,
) -> Result<CallToolResult, McpError> {
let body = serde_json::to_value(&params).map_err(|err| mcp_error("failed to encode params", err))?;
json_result(self.send_json(reqwest::Method::POST, "/api/settings/profiles", Some(&body)).await?)
let body = serde_json::to_value(&params)
.map_err(|err| mcp_error("failed to encode params", err))?;
json_result(
self.send_json(reqwest::Method::POST, "/api/settings/profiles", Some(&body))
.await?,
)
}
#[tool(description = "Delete a profile by name.")]
@@ -860,7 +953,9 @@ impl OttylMcp {
)
}
#[tool(description = "Activate a profile: swaps in its param/metric declarations and dashboards as the running instance's live state.")]
#[tool(
description = "Activate a profile: swaps in its param/metric declarations and dashboards as the running instance's live state."
)]
async fn activate_profile(
&self,
Parameters(params): Parameters<ProfileNameParams>,
@@ -868,7 +963,10 @@ impl OttylMcp {
json_result(
self.send_json(
reqwest::Method::POST,
&format!("/api/settings/profiles/{}/activate", urlencoding_path(&params.name)),
&format!(
"/api/settings/profiles/{}/activate",
urlencoding_path(&params.name)
),
None,
)
.await?,