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:
co-authored by
Claude Sonnet 5
parent
2cc8ac8138
commit
c3108677ce
+342
-82
@@ -7,23 +7,23 @@ use std::sync::Arc;
|
||||
|
||||
use crate::api::dto::{
|
||||
BucketValuesParams, CreateAlertRuleRequest, CreateDashboardRequest, CreateProfileRequest,
|
||||
ErrorDto, MetricDeclarationDto,
|
||||
ParamDeclarationDto, LogsParams, ProfileSummaryDto, PromqlParams, SaveDashboardRequest,
|
||||
SettingsDto, TestAlertNotificationRequest, TimelineParams, UpdateAlertRuleRequest, UpdateSettingsRequest,
|
||||
ErrorDto, LogsParams, MetricDeclarationDto, ParamDeclarationDto, ProfileSummaryDto,
|
||||
PromqlParams, SaveDashboardRequest, SettingsDto, TestAlertNotificationRequest, TimelineParams,
|
||||
UpdateAlertRuleRequest, UpdateSettingsRequest,
|
||||
};
|
||||
use crate::config::SettingsHandle;
|
||||
use crate::config::profile::{Dashboard, ParamDeclarationRecord, Profile, ProfileError};
|
||||
use crate::engine::alerts::{AlertError, deliver_ntfy};
|
||||
use crate::engine::data_value::MetricPrimitiveKind;
|
||||
use crate::engine::{
|
||||
DeclareMetricRequest, DeclareParamRequest, EngineHandle, EngineMessage, EngineQuery,
|
||||
EngineResponse,
|
||||
};
|
||||
use crate::engine::alerts::{deliver_ntfy, AlertError};
|
||||
use crate::config::profile::{Dashboard, ParamDeclarationRecord, Profile, ProfileError};
|
||||
use crate::config::SettingsHandle;
|
||||
use axum::Router;
|
||||
use axum::extract::{Path as AxumPath, Query, State};
|
||||
use axum::http::{header, StatusCode};
|
||||
use axum::http::{StatusCode, header};
|
||||
use axum::response::{IntoResponse, Json, Response};
|
||||
use axum::routing::{delete, get, post, put};
|
||||
use axum::Router;
|
||||
use fs_embed::Dir as EmbedDir;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
@@ -49,7 +49,9 @@ impl ApiState {
|
||||
let Some(profile_name) = settings.active_profile.clone() else {
|
||||
return;
|
||||
};
|
||||
if let Err(err) = settings.record_param_declaration(&self.settings_path, &profile_name, request) {
|
||||
if let Err(err) =
|
||||
settings.record_param_declaration(&self.settings_path, &profile_name, request)
|
||||
{
|
||||
eprintln!(
|
||||
"warning: failed to persist param declaration '{}' into profile '{profile_name}': {err}",
|
||||
request.name
|
||||
@@ -79,7 +81,10 @@ impl ApiState {
|
||||
};
|
||||
let existing_params = {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
settings.profile(&profile_name).map(|p| p.params.clone()).unwrap_or_default()
|
||||
settings
|
||||
.profile(&profile_name)
|
||||
.map(|p| p.params.clone())
|
||||
.unwrap_or_default()
|
||||
};
|
||||
let promoted = promoted_param_records(&self.engine, &existing_params, request).await;
|
||||
let mut settings = self.settings.lock().unwrap();
|
||||
@@ -96,7 +101,11 @@ impl ApiState {
|
||||
}
|
||||
}
|
||||
|
||||
async fn persist_metric_declaration_update(&self, old_name: &str, request: &DeclareMetricRequest) {
|
||||
async fn persist_metric_declaration_update(
|
||||
&self,
|
||||
old_name: &str,
|
||||
request: &DeclareMetricRequest,
|
||||
) {
|
||||
let profile_name = {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
let Some(profile_name) = settings.active_profile.clone() else {
|
||||
@@ -106,7 +115,10 @@ impl ApiState {
|
||||
};
|
||||
let existing_params = {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
settings.profile(&profile_name).map(|p| p.params.clone()).unwrap_or_default()
|
||||
settings
|
||||
.profile(&profile_name)
|
||||
.map(|p| p.params.clone())
|
||||
.unwrap_or_default()
|
||||
};
|
||||
let promoted = promoted_param_records(&self.engine, &existing_params, request).await;
|
||||
let mut settings = self.settings.lock().unwrap();
|
||||
@@ -129,7 +141,9 @@ impl ApiState {
|
||||
let Some(profile_name) = settings.active_profile.clone() else {
|
||||
return;
|
||||
};
|
||||
if let Err(err) = settings.remove_metric_declaration(&self.settings_path, &profile_name, name) {
|
||||
if let Err(err) =
|
||||
settings.remove_metric_declaration(&self.settings_path, &profile_name, name)
|
||||
{
|
||||
eprintln!(
|
||||
"warning: failed to remove metric declaration '{name}' from profile '{profile_name}': {err}"
|
||||
);
|
||||
@@ -149,39 +163,58 @@ pub fn router(engine: EngineHandle, settings: SettingsHandle, settings_path: Pat
|
||||
.route("/api/logs", get(get_logs))
|
||||
.route("/api/timeline", get(get_timeline))
|
||||
.route("/api/stats", get(get_stats))
|
||||
.route("/api/params", get(list_params).post(declare_param).delete(clear_params))
|
||||
.route(
|
||||
"/api/params",
|
||||
get(list_params).post(declare_param).delete(clear_params),
|
||||
)
|
||||
.route("/api/metrics", get(list_metrics).post(declare_metric))
|
||||
.route(
|
||||
"/api/metrics/{name}",
|
||||
get(get_metric_bucket_values).put(update_metric).delete(delete_metric),
|
||||
get(get_metric_bucket_values)
|
||||
.put(update_metric)
|
||||
.delete(delete_metric),
|
||||
)
|
||||
.route("/api/query", get(run_promql_query))
|
||||
.route("/api/settings", get(get_settings).patch(update_settings))
|
||||
.route("/api/themes", get(list_themes))
|
||||
.route("/api/themes/{name}", get(get_theme_content))
|
||||
.route("/api/settings/alerts", get(list_alert_rules).post(create_alert_rule))
|
||||
.route(
|
||||
"/api/settings/alerts",
|
||||
get(list_alert_rules).post(create_alert_rule),
|
||||
)
|
||||
.route(
|
||||
"/api/settings/alerts/{id}",
|
||||
put(update_alert_rule).delete(delete_alert_rule),
|
||||
)
|
||||
.route("/api/settings/alerts/test", post(test_alert_notification))
|
||||
.route("/api/settings/profiles", get(list_profiles).post(create_profile))
|
||||
.route(
|
||||
"/api/settings/profiles",
|
||||
get(list_profiles).post(create_profile),
|
||||
)
|
||||
.route("/api/settings/profiles/{name}", delete(delete_profile))
|
||||
.route("/api/settings/profiles/{name}/activate", post(activate_profile))
|
||||
.route(
|
||||
"/api/settings/profiles/{name}/activate",
|
||||
post(activate_profile),
|
||||
)
|
||||
.route(
|
||||
"/api/settings/profiles/{name}/dashboards",
|
||||
get(list_dashboards).post(create_dashboard),
|
||||
)
|
||||
.route(
|
||||
"/api/settings/profiles/{name}/dashboards/{dashboard_id}",
|
||||
get(get_dashboard).put(save_dashboard).delete(delete_dashboard),
|
||||
get(get_dashboard)
|
||||
.put(save_dashboard)
|
||||
.delete(delete_dashboard),
|
||||
)
|
||||
.with_state(state)
|
||||
.fallback(serve_webapp)
|
||||
}
|
||||
|
||||
async fn get_skill_md() -> impl IntoResponse {
|
||||
([(header::CONTENT_TYPE, "text/markdown; charset=utf-8")], SKILL_MD)
|
||||
(
|
||||
[(header::CONTENT_TYPE, "text/markdown; charset=utf-8")],
|
||||
SKILL_MD,
|
||||
)
|
||||
}
|
||||
|
||||
async fn serve_webapp(uri: axum::http::Uri) -> Response {
|
||||
@@ -225,10 +258,15 @@ impl EngineHandle {
|
||||
if needs_build {
|
||||
let (respond_to, response) = oneshot::channel();
|
||||
self.tx
|
||||
.send(EngineMessage::EnsureSortHelperIndex { param_name: sort_by.to_string(), respond_to })
|
||||
.send(EngineMessage::EnsureSortHelperIndex {
|
||||
param_name: sort_by.to_string(),
|
||||
respond_to,
|
||||
})
|
||||
.await
|
||||
.map_err(|_| StatusCode::SERVICE_UNAVAILABLE)?;
|
||||
response
|
||||
.await
|
||||
.map_err(|_| StatusCode::SERVICE_UNAVAILABLE)?;
|
||||
response.await.map_err(|_| StatusCode::SERVICE_UNAVAILABLE)?;
|
||||
}
|
||||
}
|
||||
let guard = read_handle.enter().ok_or(StatusCode::SERVICE_UNAVAILABLE)?;
|
||||
@@ -363,7 +401,10 @@ async fn declare_param(
|
||||
if state
|
||||
.engine
|
||||
.tx
|
||||
.send(EngineMessage::DeclareParam { request: body, respond_to })
|
||||
.send(EngineMessage::DeclareParam {
|
||||
request: body,
|
||||
respond_to,
|
||||
})
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
@@ -391,7 +432,13 @@ async fn declare_param(
|
||||
|
||||
async fn clear_params(State(state): State<ApiState>) -> impl IntoResponse {
|
||||
let (respond_to, response) = oneshot::channel();
|
||||
if state.engine.tx.send(EngineMessage::ClearParams { respond_to }).await.is_err() {
|
||||
if state
|
||||
.engine
|
||||
.tx
|
||||
.send(EngineMessage::ClearParams { respond_to })
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return StatusCode::SERVICE_UNAVAILABLE.into_response();
|
||||
}
|
||||
match response.await {
|
||||
@@ -420,7 +467,10 @@ async fn declare_metric(
|
||||
if state
|
||||
.engine
|
||||
.tx
|
||||
.send(EngineMessage::DeclareMetric { request: body, respond_to })
|
||||
.send(EngineMessage::DeclareMetric {
|
||||
request: body,
|
||||
respond_to,
|
||||
})
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
@@ -454,7 +504,11 @@ async fn update_metric(
|
||||
if state
|
||||
.engine
|
||||
.tx
|
||||
.send(EngineMessage::UpdateMetric { name: name.clone(), request: body, respond_to })
|
||||
.send(EngineMessage::UpdateMetric {
|
||||
name: name.clone(),
|
||||
request: body,
|
||||
respond_to,
|
||||
})
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
@@ -462,7 +516,9 @@ async fn update_metric(
|
||||
}
|
||||
match response.await {
|
||||
Ok(Ok(())) => {
|
||||
state.persist_metric_declaration_update(&name, &request).await;
|
||||
state
|
||||
.persist_metric_declaration_update(&name, &request)
|
||||
.await;
|
||||
let dto = MetricDeclarationDto {
|
||||
name: request.name,
|
||||
message_match_regex: request.message_match_regex,
|
||||
@@ -486,7 +542,10 @@ async fn delete_metric(
|
||||
if state
|
||||
.engine
|
||||
.tx
|
||||
.send(EngineMessage::RemoveMetricDeclaration { name: name.clone(), respond_to })
|
||||
.send(EngineMessage::RemoveMetricDeclaration {
|
||||
name: name.clone(),
|
||||
respond_to,
|
||||
})
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
@@ -552,7 +611,10 @@ async fn run_promql_query(
|
||||
async fn list_themes() -> impl IntoResponse {
|
||||
let mut names: Vec<String> = THEMES
|
||||
.walk()
|
||||
.filter_map(|file| file.file_name().map(|name| name.trim_end_matches(".css").to_string()))
|
||||
.filter_map(|file| {
|
||||
file.file_name()
|
||||
.map(|name| name.trim_end_matches(".css").to_string())
|
||||
})
|
||||
.collect();
|
||||
names.sort();
|
||||
Json(names).into_response()
|
||||
@@ -561,7 +623,11 @@ async fn list_themes() -> impl IntoResponse {
|
||||
async fn get_theme_content(AxumPath(name): AxumPath<String>) -> impl IntoResponse {
|
||||
// Themes are just filenames within a flat embedded dir, not filesystem paths, so reject
|
||||
// anything that isn't a plain name to rule out path traversal.
|
||||
if name.is_empty() || !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-') {
|
||||
if name.is_empty()
|
||||
|| !name
|
||||
.chars()
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
|
||||
{
|
||||
return StatusCode::NOT_FOUND.into_response();
|
||||
}
|
||||
match THEMES.get_file(&format!("{name}.css")) {
|
||||
@@ -577,7 +643,9 @@ impl AlertError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
match self {
|
||||
AlertError::NotFound => (StatusCode::NOT_FOUND, self.to_string()).into_response(),
|
||||
AlertError::Io(_) => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response(),
|
||||
AlertError::Io(_) => {
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -592,7 +660,12 @@ async fn create_alert_rule(
|
||||
Json(body): Json<CreateAlertRuleRequest>,
|
||||
) -> impl IntoResponse {
|
||||
let mut settings = state.settings.lock().unwrap();
|
||||
match settings.create_alert_rule(&state.settings_path, body.name, body.enabled, body.condition) {
|
||||
match settings.create_alert_rule(
|
||||
&state.settings_path,
|
||||
body.name,
|
||||
body.enabled,
|
||||
body.condition,
|
||||
) {
|
||||
Ok(rule) => (StatusCode::CREATED, Json(rule)).into_response(),
|
||||
Err(err) => err.into_response(),
|
||||
}
|
||||
@@ -604,13 +677,22 @@ async fn update_alert_rule(
|
||||
Json(body): Json<UpdateAlertRuleRequest>,
|
||||
) -> impl IntoResponse {
|
||||
let mut settings = state.settings.lock().unwrap();
|
||||
match settings.update_alert_rule(&state.settings_path, &id, body.name, body.enabled, body.condition) {
|
||||
match settings.update_alert_rule(
|
||||
&state.settings_path,
|
||||
&id,
|
||||
body.name,
|
||||
body.enabled,
|
||||
body.condition,
|
||||
) {
|
||||
Ok(rule) => Json(rule).into_response(),
|
||||
Err(err) => err.into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete_alert_rule(State(state): State<ApiState>, AxumPath(id): AxumPath<String>) -> impl IntoResponse {
|
||||
async fn delete_alert_rule(
|
||||
State(state): State<ApiState>,
|
||||
AxumPath(id): AxumPath<String>,
|
||||
) -> impl IntoResponse {
|
||||
let mut settings = state.settings.lock().unwrap();
|
||||
match settings.remove_alert_rule(&state.settings_path, &id) {
|
||||
Ok(()) => StatusCode::NO_CONTENT.into_response(),
|
||||
@@ -618,7 +700,9 @@ async fn delete_alert_rule(State(state): State<ApiState>, AxumPath(id): AxumPath
|
||||
}
|
||||
}
|
||||
|
||||
async fn test_alert_notification(Json(body): Json<TestAlertNotificationRequest>) -> impl IntoResponse {
|
||||
async fn test_alert_notification(
|
||||
Json(body): Json<TestAlertNotificationRequest>,
|
||||
) -> impl IntoResponse {
|
||||
let server = body.server.unwrap_or_else(|| "https://ntfy.sh".to_string());
|
||||
match deliver_ntfy(&server, &body.topic, "Test notification from ottyl").await {
|
||||
Ok(()) => StatusCode::NO_CONTENT.into_response(),
|
||||
@@ -652,7 +736,9 @@ async fn update_settings(
|
||||
}
|
||||
match settings.save(&state.settings_path) {
|
||||
Ok(()) => (),
|
||||
Err(err) => return (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response(),
|
||||
Err(err) => {
|
||||
return (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response();
|
||||
}
|
||||
}
|
||||
(SettingsDto::from(&*settings), settings.max_logs)
|
||||
};
|
||||
@@ -671,16 +757,26 @@ impl ProfileError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
match self {
|
||||
ProfileError::NotFound => (StatusCode::NOT_FOUND, self.to_string()).into_response(),
|
||||
ProfileError::DuplicateName => (StatusCode::BAD_REQUEST, self.to_string()).into_response(),
|
||||
ProfileError::CannotDeleteDefault => (StatusCode::BAD_REQUEST, self.to_string()).into_response(),
|
||||
ProfileError::Io(_) => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response(),
|
||||
ProfileError::DuplicateName => {
|
||||
(StatusCode::BAD_REQUEST, self.to_string()).into_response()
|
||||
}
|
||||
ProfileError::CannotDeleteDefault => {
|
||||
(StatusCode::BAD_REQUEST, self.to_string()).into_response()
|
||||
}
|
||||
ProfileError::Io(_) => {
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_profiles(State(state): State<ApiState>) -> impl IntoResponse {
|
||||
let settings = state.settings.lock().unwrap();
|
||||
let summaries: Vec<ProfileSummaryDto> = settings.profiles.iter().map(ProfileSummaryDto::from).collect();
|
||||
let summaries: Vec<ProfileSummaryDto> = settings
|
||||
.profiles
|
||||
.iter()
|
||||
.map(ProfileSummaryDto::from)
|
||||
.collect();
|
||||
Json(summaries).into_response()
|
||||
}
|
||||
|
||||
@@ -691,7 +787,9 @@ async fn create_profile(
|
||||
let mut settings = state.settings.lock().unwrap();
|
||||
match settings.create_profile(&state.settings_path, body.name.clone()) {
|
||||
Ok(()) => {
|
||||
let profile = settings.profile(&body.name).expect("profile was just created by create_profile");
|
||||
let profile = settings
|
||||
.profile(&body.name)
|
||||
.expect("profile was just created by create_profile");
|
||||
(StatusCode::CREATED, Json(ProfileSummaryDto::from(profile))).into_response()
|
||||
}
|
||||
Err(err) => err.into_response(),
|
||||
@@ -717,7 +815,10 @@ async fn reload_engine_declarations(engine: &EngineHandle, profile: &Profile) {
|
||||
let (respond_to, response) = oneshot::channel();
|
||||
if engine
|
||||
.tx
|
||||
.send(EngineMessage::RemoveParamDeclaration { name: param.name, respond_to })
|
||||
.send(EngineMessage::RemoveParamDeclaration {
|
||||
name: param.name,
|
||||
respond_to,
|
||||
})
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
@@ -732,7 +833,10 @@ async fn reload_engine_declarations(engine: &EngineHandle, profile: &Profile) {
|
||||
let (respond_to, response) = oneshot::channel();
|
||||
if engine
|
||||
.tx
|
||||
.send(EngineMessage::RemoveMetricDeclaration { name: metric.name, respond_to })
|
||||
.send(EngineMessage::RemoveMetricDeclaration {
|
||||
name: metric.name,
|
||||
respond_to,
|
||||
})
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
@@ -835,13 +939,13 @@ async fn delete_dashboard(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::engine::{self, IngestedLine};
|
||||
use crate::config::Settings;
|
||||
use crate::engine::timestamp::Timestamp;
|
||||
use crate::engine::{self, IngestedLine};
|
||||
use axum::body::Body;
|
||||
use axum::http::Request;
|
||||
use http_body_util::BodyExt;
|
||||
use serde_json::{json, Value};
|
||||
use serde_json::{Value, json};
|
||||
use std::sync::Mutex;
|
||||
use tower::ServiceExt;
|
||||
|
||||
@@ -875,7 +979,11 @@ mod tests {
|
||||
}
|
||||
|
||||
async fn get_json(app: &Router, uri: &str) -> (StatusCode, Value) {
|
||||
let request = Request::builder().method("GET").uri(uri).body(Body::empty()).unwrap();
|
||||
let request = Request::builder()
|
||||
.method("GET")
|
||||
.uri(uri)
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
let response = app.clone().oneshot(request).await.unwrap();
|
||||
let status = response.status();
|
||||
let bytes = response.into_body().collect().await.unwrap().to_bytes();
|
||||
@@ -982,11 +1090,22 @@ mod tests {
|
||||
let settings_path = dir.path().join("settings.json");
|
||||
{
|
||||
let mut settings = settings_handle.lock().unwrap();
|
||||
settings.create_profile(&settings_path, "web".to_string()).unwrap();
|
||||
settings.set_active_profile(&settings_path, Some("web".to_string())).unwrap();
|
||||
settings
|
||||
.create_profile(&settings_path, "web".to_string())
|
||||
.unwrap();
|
||||
settings
|
||||
.set_active_profile(&settings_path, Some("web".to_string()))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
declare_test_param(&app, "path", r"request path=(?P<path>\S+)", "path", "string").await;
|
||||
declare_test_param(
|
||||
&app,
|
||||
"path",
|
||||
r"request path=(?P<path>\S+)",
|
||||
"path",
|
||||
"string",
|
||||
)
|
||||
.await;
|
||||
ingest_lines(&engine, 0, &["request path=/a", "level=info x=1"]).await;
|
||||
|
||||
let (_, before) = get_json(&app, "/api/params").await;
|
||||
@@ -1008,8 +1127,12 @@ mod tests {
|
||||
assert_eq!(result["logs_considered"], 2);
|
||||
|
||||
let (_, after) = get_json(&app, "/api/params").await;
|
||||
let after_declared: Vec<_> =
|
||||
after.as_array().unwrap().iter().filter(|p| p["reserved"] != true).collect();
|
||||
let after_declared: Vec<_> = after
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|p| p["reserved"] != true)
|
||||
.collect();
|
||||
assert_eq!(after_declared.len(), 3);
|
||||
assert!(after_declared.iter().all(|p| p["auto_derived"] == true));
|
||||
|
||||
@@ -1037,7 +1160,13 @@ mod tests {
|
||||
assert!(body["error"].as_str().unwrap().contains("invalid regex"));
|
||||
}
|
||||
|
||||
async fn declare_test_param(app: &Router, name: &str, pattern: &str, capture_group: &str, kind: &str) {
|
||||
async fn declare_test_param(
|
||||
app: &Router,
|
||||
name: &str,
|
||||
pattern: &str,
|
||||
capture_group: &str,
|
||||
kind: &str,
|
||||
) {
|
||||
let (status, _body) = send_json(
|
||||
app,
|
||||
"POST",
|
||||
@@ -1056,7 +1185,14 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn declares_metric_and_shows_up_in_list() {
|
||||
let (app, _engine, _settings, _dir) = test_app();
|
||||
declare_test_param(&app, "path", r"request path=(?P<path>\S+)", "path", "string").await;
|
||||
declare_test_param(
|
||||
&app,
|
||||
"path",
|
||||
r"request path=(?P<path>\S+)",
|
||||
"path",
|
||||
"string",
|
||||
)
|
||||
.await;
|
||||
let (status, body) = send_json(
|
||||
&app,
|
||||
"POST",
|
||||
@@ -1099,8 +1235,22 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn updates_metric_declaration_in_place_rather_than_duplicating() {
|
||||
let (app, _engine, _settings, _dir) = test_app();
|
||||
declare_test_param(&app, "path", r"request path=(?P<path>\S+)", "path", "string").await;
|
||||
declare_test_param(&app, "duration", r"duration=(?P<duration>\d+)", "duration", "number").await;
|
||||
declare_test_param(
|
||||
&app,
|
||||
"path",
|
||||
r"request path=(?P<path>\S+)",
|
||||
"path",
|
||||
"string",
|
||||
)
|
||||
.await;
|
||||
declare_test_param(
|
||||
&app,
|
||||
"duration",
|
||||
r"duration=(?P<duration>\d+)",
|
||||
"duration",
|
||||
"number",
|
||||
)
|
||||
.await;
|
||||
send_json(
|
||||
&app,
|
||||
"POST",
|
||||
@@ -1220,8 +1370,12 @@ mod tests {
|
||||
let settings_path = dir.path().join("settings.json");
|
||||
{
|
||||
let mut settings = settings_handle.lock().unwrap();
|
||||
settings.create_profile(&settings_path, "web".to_string()).unwrap();
|
||||
settings.set_active_profile(&settings_path, Some("web".to_string())).unwrap();
|
||||
settings
|
||||
.create_profile(&settings_path, "web".to_string())
|
||||
.unwrap();
|
||||
settings
|
||||
.set_active_profile(&settings_path, Some("web".to_string()))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
send_json(
|
||||
@@ -1235,19 +1389,39 @@ mod tests {
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(Settings::load(&settings_path).profile("web").unwrap().metrics.len(), 1);
|
||||
assert_eq!(
|
||||
Settings::load(&settings_path)
|
||||
.profile("web")
|
||||
.unwrap()
|
||||
.metrics
|
||||
.len(),
|
||||
1
|
||||
);
|
||||
|
||||
let status = delete_request(&app, "/api/metrics/requests").await;
|
||||
assert_eq!(status, StatusCode::NO_CONTENT);
|
||||
|
||||
assert!(Settings::load(&settings_path).profile("web").unwrap().metrics.is_empty());
|
||||
assert!(
|
||||
Settings::load(&settings_path)
|
||||
.profile("web")
|
||||
.unwrap()
|
||||
.metrics
|
||||
.is_empty()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn promql_query_against_deleted_metric_fails_with_readable_error() {
|
||||
let (app, engine, _settings, _dir) = test_app();
|
||||
|
||||
declare_test_param(&app, "path", r"request path=(?P<path>\S+)", "path", "string").await;
|
||||
declare_test_param(
|
||||
&app,
|
||||
"path",
|
||||
r"request path=(?P<path>\S+)",
|
||||
"path",
|
||||
"string",
|
||||
)
|
||||
.await;
|
||||
send_json(
|
||||
&app,
|
||||
"POST",
|
||||
@@ -1288,7 +1462,14 @@ mod tests {
|
||||
async fn queries_bucket_values_with_label_filter_and_range() {
|
||||
let (app, engine, _settings, _dir) = test_app();
|
||||
|
||||
declare_test_param(&app, "path", r"request path=(?P<path>\S+)", "path", "string").await;
|
||||
declare_test_param(
|
||||
&app,
|
||||
"path",
|
||||
r"request path=(?P<path>\S+)",
|
||||
"path",
|
||||
"string",
|
||||
)
|
||||
.await;
|
||||
send_json(
|
||||
&app,
|
||||
"POST",
|
||||
@@ -1341,7 +1522,14 @@ mod tests {
|
||||
async fn promql_query_returns_series_for_seeded_fixture() {
|
||||
let (app, engine, _settings, _dir) = test_app();
|
||||
|
||||
declare_test_param(&app, "path", r"request path=(?P<path>\S+)", "path", "string").await;
|
||||
declare_test_param(
|
||||
&app,
|
||||
"path",
|
||||
r"request path=(?P<path>\S+)",
|
||||
"path",
|
||||
"string",
|
||||
)
|
||||
.await;
|
||||
send_json(
|
||||
&app,
|
||||
"POST",
|
||||
@@ -1398,8 +1586,12 @@ mod tests {
|
||||
let settings_path = dir.path().join("settings.json");
|
||||
{
|
||||
let mut settings = settings_handle.lock().unwrap();
|
||||
settings.create_profile(&settings_path, "web".to_string()).unwrap();
|
||||
settings.set_active_profile(&settings_path, Some("web".to_string())).unwrap();
|
||||
settings
|
||||
.create_profile(&settings_path, "web".to_string())
|
||||
.unwrap();
|
||||
settings
|
||||
.set_active_profile(&settings_path, Some("web".to_string()))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let (status, _body) = send_json(
|
||||
@@ -1429,8 +1621,12 @@ mod tests {
|
||||
let settings_path = dir.path().join("settings.json");
|
||||
{
|
||||
let mut settings = settings_handle.lock().unwrap();
|
||||
settings.create_profile(&settings_path, "web".to_string()).unwrap();
|
||||
settings.set_active_profile(&settings_path, Some("web".to_string())).unwrap();
|
||||
settings
|
||||
.create_profile(&settings_path, "web".to_string())
|
||||
.unwrap();
|
||||
settings
|
||||
.set_active_profile(&settings_path, Some("web".to_string()))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
ingest_lines(&engine, 0, &["dx=1 moved"]).await;
|
||||
@@ -1459,7 +1655,10 @@ mod tests {
|
||||
let fresh_engine_tx = crate::engine::spawn(fresh_settings_handle, 16);
|
||||
profile.load_into_engine(&fresh_engine_tx).await;
|
||||
|
||||
let response = fresh_engine_tx.ask(EngineQuery::MetricDeclarations).await.unwrap();
|
||||
let response = fresh_engine_tx
|
||||
.ask(EngineQuery::MetricDeclarations)
|
||||
.await
|
||||
.unwrap();
|
||||
let EngineResponse::MetricDeclarations(metrics) = response else {
|
||||
panic!("unexpected response");
|
||||
};
|
||||
@@ -1468,12 +1667,20 @@ mod tests {
|
||||
}
|
||||
|
||||
async fn delete_request(app: &Router, uri: &str) -> StatusCode {
|
||||
let request = Request::builder().method("DELETE").uri(uri).body(Body::empty()).unwrap();
|
||||
let request = Request::builder()
|
||||
.method("DELETE")
|
||||
.uri(uri)
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
app.clone().oneshot(request).await.unwrap().status()
|
||||
}
|
||||
|
||||
async fn get_status(app: &Router, uri: &str) -> StatusCode {
|
||||
let request = Request::builder().method("GET").uri(uri).body(Body::empty()).unwrap();
|
||||
let request = Request::builder()
|
||||
.method("GET")
|
||||
.uri(uri)
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
app.clone().oneshot(request).await.unwrap().status()
|
||||
}
|
||||
|
||||
@@ -1481,7 +1688,13 @@ mod tests {
|
||||
async fn dashboard_crud_round_trips_through_http() {
|
||||
let (app, _engine, _settings, _dir) = test_app();
|
||||
|
||||
let (status, _body) = send_json(&app, "POST", "/api/settings/profiles", json!({ "name": "web" })).await;
|
||||
let (status, _body) = send_json(
|
||||
&app,
|
||||
"POST",
|
||||
"/api/settings/profiles",
|
||||
json!({ "name": "web" }),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::CREATED);
|
||||
|
||||
let (status, body) = send_json(
|
||||
@@ -1518,12 +1731,19 @@ mod tests {
|
||||
assert_eq!(panels.len(), 1);
|
||||
assert_eq!(panels[0]["chart_type"], "line");
|
||||
|
||||
let (status, body) =
|
||||
get_json(&app, &format!("/api/settings/profiles/web/dashboards/{dashboard_id}")).await;
|
||||
let (status, body) = get_json(
|
||||
&app,
|
||||
&format!("/api/settings/profiles/web/dashboards/{dashboard_id}"),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK);
|
||||
assert_eq!(body["panels"].as_array().unwrap().len(), 1);
|
||||
|
||||
let status = delete_request(&app, &format!("/api/settings/profiles/web/dashboards/{dashboard_id}")).await;
|
||||
let status = delete_request(
|
||||
&app,
|
||||
&format!("/api/settings/profiles/web/dashboards/{dashboard_id}"),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::NO_CONTENT);
|
||||
let (status, body) = get_json(&app, "/api/settings/profiles/web/dashboards").await;
|
||||
assert_eq!(status, StatusCode::OK);
|
||||
@@ -1536,7 +1756,13 @@ mod tests {
|
||||
let status = get_status(&app, "/api/settings/profiles/ghost/dashboards").await;
|
||||
assert_eq!(status, StatusCode::NOT_FOUND);
|
||||
|
||||
let (status, _body) = send_json(&app, "POST", "/api/settings/profiles", json!({ "name": "web" })).await;
|
||||
let (status, _body) = send_json(
|
||||
&app,
|
||||
"POST",
|
||||
"/api/settings/profiles",
|
||||
json!({ "name": "web" }),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::CREATED);
|
||||
let status = get_status(&app, "/api/settings/profiles/web/dashboards/does-not-exist").await;
|
||||
assert_eq!(status, StatusCode::NOT_FOUND);
|
||||
@@ -1546,7 +1772,13 @@ mod tests {
|
||||
async fn saved_dashboard_persists_across_simulated_restart() {
|
||||
let (app, _engine, _settings_handle, dir) = test_app();
|
||||
|
||||
let (status, _body) = send_json(&app, "POST", "/api/settings/profiles", json!({ "name": "web" })).await;
|
||||
let (status, _body) = send_json(
|
||||
&app,
|
||||
"POST",
|
||||
"/api/settings/profiles",
|
||||
json!({ "name": "web" }),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::CREATED);
|
||||
let (status, body) = send_json(
|
||||
&app,
|
||||
@@ -1605,7 +1837,8 @@ mod tests {
|
||||
assert_eq!(status, StatusCode::OK);
|
||||
assert_eq!(body.as_array().unwrap().len(), 3);
|
||||
|
||||
let (status, body) = send_json(&app, "PATCH", "/api/settings", json!({ "max_logs": 2 })).await;
|
||||
let (status, body) =
|
||||
send_json(&app, "PATCH", "/api/settings", json!({ "max_logs": 2 })).await;
|
||||
assert_eq!(status, StatusCode::OK);
|
||||
assert_eq!(body["max_logs"], 2);
|
||||
|
||||
@@ -1644,7 +1877,8 @@ mod tests {
|
||||
let (_, body) = get_json(&app, "/api/logs").await;
|
||||
assert_eq!(body.as_array().unwrap().len(), 1);
|
||||
|
||||
let (status, body) = send_json(&app, "PATCH", "/api/settings", json!({ "max_logs": null })).await;
|
||||
let (status, body) =
|
||||
send_json(&app, "PATCH", "/api/settings", json!({ "max_logs": null })).await;
|
||||
assert_eq!(status, StatusCode::OK);
|
||||
assert_eq!(body["max_logs"], Value::Null);
|
||||
|
||||
@@ -1657,7 +1891,14 @@ mod tests {
|
||||
async fn get_logs_sort_by_orders_by_param_and_toggles_missing_param_handling() {
|
||||
let (app, engine, _settings, _dir) = test_app();
|
||||
|
||||
declare_test_param(&app, "priority", r"priority=(?P<priority>\d+)", "priority", "number").await;
|
||||
declare_test_param(
|
||||
&app,
|
||||
"priority",
|
||||
r"priority=(?P<priority>\d+)",
|
||||
"priority",
|
||||
"number",
|
||||
)
|
||||
.await;
|
||||
|
||||
ingest_lines(
|
||||
&engine,
|
||||
@@ -1673,17 +1914,36 @@ mod tests {
|
||||
|
||||
let (status, body) = get_json(&app, "/api/logs").await;
|
||||
assert_eq!(status, StatusCode::OK);
|
||||
let sequences: Vec<u64> = body.as_array().unwrap().iter().map(|e| e["sequence"].as_u64().unwrap()).collect();
|
||||
let sequences: Vec<u64> = body
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|e| e["sequence"].as_u64().unwrap())
|
||||
.collect();
|
||||
assert_eq!(sequences, vec![0, 1, 2, 3]);
|
||||
|
||||
let (status, body) = get_json(&app, "/api/logs?sort_by=priority").await;
|
||||
assert_eq!(status, StatusCode::OK);
|
||||
let sequences: Vec<u64> = body.as_array().unwrap().iter().map(|e| e["sequence"].as_u64().unwrap()).collect();
|
||||
let sequences: Vec<u64> = body
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|e| e["sequence"].as_u64().unwrap())
|
||||
.collect();
|
||||
assert_eq!(sequences, vec![2, 0, 1, 3]);
|
||||
|
||||
let (status, body) = get_json(&app, "/api/logs?sort_by=priority&sort_missing_fallback=false").await;
|
||||
let (status, body) = get_json(
|
||||
&app,
|
||||
"/api/logs?sort_by=priority&sort_missing_fallback=false",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK);
|
||||
let sequences: Vec<u64> = body.as_array().unwrap().iter().map(|e| e["sequence"].as_u64().unwrap()).collect();
|
||||
let sequences: Vec<u64> = body
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|e| e["sequence"].as_u64().unwrap())
|
||||
.collect();
|
||||
assert_eq!(sequences, vec![2, 0]);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user