From 0b3552ee4f9d990bbbbc1aa656ced2a7247f5b4c Mon Sep 17 00:00:00 2001 From: jedarden Date: Mon, 25 May 2026 05:15:22 -0400 Subject: [PATCH] fix(clippy): apply auto-fixes for unused imports and variables Apply cargo clippy --fix to remove unused imports, prefix unused variables with underscore, and fix various clippy warnings across miroir-core, miroir-proxy, and miroir-ctl. Co-Authored-By: Claude Opus 4.7 --- crates/miroir-core/benches/router_bench.rs | 12 +- crates/miroir-core/src/alias/mod.rs | 6 +- crates/miroir-core/src/canary.rs | 21 +- crates/miroir-core/src/cdc.rs | 2 +- crates/miroir-core/src/drift_reconciler.rs | 17 +- crates/miroir-core/src/dump.rs | 6 + crates/miroir-core/src/dump_chunking.rs | 2 +- crates/miroir-core/src/dump_import.rs | 12 +- crates/miroir-core/src/explainer.rs | 6 +- crates/miroir-core/src/group_sync_worker.rs | 26 +-- crates/miroir-core/src/ilm.rs | 58 +++-- crates/miroir-core/src/leader_election/mod.rs | 2 +- crates/miroir-core/src/merger.rs | 2 +- crates/miroir-core/src/mode_c_coordinator.rs | 29 +-- crates/miroir-core/src/mode_c_worker/mod.rs | 26 ++- crates/miroir-core/src/peer_discovery.rs | 6 +- crates/miroir-core/src/query_planner.rs | 16 +- crates/miroir-core/src/rebalancer.rs | 74 +++---- .../rebalancer_worker/anti_entropy_worker.rs | 27 ++- .../miroir-core/src/rebalancer_worker/mod.rs | 6 +- crates/miroir-core/src/replica_selection.rs | 4 +- crates/miroir-core/src/reshard.rs | 128 ++++++----- crates/miroir-core/src/reshard/executor.rs | 6 +- crates/miroir-core/src/reshard_chunking.rs | 6 +- crates/miroir-core/src/scatter.rs | 2 +- crates/miroir-core/src/scoped_key_rotation.rs | 4 +- crates/miroir-core/src/settings.rs | 20 +- crates/miroir-core/src/shadow.rs | 4 +- crates/miroir-core/src/task_registry.rs | 40 ++-- crates/miroir-core/src/task_store/redis.rs | 204 +++++++++--------- crates/miroir-core/src/task_store/sqlite.rs | 10 +- crates/miroir-core/src/topology.rs | 3 +- crates/miroir-core/src/ttl.rs | 2 +- crates/miroir-core/src/vector.rs | 2 +- crates/miroir-core/tests/dfs_skewed_corpus.rs | 2 +- crates/miroir-core/tests/hash_fixtures.rs | 3 +- crates/miroir-core/tests/merger_proptest.rs | 18 +- .../tests/p13_10_idempotency_coalescing.rs | 15 +- .../tests/p13_18_canary_acceptance_tests.rs | 16 +- .../miroir-core/tests/p13_2_hedging_chaos.rs | 39 +--- .../tests/p13_3_adaptive_replica_selection.rs | 70 ++---- .../miroir-core/tests/p13_4_query_planner.rs | 2 +- .../tests/p13_7_alias_acceptance_tests.rs | 12 +- .../miroir-core/tests/p13_8_anti_entropy.rs | 4 +- crates/miroir-core/tests/p22_write_path.rs | 12 +- .../tests/p22_write_path_acceptance.rs | 33 ++- .../miroir-core/tests/p23_search_read_path.rs | 16 +- .../tests/p28_api_compatibility.rs | 28 +-- crates/miroir-core/tests/p3_sqlite_restart.rs | 2 +- .../tests/p3_task_store_proptest.rs | 2 +- crates/miroir-core/tests/p43_node_drain.rs | 36 ++-- crates/miroir-core/tests/router_proptest.rs | 19 +- crates/miroir-ctl/src/credentials.rs | 8 +- crates/miroir-proxy/src/admin_session.rs | 2 +- crates/miroir-proxy/src/auth.rs | 23 +- crates/miroir-proxy/src/client.rs | 54 ++--- crates/miroir-proxy/src/middleware.rs | 43 ++-- crates/miroir-proxy/src/routes/admin.rs | 2 +- .../src/routes/admin_endpoints.rs | 124 +++++------ crates/miroir-proxy/src/routes/aliases.rs | 31 ++- crates/miroir-proxy/src/routes/canary.rs | 10 +- crates/miroir-proxy/src/routes/cdc.rs | 4 +- crates/miroir-proxy/src/routes/documents.rs | 49 ++--- crates/miroir-proxy/src/routes/dumps.rs | 16 +- crates/miroir-proxy/src/routes/explain.rs | 13 +- crates/miroir-proxy/src/routes/indexes.rs | 164 +++++++------- crates/miroir-proxy/src/routes/keys.rs | 40 ++-- .../miroir-proxy/src/routes/multi_search.rs | 7 +- crates/miroir-proxy/src/routes/search.rs | 12 +- crates/miroir-proxy/src/routes/search_ui.rs | 30 ++- crates/miroir-proxy/src/routes/session.rs | 6 +- crates/miroir-proxy/src/routes/tasks.rs | 4 +- .../miroir-proxy/src/scoped_key_rotation.rs | 15 +- 73 files changed, 794 insertions(+), 983 deletions(-) diff --git a/crates/miroir-core/benches/router_bench.rs b/crates/miroir-core/benches/router_bench.rs index 378e9ae..7148fc8 100644 --- a/crates/miroir-core/benches/router_bench.rs +++ b/crates/miroir-core/benches/router_bench.rs @@ -41,7 +41,7 @@ fn bench_shard_for_key_batch(c: &mut Criterion) { /// Benchmark: assign_shard_in_group for a single shard. fn bench_assign_shard_single(c: &mut Criterion) { let nodes: Vec = (0..TARGET_NODES) - .map(|i| NodeId::new(format!("node-{}", i))) + .map(|i| NodeId::new(format!("node-{i}"))) .collect(); c.bench_function("assign_shard_in_group_single", |b| { @@ -58,7 +58,7 @@ fn bench_assign_shard_single(c: &mut Criterion) { /// Benchmark: assign_shard_in_group for all shards. fn bench_assign_shard_all(c: &mut Criterion) { let nodes: Vec = (0..TARGET_NODES) - .map(|i| NodeId::new(format!("node-{}", i))) + .map(|i| NodeId::new(format!("node-{i}"))) .collect(); c.bench_function("assign_shard_in_group_64_shards", |b| { @@ -86,7 +86,7 @@ fn bench_full_routing_pipeline(c: &mut Criterion) { .collect(); let nodes: Vec = (0..TARGET_NODES) - .map(|i| NodeId::new(format!("node-{}", i))) + .map(|i| NodeId::new(format!("node-{i}"))) .collect(); // Pre-compute shard assignments @@ -110,7 +110,7 @@ fn bench_full_routing_pipeline(c: &mut Criterion) { /// Benchmark: Varying shard counts. fn bench_varying_shard_count(c: &mut Criterion) { let nodes: Vec = (0..TARGET_NODES) - .map(|i| NodeId::new(format!("node-{}", i))) + .map(|i| NodeId::new(format!("node-{i}"))) .collect(); let mut group = c.benchmark_group("varying_shard_count"); @@ -141,7 +141,7 @@ fn bench_varying_node_count(c: &mut Criterion) { let mut group = c.benchmark_group("varying_node_count"); for node_count in [2, 3, 4, 5, 8, 10].iter() { let nodes: Vec = (0..*node_count) - .map(|i| NodeId::new(format!("node-{}", i))) + .map(|i| NodeId::new(format!("node-{i}"))) .collect(); group.bench_with_input( @@ -168,7 +168,7 @@ fn bench_varying_node_count(c: &mut Criterion) { /// Benchmark: Varying replication factors. fn bench_varying_rf(c: &mut Criterion) { let nodes: Vec = (0..10) - .map(|i| NodeId::new(format!("node-{}", i))) + .map(|i| NodeId::new(format!("node-{i}"))) .collect(); let mut group = c.benchmark_group("varying_rf"); diff --git a/crates/miroir-core/src/alias/mod.rs b/crates/miroir-core/src/alias/mod.rs index 625d9a3..adf79fe 100644 --- a/crates/miroir-core/src/alias/mod.rs +++ b/crates/miroir-core/src/alias/mod.rs @@ -248,7 +248,7 @@ impl AliasRegistry { let mut aliases = self.aliases.write().await; let alias = aliases .get_mut(name) - .ok_or_else(|| MiroirError::NotFound(format!("alias '{}' not found", name)))?; + .ok_or_else(|| MiroirError::NotFound(format!("alias '{name}' not found")))?; alias.flip(new_target)?; Ok(()) } @@ -258,7 +258,7 @@ impl AliasRegistry { let mut aliases = self.aliases.write().await; let alias = aliases .get_mut(name) - .ok_or_else(|| MiroirError::NotFound(format!("alias '{}' not found", name)))?; + .ok_or_else(|| MiroirError::NotFound(format!("alias '{name}' not found")))?; alias.update_targets(new_targets)?; Ok(()) } @@ -301,7 +301,7 @@ impl AliasFlipCoordinator { new_uid: String, pod_id: String, ) -> Self { - let scope = format!("alias_flip:{}", alias_name); + let scope = format!("alias_flip:{alias_name}"); let extra_state = AliasFlipExtraState { new_uid, diff --git a/crates/miroir-core/src/canary.rs b/crates/miroir-core/src/canary.rs index aeebb7b..d84e7cf 100644 --- a/crates/miroir-core/src/canary.rs +++ b/crates/miroir-core/src/canary.rs @@ -250,12 +250,12 @@ impl CanaryRunner { // Parse query let query: SearchQuery = serde_json::from_str(&canary.query_json) - .map_err(|e| MiroirError::InvalidRequest(format!("Invalid canary query: {}", e)))?; + .map_err(|e| MiroirError::InvalidRequest(format!("Invalid canary query: {e}")))?; // Parse assertions let assertions: Vec = serde_json::from_str(&canary.assertions_json) .map_err(|e| { - MiroirError::InvalidRequest(format!("Invalid canary assertions: {}", e)) + MiroirError::InvalidRequest(format!("Invalid canary assertions: {e}")) })?; // Execute the search query against the index @@ -269,7 +269,7 @@ impl CanaryRunner { let mut failed_assertions = Vec::new(); for assertion in &assertions { if let Some(failure) = - self.evaluate_assertion(&assertion, &search_response, latency_ms, &canary.index_uid) + self.evaluate_assertion(assertion, &search_response, latency_ms, &canary.index_uid) { failed_assertions.push(failure); } @@ -346,7 +346,7 @@ impl CanaryRunner { assertion_type: "top_hit_id".to_string(), expected: serde_json::json!(value), actual: serde_json::json!(actual), - message: format!("Top hit ID mismatch: expected {}, got {}", value, actual), + message: format!("Top hit ID mismatch: expected {value}, got {actual}"), }); } } @@ -369,7 +369,7 @@ impl CanaryRunner { assertion_type: "top_k_contains".to_string(), expected: serde_json::json!(ids), actual: serde_json::json!(top_k_ids), - message: format!("Top {} missing IDs: {:?}", k, missing), + message: format!("Top {k} missing IDs: {missing:?}"), }); } } @@ -393,7 +393,7 @@ impl CanaryRunner { assertion_type: "max_p95_ms".to_string(), expected: serde_json::json!(value), actual: serde_json::json!(latency_ms), - message: format!("Latency exceeded p95: {}ms > {}ms", latency_ms, value), + message: format!("Latency exceeded p95: {latency_ms}ms > {value}ms"), }); } } @@ -407,8 +407,7 @@ impl CanaryRunner { expected: serde_json::json!(value), actual: serde_json::json!(current_version), message: format!( - "Settings version below minimum: {} < {}", - current_version, value + "Settings version below minimum: {current_version} < {value}" ), }); } @@ -427,7 +426,7 @@ impl CanaryRunner { assertion_type: "must_not_contain_id".to_string(), expected: serde_json::json!(null), actual: serde_json::json!(id), - message: format!("Results contain forbidden ID: {}", id), + message: format!("Results contain forbidden ID: {id}"), }); } } @@ -499,10 +498,10 @@ pub fn create_canary( index_uid, interval_s, query_json: serde_json::to_string(&query).map_err(|e| { - MiroirError::InvalidRequest(format!("Failed to serialize query: {}", e)) + MiroirError::InvalidRequest(format!("Failed to serialize query: {e}")) })?, assertions_json: serde_json::to_string(&assertions).map_err(|e| { - MiroirError::InvalidRequest(format!("Failed to serialize assertions: {}", e)) + MiroirError::InvalidRequest(format!("Failed to serialize assertions: {e}")) })?, enabled: true, created_at: now, diff --git a/crates/miroir-core/src/cdc.rs b/crates/miroir-core/src/cdc.rs index 882b20c..f2ff3be 100644 --- a/crates/miroir-core/src/cdc.rs +++ b/crates/miroir-core/src/cdc.rs @@ -420,7 +420,7 @@ impl CdcInternalQueue { // Convert analytics event to a CdcEvent for storage let cdc_event = CdcEvent { - mtask_id: format!("analytics:{}", event_id), + mtask_id: format!("analytics:{event_id}"), index: index.clone(), operation: if event_type == "click_through" { CdcOperation::ClickThrough diff --git a/crates/miroir-core/src/drift_reconciler.rs b/crates/miroir-core/src/drift_reconciler.rs index 6f99df2..60a803b 100644 --- a/crates/miroir-core/src/drift_reconciler.rs +++ b/crates/miroir-core/src/drift_reconciler.rs @@ -96,7 +96,7 @@ impl DriftReconciler { } _ = leader_election_interval.tick() => { // Renew leader lease - let _ = self.renew_leader_lease(); + self.renew_leader_lease(); } } } @@ -208,7 +208,7 @@ impl DriftReconciler { ) .send() .await - .map_err(|e| MiroirError::Task(format!("failed to list indexes: {}", e)))?; + .map_err(|e| MiroirError::Task(format!("failed to list indexes: {e}")))?; if !response.status().is_success() { return Err(MiroirError::Task(format!( @@ -220,7 +220,7 @@ impl DriftReconciler { let json: Value = response .json() .await - .map_err(|e| MiroirError::Task(format!("failed to parse indexes: {}", e)))?; + .map_err(|e| MiroirError::Task(format!("failed to parse indexes: {e}")))?; let results = json .get("results") @@ -272,8 +272,7 @@ impl DriftReconciler { } Err(e) => { return Ok(DriftCheckResult::Error(MiroirError::Task(format!( - "node {} request failed: {}", - node_id, e + "node {node_id} request failed: {e}" )))); } } @@ -337,7 +336,7 @@ impl DriftReconciler { .send() .await .map_err(|e| { - MiroirError::Task(format!("failed to fetch settings for repair: {}", e)) + MiroirError::Task(format!("failed to fetch settings for repair: {e}")) })?; if !response.status().is_success() { @@ -348,7 +347,7 @@ impl DriftReconciler { } let correct_settings: Value = response.json().await.map_err(|e| { - MiroirError::Task(format!("failed to parse settings for repair: {}", e)) + MiroirError::Task(format!("failed to parse settings for repair: {e}")) })?; // PATCH the drifted node with correct settings @@ -367,7 +366,7 @@ impl DriftReconciler { .json(&correct_settings) .send() .await - .map_err(|e| MiroirError::Task(format!("failed to repair settings: {}", e)))?; + .map_err(|e| MiroirError::Task(format!("failed to repair settings: {e}")))?; if !patch_response.status().is_success() { return Err(MiroirError::Task(format!( @@ -390,7 +389,7 @@ impl DriftReconciler { .node_addresses .iter() .enumerate() - .map(|(i, addr)| (format!("node-{}", i), addr.clone())) + .map(|(i, addr)| (format!("node-{i}"), addr.clone())) .collect() } } diff --git a/crates/miroir-core/src/dump.rs b/crates/miroir-core/src/dump.rs index 33f1c13..759a2af 100644 --- a/crates/miroir-core/src/dump.rs +++ b/crates/miroir-core/src/dump.rs @@ -3,6 +3,12 @@ /// Placeholder dump handler pub struct DumpHandler; +impl Default for DumpHandler { + fn default() -> Self { + Self::new() + } +} + impl DumpHandler { pub fn new() -> Self { Self diff --git a/crates/miroir-core/src/dump_chunking.rs b/crates/miroir-core/src/dump_chunking.rs index 106d9a2..dfb1717 100644 --- a/crates/miroir-core/src/dump_chunking.rs +++ b/crates/miroir-core/src/dump_chunking.rs @@ -59,7 +59,7 @@ pub fn split_dump_into_chunks(data: &[u8], chunk_size_bytes: u64) -> Vec DumpImportManager { // Parse NDJSON and route documents let data_str = std::str::from_utf8(&dump_data) - .map_err(|e| MiroirError::InvalidRequest(format!("invalid UTF-8 in dump: {}", e)))?; + .map_err(|e| MiroirError::InvalidRequest(format!("invalid UTF-8 in dump: {e}")))?; // Per-target buffers: (node_id, shard_id) -> Vec let mut per_target_buffers: HashMap<(NodeId, u32), Vec> = HashMap::new(); @@ -252,7 +252,7 @@ impl DumpImportManager { continue; } let mut doc: Value = serde_json::from_str(line) - .map_err(|e| MiroirError::InvalidRequest(format!("invalid JSON in dump: {}", e)))?; + .map_err(|e| MiroirError::InvalidRequest(format!("invalid JSON in dump: {e}")))?; // Extract primary key value let pk_value = doc @@ -260,8 +260,7 @@ impl DumpImportManager { .and_then(|v| v.as_str()) .ok_or_else(|| { MiroirError::InvalidRequest(format!( - "missing or invalid primary key field: {}", - primary_key + "missing or invalid primary key field: {primary_key}" )) })?; @@ -279,8 +278,7 @@ impl DumpImportManager { if target_nodes.is_empty() { return Err(MiroirError::Topology(format!( - "no nodes for shard {}", - shard_id + "no nodes for shard {shard_id}" ))); } @@ -288,7 +286,7 @@ impl DumpImportManager { for node in &target_nodes { per_target_buffers .entry((node.clone(), shard_id)) - .or_insert_with(Vec::new) + .or_default() .push(doc.clone()); } diff --git a/crates/miroir-core/src/explainer.rs b/crates/miroir-core/src/explainer.rs index 4c4eb5d..3d0cf18 100644 --- a/crates/miroir-core/src/explainer.rs +++ b/crates/miroir-core/src/explainer.rs @@ -182,7 +182,7 @@ impl Explainer { let coalescing_eligible = self.config.query_coalescing.enabled; // Check cache candidate - let cache_candidate = !query.filter.is_some() && query.q.is_some(); + let cache_candidate = query.filter.is_none() && query.q.is_some(); // Estimate p95 latency let estimated_p95_ms = self.estimate_latency(topology, chosen_group.id, &target_shards); @@ -250,7 +250,7 @@ impl Explainer { let group_id = self.hash_tenant_to_group(tenant, topology); return ChosenGroup { id: group_id, - reason: format!("tenant affinity: {}", tenant), + reason: format!("tenant affinity: {tenant}"), }; } "explicit" => { @@ -258,7 +258,7 @@ impl Explainer { { return ChosenGroup { id: group_id, - reason: format!("explicit tenant mapping: {}", tenant), + reason: format!("explicit tenant mapping: {tenant}"), }; } } diff --git a/crates/miroir-core/src/group_sync_worker.rs b/crates/miroir-core/src/group_sync_worker.rs index af8429f..ea4a98f 100644 --- a/crates/miroir-core/src/group_sync_worker.rs +++ b/crates/miroir-core/src/group_sync_worker.rs @@ -173,22 +173,21 @@ impl GroupSyncWorker { // Get source group let source = topology.group(source_group).ok_or_else(|| { - crate::error::MiroirError::Topology(format!("source group {} not found", source_group)) + crate::error::MiroirError::Topology(format!("source group {source_group} not found")) })?; // Get target group (the new group being added) let target_group_id = { let coord = self.coordinator.read().await; let state = coord.get_state(addition_id).ok_or_else(|| { - crate::error::MiroirError::Topology(format!("addition {} not found", addition_id)) + crate::error::MiroirError::Topology(format!("addition {addition_id} not found")) })?; state.group_id }; let target = topology.group(target_group_id).ok_or_else(|| { crate::error::MiroirError::Topology(format!( - "target group {} not found", - target_group_id + "target group {target_group_id} not found" )) })?; @@ -199,15 +198,13 @@ impl GroupSyncWorker { let source_node = source_healthy.first().ok_or_else(|| { crate::error::MiroirError::Topology(format!( - "no healthy nodes in source group {}", - source_group + "no healthy nodes in source group {source_group}" )) })?; let target_node = target_healthy.first().ok_or_else(|| { crate::error::MiroirError::Topology(format!( - "no healthy nodes in target group {}", - target_group_id + "no healthy nodes in target group {target_group_id}" )) })?; @@ -242,14 +239,12 @@ impl GroupSyncWorker { .await .map_err(|_| { crate::error::MiroirError::Routing(format!( - "fetch timeout for shard {} from group {}", - shard_id, source_group + "fetch timeout for shard {shard_id} from group {source_group}" )) })? .map_err(|e| { crate::error::MiroirError::Routing(format!( - "fetch failed for shard {}: {}", - shard_id, e + "fetch failed for shard {shard_id}: {e}" )) })?; @@ -281,8 +276,7 @@ impl GroupSyncWorker { .await .map_err(|e| { crate::error::MiroirError::Routing(format!( - "write failed for shard {}: {}", - shard_id, e + "write failed for shard {shard_id}: {e}" )) })?; @@ -309,7 +303,7 @@ impl GroupSyncWorker { coord .shard_sync_complete(addition_id, shard_id, total_copied) .map_err(|e| { - crate::error::MiroirError::Topology(format!("failed to mark shard complete: {}", e)) + crate::error::MiroirError::Topology(format!("failed to mark shard complete: {e}")) })?; info!( @@ -349,7 +343,7 @@ impl GroupSyncWorker { let mut coord = self.coordinator.write().await; let _ = coord - .fail_addition(addition_id, format!("sync timeout after {:?}", timeout)); + .fail_addition(addition_id, format!("sync timeout after {timeout:?}")); } } } diff --git a/crates/miroir-core/src/ilm.rs b/crates/miroir-core/src/ilm.rs index cdec3ab..6e8afbd 100644 --- a/crates/miroir-core/src/ilm.rs +++ b/crates/miroir-core/src/ilm.rs @@ -175,16 +175,12 @@ struct NodeIndexStats { } #[derive(Debug, Clone, Deserialize)] +#[derive(Default)] struct NodeStatsDetail { #[serde(rename = "databaseSize", default)] pub database_size: u64, } -impl Default for NodeStatsDetail { - fn default() -> Self { - Self { database_size: 0 } - } -} /// Aggregated index stats across all nodes. #[derive(Debug, Clone)] @@ -293,7 +289,7 @@ impl IlmManager { let policy_rows = task_store .list_rollover_policies() - .map_err(|e| IlmError::CoordinatorError(format!("failed to load policies: {}", e)))?; + .map_err(|e| IlmError::CoordinatorError(format!("failed to load policies: {e}")))?; let policies: Vec = policy_rows .into_iter() @@ -314,13 +310,13 @@ impl IlmManager { /// Convert a task store row to a RolloverPolicy. fn row_to_policy(row: RolloverPolicyRow) -> std::result::Result { let triggers: RolloverTriggers = serde_json::from_str(&row.triggers_json) - .map_err(|e| IlmError::CoordinatorError(format!("invalid triggers JSON: {}", e)))?; + .map_err(|e| IlmError::CoordinatorError(format!("invalid triggers JSON: {e}")))?; let retention: RetentionPolicy = serde_json::from_str(&row.retention_json) - .map_err(|e| IlmError::CoordinatorError(format!("invalid retention JSON: {}", e)))?; + .map_err(|e| IlmError::CoordinatorError(format!("invalid retention JSON: {e}")))?; let template: IndexTemplate = serde_json::from_str(&row.template_json) - .map_err(|e| IlmError::CoordinatorError(format!("invalid template JSON: {}", e)))?; + .map_err(|e| IlmError::CoordinatorError(format!("invalid template JSON: {e}")))?; Ok(RolloverPolicy { name: row.name, @@ -439,7 +435,7 @@ impl IlmManager { .iter() .take(config.max_rollovers_per_check as usize) { - if let Err(e) = Self::evaluate_policy(&state, &policy, &config).await { + if let Err(e) = Self::evaluate_policy(&state, policy, &config).await { error!("ILM: error evaluating policy '{}': {}", policy.name, e); } } @@ -590,7 +586,7 @@ impl IlmWorker { let policy_rows = self .task_store .list_rollover_policies() - .map_err(|e| IlmError::CoordinatorError(format!("failed to list policies: {}", e)))?; + .map_err(|e| IlmError::CoordinatorError(format!("failed to list policies: {e}")))?; let mut rollover_count = 0; @@ -635,7 +631,7 @@ impl IlmWorker { // Mark the rollover as failed in the coordinator let _ = self .coordinator - .fail(format!("rollover failed: {}", e)) + .fail(format!("rollover failed: {e}")) .await; } } @@ -695,8 +691,7 @@ impl IlmWorker { let mut fired_triggers = Vec::new(); if age_triggered { fired_triggers.push(format!( - "max_age ({}s >= {}s)", - age_seconds, max_age_seconds + "max_age ({age_seconds}s >= {max_age_seconds}s)" )); } if docs_triggered { @@ -794,13 +789,13 @@ impl IlmWorker { .header("Authorization", format!("Bearer {}", &*self.master_key)) .send() .await - .map_err(|e| IlmError::CoordinatorError(format!("request failed: {}", e)))?; + .map_err(|e| IlmError::CoordinatorError(format!("request failed: {e}")))?; let status = response.status(); let body_text = response .text() .await - .map_err(|e| IlmError::CoordinatorError(format!("failed to read response: {}", e)))?; + .map_err(|e| IlmError::CoordinatorError(format!("failed to read response: {e}")))?; if status.as_u16() == 404 { // Index doesn't exist on this node @@ -819,7 +814,7 @@ impl IlmWorker { } serde_json::from_str(&body_text) - .map_err(|e| IlmError::CoordinatorError(format!("failed to parse stats: {}", e))) + .map_err(|e| IlmError::CoordinatorError(format!("failed to parse stats: {e}"))) } /// Execute a rollover operation for a policy. @@ -911,14 +906,14 @@ impl IlmWorker { .send() .await .map_err(|e| { - IlmError::RolloverFailed(format!("request to {} failed: {}", url, e)) + IlmError::RolloverFailed(format!("request to {url} failed: {e}")) })?; let status = response.status(); let body_text = response .text() .await - .map_err(|e| IlmError::RolloverFailed(format!("failed to read response: {}", e)))?; + .map_err(|e| IlmError::RolloverFailed(format!("failed to read response: {e}")))?; if status.as_u16() == 409 { // Index already exists - this is ok for ILM (might have been partially created) @@ -956,12 +951,12 @@ impl IlmWorker { .flip(alias_name, new_index.to_string()) .await .map_err(|e| { - IlmError::AliasError(format!("failed to flip alias '{}': {}", alias_name, e)) + IlmError::AliasError(format!("failed to flip alias '{alias_name}': {e}")) })?; // Persist to task store let alias = self.alias_registry.get(alias_name).await.ok_or_else(|| { - IlmError::AliasError(format!("alias '{}' not found in registry", alias_name)) + IlmError::AliasError(format!("alias '{alias_name}' not found in registry")) })?; // Update task store @@ -977,7 +972,7 @@ impl IlmWorker { self.task_store .create_alias(&new_alias) - .map_err(|e| IlmError::AliasError(format!("failed to persist alias flip: {}", e)))?; + .map_err(|e| IlmError::AliasError(format!("failed to persist alias flip: {e}")))?; info!( "ILM: flipped write alias '{}' to '{}'", @@ -1019,14 +1014,13 @@ impl IlmWorker { .await .map_err(|e| { IlmError::AliasError(format!( - "failed to update multi-target alias '{}': {}", - alias_name, e + "failed to update multi-target alias '{alias_name}': {e}" )) })?; // Persist to task store let alias = self.alias_registry.get(alias_name).await.ok_or_else(|| { - IlmError::AliasError(format!("alias '{}' not found in registry", alias_name)) + IlmError::AliasError(format!("alias '{alias_name}' not found in registry")) })?; let new_alias = crate::task_store::NewAlias { @@ -1040,7 +1034,7 @@ impl IlmWorker { }; self.task_store.create_alias(&new_alias).map_err(|e| { - IlmError::AliasError(format!("failed to persist read alias update: {}", e)) + IlmError::AliasError(format!("failed to persist read alias update: {e}")) })?; info!( @@ -1140,7 +1134,7 @@ impl IlmWorker { .header("Authorization", format!("Bearer {}", &*self.master_key)) .send() .await - .map_err(|e| IlmError::RolloverFailed(format!("delete request failed: {}", e)))?; + .map_err(|e| IlmError::RolloverFailed(format!("delete request failed: {e}")))?; let status = response.status(); @@ -1179,7 +1173,7 @@ fn parse_index_date(date_str: &str) -> std::result::Result { use chrono::NaiveDate; let date = NaiveDate::parse_from_str(date_str, "%Y-%m-%d") - .map_err(|_| IlmError::CoordinatorError(format!("invalid date format: {}", date_str)))?; + .map_err(|_| IlmError::CoordinatorError(format!("invalid date format: {date_str}")))?; let datetime = date .and_hms_opt(0, 0, 0) @@ -1196,23 +1190,23 @@ fn parse_duration(duration: &str) -> std::result::Result { if duration.ends_with('d') { let days = duration[..duration.len() - 1] .parse::() - .map_err(|_| IlmError::CoordinatorError(format!("invalid duration: {}", duration)))?; + .map_err(|_| IlmError::CoordinatorError(format!("invalid duration: {duration}")))?; Ok(days * 86400) } else if duration.ends_with('h') { let hours = duration[..duration.len() - 1] .parse::() - .map_err(|_| IlmError::CoordinatorError(format!("invalid duration: {}", duration)))?; + .map_err(|_| IlmError::CoordinatorError(format!("invalid duration: {duration}")))?; Ok(hours * 3600) } else if duration.ends_with('m') { let minutes = duration[..duration.len() - 1] .parse::() - .map_err(|_| IlmError::CoordinatorError(format!("invalid duration: {}", duration)))?; + .map_err(|_| IlmError::CoordinatorError(format!("invalid duration: {duration}")))?; Ok(minutes * 60) } else { // Assume seconds if no unit duration .parse::() - .map_err(|_| IlmError::CoordinatorError(format!("invalid duration: {}", duration))) + .map_err(|_| IlmError::CoordinatorError(format!("invalid duration: {duration}"))) } } diff --git a/crates/miroir-core/src/leader_election/mod.rs b/crates/miroir-core/src/leader_election/mod.rs index 0b88bd7..409957d 100644 --- a/crates/miroir-core/src/leader_election/mod.rs +++ b/crates/miroir-core/src/leader_election/mod.rs @@ -325,7 +325,7 @@ impl LeaderElection { let current = self.task_store.get_leader_lease(scope)?; let held = current .as_ref() - .map(|l| &l.holder == &self.pod_id && l.expires_at > now_ms) + .map(|l| l.holder == self.pod_id && l.expires_at > now_ms) .unwrap_or(false); if held { diff --git a/crates/miroir-core/src/merger.rs b/crates/miroir-core/src/merger.rs index 63e53a5..f025238 100644 --- a/crates/miroir-core/src/merger.rs +++ b/crates/miroir-core/src/merger.rs @@ -8,7 +8,7 @@ use crate::vector::{VectorHit, VectorMerger, VectorSearchConfig}; use crate::Result; use serde_json::{Map, Value}; use std::cmp::Ordering; -use std::collections::{BTreeMap, HashMap}; +use std::collections::BTreeMap; /// Input to the merge operation. #[derive(Debug, Clone)] diff --git a/crates/miroir-core/src/mode_c_coordinator.rs b/crates/miroir-core/src/mode_c_coordinator.rs index 7cd1f15..3228835 100644 --- a/crates/miroir-core/src/mode_c_coordinator.rs +++ b/crates/miroir-core/src/mode_c_coordinator.rs @@ -129,6 +129,7 @@ impl JobType { /// Job progress tracking. #[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Default)] pub struct JobProgress { /// Bytes processed so far (for dump import). pub bytes_processed: u64, @@ -140,16 +141,6 @@ pub struct JobProgress { pub error: Option, } -impl Default for JobProgress { - fn default() -> Self { - Self { - bytes_processed: 0, - docs_routed: 0, - last_cursor: String::new(), - error: None, - } - } -} /// Chunk specification for a job. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -243,10 +234,10 @@ impl ModeCCoordinator { pub fn enqueue_job(&self, type_: JobType, params: JobParams) -> Result { let job_id = format!("{}-{}", type_.as_str(), uuid::Uuid::new_v4()); let params_json = serde_json::to_string(¶ms) - .map_err(|e| MiroirError::TaskStore(format!("failed to serialize params: {}", e)))?; + .map_err(|e| MiroirError::TaskStore(format!("failed to serialize params: {e}")))?; let progress = JobProgress::default(); let progress_json = serde_json::to_string(&progress) - .map_err(|e| MiroirError::TaskStore(format!("failed to serialize progress: {}", e)))?; + .map_err(|e| MiroirError::TaskStore(format!("failed to serialize progress: {e}")))?; let new_job = NewJob { id: job_id.clone(), @@ -346,7 +337,7 @@ impl ModeCCoordinator { state: JobState, ) -> Result<()> { let progress_json = serde_json::to_string(progress) - .map_err(|e| MiroirError::TaskStore(format!("failed to serialize progress: {}", e)))?; + .map_err(|e| MiroirError::TaskStore(format!("failed to serialize progress: {e}")))?; self.task_store .update_job_progress(job_id, state.as_str(), &progress_json)?; @@ -379,7 +370,7 @@ impl ModeCCoordinator { failed_progress.error = Some(error.clone()); let progress_json = serde_json::to_string(&failed_progress) - .map_err(|e| MiroirError::TaskStore(format!("failed to serialize progress: {}", e)))?; + .map_err(|e| MiroirError::TaskStore(format!("failed to serialize progress: {e}")))?; self.task_store .update_job_progress(job_id, JobState::Failed.as_str(), &progress_json)?; @@ -403,7 +394,7 @@ impl ModeCCoordinator { chunk_specs: Vec, ) -> Result> { let params: JobParams = serde_json::from_str(&job.params) - .map_err(|e| MiroirError::TaskStore(format!("failed to deserialize params: {}", e)))?; + .map_err(|e| MiroirError::TaskStore(format!("failed to deserialize params: {e}")))?; let total_chunks = chunk_specs.len() as u32; let mut chunk_job_ids = Vec::new(); @@ -424,11 +415,11 @@ impl ModeCCoordinator { let chunk_job_id = format!("{}-chunk-{}", job.id, idx); let params_json = serde_json::to_string(&chunk_params).map_err(|e| { - MiroirError::TaskStore(format!("failed to serialize chunk params: {}", e)) + MiroirError::TaskStore(format!("failed to serialize chunk params: {e}")) })?; let progress = JobProgress::default(); let progress_json = serde_json::to_string(&progress).map_err(|e| { - MiroirError::TaskStore(format!("failed to serialize progress: {}", e)) + MiroirError::TaskStore(format!("failed to serialize progress: {e}")) })?; let new_job = NewJob { @@ -555,13 +546,13 @@ impl ClaimedJob { /// Parse the job parameters. pub fn parse_params(&self) -> Result { serde_json::from_str(&self.params) - .map_err(|e| MiroirError::TaskStore(format!("failed to deserialize params: {}", e))) + .map_err(|e| MiroirError::TaskStore(format!("failed to deserialize params: {e}"))) } /// Parse the current progress. pub fn parse_progress(&self) -> Result { serde_json::from_str(&self.progress) - .map_err(|e| MiroirError::TaskStore(format!("failed to deserialize progress: {}", e))) + .map_err(|e| MiroirError::TaskStore(format!("failed to deserialize progress: {e}"))) } /// Check if this is a chunk job. diff --git a/crates/miroir-core/src/mode_c_worker/mod.rs b/crates/miroir-core/src/mode_c_worker/mod.rs index a7e78b1..7f6544f 100644 --- a/crates/miroir-core/src/mode_c_worker/mod.rs +++ b/crates/miroir-core/src/mode_c_worker/mod.rs @@ -258,7 +258,7 @@ impl ModeCWorker { } // Calculate number of chunks (ceiling division) - let total_chunks = ((source_size + chunk_size_bytes - 1) / chunk_size_bytes) as u32; + let total_chunks = source_size.div_ceil(chunk_size_bytes) as u32; (0..total_chunks) .map(|i| { @@ -461,7 +461,7 @@ impl ModeCWorker { // If this is a chunk job, process the shard range if let Some(chunk) = ¶ms.chunk { let (start_shard, end_shard) = reshard_chunking::parse_reshard_chunk(chunk) - .map_err(|e| MiroirError::InvalidRequest(format!("invalid chunk spec: {}", e)))?; + .map_err(|e| MiroirError::InvalidRequest(format!("invalid chunk spec: {e}")))?; info!( "Processing reshard chunk {}/{} (shards {}-{})", @@ -560,7 +560,7 @@ impl ModeCWorker { let client = reqwest::Client::builder() .timeout(Duration::from_secs(30)) .build() - .map_err(|e| MiroirError::Task(format!("failed to create HTTP client: {}", e)))?; + .map_err(|e| MiroirError::Task(format!("failed to create HTTP client: {e}")))?; // Get node addresses from environment or topology // For now, use a placeholder - in production this would come from Topology @@ -577,7 +577,7 @@ impl ModeCWorker { // Pagination through documents in this shard loop { // Fetch documents from live index with _miroir_shard filter - let filter = format!("_miroir_shard={}", shard_id); + let filter = format!("_miroir_shard={shard_id}"); let url = format!( "{}/indexes/{}/documents?filter={}&limit={}&offset={}", node_addresses.trim_end_matches('/'), @@ -589,10 +589,10 @@ impl ModeCWorker { let response = client .get(&url) - .header("Authorization", format!("Bearer {}", node_master_key)) + .header("Authorization", format!("Bearer {node_master_key}")) .send() .await - .map_err(|e| MiroirError::Task(format!("fetch failed: {}", e)))?; + .map_err(|e| MiroirError::Task(format!("fetch failed: {e}")))?; if !response.status().is_success() { let status = response.status(); @@ -601,15 +601,14 @@ impl ModeCWorker { .await .unwrap_or_else(|_| "unable to read error".to_string()); return Err(MiroirError::Task(format!( - "failed to fetch documents: HTTP {} - {}", - status, body + "failed to fetch documents: HTTP {status} - {body}" ))); } let json_body: serde_json::Value = response .json() .await - .map_err(|e| MiroirError::Task(format!("parse response failed: {}", e)))?; + .map_err(|e| MiroirError::Task(format!("parse response failed: {e}")))?; let results = json_body .get("results") @@ -657,11 +656,11 @@ impl ModeCWorker { let response = client .post(&write_url) - .header("Authorization", format!("Bearer {}", node_master_key)) + .header("Authorization", format!("Bearer {node_master_key}")) .json(&shadow_documents) .send() .await - .map_err(|e| MiroirError::Task(format!("write failed: {}", e)))?; + .map_err(|e| MiroirError::Task(format!("write failed: {e}")))?; if !response.status().is_success() { let status = response.status(); @@ -670,8 +669,7 @@ impl ModeCWorker { .await .unwrap_or_else(|_| "unable to read error".to_string()); return Err(MiroirError::Task(format!( - "failed to write to shadow index: HTTP {} - {}", - status, body + "failed to write to shadow index: HTTP {status} - {body}" ))); } @@ -691,7 +689,7 @@ impl ModeCWorker { let progress = JobProgress { bytes_processed: 0, docs_routed: docs_backfilled, - last_cursor: format!("{}:{}", shard_id, offset), + last_cursor: format!("{shard_id}:{offset}"), error: None, }; coordinator.update_progress( diff --git a/crates/miroir-core/src/peer_discovery.rs b/crates/miroir-core/src/peer_discovery.rs index 198c85a..f23c47f 100644 --- a/crates/miroir-core/src/peer_discovery.rs +++ b/crates/miroir-core/src/peer_discovery.rs @@ -140,15 +140,15 @@ impl PeerDiscovery { // Use system resolver config from /etc/resolv.conf (plan §14.5) let resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default()) .map_err(|e| { - MiroirError::Discovery(format!("failed to create DNS resolver: {}", e)) + MiroirError::Discovery(format!("failed to create DNS resolver: {e}")) })?; resolver.srv_lookup(&srv_name).map_err(|e| { - MiroirError::Discovery(format!("SRV lookup failed for {}: {}", srv_name, e)) + MiroirError::Discovery(format!("SRV lookup failed for {srv_name}: {e}")) }) }) .await - .map_err(|e| MiroirError::Discovery(format!("SRV lookup task failed: {}", e)))??; + .map_err(|e| MiroirError::Discovery(format!("SRV lookup task failed: {e}")))??; // Extract pod names from SRV targets // Each SRV record has a target like "miroir-miroir-0.miroir-headless.default.svc.cluster.local" diff --git a/crates/miroir-core/src/query_planner.rs b/crates/miroir-core/src/query_planner.rs index a7a4eb0..71fdacd 100644 --- a/crates/miroir-core/src/query_planner.rs +++ b/crates/miroir-core/src/query_planner.rs @@ -136,7 +136,7 @@ impl QueryPlanner { let shard_id = shard_for_key(&literal, shard_count); QueryPlan { narrowed: true, - reason: format!("PK equality: {} = {}", pk_field, literal), + reason: format!("PK equality: {pk_field} = {literal}"), target_shards: vec![shard_id], warnings: vec![], filter: Some(filter.clone()), @@ -176,7 +176,7 @@ impl QueryPlanner { } Err(e) => QueryPlan { narrowed: false, - reason: format!("filter not narrowable: {}", e), + reason: format!("filter not narrowable: {e}"), target_shards: vec![], warnings: vec![], filter: Some(filter.clone()), @@ -201,8 +201,8 @@ impl QueryPlanner { )); } - if filter.contains(&format!("{} != ", pk_field)) - || filter.contains(&format!("{}<>", pk_field)) + if filter.contains(&format!("{pk_field} != ")) + || filter.contains(&format!("{pk_field}<>")) { return Err(MiroirError::InvalidState( "PK negation is not narrowable".to_string(), @@ -210,8 +210,8 @@ impl QueryPlanner { } // Try equality: pk = "literal" - let eq_pattern = format!(r#"{}\s*=\s*["']([^"']+)["']"#, pk_field); - if let Some(re) = regex::Regex::new(&eq_pattern).ok() { + let eq_pattern = format!(r#"{pk_field}\s*=\s*["']([^"']+)["']"#); + if let Ok(re) = regex::Regex::new(&eq_pattern) { if let Some(caps) = re.captures(filter) { if let Some(literal) = caps.get(1) { return Ok(PkConstraint::Eq(literal.as_str().to_string())); @@ -220,8 +220,8 @@ impl QueryPlanner { } // Try IN list: pk IN ["literal1", "literal2", ...] - let in_pattern = format!(r#"{}\s+IN\s+\[(.+)\]"#, pk_field); - if let Some(re) = regex::Regex::new(&in_pattern).ok() { + let in_pattern = format!(r#"{pk_field}\s+IN\s+\[(.+)\]"#); + if let Ok(re) = regex::Regex::new(&in_pattern) { if let Some(caps) = re.captures(filter) { if let Some(list) = caps.get(1) { let literals = self.parse_string_list(list.as_str())?; diff --git a/crates/miroir-core/src/rebalancer.rs b/crates/miroir-core/src/rebalancer.rs index cf9451f..2848f37 100644 --- a/crates/miroir-core/src/rebalancer.rs +++ b/crates/miroir-core/src/rebalancer.rs @@ -586,7 +586,7 @@ impl Rebalancer { for op in ops.values() { for &mid in &op.migrations { if let Some(state) = coordinator.get_state(mid) { - let key = format!("{}", mid); + let key = format!("{mid}"); let status = MigrationStatus { id: mid.0, new_node: state.new_node.to_string(), @@ -746,8 +746,7 @@ impl Rebalancer { Ok(TopologyOperationResult { id: op_id, message: format!( - "Node {} addition started with {} shard migrations", - node_id_for_result, migrations_count + "Node {node_id_for_result} addition started with {migrations_count} shard migrations" ), migrations_count, }) @@ -772,7 +771,7 @@ impl Rebalancer { let group = topo .groups() .find(|g| g.id == node.replica_group) - .ok_or_else(|| RebalancerError::GroupNotFound(node.replica_group))?; + .ok_or(RebalancerError::GroupNotFound(node.replica_group))?; if group.nodes().len() <= 1 { return Err(RebalancerError::CannotRemoveLastNode); @@ -914,7 +913,7 @@ impl Rebalancer { let group = topo .groups() .find(|g| g.id == node.replica_group) - .ok_or_else(|| RebalancerError::GroupNotFound(node.replica_group))?; + .ok_or(RebalancerError::GroupNotFound(node.replica_group))?; if group.nodes().len() <= 1 { return Err(RebalancerError::CannotRemoveLastNode); @@ -1211,7 +1210,7 @@ impl Rebalancer { Ok(TopologyOperationResult { id: op_id, - message: format!("Node {} marked as failed", node_id), + message: format!("Node {node_id} marked as failed"), migrations_count: 0, }) } @@ -1243,7 +1242,7 @@ impl Rebalancer { // Check if RF needs to be restored (other healthy nodes exist in group) let group = topo.groups().find(|g| g.id == replica_group); - let has_other_healthy = group.map_or(false, |g| { + let has_other_healthy = group.is_some_and(|g| { g.nodes().iter().any(|nid| { nid != &node_id_obj && topo.node(nid).map(|n| n.is_healthy()).unwrap_or(false) }) @@ -1263,8 +1262,7 @@ impl Rebalancer { return Ok(TopologyOperationResult { id: 0, message: format!( - "Node {} recovered (no RF restore needed - no other healthy nodes in group)", - node_id + "Node {node_id} recovered (no RF restore needed - no other healthy nodes in group)" ), migrations_count: 0, }); @@ -1371,8 +1369,7 @@ impl Rebalancer { Ok(TopologyOperationResult { id: op_id, message: format!( - "Node {} recovered with RF restore ({} shards)", - node_id, migrations_count + "Node {node_id} recovered with RF restore ({migrations_count} shards)" ), migrations_count, }) @@ -1397,7 +1394,7 @@ impl Rebalancer { Ok(TopologyOperationResult { id: op_id, - message: format!("Node {} recovered (no shards needed restoration)", node_id), + message: format!("Node {node_id} recovered (no shards needed restoration)"), migrations_count: 0, }) } @@ -1417,7 +1414,7 @@ impl Rebalancer { let group = topo .groups() .find(|g| g.id == replica_group) - .ok_or_else(|| RebalancerError::GroupNotFound(replica_group))?; + .ok_or(RebalancerError::GroupNotFound(replica_group))?; let mut shards_to_restore = Vec::new(); @@ -1447,7 +1444,7 @@ impl Rebalancer { let group = topo .groups() .find(|g| g.id == replica_group) - .ok_or_else(|| RebalancerError::GroupNotFound(replica_group))?; + .ok_or(RebalancerError::GroupNotFound(replica_group))?; let assignment = assign_shard_in_group(shard.0, group.nodes(), topo.rf()); @@ -1488,9 +1485,9 @@ impl Rebalancer { let group = topo .groups() .find(|g| g.id == replica_group) - .ok_or_else(|| RebalancerError::GroupNotFound(replica_group))?; + .ok_or(RebalancerError::GroupNotFound(replica_group))?; - let existing_nodes: Vec<_> = group.nodes().iter().cloned().collect(); + let existing_nodes: Vec<_> = group.nodes().to_vec(); let mut affected_shards = Vec::new(); // For each shard, check if the new node is in the new assignment @@ -1563,7 +1560,7 @@ impl Rebalancer { let group = topo .groups() .find(|g| g.id == replica_group) - .ok_or_else(|| RebalancerError::GroupNotFound(replica_group))?; + .ok_or(RebalancerError::GroupNotFound(replica_group))?; let other_nodes: Vec<_> = group .nodes() @@ -1763,7 +1760,7 @@ async fn run_migration_task( offset, ) .await - .map_err(|e| RebalancerError::InvalidState(format!("fetch failed: {}", e)))?; + .map_err(|e| RebalancerError::InvalidState(format!("fetch failed: {e}")))?; if docs.is_empty() { break; // No more documents @@ -1772,7 +1769,7 @@ async fn run_migration_task( // Write documents to target exec.write_documents(&new_node, &new_node_address, &index_uid, docs.clone()) .await - .map_err(|e| RebalancerError::InvalidState(format!("write failed: {}", e)))?; + .map_err(|e| RebalancerError::InvalidState(format!("write failed: {e}")))?; total_docs_copied += docs.len() as u64; offset += limit; @@ -1828,14 +1825,14 @@ async fn run_migration_task( 0, ) .await - .map_err(|e| RebalancerError::InvalidState(format!("delta fetch failed: {}", e)))?; + .map_err(|e| RebalancerError::InvalidState(format!("delta fetch failed: {e}")))?; if !docs.is_empty() { // Write any stragglers to target exec.write_documents(&new_node, &new_node_address, &index_uid, docs) .await .map_err(|e| { - RebalancerError::InvalidState(format!("delta write failed: {}", e)) + RebalancerError::InvalidState(format!("delta write failed: {e}")) })?; } @@ -2023,7 +2020,7 @@ async fn run_drain_task( }; // For each shard being drained - for (shard_id, _old_node) in &old_owners { + for shard_id in old_owners.keys() { info!( migration_id = %mid, shard_id = shard_id.0, @@ -2049,7 +2046,7 @@ async fn run_drain_task( offset, ) .await - .map_err(|e| RebalancerError::InvalidState(format!("fetch failed: {}", e)))?; + .map_err(|e| RebalancerError::InvalidState(format!("fetch failed: {e}")))?; if docs.is_empty() { break; // No more documents @@ -2058,7 +2055,7 @@ async fn run_drain_task( // Write documents to new node exec.write_documents(&new_node, &new_node_address, &index_uid, docs.clone()) .await - .map_err(|e| RebalancerError::InvalidState(format!("write failed: {}", e)))?; + .map_err(|e| RebalancerError::InvalidState(format!("write failed: {e}")))?; total_docs_copied += docs.len() as u64; offset += limit; @@ -2092,7 +2089,7 @@ async fn run_drain_task( } // Delta pass: re-read from draining node to catch stragglers - for (shard_id, _old_node) in &old_owners { + for shard_id in old_owners.keys() { let (docs, _) = exec .fetch_documents( &drain_node_id, @@ -2103,14 +2100,14 @@ async fn run_drain_task( 0, ) .await - .map_err(|e| RebalancerError::InvalidState(format!("delta fetch failed: {}", e)))?; + .map_err(|e| RebalancerError::InvalidState(format!("delta fetch failed: {e}")))?; if !docs.is_empty() { // Write any stragglers to new node exec.write_documents(&new_node, &new_node_address, &index_uid, docs) .await .map_err(|e| { - RebalancerError::InvalidState(format!("delta write failed: {}", e)) + RebalancerError::InvalidState(format!("delta write failed: {e}")) })?; } @@ -2127,7 +2124,7 @@ async fn run_drain_task( } // Delete drained shards from the draining node - for (shard_id, _old_node) in &old_owners { + for shard_id in old_owners.keys() { if let Err(e) = exec .delete_shard(&drain_node_id, &drain_node_address, &index_uid, shard_id.0) .await @@ -2214,7 +2211,7 @@ impl HttpMigrationExecutor { /// Build the filter string for fetching documents by shard. fn shard_filter(&self, shard_id: u32) -> String { - format!("_miroir_shard = {}", shard_id) + format!("_miroir_shard = {shard_id}") } /// Make an authenticated GET request to a node. @@ -2238,7 +2235,7 @@ impl HttpMigrationExecutor { .header("Authorization", format!("Bearer {}", self.node_master_key)) .send() .await - .map_err(|e| format!("GET {} failed: {}", url, e)) + .map_err(|e| format!("GET {url} failed: {e}")) } /// Make an authenticated POST request to a node. @@ -2264,7 +2261,7 @@ impl HttpMigrationExecutor { .json(&body) .send() .await - .map_err(|e| format!("POST {} failed: {}", url, e)) + .map_err(|e| format!("POST {url} failed: {e}")) } } @@ -2301,15 +2298,14 @@ impl MigrationExecutor for HttpMigrationExecutor { .await .unwrap_or_else(|_| "unable to read error".to_string()); return Err(format!( - "Failed to fetch documents from {}: HTTP {} - {}", - source_address, status, error_text + "Failed to fetch documents from {source_address}: HTTP {status} - {error_text}" )); } let json_body: serde_json::Value = response .json() .await - .map_err(|e| format!("Failed to parse response from {}: {}", source_address, e))?; + .map_err(|e| format!("Failed to parse response from {source_address}: {e}"))?; // Meilisearch returns { results: [...], total: 123, limit: 20, offset: 0 } let results = json_body @@ -2317,8 +2313,7 @@ impl MigrationExecutor for HttpMigrationExecutor { .and_then(|v| v.as_array()) .ok_or_else(|| { format!( - "Invalid response from {}: missing 'results' field", - source_address + "Invalid response from {source_address}: missing 'results' field" ) })?; @@ -2342,7 +2337,7 @@ impl MigrationExecutor for HttpMigrationExecutor { return Ok(()); } - let path = format!("indexes/{}/documents", index_uid); + let path = format!("indexes/{index_uid}/documents"); let response = self .post_node(target_address, &path, serde_json::json!(documents)) @@ -2380,7 +2375,7 @@ impl MigrationExecutor for HttpMigrationExecutor { shard_id: u32, ) -> std::result::Result<(), String> { let filter = self.shard_filter(shard_id); - let path = format!("indexes/{}/documents/delete", index_uid); + let path = format!("indexes/{index_uid}/documents/delete"); let body = serde_json::json!({ "filter": filter @@ -2395,8 +2390,7 @@ impl MigrationExecutor for HttpMigrationExecutor { .await .unwrap_or_else(|_| "unable to read error".to_string()); return Err(format!( - "Failed to delete shard {} from {}: HTTP {} - {}", - shard_id, node_address, status, error_text + "Failed to delete shard {shard_id} from {node_address}: HTTP {status} - {error_text}" )); } diff --git a/crates/miroir-core/src/rebalancer_worker/anti_entropy_worker.rs b/crates/miroir-core/src/rebalancer_worker/anti_entropy_worker.rs index b9ce52e..845d376 100644 --- a/crates/miroir-core/src/rebalancer_worker/anti_entropy_worker.rs +++ b/crates/miroir-core/src/rebalancer_worker/anti_entropy_worker.rs @@ -174,7 +174,7 @@ impl NodeClient for HttpNodeClient { .json(&request.documents) .send() .await - .map_err(|e| NodeError::NetworkError(format!("write failed: {}", e)))?; + .map_err(|e| NodeError::NetworkError(format!("write failed: {e}")))?; if !response.status().is_success() { let status = response.status(); @@ -191,7 +191,7 @@ impl NodeClient for HttpNodeClient { let json: Value = response .json() .await - .map_err(|e| NodeError::NetworkError(format!("parse response failed: {}", e)))?; + .map_err(|e| NodeError::NetworkError(format!("parse response failed: {e}")))?; let success = json .get("success") @@ -252,7 +252,7 @@ impl NodeClient for HttpNodeClient { .header("Authorization", format!("Bearer {}", self.node_master_key)) .send() .await - .map_err(|e| NodeError::NetworkError(format!("fetch failed: {}", e)))?; + .map_err(|e| NodeError::NetworkError(format!("fetch failed: {e}")))?; if !response.status().is_success() { let status = response.status(); @@ -269,12 +269,11 @@ impl NodeClient for HttpNodeClient { let json: Value = response .json() .await - .map_err(|e| NodeError::NetworkError(format!("parse failed: {}", e)))?; + .map_err(|e| NodeError::NetworkError(format!("parse failed: {e}")))?; let results = json .get("results") - .and_then(|v| v.as_array()) - .map(|v| v.clone()) + .and_then(|v| v.as_array()).cloned() .unwrap_or_default(); let total = json.get("total").and_then(|v| v.as_u64()).unwrap_or(0); @@ -306,7 +305,7 @@ impl NodeClient for HttpNodeClient { .json(&request.body) .send() .await - .map_err(|e| NodeError::NetworkError(format!("search failed: {}", e)))?; + .map_err(|e| NodeError::NetworkError(format!("search failed: {e}")))?; if !response.status().is_success() { let status = response.status(); @@ -323,7 +322,7 @@ impl NodeClient for HttpNodeClient { response .json() .await - .map_err(|e| NodeError::NetworkError(format!("parse response failed: {}", e))) + .map_err(|e| NodeError::NetworkError(format!("parse response failed: {e}"))) } async fn preflight_node( @@ -350,7 +349,7 @@ impl NodeClient for HttpNodeClient { .header("Authorization", format!("Bearer {}", self.node_master_key)) .send() .await - .map_err(|e| NodeError::NetworkError(format!("preflight failed: {}", e)))?; + .map_err(|e| NodeError::NetworkError(format!("preflight failed: {e}")))?; if !response.status().is_success() { let status = response.status(); @@ -367,7 +366,7 @@ impl NodeClient for HttpNodeClient { let json: Value = response .json() .await - .map_err(|e| NodeError::NetworkError(format!("parse response failed: {}", e)))?; + .map_err(|e| NodeError::NetworkError(format!("parse response failed: {e}")))?; let total_docs = json.get("total").and_then(|v| v.as_u64()).unwrap_or(0); @@ -412,7 +411,7 @@ impl NodeClient for HttpNodeClient { .json(&body) .send() .await - .map_err(|e| NodeError::NetworkError(format!("delete failed: {}", e)))?; + .map_err(|e| NodeError::NetworkError(format!("delete failed: {e}")))?; if !response.status().is_success() { let status = response.status(); @@ -429,7 +428,7 @@ impl NodeClient for HttpNodeClient { let json: Value = response .json() .await - .map_err(|e| NodeError::NetworkError(format!("parse response failed: {}", e)))?; + .map_err(|e| NodeError::NetworkError(format!("parse response failed: {e}")))?; Ok(crate::scatter::DeleteResponse { success: json @@ -658,7 +657,7 @@ impl AntiEntropyWorker { } Err(e) => { error!(scope = %scope, error = %e, "spawn_blocking task failed"); - return Err(format!("spawn_blocking task failed: {}", e)); + return Err(format!("spawn_blocking task failed: {e}")); } } } @@ -710,7 +709,7 @@ impl AntiEntropyWorker { Ok(()) } - Err(e) => Err(format!("anti-entropy pass failed: {}", e)), + Err(e) => Err(format!("anti-entropy pass failed: {e}")), } } } diff --git a/crates/miroir-core/src/rebalancer_worker/mod.rs b/crates/miroir-core/src/rebalancer_worker/mod.rs index 9fd5a85..9ee1ce3 100644 --- a/crates/miroir-core/src/rebalancer_worker/mod.rs +++ b/crates/miroir-core/src/rebalancer_worker/mod.rs @@ -1887,15 +1887,15 @@ mod tests { #[tokio::test] async fn test_compute_affected_shards_for_add() { let topo = Arc::new(RwLock::new(test_topology())); - let config = RebalancerWorkerConfig::default(); + let _config = RebalancerWorkerConfig::default(); // Create a mock task store (in-memory for testing) // Note: This would need a proper mock TaskStore implementation // For now, we'll skip the full integration test // Test that adding a node to group 0 affects some shards - let new_node_id = "node-new"; - let replica_group = 0; + let _new_node_id = "node-new"; + let _replica_group = 0; // We'd need to instantiate the worker with a proper mock task store // This is a placeholder for the actual test diff --git a/crates/miroir-core/src/replica_selection.rs b/crates/miroir-core/src/replica_selection.rs index e6f2da5..343cd31 100644 --- a/crates/miroir-core/src/replica_selection.rs +++ b/crates/miroir-core/src/replica_selection.rs @@ -8,7 +8,7 @@ use rand::prelude::*; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::Arc; -use std::time::{Duration, Instant}; +use std::time::Instant; use tokio::sync::RwLock; /// Replica selection strategy. @@ -270,7 +270,7 @@ impl ReplicaSelector { /// Round-robin selection. async fn select_round_robin(&self, candidates: &[NodeId], group_id: u64) -> Option { - let key = format!("group_{}", group_id); + let key = format!("group_{group_id}"); let mut counter = self.rr_counter.write().await; let idx = *counter.entry(key.clone()).or_insert(0) as usize % candidates.len(); *counter.get_mut(&key).unwrap() += 1; diff --git a/crates/miroir-core/src/reshard.rs b/crates/miroir-core/src/reshard.rs index a55febb..6580455 100644 --- a/crates/miroir-core/src/reshard.rs +++ b/crates/miroir-core/src/reshard.rs @@ -10,7 +10,7 @@ pub mod executor; -use crate::mode_b_coordinator::{ModeBOpLeader, PhaseState}; +use crate::mode_b_coordinator::ModeBOpLeader; use crate::router::{assign_shard_in_group, shard_for_key}; use crate::topology::{Group, NodeId}; use serde::{Deserialize, Serialize}; @@ -778,7 +778,7 @@ impl ReshardOperation { /// Create a new resharding operation. pub fn new(index_uid: String, old_shards: u32, target_shards: u32) -> Self { let id = format!("reshard-{}-{}", index_uid, uuid::Uuid::new_v4()); - let shadow_index = format!("{}__reshard_{}", index_uid, target_shards); + let shadow_index = format!("{index_uid}__reshard_{target_shards}"); let now = millis_now(); Self { id, @@ -897,8 +897,7 @@ impl ReshardingRegistry { ) -> Result<(), String> { if self.active_operations.contains_key(&index_uid) { return Err(format!( - "Resharding already in progress for index '{}'", - index_uid + "Resharding already in progress for index '{index_uid}'" )); } tracing::info!( @@ -923,7 +922,7 @@ impl ReshardingRegistry { let op = self .active_operations .get_mut(index_uid) - .ok_or_else(|| format!("No resharding operation for index '{}'", index_uid))?; + .ok_or_else(|| format!("No resharding operation for index '{index_uid}'"))?; op.phase = new_phase; tracing::info!( index_uid = %index_uid, @@ -936,7 +935,7 @@ impl ReshardingRegistry { /// Remove a completed resharding operation. pub fn remove(&mut self, index_uid: &str) -> Result<(), String> { if self.active_operations.remove(index_uid).is_none() { - return Err(format!("No resharding operation for index '{}'", index_uid)); + return Err(format!("No resharding operation for index '{index_uid}'")); } tracing::info!( index_uid = %index_uid, @@ -1023,8 +1022,8 @@ impl ReshardCoordinator { target_shards: u32, pod_id: String, ) -> Self { - let scope = format!("reshard:{}", index_uid); - let shadow_index = format!("{}__reshard_{}", index_uid, target_shards); + let scope = format!("reshard:{index_uid}"); + let shadow_index = format!("{index_uid}__reshard_{target_shards}"); let extra_state = ReshardExtraState { index_uid, @@ -1222,9 +1221,9 @@ impl ReshardRegistry { let op = self .operations .get(id) - .ok_or_else(|| format!("Operation '{}' not found", id))?; + .ok_or_else(|| format!("Operation '{id}' not found"))?; if !op.is_terminal() { - return Err(format!("Operation '{}' is not in a terminal state", id)); + return Err(format!("Operation '{id}' is not in a terminal state")); } self.index_ops.remove(&op.index_uid); Ok(()) @@ -1312,7 +1311,7 @@ pub async fn shadow_create_phase( master_key: &str, primary_key: Option, ) -> Result { - let shadow_index = format!("{}__reshard_{}", live_index_uid, target_shards); + let shadow_index = format!("{live_index_uid}__reshard_{target_shards}"); tracing::info!( live_index = %live_index_uid, @@ -1327,7 +1326,7 @@ pub async fn shadow_create_phase( .build() .map_err(|e| ShadowCreateError::NodeCreationFailed { node: "client".to_string(), - error: format!("failed to create HTTP client: {}", e), + error: format!("failed to create HTTP client: {e}"), })?; // Step 1: Create shadow index on every node sequentially @@ -1358,8 +1357,7 @@ pub async fn shadow_create_phase( return Err(match e { ShadowCreateError::IndexAlreadyExists(_) => e, other => ShadowCreateError::RollbackRequired(format!( - "creation failed on {}: {}", - address, other + "creation failed on {address}: {other}" )), }); } @@ -1387,8 +1385,7 @@ pub async fn shadow_create_phase( Err(e) => { rollback_shadow_index(&client, &shadow_index, &created_on, master_key).await; return Err(ShadowCreateError::SettingsBroadcastFailed(format!( - "failed to fetch live index settings: {}", - e + "failed to fetch live index settings: {e}" ))); } }; @@ -1419,8 +1416,7 @@ pub async fn shadow_create_phase( // Settings broadcast failed - rollback shadow index creation rollback_shadow_index(&client, &shadow_index, &created_on, master_key).await; return Err(ShadowCreateError::SettingsBroadcastFailed(format!( - "two-phase broadcast failed: {}", - e + "two-phase broadcast failed: {e}" ))); } }; @@ -1444,13 +1440,13 @@ async fn create_index_on_node( ) -> Result, ShadowCreateError> { let response = client .post(url) - .header("Authorization", format!("Bearer {}", master_key)) + .header("Authorization", format!("Bearer {master_key}")) .json(body) .send() .await .map_err(|e| ShadowCreateError::NodeCreationFailed { node: address.to_string(), - error: format!("request failed: {}", e), + error: format!("request failed: {e}"), })?; let status = response.status(); @@ -1459,7 +1455,7 @@ async fn create_index_on_node( .await .map_err(|e| ShadowCreateError::NodeCreationFailed { node: address.to_string(), - error: format!("failed to read response: {}", e), + error: format!("failed to read response: {e}"), })?; if status.as_u16() == 409 { @@ -1497,22 +1493,22 @@ async fn fetch_index_settings( let response = client .get(&url) - .header("Authorization", format!("Bearer {}", master_key)) + .header("Authorization", format!("Bearer {master_key}")) .send() .await - .map_err(|e| format!("request failed: {}", e))?; + .map_err(|e| format!("request failed: {e}"))?; let status = response.status(); let body_text = response .text() .await - .map_err(|e| format!("failed to read response: {}", e))?; + .map_err(|e| format!("failed to read response: {e}"))?; if !status.is_success() { return Err(format!("HTTP {}: {}", status.as_u16(), body_text)); } - serde_json::from_str(&body_text).map_err(|e| format!("failed to parse settings JSON: {}", e)) + serde_json::from_str(&body_text).map_err(|e| format!("failed to parse settings JSON: {e}")) } /// Ensure `_miroir_shard` is in filterableAttributes. @@ -1560,7 +1556,7 @@ async fn two_phase_broadcast_settings( ); let result = client .patch(&url) - .header("Authorization", format!("Bearer {}", key)) + .header("Authorization", format!("Bearer {key}")) .json(&settings) .send() .await; @@ -1578,7 +1574,7 @@ async fn two_phase_broadcast_settings( let _text = resp.text().await.unwrap_or_default(); Err(format!("{}: HTTP {}", address, status.as_u16())) } - Err(e) => Err(format!("{}: {}", address, e)), + Err(e) => Err(format!("{address}: {e}")), } } }) @@ -1598,7 +1594,7 @@ async fn two_phase_broadcast_settings( node_task_uids.push((address, 0)); } Err(e) => { - return Err(format!("Phase 1 propose failed: {}", e)); + return Err(format!("Phase 1 propose failed: {e}")); } } } @@ -1619,7 +1615,7 @@ async fn two_phase_broadcast_settings( ); let result = client .get(&url) - .header("Authorization", format!("Bearer {}", key)) + .header("Authorization", format!("Bearer {key}")) .send() .await; @@ -1630,11 +1626,11 @@ async fn two_phase_broadcast_settings( let hash = crate::settings::fingerprint_settings(&settings); Ok((address, hash)) } else { - Err(format!("{}: failed to parse settings", address)) + Err(format!("{address}: failed to parse settings")) } } Ok(resp) => Err(format!("{}: HTTP {}", address, resp.status().as_u16())), - Err(e) => Err(format!("{}: {}", address, e)), + Err(e) => Err(format!("{address}: {e}")), } } }) @@ -1653,14 +1649,13 @@ async fn two_phase_broadcast_settings( Ok((address, hash)) => { if hash != expected_fingerprint { return Err(format!( - "Phase 2 verify failed: hash mismatch on {}", - address + "Phase 2 verify failed: hash mismatch on {address}" )); } node_hashes.insert(address, hash); } Err(e) => { - return Err(format!("Phase 2 verify failed: {}", e)); + return Err(format!("Phase 2 verify failed: {e}")); } } } @@ -1689,7 +1684,7 @@ async fn rollback_shadow_index( match client .delete(&url) - .header("Authorization", format!("Bearer {}", master_key)) + .header("Authorization", format!("Bearer {master_key}")) .send() .await { @@ -2557,7 +2552,7 @@ pub async fn verify_phase( let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(30)) .build() - .map_err(|e| VerifyPhaseError::NodeFetchFailed(format!("HTTP client: {}", e)))?; + .map_err(|e| VerifyPhaseError::NodeFetchFailed(format!("HTTP client: {e}")))?; // Use the same node for all scans (first in list) - documents are identical // across replicas within the same shard due to RF replication @@ -2770,16 +2765,16 @@ async fn scan_shard_to_pk_buckets( let response = client .get(&url) - .header("Authorization", format!("Bearer {}", master_key)) + .header("Authorization", format!("Bearer {master_key}")) .send() .await - .map_err(|e| format!("request failed: {}", e))?; + .map_err(|e| format!("request failed: {e}"))?; let status = response.status(); let body_text = response .text() .await - .map_err(|e| format!("failed to read response: {}", e))?; + .map_err(|e| format!("failed to read response: {e}"))?; if !status.is_success() { return Err(format!("HTTP {}: {}", status.as_u16(), body_text)); @@ -2787,12 +2782,12 @@ async fn scan_shard_to_pk_buckets( // Parse response let docs_json: serde_json::Value = - serde_json::from_str(&body_text).map_err(|e| format!("JSON parse: {}", e))?; + serde_json::from_str(&body_text).map_err(|e| format!("JSON parse: {e}"))?; let results = docs_json .get("results") .and_then(|v| v.as_array()) - .ok_or_else(|| format!("missing results array"))?; + .ok_or_else(|| "missing results array".to_string())?; if results.is_empty() { break; // No more documents @@ -2803,7 +2798,7 @@ async fn scan_shard_to_pk_buckets( let pk_value = doc.get(primary_key).or(doc.get("id")).or(doc.get("_id")); let primary_key = pk_value .and_then(|v| v.as_str()) - .ok_or_else(|| format!("document missing primary key field"))?; + .ok_or_else(|| "document missing primary key field".to_string())?; // Compute content hash (reuse anti-entropy logic) let content_hash = compute_content_hash_for_verify(doc)?; @@ -2838,9 +2833,9 @@ fn compute_content_hash_for_verify(document: &serde_json::Value) -> Result = obj.iter().collect(); - serde_json::to_string(&sorted).map_err(|e| format!("JSON serialize: {}", e))? + serde_json::to_string(&sorted).map_err(|e| format!("JSON serialize: {e}"))? } else { - serde_json::to_string(&canonical).map_err(|e| format!("JSON serialize: {}", e))? + serde_json::to_string(&canonical).map_err(|e| format!("JSON serialize: {e}"))? }; // Hash using xxh3 @@ -2923,7 +2918,7 @@ pub async fn alias_swap_phase( // Step 1: Get the current alias state to capture old_target for rollback info let existing = task_store .get_alias(alias_name) - .map_err(|e| AliasSwapError::LookupFailed(format!("{}", e)))? + .map_err(|e| AliasSwapError::LookupFailed(format!("{e}")))? .ok_or_else(|| AliasSwapError::AliasNotFound(alias_name.to_string()))?; if existing.kind != "single" { @@ -2944,7 +2939,7 @@ pub async fn alias_swap_phase( // Step 2: Perform the atomic alias flip via task store let flipped = task_store .flip_alias(alias_name, new_target_uid, history_retention) - .map_err(|e| AliasSwapError::FlipFailed(format!("{}", e)))?; + .map_err(|e| AliasSwapError::FlipFailed(format!("{e}")))?; if !flipped { return Err(AliasSwapError::FlipFailed( @@ -2955,7 +2950,7 @@ pub async fn alias_swap_phase( // Step 3: Get the updated alias to capture new version let updated = task_store .get_alias(alias_name) - .map_err(|e| AliasSwapError::LookupFailed(format!("{}", e)))? + .map_err(|e| AliasSwapError::LookupFailed(format!("{e}")))? .ok_or_else(|| AliasSwapError::LookupFailed("alias disappeared after flip".to_string()))?; let flipped_at = SystemTime::now() @@ -3291,7 +3286,7 @@ pub async fn backfill_phase( batch_size: usize, progress_callback: Option, ) -> Result { - use std::time::{SystemTime, UNIX_EPOCH}; + use std::time::SystemTime; let start_time = SystemTime::now(); tracing::info!( @@ -3306,7 +3301,7 @@ pub async fn backfill_phase( let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(30)) .build() - .map_err(|e| BackfillError::NodeFetchFailed(format!("HTTP client: {}", e)))?; + .map_err(|e| BackfillError::NodeFetchFailed(format!("HTTP client: {e}")))?; // Use the first node for all operations (documents are identical across replicas) let target_node = node_addresses @@ -3442,16 +3437,16 @@ async fn backfill_single_shard( let response = client .get(&url) - .header("Authorization", format!("Bearer {}", master_key)) + .header("Authorization", format!("Bearer {master_key}")) .send() .await - .map_err(|e| format!("fetch failed: {}", e))?; + .map_err(|e| format!("fetch failed: {e}"))?; let status = response.status(); let body_text = response .text() .await - .map_err(|e| format!("failed to read response: {}", e))?; + .map_err(|e| format!("failed to read response: {e}"))?; if !status.is_success() { return Err(format!("HTTP {}: {}", status.as_u16(), body_text)); @@ -3459,12 +3454,12 @@ async fn backfill_single_shard( // Parse response let docs_json: serde_json::Value = - serde_json::from_str(&body_text).map_err(|e| format!("JSON parse: {}", e))?; + serde_json::from_str(&body_text).map_err(|e| format!("JSON parse: {e}"))?; let results = docs_json .get("results") .and_then(|v| v.as_array()) - .ok_or_else(|| format!("missing results array"))?; + .ok_or_else(|| "missing results array".to_string())?; if results.is_empty() { break; // No more documents @@ -3479,7 +3474,7 @@ async fn backfill_single_shard( .or(doc.get("id")) .or(doc.get("_id")) .and_then(|v| v.as_str()) - .ok_or_else(|| format!("document missing primary key field: {}", primary_key))?; + .ok_or_else(|| format!("document missing primary key field: {primary_key}"))?; // Compute new shard assignment for shadow index let new_shard_id = crate::router::shard_for_key(pk_value, new_shards); @@ -3525,11 +3520,11 @@ async fn write_backfill_batch( let response = client .post(&url) - .header("Authorization", format!("Bearer {}", master_key)) + .header("Authorization", format!("Bearer {master_key}")) .json(documents) .send() .await - .map_err(|e| format!("request failed: {}", e))?; + .map_err(|e| format!("request failed: {e}"))?; let status = response.status(); let body_text = response.text().await.unwrap_or_default(); @@ -3625,8 +3620,7 @@ pub async fn cleanup_phase( "retention period not yet reached, skipping cleanup" ); return Err(CleanupError::CleanupAborted(format!( - "retention period not reached: {} hours remaining", - remaining_hours + "retention period not reached: {remaining_hours} hours remaining" ))); } } else { @@ -3643,7 +3637,7 @@ pub async fn cleanup_phase( let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(30)) .build() - .map_err(|e| CleanupError::CleanupAborted(format!("HTTP client: {}", e)))?; + .map_err(|e| CleanupError::CleanupAborted(format!("HTTP client: {e}")))?; let mut nodes_deleted_from = Vec::new(); let mut errors = Vec::new(); @@ -3657,7 +3651,7 @@ pub async fn cleanup_phase( match client .delete(&url) - .header("Authorization", format!("Bearer {}", master_key)) + .header("Authorization", format!("Bearer {master_key}")) .send() .await { @@ -3869,7 +3863,7 @@ pub struct ReshardOrchestratorResult { pub async fn execute_reshard( config: ReshardOrchestratorConfig, ) -> Result { - use std::time::{SystemTime, UNIX_EPOCH}; + use std::time::SystemTime; let start_time = SystemTime::now(); let old_shards = config.target_shards / 2; // Assume doubling for now @@ -3901,7 +3895,7 @@ pub async fn execute_reshard( .await .map_err(|e| { // Phase 1 already handles rollback internally - format!("Phase 1 shadow create failed: {}", e) + format!("Phase 1 shadow create failed: {e}") })?; tracing::info!( @@ -3932,7 +3926,7 @@ pub async fn execute_reshard( // Rollback: delete shadow index tracing::error!(error = %e, "Phase 3 backfill failed, rolling back"); let _ = rollback_shadow_orchestrator(&shadow_index, &config); - format!("Phase 3 backfill failed: {}", e) + format!("Phase 3 backfill failed: {e}") })?; emit_phase( @@ -3963,7 +3957,7 @@ pub async fn execute_reshard( // Rollback: delete shadow index tracing::error!(error = %e, "Phase 4 verify failed, rolling back"); let _ = rollback_shadow_orchestrator(&shadow_index, &config); - format!("Phase 4 verify failed: {}", e) + format!("Phase 4 verify failed: {e}") })?; if !verify_result.passed { @@ -3991,7 +3985,7 @@ pub async fn execute_reshard( config.alias_history_retention, ) .await - .map_err(|e| format!("Phase 5 alias swap failed: {}", e))? + .map_err(|e| format!("Phase 5 alias swap failed: {e}"))? } else { // No task store - skip alias swap (for testing) tracing::warn!("no task store, skipping alias swap"); @@ -4051,7 +4045,7 @@ async fn rollback_shadow_orchestrator( let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(30)) .build() - .map_err(|e| format!("HTTP client: {}", e))?; + .map_err(|e| format!("HTTP client: {e}"))?; for address in &config.node_addresses { let url = format!("{}/indexes/{}", address.trim_end_matches('/'), shadow_index); diff --git a/crates/miroir-core/src/reshard/executor.rs b/crates/miroir-core/src/reshard/executor.rs index fe9f7ee..97e6231 100644 --- a/crates/miroir-core/src/reshard/executor.rs +++ b/crates/miroir-core/src/reshard/executor.rs @@ -315,9 +315,7 @@ impl ReshardExecutor { // Get a healthy node from topology for verification let topology = self.topology.read().await; let node = topology - .nodes() - .filter(|n| n.is_healthy()) - .next() + .nodes().find(|n| n.is_healthy()) .ok_or_else(|| { MiroirError::Topology("No healthy nodes available for verification".to_string()) })?; @@ -433,7 +431,7 @@ impl ReshardExecutor { let history_retention = 10; // Default history retention for rollback let flipped = task_store .flip_alias(&state.index_uid, shadow_name, history_retention) - .map_err(|e| MiroirError::AliasSwapFailed(format!("{}", e)))?; + .map_err(|e| MiroirError::AliasSwapFailed(format!("{e}")))?; if !flipped { return Err(MiroirError::AliasSwapFailed(format!( diff --git a/crates/miroir-core/src/reshard_chunking.rs b/crates/miroir-core/src/reshard_chunking.rs index 17c2caf..8b01879 100644 --- a/crates/miroir-core/src/reshard_chunking.rs +++ b/crates/miroir-core/src/reshard_chunking.rs @@ -3,7 +3,7 @@ //! Splits reshard backfill work by shard-id ranges. //! Each chunk can process a range of old shards independently. -use crate::mode_c_coordinator::{JobChunk, JobParams}; +use crate::mode_c_coordinator::JobChunk; /// Chunk specification for a reshard backfill. #[derive(Debug, Clone)] @@ -102,11 +102,11 @@ pub fn parse_reshard_chunk(chunk: &JobChunk) -> Result<(u32, u32), String> { let start_shard = chunk .start .parse::() - .map_err(|e| format!("invalid start shard: {}", e))?; + .map_err(|e| format!("invalid start shard: {e}"))?; let end_shard = chunk .end .parse::() - .map_err(|e| format!("invalid end shard: {}", e))?; + .map_err(|e| format!("invalid end shard: {e}"))?; Ok((start_shard, end_shard)) } diff --git a/crates/miroir-core/src/scatter.rs b/crates/miroir-core/src/scatter.rs index 4d577ec..ba7165b 100644 --- a/crates/miroir-core/src/scatter.rs +++ b/crates/miroir-core/src/scatter.rs @@ -12,7 +12,7 @@ use serde_json::Value; use std::collections::HashMap; use std::sync::Arc; use std::time::Instant; -use tokio::time::{sleep, Duration}; +use tokio::time::Duration; use tracing::{info_span, instrument, Instrument}; /// Scatter plan: the exact shard→node mapping for a search query. diff --git a/crates/miroir-core/src/scoped_key_rotation.rs b/crates/miroir-core/src/scoped_key_rotation.rs index c9828e2..aa9fbe0 100644 --- a/crates/miroir-core/src/scoped_key_rotation.rs +++ b/crates/miroir-core/src/scoped_key_rotation.rs @@ -4,7 +4,7 @@ //! Uses leader-only singleton coordination (plan §14.5) to ensure only one pod //! performs key rotation for a given index at a time. -use crate::error::{MiroirError, Result}; +use crate::error::Result; use crate::leader_election::LeaderElection; use crate::mode_b_coordinator::ModeBOpLeader; use crate::task_store::TaskStore; @@ -94,7 +94,7 @@ impl ScopedKeyRotationCoordinator { drain_target_s: u64, pod_id: String, ) -> Self { - let scope = format!("search_ui_key_rotation:{}", index_uid); + let scope = format!("search_ui_key_rotation:{index_uid}"); let extra_state = ScopedKeyRotationExtraState { index_uid, diff --git a/crates/miroir-core/src/settings.rs b/crates/miroir-core/src/settings.rs index 6f61a8b..f3c88c7 100644 --- a/crates/miroir-core/src/settings.rs +++ b/crates/miroir-core/src/settings.rs @@ -140,8 +140,7 @@ impl SettingsBroadcast { if in_flight.contains_key(&index) { return Err(MiroirError::InvalidState(format!( - "settings broadcast already in flight for index '{}'", - index + "settings broadcast already in flight for index '{index}'" ))); } @@ -176,7 +175,7 @@ impl SettingsBroadcast { let mut in_flight = self.in_flight.write().await; let status = in_flight .get_mut(index) - .ok_or_else(|| MiroirError::NotFound(format!("index '{}'", index)))?; + .ok_or_else(|| MiroirError::NotFound(format!("index '{index}'")))?; if status.phase != BroadcastPhase::Propose { return Err(MiroirError::InvalidState("expected Propose phase".into())); @@ -200,7 +199,7 @@ impl SettingsBroadcast { let mut in_flight = self.in_flight.write().await; let status = in_flight .get_mut(index) - .ok_or_else(|| MiroirError::NotFound(format!("index '{}'", index)))?; + .ok_or_else(|| MiroirError::NotFound(format!("index '{index}'")))?; if status.phase != BroadcastPhase::Verify { return Err(MiroirError::InvalidState("expected Verify phase".into())); @@ -212,8 +211,7 @@ impl SettingsBroadcast { for (node, hash) in &node_hashes { if hash != expected_fingerprint { status.error = Some(format!( - "node '{}' hash mismatch: expected {}, got {}", - node, expected_fingerprint, hash + "node '{node}' hash mismatch: expected {expected_fingerprint}, got {hash}" )); status.verify_ok = false; return Err(MiroirError::SettingsDivergence); @@ -231,7 +229,7 @@ impl SettingsBroadcast { let mut in_flight = self.in_flight.write().await; let status = in_flight .get_mut(index) - .ok_or_else(|| MiroirError::NotFound(format!("index '{}'", index)))?; + .ok_or_else(|| MiroirError::NotFound(format!("index '{index}'")))?; if status.phase != BroadcastPhase::Verify { return Err(MiroirError::InvalidState("expected Verify phase".into())); @@ -271,7 +269,7 @@ impl SettingsBroadcast { let mut in_flight = self.in_flight.write().await; in_flight .remove(index) - .ok_or_else(|| MiroirError::NotFound(format!("index '{}'", index)))?; + .ok_or_else(|| MiroirError::NotFound(format!("index '{index}'")))?; Ok(()) } @@ -283,7 +281,7 @@ impl SettingsBroadcast { } in_flight .remove(index) - .ok_or_else(|| MiroirError::NotFound(format!("index '{}'", index)))?; + .ok_or_else(|| MiroirError::NotFound(format!("index '{index}'")))?; Ok(()) } @@ -336,7 +334,7 @@ impl SettingsBroadcastCoordinator { index_uid: String, pod_id: String, ) -> Self { - let scope = format!("settings_broadcast:{}", index_uid); + let scope = format!("settings_broadcast:{index_uid}"); let extra_state = SettingsBroadcastExtraState { index_uid, @@ -396,7 +394,7 @@ impl SettingsBroadcastCoordinator { /// Should be called after each phase boundary so that a new leader can /// resume from the last committed phase. pub async fn advance_phase(&mut self, new_phase: BroadcastPhase) -> Result<()> { - let phase_name = format!("{:?}", new_phase); + let phase_name = format!("{new_phase:?}"); self.leader.persist_phase(phase_name.to_lowercase()).await } diff --git a/crates/miroir-core/src/shadow.rs b/crates/miroir-core/src/shadow.rs index 076d334..1a2dfcd 100644 --- a/crates/miroir-core/src/shadow.rs +++ b/crates/miroir-core/src/shadow.rs @@ -189,7 +189,7 @@ impl ShadowManager { // Build request with optional API key let mut request_builder = self.client.post(&url).json(request_body); if let Some(key) = &api_key { - request_builder = request_builder.header("Authorization", format!("Bearer {}", key)); + request_builder = request_builder.header("Authorization", format!("Bearer {key}")); } // Send shadow request with timeout @@ -298,7 +298,7 @@ impl ShadowManager { use sha2::{Digest, Sha256}; let json = serde_json::to_string(body).unwrap_or_default(); let hash = Sha256::digest(json.as_bytes()); - format!("{:x}", hash) + format!("{hash:x}") } /// Compute symmetric diff and Kendall tau correlation. diff --git a/crates/miroir-core/src/task_registry.rs b/crates/miroir-core/src/task_registry.rs index c56aca9..44a10af 100644 --- a/crates/miroir-core/src/task_registry.rs +++ b/crates/miroir-core/src/task_registry.rs @@ -60,7 +60,7 @@ impl NodePoller for ClientNodePoller { .get_task_status(node_id, address, &req) .await .map(|resp| resp.to_node_status()) - .map_err(|e| format!("{:?}", e)) + .map_err(|e| format!("{e:?}")) } } @@ -88,7 +88,7 @@ impl InMemoryTaskRegistry { let miroir_id = format!("mtask-{}", Uuid::new_v4()); let created_at = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) - .map_err(|e| MiroirError::Task(format!("clock error: {}", e)))? + .map_err(|e| MiroirError::Task(format!("clock error: {e}")))? .as_millis() as u64; let mut tasks = HashMap::new(); @@ -144,7 +144,7 @@ impl InMemoryTaskRegistry { let miroir_id = format!("mtask-{}", Uuid::new_v4()); let created_at = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) - .map_err(|e| MiroirError::Task(format!("clock error: {}", e)))? + .map_err(|e| MiroirError::Task(format!("clock error: {e}")))? .as_millis() as u64; let mut tasks = HashMap::new(); @@ -218,7 +218,7 @@ impl InMemoryTaskRegistry { task.started_at = Some( std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) - .map_err(|e| MiroirError::Task(format!("clock error: {}", e)))? + .map_err(|e| MiroirError::Task(format!("clock error: {e}")))? .as_millis() as u64, ); } @@ -231,7 +231,7 @@ impl InMemoryTaskRegistry { task.finished_at = Some( std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) - .map_err(|e| MiroirError::Task(format!("clock error: {}", e)))? + .map_err(|e| MiroirError::Task(format!("clock error: {e}")))? .as_millis() as u64, ); } @@ -276,7 +276,7 @@ impl InMemoryTaskRegistry { let mut any_failed = false; let mut all_terminal = true; - for (_node_id, node_task) in &task.node_tasks { + for node_task in task.node_tasks.values() { match node_task.status { NodeTaskStatus::Enqueued | NodeTaskStatus::Processing => { all_terminal = false; @@ -341,7 +341,7 @@ impl InMemoryTaskRegistry { // Check each node task's status let mut all_terminal = true; - for (_node_id, node_task) in &task.node_tasks { + for node_task in task.node_tasks.values() { match node_task.status { NodeTaskStatus::Enqueued | NodeTaskStatus::Processing => { all_terminal = false; @@ -356,7 +356,7 @@ impl InMemoryTaskRegistry { // Simulate completion for testing let mut tasks = self.tasks.write().await; if let Some(t) = tasks.get_mut(miroir_id) { - for (_node_id, node_task) in &mut t.node_tasks { + for node_task in t.node_tasks.values_mut() { if matches!( node_task.status, NodeTaskStatus::Enqueued | NodeTaskStatus::Processing @@ -367,7 +367,7 @@ impl InMemoryTaskRegistry { // Update overall status let mut all_succeeded = true; let mut any_failed = false; - for (_node_id, node_task) in &t.node_tasks { + for node_task in t.node_tasks.values() { match node_task.status { NodeTaskStatus::Succeeded => {} NodeTaskStatus::Failed => any_failed = true, @@ -447,7 +447,7 @@ impl InMemoryTaskRegistry { if let Some(t) = tasks.get_mut(miroir_id) { let mut all_succeeded = true; let mut any_failed = false; - for (_node_id, node_task) in &t.node_tasks { + for node_task in t.node_tasks.values() { match node_task.status { NodeTaskStatus::Succeeded => {} NodeTaskStatus::Failed => any_failed = true, @@ -486,7 +486,7 @@ impl InMemoryTaskRegistry { // For now, use a mock address - in production, this would come from the topology let address = format!("http://{}", node_id.as_str()); - match poller.poll_node_task(&node_id, &address, *task_uid).await { + match poller.poll_node_task(node_id, &address, *task_uid).await { Ok(status) => { node_statuses.insert(node_id.clone(), status); } @@ -549,12 +549,12 @@ impl InMemoryTaskRegistry { // Apply index_uid filter if let Some(index_uid) = &filter.index_uid { - result.retain(|t| t.index_uid.as_ref().map_or(false, |uid| uid == index_uid)); + result.retain(|t| t.index_uid.as_ref() == Some(index_uid)); } // Apply task_type filter if let Some(task_type) = &filter.task_type { - result.retain(|t| t.task_type.as_ref().map_or(false, |ty| ty == task_type)); + result.retain(|t| t.task_type.as_ref() == Some(task_type)); } // Apply offset @@ -651,7 +651,7 @@ impl crate::task::TaskRegistry for InMemoryTaskRegistry { let registry = self.clone(); tokio::task::block_in_place(|| { let rt = tokio::runtime::Handle::try_current() - .map_err(|e| MiroirError::Task(format!("runtime error: {}", e)))?; + .map_err(|e| MiroirError::Task(format!("runtime error: {e}")))?; rt.block_on(async move { registry .register_async_with_metadata(node_tasks, index_uid, task_type) @@ -665,7 +665,7 @@ impl crate::task::TaskRegistry for InMemoryTaskRegistry { let miroir_id = miroir_id.to_string(); tokio::task::block_in_place(|| { let rt = tokio::runtime::Handle::try_current() - .map_err(|e| MiroirError::Task(format!("runtime error: {}", e)))?; + .map_err(|e| MiroirError::Task(format!("runtime error: {e}")))?; rt.block_on(async move { Ok(registry.get_async(&miroir_id).await) }) }) } @@ -675,7 +675,7 @@ impl crate::task::TaskRegistry for InMemoryTaskRegistry { let miroir_id = miroir_id.to_string(); tokio::task::block_in_place(|| { let rt = tokio::runtime::Handle::try_current() - .map_err(|e| MiroirError::Task(format!("runtime error: {}", e)))?; + .map_err(|e| MiroirError::Task(format!("runtime error: {e}")))?; rt.block_on(async move { let mut tasks = registry.tasks.write().await; if let Some(task) = tasks.get_mut(&miroir_id) { @@ -697,7 +697,7 @@ impl crate::task::TaskRegistry for InMemoryTaskRegistry { let node_id = node_id.to_string(); tokio::task::block_in_place(|| { let rt = tokio::runtime::Handle::try_current() - .map_err(|e| MiroirError::Task(format!("runtime error: {}", e)))?; + .map_err(|e| MiroirError::Task(format!("runtime error: {e}")))?; rt.block_on(async move { let mut tasks = registry.tasks.write().await; if let Some(task) = tasks.get_mut(&miroir_id) { @@ -714,7 +714,7 @@ impl crate::task::TaskRegistry for InMemoryTaskRegistry { let registry = self.clone(); tokio::task::block_in_place(|| { let rt = tokio::runtime::Handle::try_current() - .map_err(|e| MiroirError::Task(format!("runtime error: {}", e)))?; + .map_err(|e| MiroirError::Task(format!("runtime error: {e}")))?; rt.block_on(async move { registry.list_async(&filter).await }) }) } @@ -879,7 +879,7 @@ impl TaskRegistryImpl { let miroir_id = format!("mtask-{}", Uuid::new_v4()); let created_at = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) - .map_err(|e| MiroirError::Task(format!("clock error: {}", e)))? + .map_err(|e| MiroirError::Task(format!("clock error: {e}")))? .as_millis() as i64; let new_task = crate::task_store::NewTask { @@ -927,7 +927,7 @@ impl TaskRegistryImpl { let miroir_id = format!("mtask-{}", Uuid::new_v4()); let created_at = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) - .map_err(|e| MiroirError::Task(format!("clock error: {}", e)))? + .map_err(|e| MiroirError::Task(format!("clock error: {e}")))? .as_millis() as i64; let new_task = crate::task_store::NewTask { diff --git a/crates/miroir-core/src/task_store/redis.rs b/crates/miroir-core/src/task_store/redis.rs index 0494fe8..7982933 100644 --- a/crates/miroir-core/src/task_store/redis.rs +++ b/crates/miroir-core/src/task_store/redis.rs @@ -248,7 +248,7 @@ impl TaskStore for RedisTaskStore { fn migrate(&self) -> Result<()> { let manager = self.pool.manager.clone(); let key_prefix = self.key_prefix.clone(); - let version_key = format!("{}:schema_version", key_prefix); + let version_key = format!("{key_prefix}:schema_version"); self.block_on(async move { let mut conn = manager.lock().await; let current: Option = conn @@ -288,7 +288,7 @@ impl TaskStore for RedisTaskStore { let key_prefix = self.key_prefix.clone(); let task = task.clone(); let key = format!("{}:tasks:{}", key_prefix, task.miroir_id); - let index_key = format!("{}:tasks:_index", key_prefix); + let index_key = format!("{key_prefix}:tasks:_index"); let created_at_str = task.created_at.to_string(); self.block_on(async move { @@ -443,7 +443,7 @@ impl TaskStore for RedisTaskStore { let mut tasks = Vec::new(); for miroir_id in all_ids { - let key = format!("{}:tasks:{}", key_prefix, miroir_id); + let key = format!("{key_prefix}:tasks:{miroir_id}"); let fields: HashMap = conn .hgetall(&key) .await @@ -511,7 +511,7 @@ impl TaskStore for RedisTaskStore { let mut to_delete = Vec::new(); for miroir_id in all_ids.into_iter().take(batch_size as usize) { - let key = format!("{}:tasks:{}", key_prefix, miroir_id); + let key = format!("{key_prefix}:tasks:{miroir_id}"); // Use a pipeline to get both fields atomically let mut p = pipe(); @@ -536,7 +536,7 @@ impl TaskStore for RedisTaskStore { // Delete tasks and remove from index let mut pipe = pipe(); for miroir_id in &to_delete { - let key = format!("{}:tasks:{}", key_prefix, miroir_id); + let key = format!("{key_prefix}:tasks:{miroir_id}"); pipe.del(&key); pipe.srem(&index_key, miroir_id); } @@ -559,7 +559,7 @@ impl TaskStore for RedisTaskStore { self.block_on(async move { let mut conn = manager.lock().await; - let index_key = format!("{}:tasks:_index", key_prefix); + let index_key = format!("{key_prefix}:tasks:_index"); let all_ids: Vec = conn .smembers(&index_key) .await @@ -573,7 +573,7 @@ impl TaskStore for RedisTaskStore { if added >= limit { break; } - let key = format!("{}:tasks:{}", key_prefix, miroir_id); + let key = format!("{key_prefix}:tasks:{miroir_id}"); // Get created_at and status let mut p = pipe(); @@ -654,13 +654,13 @@ impl TaskStore for RedisTaskStore { fn delete_tasks_batch(&self, miroir_ids: &[&str]) -> Result { let pool = self.pool.clone(); let key_prefix = self.key_prefix.clone(); - let index_key = format!("{}:tasks:_index", key_prefix); + let index_key = format!("{key_prefix}:tasks:_index"); let ids: Vec = miroir_ids.iter().map(|s| s.to_string()).collect(); self.block_on(async move { let mut pipe = pipe(); for miroir_id in &ids { - let key = format!("{}:tasks:{}", key_prefix, miroir_id); + let key = format!("{key_prefix}:tasks:{miroir_id}"); pipe.del(&key); pipe.srem(&index_key, miroir_id); } @@ -698,15 +698,14 @@ impl TaskStore for RedisTaskStore { let index_uid = index_uid.to_string(); let node_id = node_id.to_string(); let key = format!( - "{}:node_settings_version:{}:{}", - key_prefix, index_uid, node_id + "{key_prefix}:node_settings_version:{index_uid}:{node_id}" ); - let index_key = format!("{}:node_settings_version:_index", key_prefix); + let index_key = format!("{key_prefix}:node_settings_version:_index"); self.block_on(async move { let version_str = version.to_string(); let updated_at_str = updated_at.to_string(); - let index_value = format!("{}:{}", index_uid, node_id); + let index_value = format!("{index_uid}:{node_id}"); let mut pipe = pipe(); pipe.hset_multiple( @@ -734,8 +733,7 @@ impl TaskStore for RedisTaskStore { let index_uid = index_uid.to_string(); let node_id = node_id.to_string(); let key = format!( - "{}:node_settings_version:{}:{}", - key_prefix, index_uid, node_id + "{key_prefix}:node_settings_version:{index_uid}:{node_id}" ); self.block_on(async move { @@ -768,7 +766,7 @@ impl TaskStore for RedisTaskStore { let target_uids_json = alias .target_uids .as_ref() - .map(|uids| serde_json::to_string(uids)) + .map(serde_json::to_string) .transpose()? .unwrap_or_default(); let history_json = serde_json::to_string(&alias.history)?; @@ -776,8 +774,8 @@ impl TaskStore for RedisTaskStore { let created_at_str = alias.created_at.to_string(); let current_uid = alias.current_uid.clone(); let has_target_uids = alias.target_uids.is_some(); - let key = format!("{}:aliases:{}", key_prefix, name); - let index_key = format!("{}:aliases:_index", key_prefix); + let key = format!("{key_prefix}:aliases:{name}"); + let index_key = format!("{key_prefix}:aliases:_index"); self.block_on(async move { let mut pipe = pipe(); @@ -807,7 +805,7 @@ impl TaskStore for RedisTaskStore { let manager = self.pool.manager.clone(); let key_prefix = self.key_prefix.clone(); let name = name.to_string(); - let key = format!("{}:aliases:{}", key_prefix, name); + let key = format!("{key_prefix}:aliases:{name}"); self.block_on(async move { let mut conn = manager.lock().await; @@ -850,7 +848,7 @@ impl TaskStore for RedisTaskStore { let key_prefix = self.key_prefix.clone(); let name = name.to_string(); let new_uid = new_uid.to_string(); - let key = format!("{}:aliases:{}", key_prefix, name); + let key = format!("{key_prefix}:aliases:{name}"); self.block_on(async move { let mut conn = manager.lock().await; @@ -897,8 +895,8 @@ impl TaskStore for RedisTaskStore { let pool = self.pool.clone(); let key_prefix = self.key_prefix.clone(); let name = name.to_string(); - let key = format!("{}:aliases:{}", key_prefix, name); - let index_key = format!("{}:aliases:_index", key_prefix); + let key = format!("{key_prefix}:aliases:{name}"); + let index_key = format!("{key_prefix}:aliases:_index"); self.block_on(async move { let mut conn = pool.manager.lock().await; @@ -924,7 +922,7 @@ impl TaskStore for RedisTaskStore { fn list_aliases(&self) -> Result> { let pool = self.pool.clone(); let key_prefix = self.key_prefix.clone(); - let index_key = format!("{}:aliases:_index", key_prefix); + let index_key = format!("{key_prefix}:aliases:_index"); self.block_on(async move { let mut conn = pool.manager.lock().await; @@ -937,7 +935,7 @@ impl TaskStore for RedisTaskStore { let mut result = Vec::new(); for name in names { - let key = format!("{}:aliases:{}", key_prefix, name); + let key = format!("{key_prefix}:aliases:{name}"); let fields: HashMap = conn .hgetall(&key) .await @@ -991,7 +989,7 @@ impl TaskStore for RedisTaskStore { let manager = self.pool.manager.clone(); let key_prefix = self.key_prefix.clone(); let session_id = session_id.to_string(); - let key = format!("{}:session:{}", key_prefix, session_id); + let key = format!("{key_prefix}:session:{session_id}"); self.block_on(async move { let mut conn = manager.lock().await; @@ -1052,7 +1050,7 @@ impl TaskStore for RedisTaskStore { let manager = self.pool.manager.clone(); let key_prefix = self.key_prefix.clone(); let key = key.to_string(); - let redis_key = format!("{}:idemp:{}", key_prefix, key); + let redis_key = format!("{key_prefix}:idemp:{key}"); self.block_on(async move { let mut conn = manager.lock().await; @@ -1090,8 +1088,8 @@ impl TaskStore for RedisTaskStore { let key_prefix = self.key_prefix.clone(); let job = job.clone(); let key = format!("{}:jobs:{}", key_prefix, job.id); - let queued_key = format!("{}:jobs:_queued", key_prefix); - let index_key = format!("{}:jobs:_index", key_prefix); + let queued_key = format!("{key_prefix}:jobs:_queued"); + let index_key = format!("{key_prefix}:jobs:_index"); self.block_on(async move { let mut pipe = pipe(); @@ -1139,7 +1137,7 @@ impl TaskStore for RedisTaskStore { let manager = self.pool.manager.clone(); let key_prefix = self.key_prefix.clone(); let id = id.to_string(); - let key = format!("{}:jobs:{}", key_prefix, id); + let key = format!("{key_prefix}:jobs:{id}"); self.block_on(async move { let mut conn = manager.lock().await; @@ -1173,8 +1171,8 @@ impl TaskStore for RedisTaskStore { let key_prefix = self.key_prefix.clone(); let id = id.to_string(); let claimed_by = claimed_by.to_string(); - let key = format!("{}:jobs:{}", key_prefix, id); - let queued_key = format!("{}:jobs:_queued", key_prefix); + let key = format!("{key_prefix}:jobs:{id}"); + let queued_key = format!("{key_prefix}:jobs:_queued"); self.block_on(async move { let mut conn = pool.manager.lock().await; @@ -1206,7 +1204,7 @@ impl TaskStore for RedisTaskStore { let id = id.to_string(); let state = state.to_string(); let progress = progress.to_string(); - let key = format!("{}:jobs:{}", key_prefix, id); + let key = format!("{key_prefix}:jobs:{id}"); self.block_on(async move { let mut conn = pool.manager.lock().await; @@ -1232,7 +1230,7 @@ impl TaskStore for RedisTaskStore { let manager = self.pool.manager.clone(); let key_prefix = self.key_prefix.clone(); let id = id.to_string(); - let key = format!("{}:jobs:{}", key_prefix, id); + let key = format!("{key_prefix}:jobs:{id}"); self.block_on(async move { let mut conn = manager.lock().await; @@ -1264,14 +1262,14 @@ impl TaskStore for RedisTaskStore { let mut conn = manager.lock().await; // Use the _index set for O(cardinality) iteration (no SCAN). - let index_key = format!("{}:jobs:_index", key_prefix); + let index_key = format!("{key_prefix}:jobs:_index"); let ids: Vec = conn .smembers(&index_key) .await .map_err(|e| MiroirError::Redis(e.to_string()))?; for id in ids { - let key = format!("{}:jobs:{}", key_prefix, id); + let key = format!("{key_prefix}:jobs:{id}"); let fields: HashMap = conn .hgetall(&key) .await @@ -1313,7 +1311,7 @@ impl TaskStore for RedisTaskStore { // For queued state, use the _queued set for O(1) count // This is used for HPA queue depth metric per plan §14.4 if state == "queued" { - let queued_key = format!("{}:jobs:_queued", key_prefix); + let queued_key = format!("{key_prefix}:jobs:_queued"); let count: u64 = conn .scard(&queued_key) .await @@ -1324,7 +1322,7 @@ impl TaskStore for RedisTaskStore { // For other states, iterate through _index and count by state // This is O(n) but acceptable for non-queued states which are // typically few (only actively running jobs) - let index_key = format!("{}:jobs:_index", key_prefix); + let index_key = format!("{key_prefix}:jobs:_index"); let ids: Vec = conn .smembers(&index_key) .await @@ -1332,7 +1330,7 @@ impl TaskStore for RedisTaskStore { let mut count = 0u64; for id in ids { - let key = format!("{}:jobs:{}", key_prefix, id); + let key = format!("{key_prefix}:jobs:{id}"); let job_state: Option = conn .hget(&key, "state") .await @@ -1355,14 +1353,14 @@ impl TaskStore for RedisTaskStore { let mut conn = manager.lock().await; // Use the _index set for O(cardinality) iteration - let index_key = format!("{}:jobs:_index", key_prefix); + let index_key = format!("{key_prefix}:jobs:_index"); let ids: Vec = conn .smembers(&index_key) .await .map_err(|e| MiroirError::Redis(e.to_string()))?; for id in ids { - let key = format!("{}:jobs:{}", key_prefix, id); + let key = format!("{key_prefix}:jobs:{id}"); let fields: HashMap = conn .hgetall(&key) .await @@ -1411,14 +1409,14 @@ impl TaskStore for RedisTaskStore { let mut conn = manager.lock().await; // Use the _index set for iteration - let index_key = format!("{}:jobs:_index", key_prefix); + let index_key = format!("{key_prefix}:jobs:_index"); let ids: Vec = conn .smembers(&index_key) .await .map_err(|e| MiroirError::Redis(e.to_string()))?; for id in ids { - let key = format!("{}:jobs:{}", key_prefix, id); + let key = format!("{key_prefix}:jobs:{id}"); let fields: HashMap = conn .hgetall(&key) .await @@ -1454,11 +1452,11 @@ impl TaskStore for RedisTaskStore { let id = id.to_string(); let state = state.to_string(); let progress = progress.to_string(); - let key = format!("{}:jobs:{}", key_prefix, id); - let queued_key = format!("{}:jobs:_queued", key_prefix); + let key = format!("{key_prefix}:jobs:{id}"); + let queued_key = format!("{key_prefix}:jobs:_queued"); self.block_on(async move { - let mut conn = pool.manager.lock().await; + let conn = pool.manager.lock().await; let mut pipe = pipe(); pipe.hset(&key, "state", &state); @@ -1485,7 +1483,7 @@ impl TaskStore for RedisTaskStore { let key_prefix = self.key_prefix.clone(); let scope = scope.to_string(); let holder = holder.to_string(); - let key = format!("{}:lease:{}", key_prefix, scope); + let key = format!("{key_prefix}:lease:{scope}"); let ttl_seconds = ((expires_at - now_ms) / 1000).max(1) as u64; self.block_on(async move { @@ -1564,7 +1562,7 @@ impl TaskStore for RedisTaskStore { let key_prefix = self.key_prefix.clone(); let scope = scope.to_string(); let holder = holder.to_string(); - let key = format!("{}:lease:{}", key_prefix, scope); + let key = format!("{key_prefix}:lease:{scope}"); let ttl_seconds = ((expires_at - now_ms()) / 1000).max(1) as u64; self.block_on(async move { @@ -1587,7 +1585,7 @@ impl TaskStore for RedisTaskStore { let manager = self.pool.manager.clone(); let key_prefix = self.key_prefix.clone(); let scope = scope.to_string(); - let key = format!("{}:lease:{}", key_prefix, scope); + let key = format!("{key_prefix}:lease:{scope}"); self.block_on(async move { let mut conn = manager.lock().await; @@ -1633,7 +1631,7 @@ impl TaskStore for RedisTaskStore { let key_prefix = self.key_prefix.clone(); let canary = canary.clone(); let key = format!("{}:canary:{}", key_prefix, canary.id); - let index_key = format!("{}:canary:_index", key_prefix); + let index_key = format!("{key_prefix}:canary:_index"); let interval_s_str = canary.interval_s.to_string(); let enabled_str = (canary.enabled as i64).to_string(); @@ -1664,7 +1662,7 @@ impl TaskStore for RedisTaskStore { let manager = self.pool.manager.clone(); let key_prefix = self.key_prefix.clone(); let id = id.to_string(); - let key = format!("{}:canary:{}", key_prefix, id); + let key = format!("{key_prefix}:canary:{id}"); self.block_on(async move { let mut conn = manager.lock().await; @@ -1686,7 +1684,7 @@ impl TaskStore for RedisTaskStore { let key_prefix = self.key_prefix.clone(); self.block_on(async move { - let index_key = format!("{}:canary:_index", key_prefix); + let index_key = format!("{key_prefix}:canary:_index"); let mut conn = manager.lock().await; let ids: Vec = conn .smembers(&index_key) @@ -1695,7 +1693,7 @@ impl TaskStore for RedisTaskStore { let mut result = Vec::new(); for id in ids { - let key = format!("{}:canary:{}", key_prefix, id); + let key = format!("{key_prefix}:canary:{id}"); let fields: HashMap = conn .hgetall(&key) .await @@ -1714,8 +1712,8 @@ impl TaskStore for RedisTaskStore { let pool = self.pool.clone(); let key_prefix = self.key_prefix.clone(); let id = id.to_string(); - let key = format!("{}:canary:{}", key_prefix, id); - let index_key = format!("{}:canary:_index", key_prefix); + let key = format!("{key_prefix}:canary:{id}"); + let index_key = format!("{key_prefix}:canary:_index"); self.block_on(async move { let mut conn = pool.manager.lock().await; @@ -1772,7 +1770,7 @@ impl TaskStore for RedisTaskStore { let manager = self.pool.manager.clone(); let key_prefix = self.key_prefix.clone(); let canary_id = canary_id.to_string(); - let key = format!("{}:canary_runs:{}", key_prefix, canary_id); + let key = format!("{key_prefix}:canary_runs:{canary_id}"); self.block_on(async move { let mut conn = manager.lock().await; @@ -1835,7 +1833,7 @@ impl TaskStore for RedisTaskStore { let key_prefix = self.key_prefix.clone(); let sink_name = sink_name.to_string(); let index_uid = index_uid.to_string(); - let key = format!("{}:cdc_cursor:{}:{}", key_prefix, sink_name, index_uid); + let key = format!("{key_prefix}:cdc_cursor:{sink_name}:{index_uid}"); self.block_on(async move { let mut conn = manager.lock().await; @@ -1861,7 +1859,7 @@ impl TaskStore for RedisTaskStore { let pool = self.pool.clone(); let key_prefix = self.key_prefix.clone(); let sink_name = sink_name.to_string(); - let index_key = format!("{}:cdc_cursor:_index:{}", key_prefix, sink_name); + let index_key = format!("{key_prefix}:cdc_cursor:_index:{sink_name}"); self.block_on(async move { // Use the _index set for O(cardinality) iteration (no SCAN). @@ -1882,7 +1880,7 @@ impl TaskStore for RedisTaskStore { Some(idx) => idx.to_string(), None => continue, }; - let key = format!("{}:cdc_cursor:{}:{}", key_prefix, sink_name, idx); + let key = format!("{key_prefix}:cdc_cursor:{sink_name}:{idx}"); let fields: HashMap = conn .hgetall(&key) .await @@ -1911,7 +1909,7 @@ impl TaskStore for RedisTaskStore { let tenant_id = mapping.tenant_id.clone(); let group_id = mapping.group_id; let hex_hash = hex::encode(&api_key_hash); - let key = format!("{}:tenant_map:{}", key_prefix, hex_hash); + let key = format!("{key_prefix}:tenant_map:{hex_hash}"); self.block_on(async move { let mut pipe = pipe(); @@ -1930,7 +1928,7 @@ impl TaskStore for RedisTaskStore { let key_prefix = self.key_prefix.clone(); let api_key_hash = api_key_hash.to_vec(); let hex_hash = hex::encode(&api_key_hash); - let key = format!("{}:tenant_map:{}", key_prefix, hex_hash); + let key = format!("{key_prefix}:tenant_map:{hex_hash}"); self.block_on(async move { let mut conn = manager.lock().await; @@ -1956,7 +1954,7 @@ impl TaskStore for RedisTaskStore { let key_prefix = self.key_prefix.clone(); let api_key_hash = api_key_hash.to_vec(); let hex_hash = hex::encode(&api_key_hash); - let key = format!("{}:tenant_map:{}", key_prefix, hex_hash); + let key = format!("{key_prefix}:tenant_map:{hex_hash}"); self.block_on(async move { let mut conn = manager.lock().await; @@ -1986,7 +1984,7 @@ impl TaskStore for RedisTaskStore { let key_prefix = self.key_prefix.clone(); let policy = policy.clone(); let key = format!("{}:rollover:{}", key_prefix, policy.name); - let index_key = format!("{}:rollover:_index", key_prefix); + let index_key = format!("{key_prefix}:rollover:_index"); let enabled_str = (policy.enabled as i64).to_string(); self.block_on(async move { @@ -2014,7 +2012,7 @@ impl TaskStore for RedisTaskStore { let manager = self.pool.manager.clone(); let key_prefix = self.key_prefix.clone(); let name = name.to_string(); - let key = format!("{}:rollover:{}", key_prefix, name); + let key = format!("{key_prefix}:rollover:{name}"); self.block_on(async move { let mut conn = manager.lock().await; @@ -2045,7 +2043,7 @@ impl TaskStore for RedisTaskStore { let key_prefix = self.key_prefix.clone(); self.block_on(async move { - let index_key = format!("{}:rollover:_index", key_prefix); + let index_key = format!("{key_prefix}:rollover:_index"); let mut conn = manager.lock().await; let names: Vec = conn .smembers(&index_key) @@ -2054,7 +2052,7 @@ impl TaskStore for RedisTaskStore { let mut result = Vec::new(); for name in names { - let key = format!("{}:rollover:{}", key_prefix, name); + let key = format!("{key_prefix}:rollover:{name}"); let fields: HashMap = conn .hgetall(&key) .await @@ -2082,8 +2080,8 @@ impl TaskStore for RedisTaskStore { let pool = self.pool.clone(); let key_prefix = self.key_prefix.clone(); let name = name.to_string(); - let key = format!("{}:rollover:{}", key_prefix, name); - let index_key = format!("{}:rollover:_index", key_prefix); + let key = format!("{key_prefix}:rollover:{name}"); + let index_key = format!("{key_prefix}:rollover:_index"); self.block_on(async move { let mut conn = pool.manager.lock().await; @@ -2131,7 +2129,7 @@ impl TaskStore for RedisTaskStore { let manager = self.pool.manager.clone(); let key_prefix = self.key_prefix.clone(); let index_uid = index_uid.to_string(); - let key = format!("{}:search_ui_config:{}", key_prefix, index_uid); + let key = format!("{key_prefix}:search_ui_config:{index_uid}"); self.block_on(async move { let mut conn = manager.lock().await; @@ -2156,7 +2154,7 @@ impl TaskStore for RedisTaskStore { let manager = self.pool.manager.clone(); let key_prefix = self.key_prefix.clone(); let index_uid = index_uid.to_string(); - let key = format!("{}:search_ui_config:{}", key_prefix, index_uid); + let key = format!("{key_prefix}:search_ui_config:{index_uid}"); self.block_on(async move { let mut conn = manager.lock().await; @@ -2225,7 +2223,7 @@ impl TaskStore for RedisTaskStore { let manager = self.pool.manager.clone(); let key_prefix = self.key_prefix.clone(); let session_id = session_id.to_string(); - let key = format!("{}:admin_session:{}", key_prefix, session_id); + let key = format!("{key_prefix}:admin_session:{session_id}"); self.block_on(async move { let mut conn = manager.lock().await; @@ -2255,8 +2253,8 @@ impl TaskStore for RedisTaskStore { let manager = self.pool.manager.clone(); let key_prefix = self.key_prefix.clone(); let session_id = session_id.to_string(); - let key = format!("{}:admin_session:{}", key_prefix, session_id); - let channel = format!("{}:admin_session:revoked", key_prefix); + let key = format!("{key_prefix}:admin_session:{session_id}"); + let channel = format!("{key_prefix}:admin_session:revoked"); self.block_on(async move { let mut conn = manager.lock().await; @@ -2337,13 +2335,13 @@ impl TaskStore for RedisTaskStore { } // Store the hash - conn.hset_multiple(&key, &items) + conn.hset_multiple::<_, _, _, ()>(&key, &items) .await .map_err(|e| MiroirError::Redis(e.to_string()))?; // Add to scope index let scope_key = format!("{}:mode_b_ops_scope:{}", key_prefix, op.scope); - conn.set(&scope_key, &op.operation_id) + conn.set::<_, _, ()>(&scope_key, &op.operation_id) .await .map_err(|e| MiroirError::Redis(e.to_string()))?; @@ -2358,7 +2356,7 @@ impl TaskStore for RedisTaskStore { self.block_on(async move { let mut conn = manager.lock().await; - let key = format!("{}:mode_b_ops:{}", key_prefix, id); + let key = format!("{key_prefix}:mode_b_ops:{id}"); // Check if key exists let exists: bool = conn @@ -2412,7 +2410,7 @@ impl TaskStore for RedisTaskStore { self.block_on(async move { let mut conn = manager.lock().await; - let scope_key = format!("{}:mode_b_ops_scope:{}", key_prefix, scope); + let scope_key = format!("{key_prefix}:mode_b_ops_scope:{scope}"); // Get operation ID from scope index let operation_id: Option = conn @@ -2425,7 +2423,7 @@ impl TaskStore for RedisTaskStore { }; // Get the operation - let key = format!("{}:mode_b_ops:{}", key_prefix, id); + let key = format!("{key_prefix}:mode_b_ops:{id}"); let exists: bool = conn .exists(&key) .await @@ -2479,7 +2477,7 @@ impl TaskStore for RedisTaskStore { let mut conn = manager.lock().await; // Scan for mode_b_ops keys - let pattern = format!("{}:mode_b_ops:*", key_prefix); + let pattern = format!("{key_prefix}:mode_b_ops:*"); let keys: Vec = conn .keys(&pattern) .await @@ -2569,7 +2567,7 @@ impl TaskStore for RedisTaskStore { self.block_on(async move { let mut conn = manager.lock().await; - let key = format!("{}:mode_b_ops:{}", key_prefix, id); + let key = format!("{key_prefix}:mode_b_ops:{id}"); // Get scope for cleanup let scope: Option = conn @@ -2586,7 +2584,7 @@ impl TaskStore for RedisTaskStore { // Clean up scope index if let Some(s) = scope { - let scope_key = format!("{}:mode_b_ops_scope:{}", key_prefix, s); + let scope_key = format!("{key_prefix}:mode_b_ops_scope:{s}"); let _: () = conn .del(&scope_key) .await @@ -2605,7 +2603,7 @@ impl TaskStore for RedisTaskStore { let mut conn = manager.lock().await; // Scan for mode_b_ops keys - let pattern = format!("{}:mode_b_ops:*", key_prefix); + let pattern = format!("{key_prefix}:mode_b_ops:*"); let keys: Vec = conn .keys(&pattern) .await @@ -2650,7 +2648,7 @@ impl TaskStore for RedisTaskStore { let key_prefix = self.key_prefix.clone(); let index_uid = index_uid.to_string(); let event_id = event_id.to_string(); - let key = format!("{}:search_ui_beacon:{}", key_prefix, index_uid); + let key = format!("{key_prefix}:search_ui_beacon:{index_uid}"); let field = event_id.clone(); self.block_on(async move { @@ -2703,7 +2701,7 @@ impl RedisTaskStore { let manager = self.pool.manager.clone(); let key_prefix = self.key_prefix.clone(); let ip = ip.to_string(); - let key = format!("{}:ratelimit:searchui:{}", key_prefix, ip); + let key = format!("{key_prefix}:ratelimit:searchui:{ip}"); self.block_on(async move { let mut conn = manager.lock().await; @@ -2758,8 +2756,8 @@ impl RedisTaskStore { let pool = self.pool.clone(); let key_prefix = self.key_prefix.clone(); let ip = ip.to_string(); - let backoff_key = format!("{}:ratelimit:adminlogin:backoff:{}", key_prefix, ip); - let key = format!("{}:ratelimit:adminlogin:{}", key_prefix, ip); + let backoff_key = format!("{key_prefix}:ratelimit:adminlogin:backoff:{ip}"); + let key = format!("{key_prefix}:ratelimit:adminlogin:{ip}"); self.block_on(async move { let mut conn = pool.manager.lock().await; @@ -2819,7 +2817,7 @@ impl RedisTaskStore { let pool = self.pool.clone(); let key_prefix = self.key_prefix.clone(); let ip = ip.to_string(); - let backoff_key = format!("{}:ratelimit:adminlogin:backoff:{}", key_prefix, ip); + let backoff_key = format!("{key_prefix}:ratelimit:adminlogin:backoff:{ip}"); self.block_on(async move { let mut conn = pool.manager.lock().await; @@ -2872,8 +2870,8 @@ impl RedisTaskStore { let pool = self.pool.clone(); let key_prefix = self.key_prefix.clone(); let ip = ip.to_string(); - let key = format!("{}:ratelimit:adminlogin:{}", key_prefix, ip); - let backoff_key = format!("{}:ratelimit:adminlogin:backoff:{}", key_prefix, ip); + let key = format!("{key_prefix}:ratelimit:adminlogin:{ip}"); + let backoff_key = format!("{key_prefix}:ratelimit:adminlogin:backoff:{ip}"); self.block_on(async move { let mut pipe = pipe(); @@ -2898,7 +2896,7 @@ impl RedisTaskStore { let pool = self.pool.clone(); let key_prefix = self.key_prefix.clone(); let ip = ip.to_string(); - let key = format!("{}:ratelimit:searchui:{}", key_prefix, ip); + let key = format!("{key_prefix}:ratelimit:searchui:{ip}"); self.block_on(async move { let mut conn = pool.manager.lock().await; @@ -2933,7 +2931,7 @@ impl RedisTaskStore { let manager = self.pool.manager.clone(); let key_prefix = self.key_prefix.clone(); let index_uid = index_uid.to_string(); - let key = format!("{}:search_ui_scoped_key:{}", key_prefix, index_uid); + let key = format!("{key_prefix}:search_ui_scoped_key:{index_uid}"); self.block_on(async move { let mut conn = manager.lock().await; @@ -3008,8 +3006,7 @@ impl RedisTaskStore { let pod_id = pod_id.to_string(); let index_uid = index_uid.to_string(); let key = format!( - "{}:search_ui_scoped_key_observed:{}:{}", - key_prefix, pod_id, index_uid + "{key_prefix}:search_ui_scoped_key_observed:{pod_id}:{index_uid}" ); self.block_on(async move { @@ -3041,8 +3038,7 @@ impl RedisTaskStore { for pod_id in &live_pods { let key = format!( - "{}:search_ui_scoped_key_observed:{}:{}", - key_prefix, pod_id, index_uid + "{key_prefix}:search_ui_scoped_key_observed:{pod_id}:{index_uid}" ); let fields: HashMap = conn .hgetall(&key) @@ -3068,7 +3064,7 @@ impl RedisTaskStore { let pool = self.pool.clone(); let key_prefix = self.key_prefix.clone(); let index_uid = index_uid.to_string(); - let redis_key = format!("{}:search_ui_scoped_key:{}", key_prefix, index_uid); + let redis_key = format!("{key_prefix}:search_ui_scoped_key:{index_uid}"); self.block_on(async move { let mut pipe = pipe(); @@ -3085,7 +3081,7 @@ impl RedisTaskStore { let pool = self.pool.clone(); let key_prefix = self.key_prefix.clone(); let pod_id = pod_id.to_string(); - let key = format!("{}:live_pods", key_prefix); + let key = format!("{key_prefix}:live_pods"); let now = now_ms(); self.block_on(async move { @@ -3103,7 +3099,7 @@ impl RedisTaskStore { pub fn get_live_pods(&self) -> Result> { let manager = self.pool.manager.clone(); let key_prefix = self.key_prefix.clone(); - let key = format!("{}:live_pods", key_prefix); + let key = format!("{key_prefix}:live_pods"); let cutoff = now_ms() - 120_000; // 120 seconds ago self.block_on(async move { @@ -3122,7 +3118,7 @@ impl RedisTaskStore { let key_prefix = self.key_prefix.clone(); self.block_on(async move { - let pattern = format!("{}:search_ui_scoped_key:*", key_prefix); + let pattern = format!("{key_prefix}:search_ui_scoped_key:*"); let mut conn = pool.manager.lock().await; let mut indexes = Vec::new(); @@ -3170,8 +3166,8 @@ impl RedisTaskStore { let key_prefix = self.key_prefix.clone(); let sink_name = sink_name.to_string(); let data = data.to_vec(); - let key = format!("{}:cdc:overflow:{}", key_prefix, sink_name); - let bytes_key = format!("{}:cdc:overflow_bytes:{}", key_prefix, sink_name); + let key = format!("{key_prefix}:cdc:overflow:{sink_name}"); + let bytes_key = format!("{key_prefix}:cdc:overflow_bytes:{sink_name}"); let data_len = data.len(); self.block_on(async move { @@ -3246,8 +3242,8 @@ impl RedisTaskStore { let pool = self.pool.clone(); let key_prefix = self.key_prefix.clone(); let sink_name = sink_name.to_string(); - let key = format!("{}:cdc:overflow:{}", key_prefix, sink_name); - let bytes_key = format!("{}:cdc:overflow_bytes:{}", key_prefix, sink_name); + let key = format!("{key_prefix}:cdc:overflow:{sink_name}"); + let bytes_key = format!("{key_prefix}:cdc:overflow_bytes:{sink_name}"); self.block_on(async move { let mut conn = pool.manager.lock().await; @@ -3275,7 +3271,7 @@ impl RedisTaskStore { let manager = self.pool.manager.clone(); let key_prefix = self.key_prefix.clone(); let sink_name = sink_name.to_string(); - let key = format!("{}:cdc:overflow:{}", key_prefix, sink_name); + let key = format!("{key_prefix}:cdc:overflow:{sink_name}"); self.block_on(async move { let mut conn = manager.lock().await; @@ -3304,7 +3300,7 @@ impl RedisTaskStore { .await .map_err(|e| MiroirError::Redis(e.to_string()))?; - let channel = format!("{}:admin_session:revoked", key_prefix); + let channel = format!("{key_prefix}:admin_session:revoked"); conn.subscribe(&channel) .await .map_err(|e| MiroirError::Redis(e.to_string()))?; diff --git a/crates/miroir-core/src/task_store/sqlite.rs b/crates/miroir-core/src/task_store/sqlite.rs index 69c8572..628a70d 100644 --- a/crates/miroir-core/src/task_store/sqlite.rs +++ b/crates/miroir-core/src/task_store/sqlite.rs @@ -9,7 +9,7 @@ use std::sync::Mutex; fn registry() -> &'static MigrationRegistry { use std::sync::OnceLock; static REGISTRY: OnceLock = OnceLock::new(); - REGISTRY.get_or_init(|| build_registry()) + REGISTRY.get_or_init(build_registry) } pub struct SqliteTaskStore { @@ -304,7 +304,7 @@ impl TaskStore for SqliteTaskStore { let target_uids_json = alias .target_uids .as_ref() - .map(|uids| serde_json::to_string(uids)) + .map(serde_json::to_string) .transpose()?; let history_json = serde_json::to_string(&alias.history)?; conn.execute( @@ -796,7 +796,7 @@ impl TaskStore for SqliteTaskStore { let mut total_deleted = 0; for miroir_id in miroir_ids { let delete_sql = "DELETE FROM tasks WHERE miroir_id = ?1"; - let rows = conn.execute(delete_sql, [&*miroir_id])?; + let rows = conn.execute(delete_sql, [miroir_id])?; total_deleted += rows; } Ok(total_deleted) @@ -1394,10 +1394,10 @@ impl TaskStore for SqliteTaskStore { query.push_str(" ORDER BY updated_at DESC"); if let Some(limit) = filter.limit { - query.push_str(&format!(" LIMIT {}", limit)); + query.push_str(&format!(" LIMIT {limit}")); } if let Some(offset) = filter.offset { - query.push_str(&format!(" OFFSET {}", offset)); + query.push_str(&format!(" OFFSET {offset}")); } let mut stmt = conn.prepare(&query)?; diff --git a/crates/miroir-core/src/topology.rs b/crates/miroir-core/src/topology.rs index fa892e8..df73636 100644 --- a/crates/miroir-core/src/topology.rs +++ b/crates/miroir-core/src/topology.rs @@ -121,8 +121,7 @@ impl NodeStatus { Ok(target) } else { Err(MiroirError::Topology(format!( - "illegal state transition: {:?} → {:?}", - self, target + "illegal state transition: {self:?} → {target:?}" ))) } } diff --git a/crates/miroir-core/src/ttl.rs b/crates/miroir-core/src/ttl.rs index b1984e9..f0374b5 100644 --- a/crates/miroir-core/src/ttl.rs +++ b/crates/miroir-core/src/ttl.rs @@ -14,7 +14,7 @@ //! WriteRequest { ..., origin: Some(ORIGIN_TTL_EXPIRE.to_string()) } //! ``` -use crate::error::{MiroirError, Result}; +use crate::error::Result; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::Arc; diff --git a/crates/miroir-core/src/vector.rs b/crates/miroir-core/src/vector.rs index 7334739..d33c9a5 100644 --- a/crates/miroir-core/src/vector.rs +++ b/crates/miroir-core/src/vector.rs @@ -207,7 +207,7 @@ impl VectorMerger { // First, sort each shard's hits by their original ranking score let mut per_shard: HashMap> = HashMap::new(); for (shard_id, hit) in shard_hits { - per_shard.entry(shard_id).or_insert_with(Vec::new).push(hit); + per_shard.entry(shard_id).or_default().push(hit); } // Compute RRF scores diff --git a/crates/miroir-core/tests/dfs_skewed_corpus.rs b/crates/miroir-core/tests/dfs_skewed_corpus.rs index 8102709..75b9944 100644 --- a/crates/miroir-core/tests/dfs_skewed_corpus.rs +++ b/crates/miroir-core/tests/dfs_skewed_corpus.rs @@ -388,7 +388,7 @@ fn test_global_idf_single_shard() { }, }; - let global_idf = GlobalIdf::from_preflight_responses(&vec![response]); + let global_idf = GlobalIdf::from_preflight_responses(&[response]); assert_eq!(global_idf.total_docs, 1000); assert_eq!(global_idf.terms.get("test").unwrap().df, 50); diff --git a/crates/miroir-core/tests/hash_fixtures.rs b/crates/miroir-core/tests/hash_fixtures.rs index 2ff4f1d..f9a9c56 100644 --- a/crates/miroir-core/tests/hash_fixtures.rs +++ b/crates/miroir-core/tests/hash_fixtures.rs @@ -29,8 +29,7 @@ fn print_actual_hash_values() { let hash = hash_for_key(key); let shard = shard_for_key(key, shard_count); println!( - "(\"{}\", {}, {}), // hash={}", - key, shard_count, shard, hash + "(\"{key}\", {shard_count}, {shard}), // hash={hash}" ); } println!("========================\n"); diff --git a/crates/miroir-core/tests/merger_proptest.rs b/crates/miroir-core/tests/merger_proptest.rs index 4e3ba63..d312a1c 100644 --- a/crates/miroir-core/tests/merger_proptest.rs +++ b/crates/miroir-core/tests/merger_proptest.rs @@ -54,7 +54,7 @@ proptest! { .map(|i| { let id = shard_id * hits_per_shard + i; let score = (hits_per_shard as f64 - i as f64) / hits_per_shard as f64; - make_hit(&format!("doc-{}", id), score) + make_hit(&format!("doc-{id}"), score) }) .collect(); make_shard_response(hits, hits_per_shard as u64, 15) @@ -94,7 +94,7 @@ proptest! { .map(|i| { let id = shard_id * hits_per_shard + i; let score = (hits_per_shard as f64 - i as f64) / hits_per_shard as f64; - make_hit(&format!("doc-{}", id), score) + make_hit(&format!("doc-{id}"), score) }) .collect(); make_shard_response(hits, hits_per_shard as u64, 15) @@ -135,7 +135,7 @@ proptest! { .map(|i| { let id = shard_id * hits_per_shard + i; let score = (hits_per_shard as f64 - i as f64) / hits_per_shard as f64; - make_hit(&format!("doc-{}", id), score) + make_hit(&format!("doc-{id}"), score) }) .collect(); make_shard_response(hits, hits_per_shard as u64, 15) @@ -179,7 +179,7 @@ proptest! { .map(|i| { let id = shard_id * hits_per_shard + i; let score = (hits_per_shard as f64 - i as f64) / hits_per_shard as f64; - make_hit(&format!("doc-{}", id), score) + make_hit(&format!("doc-{id}"), score) }) .collect(); make_shard_response(hits, hits_per_shard as u64, 15) @@ -232,7 +232,7 @@ proptest! { .map(|i| { let id = shard_id * hits_per_shard + i; let score = (hits_per_shard as f64 - i as f64) / hits_per_shard as f64; - make_hit(&format!("doc-{}", id), score) + make_hit(&format!("doc-{id}"), score) }) .collect(); make_shard_response(hits, hits_per_shard as u64, 15) @@ -300,7 +300,7 @@ proptest! { .map(|i| { let id = shard_id * hits_per_shard + i; let score = (hits_per_shard as f64 - i as f64) / hits_per_shard as f64; - make_hit(&format!("doc-{}", id), score) + make_hit(&format!("doc-{id}"), score) }) .collect(); make_shard_response(hits, hits_per_shard as u64, 15) @@ -358,7 +358,7 @@ proptest! { .map(|i| { let id = shard_id * hits_per_shard + i; let score = (hits_per_shard as f64 - i as f64) / hits_per_shard as f64; - make_hit(&format!("doc-{}", id), score) + make_hit(&format!("doc-{id}"), score) }) .collect(); make_shard_response(hits, hits_per_shard as u64, 15) @@ -469,7 +469,7 @@ proptest! { .map(|i| { let id = shard_id * hits_per_shard + i; let score = (hits_per_shard as f64 - i as f64) / hits_per_shard as f64; - make_hit(&format!("doc-{}", id), score) + make_hit(&format!("doc-{id}"), score) }) .collect(); make_shard_response(hits, hits_per_shard as u64, 15) @@ -517,7 +517,7 @@ proptest! { let id = shard_id * hits_per_shard + i; // Use varying scores - RRF should sort by rank, not score let score = rand::random::() * 0.5 + 0.5; - make_hit(&format!("doc-{}", id), score) + make_hit(&format!("doc-{id}"), score) }) .collect(); make_shard_response(hits, hits_per_shard as u64, 15) diff --git a/crates/miroir-core/tests/p13_10_idempotency_coalescing.rs b/crates/miroir-core/tests/p13_10_idempotency_coalescing.rs index 3ae099a..d7b2087 100644 --- a/crates/miroir-core/tests/p13_10_idempotency_coalescing.rs +++ b/crates/miroir-core/tests/p13_10_idempotency_coalescing.rs @@ -138,8 +138,7 @@ async fn p5_10_a3_hot_query_coalesces_scatters() { // At least 90% should have coalesced (they all hit within the window) assert!( coalesced_count >= 900, - "expected at least 900 coalesced queries, got {}", - coalesced_count + "expected at least 900 coalesced queries, got {coalesced_count}" ); } @@ -159,7 +158,7 @@ async fn p5_10_a3_multiple_scatters_across_windows() { // First query in window: should miss and register let rx = coalescer.try_coalesce(fp.clone()).await; - assert!(rx.is_none(), "first query in window {} should miss", window); + assert!(rx.is_none(), "first query in window {window} should miss"); let tx = coalescer.register(fp.clone()).await.unwrap(); scatter_count += 1; @@ -169,8 +168,7 @@ async fn p5_10_a3_multiple_scatters_across_windows() { let rx = coalescer.try_coalesce(fp.clone()).await; assert!( rx.is_some(), - "subsequent queries in window {} should coalesce", - window + "subsequent queries in window {window} should coalesce" ); } @@ -185,8 +183,7 @@ async fn p5_10_a3_multiple_scatters_across_windows() { // We expect exactly 5 scatters (one per window) assert_eq!( scatter_count, 5, - "expected 5 scatters across 5 windows, got {}", - scatter_count + "expected 5 scatters across 5 windows, got {scatter_count}" ); } @@ -304,10 +301,10 @@ async fn p5_10_a5_idempotency_cache_max_entries_enforcement() { // Insert 3 entries (at capacity) for i in 0..3 { - let key = format!("key-{}", i); + let key = format!("key-{i}"); let body = json!({"id": i}); let body_hash = compute_hash(&body); - cache.insert(key, body_hash, format!("mtask-{}", i)).await; + cache.insert(key, body_hash, format!("mtask-{i}")).await; } assert_eq!(cache.size().await, 3, "cache should have 3 entries"); diff --git a/crates/miroir-core/tests/p13_18_canary_acceptance_tests.rs b/crates/miroir-core/tests/p13_18_canary_acceptance_tests.rs index b967fa6..1ab91e3 100644 --- a/crates/miroir-core/tests/p13_18_canary_acceptance_tests.rs +++ b/crates/miroir-core/tests/p13_18_canary_acceptance_tests.rs @@ -8,7 +8,7 @@ //! - Canary CRUD operations use miroir_core::{ - canary::{CanaryAssertion, CanaryStatus, QueryCapture, SearchQuery, SearchResponse}, + canary::{CanaryAssertion, QueryCapture, SearchQuery, SearchResponse}, task_store::{NewCanary, TaskStore}, }; use std::collections::HashMap; @@ -146,8 +146,7 @@ async fn ac3_assertion_failure_includes_actual_value() { assert_eq!(failure["actual"], 2); // Test multiple assertion types - let failures = vec![ - serde_json::json!({ + let failures = [serde_json::json!({ "assertion_type": "top_hit_id", "expected": "product-123", "actual": "product-456", @@ -158,8 +157,7 @@ async fn ac3_assertion_failure_includes_actual_value() { "expected": 200, "actual": 350, "message": "Latency exceeded threshold" - }), - ]; + })]; assert_eq!(failures.len(), 2); assert_eq!(failures[0]["assertion_type"], "top_hit_id"); @@ -185,7 +183,7 @@ async fn ac4_capture_flow_records_queries() { hits: vec![], estimated_total_hits: 0, processing_time_ms: 50, - query: format!("query {}", i), + query: format!("query {i}"), }, ) .await; @@ -199,7 +197,7 @@ async fn ac4_capture_flow_records_queries() { for (i, query) in captured.iter().enumerate() { assert_eq!(query.index_uid, "products"); let q = query.query.params.get("q").and_then(|v| v.as_str()); - assert_eq!(q, Some(format!("query {}", i).as_str())); + assert_eq!(q, Some(format!("query {i}").as_str())); } // Clear and verify @@ -362,8 +360,8 @@ async fn ac8_canary_list_can_be_retrieved() { // Create multiple canaries for i in 0..3 { let canary = NewCanary { - id: format!("list-test-{}", i), - name: format!("List Test Canary {}", i), + id: format!("list-test-{i}"), + name: format!("List Test Canary {i}"), index_uid: "products".to_string(), interval_s: 60, query_json: serde_json::to_string(&SearchQuery { diff --git a/crates/miroir-core/tests/p13_2_hedging_chaos.rs b/crates/miroir-core/tests/p13_2_hedging_chaos.rs index 6369336..65bc17c 100644 --- a/crates/miroir-core/tests/p13_2_hedging_chaos.rs +++ b/crates/miroir-core/tests/p13_2_hedging_chaos.rs @@ -11,7 +11,6 @@ use std::sync::Arc; use std::time::Duration; use miroir_core::hedging::{HedgeOutcome, HedgingConfig, HedgingManager}; -use miroir_core::router::assign_shard_in_group; use miroir_core::scatter::{ execute_hedged_request, NodeClient, NodeError, SearchRequest, VectorMode, }; @@ -59,6 +58,7 @@ fn make_search_request() -> SearchRequest { } /// Mock node client that can simulate delays per node. +#[derive(Default)] struct DelayedMockNodeClient { /// Responses keyed by node ID. responses: HashMap, @@ -66,14 +66,6 @@ struct DelayedMockNodeClient { delays: HashMap, } -impl Default for DelayedMockNodeClient { - fn default() -> Self { - Self { - responses: HashMap::new(), - delays: HashMap::new(), - } - } -} impl NodeClient for DelayedMockNodeClient { async fn search_node( @@ -171,8 +163,7 @@ async fn p5_2_a1_chaos_slow_node_avoided_via_hedging() { // Hedge should have fired and won assert!( outcome == Some(HedgeOutcome::HedgeWon), - "Hedge should fire and win: got {:?}", - outcome + "Hedge should fire and win: got {outcome:?}" ); // Total latency should be MUCH closer to fast replica (5ms + hedge overhead) @@ -184,8 +175,7 @@ async fn p5_2_a1_chaos_slow_node_avoided_via_hedging() { // Definitely NOT 500ms. assert!( total_latency < Duration::from_millis(100), - "Total latency {:?} should be far less than slow node's 500ms (hedging should avoid it)", - total_latency + "Total latency {total_latency:?} should be far less than slow node's 500ms (hedging should avoid it)" ); // Verify we got a response from a fast replica (not the slow one) @@ -194,8 +184,7 @@ async fn p5_2_a1_chaos_slow_node_avoided_via_hedging() { let doc_id = hits[0]["id"].as_str().unwrap(); assert!( doc_id == "fast-1" || doc_id == "fast-2", - "Response should come from a fast replica, got {}", - doc_id + "Response should come from a fast replica, got {doc_id}" ); // Hedge count should be 1 @@ -274,9 +263,9 @@ async fn p5_2_a2_hedging_p95_close_to_healthy_baseline() { let hedged_p95 = percentile(°raded_with_hedge_latencies, 95); let no_hedge_p95 = percentile(°raded_no_hedge_latencies, 95); - println!("Healthy p95: {:?}", healthy_p95); - println!("Hedged p95: {:?}", hedged_p95); - println!("No-hedge p95: {:?}", no_hedge_p95); + println!("Healthy p95: {healthy_p95:?}"); + println!("Hedged p95: {hedged_p95:?}"); + println!("No-hedge p95: {no_hedge_p95:?}"); // With hedging, p95 should be close to healthy baseline // Without hedging, p95 would be degraded by the slow node @@ -285,25 +274,20 @@ async fn p5_2_a2_hedging_p95_close_to_healthy_baseline() { // but hedging should definitely be better than no hedging assert!( hedged_p95 < no_hedge_p95, - "Hedged p95 {:?} should be better than no-hedge p95 {:?}", - hedged_p95, - no_hedge_p95 + "Hedged p95 {hedged_p95:?} should be better than no-hedge p95 {no_hedge_p95:?}" ); // Hedged p95 should not be dramatically worse than healthy baseline // (allowing 5× for test overhead with 15ms hedge trigger) assert!( hedged_p95 < healthy_p95 * 5, - "Hedged p95 {:?} should be within 5× of healthy p95 {:?}", - hedged_p95, - healthy_p95 + "Hedged p95 {hedged_p95:?} should be within 5× of healthy p95 {healthy_p95:?}" ); // Without hedging, p95 would be severely degraded (close to 500ms) assert!( no_hedge_p95 > Duration::from_millis(200), - "No-hedge p95 {:?} should be severely degraded by slow node", - no_hedge_p95 + "No-hedge p95 {no_hedge_p95:?} should be severely degraded by slow node" ); } @@ -456,8 +440,7 @@ async fn run_searches_with_latency( } println!( - "Total hedges issued: {} out of {} queries", - total_hedges, count + "Total hedges issued: {total_hedges} out of {count} queries" ); latencies diff --git a/crates/miroir-core/tests/p13_3_adaptive_replica_selection.rs b/crates/miroir-core/tests/p13_3_adaptive_replica_selection.rs index 9270978..06047b9 100644 --- a/crates/miroir-core/tests/p13_3_adaptive_replica_selection.rs +++ b/crates/miroir-core/tests/p13_3_adaptive_replica_selection.rs @@ -79,18 +79,15 @@ async fn p5_3_a1_degraded_node_receives_less_traffic() { let _expected = 200 / 3; assert!( (20..=90).contains(&count0), - "node-0 baseline count {} out of expected range 20-90", - count0 + "node-0 baseline count {count0} out of expected range 20-90" ); assert!( (20..=90).contains(&count1), - "node-1 baseline count {} out of expected range 20-90", - count1 + "node-1 baseline count {count1} out of expected range 20-90" ); assert!( (20..=90).contains(&count2), - "node-2 baseline count {} out of expected range 20-90", - count2 + "node-2 baseline count {count2} out of expected range 20-90" ); // Induce degradation on node-1: 200ms latency @@ -113,20 +110,17 @@ async fn p5_3_a1_degraded_node_receives_less_traffic() { // Expect node-1 to get <15% of traffic assert!( degraded_count1 < 30, - "degraded node-1 still receiving too much traffic: {}", - degraded_count1 + "degraded node-1 still receiving too much traffic: {degraded_count1}" ); // Healthy nodes should receive more traffic assert!( degraded_count0 > 50, - "healthy node-0 not receiving enough traffic: {}", - degraded_count0 + "healthy node-0 not receiving enough traffic: {degraded_count0}" ); assert!( degraded_count2 > 50, - "healthy node-2 not receiving enough traffic: {}", - degraded_count2 + "healthy node-2 not receiving enough traffic: {degraded_count2}" ); } @@ -161,8 +155,7 @@ async fn p5_3_a2_degraded_node_recovers() { let degraded_count1 = *degraded_dist.get("node-1").unwrap_or(&0); assert!( degraded_count1 < 20, - "node-1 should be degraded, got {} selections", - degraded_count1 + "node-1 should be degraded, got {degraded_count1} selections" ); // Clear latency: record good responses for node-1 @@ -186,24 +179,15 @@ async fn p5_3_a2_degraded_node_recovers() { assert!( (recovered_count1 as isize - expected as isize).abs() <= tolerance as isize, - "node-1 recovered count {} not close to expected {} (tolerance {})", - recovered_count1, - expected, - tolerance + "node-1 recovered count {recovered_count1} not close to expected {expected} (tolerance {tolerance})" ); assert!( (recovered_count0 as isize - expected as isize).abs() <= tolerance as isize, - "node-0 count {} not close to expected {} (tolerance {})", - recovered_count0, - expected, - tolerance + "node-0 count {recovered_count0} not close to expected {expected} (tolerance {tolerance})" ); assert!( (recovered_count2 as isize - expected as isize).abs() <= tolerance as isize, - "node-2 count {} not close to expected {} (tolerance {})", - recovered_count2, - expected, - tolerance + "node-2 count {recovered_count2} not close to expected {expected} (tolerance {tolerance})" ); } @@ -246,8 +230,7 @@ async fn p5_3_a3_exploration_samples_degraded_node() { let count2 = *dist.get("node-2").unwrap_or(&0); println!( - "Distribution: node-0={}, node-1={}, node-2={}", - count0, count1, count2 + "Distribution: node-0={count0}, node-1={count1}, node-2={count2}" ); // Node-2 is severely degraded but should still get some traffic via exploration @@ -257,16 +240,14 @@ async fn p5_3_a3_exploration_samples_degraded_node() { // Allow range 5-30 for statistical variance (3 sigma) assert!( (5..=30).contains(&count2), - "exploration not working: degraded node-2 got {} selections, expected ~17 (range 5-30)", - count2 + "exploration not working: degraded node-2 got {count2} selections, expected ~17 (range 5-30)" ); // Healthy nodes should split the remaining ~95% let healthy_total = count0 + count1; assert!( healthy_total >= 900, - "healthy nodes didn't get enough traffic: {}", - healthy_total + "healthy nodes didn't get enough traffic: {healthy_total}" ); // Each healthy node should get roughly half of remaining @@ -274,15 +255,11 @@ async fn p5_3_a3_exploration_samples_degraded_node() { let tolerance = 100; assert!( (count0 as isize - expected_healthy).abs() <= tolerance, - "node-0 count {} not close to expected {}", - count0, - expected_healthy + "node-0 count {count0} not close to expected {expected_healthy}" ); assert!( (count1 as isize - expected_healthy).abs() <= tolerance, - "node-1 count {} not close to expected {}", - count1, - expected_healthy + "node-1 count {count1} not close to expected {expected_healthy}" ); } @@ -308,10 +285,10 @@ async fn p5_3_a4_round_robin_fallback() { let fourth = selector.select(&candidates, 0).await; // Should cycle through candidates in order - assert_eq!(first, candidates.get(0).cloned()); + assert_eq!(first, candidates.first().cloned()); assert_eq!(second, candidates.get(1).cloned()); assert_eq!(third, candidates.get(2).cloned()); - assert_eq!(fourth, candidates.get(0).cloned()); // Wrap around + assert_eq!(fourth, candidates.first().cloned()); // Wrap around } // ───────────────────────────────────────────────────────────── @@ -376,9 +353,7 @@ async fn in_flight_count_affects_score() { assert!( score0 > score1, - "node-0 with in-flight requests should have higher score: {} > {}", - score0, - score1 + "node-0 with in-flight requests should have higher score: {score0} > {score1}" ); } @@ -412,9 +387,7 @@ async fn error_rate_affects_score() { assert!( score0 > score1, - "node-0 with errors should have higher score: {} > {}", - score0, - score1 + "node-0 with errors should have higher score: {score0} > {score1}" ); // Verify error_rate is set @@ -479,10 +452,7 @@ async fn test_random_strategy() { let diff = (*count as isize - expected as isize).abs(); assert!( diff <= 100, // Allow 10% variance - "{}: got {} selections, expected ~{}", - node, - count, - expected + "{node}: got {count} selections, expected ~{expected}" ); } } diff --git a/crates/miroir-core/tests/p13_4_query_planner.rs b/crates/miroir-core/tests/p13_4_query_planner.rs index d890df0..b1c0b59 100644 --- a/crates/miroir-core/tests/p13_4_query_planner.rs +++ b/crates/miroir-core/tests/p13_4_query_planner.rs @@ -246,7 +246,7 @@ async fn p13_4_a10_result_parity_narrowed_vs_full_fanout() { for pk in test_pks { let shard = shard_for_key(pk, 64); let plan = planner - .plan("products", &Some(format!("product_id = \"{}\"", pk)), 64) + .plan("products", &Some(format!("product_id = \"{pk}\"")), 64) .await; assert!(plan.narrowed, "Plan should be narrowed for each PK"); diff --git a/crates/miroir-core/tests/p13_7_alias_acceptance_tests.rs b/crates/miroir-core/tests/p13_7_alias_acceptance_tests.rs index 99cb5fb..27609e6 100644 --- a/crates/miroir-core/tests/p13_7_alias_acceptance_tests.rs +++ b/crates/miroir-core/tests/p13_7_alias_acceptance_tests.rs @@ -132,7 +132,7 @@ async fn flip_alias_history_retention() { // Perform 12 flips with history_retention=10 for i in 1..=12 { - let new_target = format!("products_v{}", i); + let new_target = format!("products_v{i}"); store.flip_alias("products", &new_target, 10).unwrap(); } @@ -146,7 +146,7 @@ async fn flip_alias_history_retention() { // Verify history contains the most recent 10 targets // After 12 flips from v0, we should have v2..v11 (10 entries) // v1 was evicted - let expected: Vec = (2..=11).map(|i| format!("products_v{}", i)).collect(); + let expected: Vec = (2..=11).map(|i| format!("products_v{i}")).collect(); let actual: Vec = alias.history.iter().map(|h| h.uid.clone()).collect(); assert_eq!(actual, expected); } @@ -371,9 +371,9 @@ async fn list_aliases() { // Create multiple aliases for i in 1..=3 { let new_alias = NewAlias { - name: format!("alias{}", i), + name: format!("alias{i}"), kind: "single".to_string(), - current_uid: Some(format!("target_v{}", i)), + current_uid: Some(format!("target_v{i}")), target_uids: None, version: 1, created_at: 1000 + (i as i64), @@ -463,9 +463,9 @@ async fn registry_sync_from_store() { // Create aliases directly in the store for i in 1..=3 { let new_alias = NewAlias { - name: format!("sync{}", i), + name: format!("sync{i}"), kind: "single".to_string(), - current_uid: Some(format!("target_v{}", i)), + current_uid: Some(format!("target_v{i}")), target_uids: None, version: 1, created_at: 1000 + (i as i64), diff --git a/crates/miroir-core/tests/p13_8_anti_entropy.rs b/crates/miroir-core/tests/p13_8_anti_entropy.rs index 9399094..d9d4f98 100644 --- a/crates/miroir-core/tests/p13_8_anti_entropy.rs +++ b/crates/miroir-core/tests/p13_8_anti_entropy.rs @@ -13,8 +13,8 @@ fn make_test_topology() -> Topology { let mut topo = Topology::new(64, 2, 2); for i in 0u32..3 { let mut node = Node::new( - NodeId::new(format!("node-{}", i)), - format!("http://node-{}:7700", i), + NodeId::new(format!("node-{i}")), + format!("http://node-{i}:7700"), i % 2, ); node.status = NodeStatus::Active; diff --git a/crates/miroir-core/tests/p22_write_path.rs b/crates/miroir-core/tests/p22_write_path.rs index c13990c..7746915 100644 --- a/crates/miroir-core/tests/p22_write_path.rs +++ b/crates/miroir-core/tests/p22_write_path.rs @@ -41,7 +41,7 @@ fn test_document_distribution_uniformity() { // Simulate 1000 documents and track which shard each goes to let mut shard_counts: std::collections::HashMap = std::collections::HashMap::new(); for i in 0..1000 { - let key = format!("doc:{}", i); + let key = format!("doc:{i}"); let shard_id = shard_for_key(&key, shard_count); *shard_counts.entry(shard_id).or_insert(0) += 1; } @@ -53,11 +53,10 @@ fn test_document_distribution_uniformity() { let max_docs_per_node = 1000 * 26 / 64; // ~406 docs // Check that no shard has unreasonable count - for (_shard, count) in &shard_counts { + for count in shard_counts.values() { assert!( *count >= 5 && *count <= 30, - "Shard has unusual count: {}", - count + "Shard has unusual count: {count}" ); } } @@ -270,11 +269,10 @@ fn test_shard_distribution_rf1() { assert_eq!(node_shard_counts.len(), 3, "All 3 nodes should have shards"); // With 64 shards and 3 nodes, each should have ~21 shards (17-26 range per plan §8) - for (_node, count) in &node_shard_counts { + for count in node_shard_counts.values() { assert!( (17..=26).contains(count), - "Node has {} shards, expected 17-26", - count + "Node has {count} shards, expected 17-26" ); } } diff --git a/crates/miroir-core/tests/p22_write_path_acceptance.rs b/crates/miroir-core/tests/p22_write_path_acceptance.rs index 54f49b9..9fc4f8e 100644 --- a/crates/miroir-core/tests/p22_write_path_acceptance.rs +++ b/crates/miroir-core/tests/p22_write_path_acceptance.rs @@ -103,8 +103,8 @@ fn test_1000_docs_indexed_retrievable_by_id() { // Verify the original fields are present assert_eq!(stored_doc["id"], doc_id); - assert_eq!(stored_doc["title"], format!("Document {}", i)); - assert_eq!(stored_doc["content"], format!("Content for document {}", i)); + assert_eq!(stored_doc["title"], format!("Document {i}")); + assert_eq!(stored_doc["content"], format!("Content for document {i}")); // Verify _miroir_shard was injected assert!( @@ -150,26 +150,19 @@ fn test_docs_distribute_uniformly_across_nodes() { for (node, count) in &node_shard_counts { assert!( (*count as f64) >= (shard_count as f64 * 0.15), - "node {} has {} shards, expected at least 15% of {}", - node, - count, - shard_count + "node {node} has {count} shards, expected at least 15% of {shard_count}" ); assert!( (*count as f64) <= (shard_count as f64 * 0.50), - "node {} has {} shards, expected at most 50% of {}", - node, - count, - shard_count + "node {node} has {count} shards, expected at most 50% of {shard_count}" ); } // Verify the exact 17-26 range from plan §8 - for (_node, count) in &node_shard_counts { + for count in node_shard_counts.values() { assert!( (17..=26).contains(count), - "node has {} shards, expected 17-26", - count + "node has {count} shards, expected 17-26" ); } } @@ -375,16 +368,16 @@ async fn test_delete_by_ids_array_produces_independent_per_shard_calls() { // Find IDs that route to different shards let s1 = 0u32; let mut s2 = 1u32; - while shard_for_key(&format!("doc-{}", s1), shard_count) - == shard_for_key(&format!("doc-{}", s2), shard_count) + while shard_for_key(&format!("doc-{s1}"), shard_count) + == shard_for_key(&format!("doc-{s2}"), shard_count) { s2 += 1; } ( - format!("doc-{}", s1), - format!("doc-{}", s2), - shard_for_key(&format!("doc-{}", s1), shard_count), - shard_for_key(&format!("doc-{}", s2), shard_count), + format!("doc-{s1}"), + format!("doc-{s2}"), + shard_for_key(&format!("doc-{s1}"), shard_count), + shard_for_key(&format!("doc-{s2}"), shard_count), ) } else { (doc_a.to_string(), doc_b.to_string(), shard_a, shard_b) @@ -424,7 +417,7 @@ async fn test_delete_by_ids_array_produces_independent_per_shard_calls() { // Each shard should have exactly one ID for (shard_id, id_list) in &shard_id_map { - assert_eq!(id_list.len(), 1, "shard {} should have 1 ID", shard_id); + assert_eq!(id_list.len(), 1, "shard {shard_id} should have 1 ID"); } } diff --git a/crates/miroir-core/tests/p23_search_read_path.rs b/crates/miroir-core/tests/p23_search_read_path.rs index eeb8f6e..cb11524 100644 --- a/crates/miroir-core/tests/p23_search_read_path.rs +++ b/crates/miroir-core/tests/p23_search_read_path.rs @@ -230,8 +230,8 @@ async fn test_paging_no_dupes_or_gaps() { for i in 0..3 { // Only 3 nodes to ensure simple routing topo.add_node(Node::new( - NodeId::new(format!("node-{}", i)), - format!("http://node-{}:7700", i), + NodeId::new(format!("node-{i}")), + format!("http://node-{i}:7700"), 0, )); } @@ -254,7 +254,7 @@ async fn test_paging_no_dupes_or_gaps() { } client.responses.insert( - NodeId::new(format!("node-{}", i)), + NodeId::new(format!("node-{i}")), json!({ "hits": hits, "estimatedTotalHits": 17, @@ -295,7 +295,7 @@ async fn test_paging_no_dupes_or_gaps() { .await .unwrap(); - assert_eq!(result.hits.len(), 10, "Page {} should have 10 hits", page); + assert_eq!(result.hits.len(), 10, "Page {page} should have 10 hits"); for hit in &result.hits { let id = hit.get("id").unwrap().as_str().unwrap().to_string(); all_ids.push(id); @@ -313,8 +313,8 @@ async fn test_paging_no_dupes_or_gaps() { // Verify all docs from doc-000 to doc-049 are present for i in 0..50 { - let expected = format!("doc-{:03}", i); - assert!(all_ids.contains(&expected), "Missing document {}", expected); + let expected = format!("doc-{i:03}"); + assert!(all_ids.contains(&expected), "Missing document {expected}"); } } @@ -664,9 +664,7 @@ async fn test_search_read_path_integration() { .unwrap(); assert!( score_i >= score_j, - "Hits should be sorted by score descending: {} >= {}", - score_i, - score_j + "Hits should be sorted by score descending: {score_i} >= {score_j}" ); } } diff --git a/crates/miroir-core/tests/p28_api_compatibility.rs b/crates/miroir-core/tests/p28_api_compatibility.rs index 22d54a6..d4ed976 100644 --- a/crates/miroir-core/tests/p28_api_compatibility.rs +++ b/crates/miroir-core/tests/p28_api_compatibility.rs @@ -23,23 +23,19 @@ fn test_all_miroir_error_codes_have_correct_shape() { // Verify all required fields exist assert!( json_val.get("message").is_some(), - "message field missing for {:?}", - code + "message field missing for {code:?}" ); assert!( json_val.get("code").is_some(), - "code field missing for {:?}", - code + "code field missing for {code:?}" ); assert!( json_val.get("type").is_some(), - "type field missing for {:?}", - code + "type field missing for {code:?}" ); assert!( json_val.get("link").is_some(), - "link field missing for {:?}", - code + "link field missing for {code:?}" ); // Verify field types @@ -60,9 +56,7 @@ fn test_error_code_strings_have_miroir_prefix() { let code_str = code.as_str(); assert!( code_str.starts_with("miroir_"), - "Error code {:?} ({}) does not start with 'miroir_'", - code, - code_str + "Error code {code:?} ({code_str}) does not start with 'miroir_'" ); } } @@ -167,7 +161,7 @@ fn test_error_json_matches_meilisearch_shape() { /// Test 6: Error with custom metadata preserves shape. #[test] fn test_error_with_custom_metadata_preserves_shape() { - let mut err = MeilisearchError::new( + let err = MeilisearchError::new( MiroirCode::ReservedField, "document contains reserved field `_miroir_shard`", ); @@ -217,7 +211,7 @@ fn test_reserved_field_error_includes_field_name() { let field_name = "_miroir_internal"; let err = MeilisearchError::new( MiroirCode::ReservedField, - &format!("document contains reserved field `{}`", field_name), + format!("document contains reserved field `{field_name}`"), ); let json_val = serde_json::to_value(&err).expect("failed to serialize"); @@ -263,15 +257,11 @@ fn test_error_link_format_is_consistent() { let link = code.doc_link(); assert!( link.starts_with("https://github.com/jedarden/miroir/blob/main/docs/errors.md#"), - "Error code {:?} has unexpected link format: {}", - code, - link + "Error code {code:?} has unexpected link format: {link}" ); assert!( link.ends_with(code.as_str()), - "Error code {:?} link doesn't end with code: {}", - code, - link + "Error code {code:?} link doesn't end with code: {link}" ); } } diff --git a/crates/miroir-core/tests/p3_sqlite_restart.rs b/crates/miroir-core/tests/p3_sqlite_restart.rs index 2b81343..742cbc1 100644 --- a/crates/miroir-core/tests/p3_sqlite_restart.rs +++ b/crates/miroir-core/tests/p3_sqlite_restart.rs @@ -397,7 +397,7 @@ fn test_task_count_survives_restart() { { let store = open_store(path).unwrap(); for i in 0..10 { - let task = new_test_task(&format!("mtask-count-{}", i)); + let task = new_test_task(&format!("mtask-count-{i}")); store.insert_task(&task).unwrap(); } } diff --git a/crates/miroir-core/tests/p3_task_store_proptest.rs b/crates/miroir-core/tests/p3_task_store_proptest.rs index f1adbdd..4819aa9 100644 --- a/crates/miroir-core/tests/p3_task_store_proptest.rs +++ b/crates/miroir-core/tests/p3_task_store_proptest.rs @@ -82,7 +82,7 @@ proptest! { let mut inserted_tasks = Vec::new(); for i in 0..count { - let miroir_id = format!("task-{}", i); + let miroir_id = format!("task-{i}"); let mut task = new_test_task(miroir_id.clone()); task.created_at = 1714500000000 + (i as i64 * 1000); diff --git a/crates/miroir-core/tests/p43_node_drain.rs b/crates/miroir-core/tests/p43_node_drain.rs index d7075e0..a6ca88a 100644 --- a/crates/miroir-core/tests/p43_node_drain.rs +++ b/crates/miroir-core/tests/p43_node_drain.rs @@ -13,8 +13,8 @@ use tokio::sync::RwLock; use miroir_core::{ config::UnavailableShardPolicy, - migration::{MigrationConfig, MigrationCoordinator, NodeId as MigrationNodeId, ShardId}, - rebalancer::{HttpMigrationExecutor, MigrationExecutor, Rebalancer, RebalancerConfig}, + migration::MigrationConfig, + rebalancer::{MigrationExecutor, Rebalancer, RebalancerConfig}, router::assign_shard_in_group, scatter::execute_scatter, scatter::{MockNodeClient, SearchRequest}, @@ -65,7 +65,7 @@ impl DrainTestExecutor { }); stored .entry((node.to_string(), shard_id)) - .or_insert_with(Vec::new) + .or_default() .push(doc); } } @@ -119,7 +119,7 @@ impl MigrationExecutor for DrainTestExecutor { let mut stored = self.stored_docs.lock().unwrap(); let docs = stored .entry((target_node.to_string(), shard_id as u32)) - .or_insert_with(Vec::new); + .or_default(); // Deduplicate by document ID if let Some(doc_id) = doc.get("id").and_then(|v| v.as_str()) { @@ -172,7 +172,7 @@ async fn p43_drain_node_searches_still_succeed_zero_degraded() { let rf = 2; // Create 3-node topology with RF=2 - let mut topo = create_test_topology(shards, 3, rf); + let topo = create_test_topology(shards, 3, rf); let executor = Arc::new(DrainTestExecutor::default()); @@ -202,7 +202,7 @@ async fn p43_drain_node_searches_still_succeed_zero_degraded() { anti_entropy_enabled: false, }; - let mut rebalancer = Rebalancer::new(config, topo_arc.clone(), migration_config) + let rebalancer = Rebalancer::new(config, topo_arc.clone(), migration_config) .with_migration_executor(executor.clone()); // Start drain operation @@ -211,7 +211,7 @@ async fn p43_drain_node_searches_still_succeed_zero_degraded() { }; let result = rebalancer.drain_node(request).await; - assert!(result.is_ok(), "Drain should succeed: {:?}", result); + assert!(result.is_ok(), "Drain should succeed: {result:?}"); // Wait for drain to complete let mut attempts = 0; @@ -290,7 +290,7 @@ async fn p43_verify_drain_returns_zero_for_all_shards() { let docs_per_shard = 50; let rf = 2; - let mut topo = create_test_topology(shards, 3, rf); + let topo = create_test_topology(shards, 3, rf); let executor = Arc::new(DrainTestExecutor::default()); // Populate node-1 with documents for shards it's actually assigned to hold @@ -309,7 +309,7 @@ async fn p43_verify_drain_returns_zero_for_all_shards() { let config = RebalancerConfig::default(); let migration_config = MigrationConfig::default(); - let mut rebalancer = Rebalancer::new(config, topo_arc.clone(), migration_config) + let rebalancer = Rebalancer::new(config, topo_arc.clone(), migration_config) .with_migration_executor(executor.clone()); let request = miroir_core::rebalancer::DrainNodeRequest { @@ -348,8 +348,7 @@ async fn p43_verify_drain_returns_zero_for_all_shards() { let count = executor.get_stored_doc_count("node-1", shard_id); assert_eq!( count, 0, - "Shard {} should have 0 documents after drain, got {}", - shard_id, count + "Shard {shard_id} should have 0 documents after drain, got {count}" ); } } @@ -369,9 +368,7 @@ async fn p43_verify_drain_returns_zero_for_all_shards() { // We verify at least some documents were migrated (not exact count) assert!( total_docs > 0, - "Shard {} should have at least some docs on remaining nodes, got {}", - shard_id, - total_docs + "Shard {shard_id} should have at least some docs on remaining nodes, got {total_docs}" ); } } @@ -386,7 +383,7 @@ async fn p43_remove_without_drain_returns_conflict() { let shards = 64; let rf = 2; - let mut topo = create_test_topology(shards, 3, rf); + let topo = create_test_topology(shards, 3, rf); // Try to remove node-1 without draining first let topo_arc = Arc::new(RwLock::new(topo.clone())); @@ -405,11 +402,10 @@ async fn p43_remove_without_drain_returns_conflict() { // Should fail with 409 Conflict assert!(result.is_err(), "Remove without drain should fail"); let err = result.unwrap_err(); - let err_msg = format!("{}", err); + let err_msg = format!("{err}"); assert!( err_msg.contains("not in draining state") || err_msg.contains("drain"), - "Error should mention draining: {}", - err + "Error should mention draining: {err}" ); } @@ -430,7 +426,7 @@ async fn p43_force_drain_rf1_surfaces_warning() { let config = RebalancerConfig::default(); let migration_config = MigrationConfig::default(); - let mut rebalancer = Rebalancer::new(config, topo_arc.clone(), migration_config); + let rebalancer = Rebalancer::new(config, topo_arc.clone(), migration_config); // Try force drain let request = miroir_core::rebalancer::DrainNodeRequest { @@ -503,7 +499,7 @@ async fn p43_cannot_drain_last_node_in_group() { let config = RebalancerConfig::default(); let migration_config = MigrationConfig::default(); - let mut rebalancer = Rebalancer::new(config, topo_arc.clone(), migration_config); + let rebalancer = Rebalancer::new(config, topo_arc.clone(), migration_config); let request = miroir_core::rebalancer::DrainNodeRequest { node_id: "node-0".to_string(), diff --git a/crates/miroir-core/tests/router_proptest.rs b/crates/miroir-core/tests/router_proptest.rs index 37bf629..a559e03 100644 --- a/crates/miroir-core/tests/router_proptest.rs +++ b/crates/miroir-core/tests/router_proptest.rs @@ -22,7 +22,7 @@ proptest! { rf in 1usize..4, ) { let nodes: Vec = (0..node_count) - .map(|i| NodeId::new(format!("node-{}", i))) + .map(|i| NodeId::new(format!("node-{i}"))) .collect(); let rf = rf.min(node_count); @@ -43,7 +43,7 @@ proptest! { rf in 1usize..4, ) { let nodes: Vec = (0..node_count) - .map(|i| NodeId::new(format!("node-{}", i))) + .map(|i| NodeId::new(format!("node-{i}"))) .collect(); let rf = rf.min(node_count); @@ -93,7 +93,7 @@ proptest! { rf in 1usize..3, ) { let nodes_old: Vec = (0..node_count) - .map(|i| NodeId::new(format!("node-{}", i))) + .map(|i| NodeId::new(format!("node-{i}"))) .collect(); let mut nodes_new = nodes_old.clone(); @@ -146,7 +146,7 @@ proptest! { rf in 1usize..3, ) { let nodes_all: Vec = (0..node_count) - .map(|i| NodeId::new(format!("node-{}", i))) + .map(|i| NodeId::new(format!("node-{i}"))) .collect(); let nodes_removed: Vec = nodes_all[..node_count - 1].to_vec(); @@ -199,7 +199,7 @@ proptest! { rf in 1usize..3, ) { let nodes: Vec = (0..node_count) - .map(|i| NodeId::new(format!("node-{}", i))) + .map(|i| NodeId::new(format!("node-{i}"))) .collect(); let rf = rf.min(node_count); @@ -241,7 +241,7 @@ proptest! { rf in 1usize..5, ) { let nodes: Vec = (0..node_count) - .map(|i| NodeId::new(format!("node-{}", i))) + .map(|i| NodeId::new(format!("node-{i}"))) .collect(); let rf = rf.min(node_count); @@ -260,7 +260,7 @@ proptest! { rf in 1usize..5, ) { let nodes: Vec = (0..node_count) - .map(|i| NodeId::new(format!("node-{}", i))) + .map(|i| NodeId::new(format!("node-{i}"))) .collect(); let rf = rf.min(node_count); @@ -286,7 +286,7 @@ proptest! { rf in 1usize..5, ) { let nodes: Vec = (0..node_count) - .map(|i| NodeId::new(format!("node-{}", i))) + .map(|i| NodeId::new(format!("node-{i}"))) .collect(); let rf = rf.min(node_count); @@ -334,8 +334,7 @@ mod regression_tests { let actual = shard_for_key(key, shard_count); assert_eq!( actual, expected, - "shard_for_key({:?}, {})", - key, shard_count + "shard_for_key({key:?}, {shard_count})" ); } } diff --git a/crates/miroir-ctl/src/credentials.rs b/crates/miroir-ctl/src/credentials.rs index 31ec83c..76f1132 100644 --- a/crates/miroir-ctl/src/credentials.rs +++ b/crates/miroir-ctl/src/credentials.rs @@ -34,9 +34,9 @@ pub enum CredentialError { impl std::fmt::Display for CredentialError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - CredentialError::NotFound(msg) => write!(f, "Credential not found: {}", msg), - CredentialError::IoError(e) => write!(f, "IO error: {}", e), - CredentialError::ParseError(msg) => write!(f, "Parse error: {}", msg), + CredentialError::NotFound(msg) => write!(f, "Credential not found: {msg}"), + CredentialError::IoError(e) => write!(f, "IO error: {e}"), + CredentialError::ParseError(msg) => write!(f, "Parse error: {msg}"), } } } @@ -94,7 +94,7 @@ fn load_from_credentials_file() -> Result, CredentialError> { let contents = fs::read_to_string(path).map_err(CredentialError::IoError)?; let creds: CredentialsFile = toml::from_str(&contents) - .map_err(|e| CredentialError::ParseError(format!("Invalid TOML: {}", e)))?; + .map_err(|e| CredentialError::ParseError(format!("Invalid TOML: {e}")))?; if let Some(profile) = creds.default { if let Some(key) = profile.admin_api_key { diff --git a/crates/miroir-proxy/src/admin_session.rs b/crates/miroir-proxy/src/admin_session.rs index 46348ee..7b03a04 100644 --- a/crates/miroir-proxy/src/admin_session.rs +++ b/crates/miroir-proxy/src/admin_session.rs @@ -292,7 +292,7 @@ mod tests { fn too_short_cookie_fails() { let key = test_key(); // Only 10 bytes — shorter than nonce + tag - let short = URL_SAFE_NO_PAD.encode(&[0u8; 10]); + let short = URL_SAFE_NO_PAD.encode([0u8; 10]); assert_eq!( unseal_session(&short, &key).unwrap_err(), SealError::MalformedCookie diff --git a/crates/miroir-proxy/src/auth.rs b/crates/miroir-proxy/src/auth.rs index 7546004..437a4cb 100644 --- a/crates/miroir-proxy/src/auth.rs +++ b/crates/miroir-proxy/src/auth.rs @@ -10,7 +10,7 @@ //! the rotation overlap window. Validation accepts either secret. use axum::{ - extract::{FromRef, Request, State}, + extract::{Request, State}, http::{HeaderMap, Method}, middleware::Next, response::{IntoResponse, Response}, @@ -99,14 +99,14 @@ pub fn jwt_encode(header: &JwtHeader, claims: &JwtClaims, secret: &[u8]) -> Resu let header_b64 = URL_SAFE_NO_PAD.encode(header_json.as_bytes()); let payload_b64 = URL_SAFE_NO_PAD.encode(claims_json.as_bytes()); - let signing_input = format!("{}.{}", header_b64, payload_b64); + let signing_input = format!("{header_b64}.{payload_b64}"); - let mut mac = HmacSha256::new_from_slice(secret).map_err(|e| format!("HMAC init: {}", e))?; + let mut mac = HmacSha256::new_from_slice(secret).map_err(|e| format!("HMAC init: {e}"))?; mac.update(signing_input.as_bytes()); let sig = mac.finalize().into_bytes(); let sig_b64 = URL_SAFE_NO_PAD.encode(sig); - Ok(format!("{}.{}.{}", header_b64, payload_b64, sig_b64)) + Ok(format!("{header_b64}.{payload_b64}.{sig_b64}")) } /// Decode and verify a JWT with the given secret. Returns (header, claims). @@ -329,7 +329,7 @@ impl std::error::Error for JwtValidationError {} pub fn generate_csrf_token() -> String { let mut bytes = [0u8; 32]; rand::rngs::OsRng.fill_bytes(&mut bytes); - URL_SAFE_NO_PAD.encode(&bytes) + URL_SAFE_NO_PAD.encode(bytes) } /// Extract the CSRF token from the `X-CSRF-Token` header. @@ -420,7 +420,7 @@ pub fn validate_origin( if allowed == "same-origin" { if let Some(host) = headers.get("host").and_then(|h| h.to_str().ok()) { // Construct origin from scheme (https) and host - let same_origin = format!("https://{}", host); + let same_origin = format!("https://{host}"); if provided_origin == same_origin || provided_origin == host { return OriginVerdict::Allowed; } @@ -1072,7 +1072,7 @@ fn epoch_seconds() -> u64 { #[cfg(test)] mod tests { use super::*; - use axum::http::StatusCode; + fn test_key() -> SealKey { SealKey::from_bytes([42u8; 32]) @@ -1893,10 +1893,7 @@ mod tests { let ratio = all_wrong_duration.as_secs_f64() / one_wrong_duration.as_secs_f64(); assert!( ratio > 0.5 && ratio < 2.0, - "Timing ratio {} suggests non-constant-time comparison: all_wrong={:?}, one_wrong={:?}", - ratio, - all_wrong_duration, - one_wrong_duration, + "Timing ratio {ratio} suggests non-constant-time comparison: all_wrong={all_wrong_duration:?}, one_wrong={one_wrong_duration:?}", ); } @@ -1989,9 +1986,7 @@ mod tests { for (method, path) in cases { assert!( is_dispatch_exempt(&method, path), - "Expected ({}, {}) to be dispatch-exempt", - method, - path, + "Expected ({method}, {path}) to be dispatch-exempt", ); } } diff --git a/crates/miroir-proxy/src/client.rs b/crates/miroir-proxy/src/client.rs index dbac920..004802a 100644 --- a/crates/miroir-proxy/src/client.rs +++ b/crates/miroir-proxy/src/client.rs @@ -91,7 +91,7 @@ impl NodeClient for HttpClient { if let Some(global_idf) = &request.global_idf { body["_miroir_global_idf"] = serde_json::to_value(global_idf).map_err(|e| { - NodeError::NetworkError(format!("Failed to serialize global_idf: {}", e)) + NodeError::NetworkError(format!("Failed to serialize global_idf: {e}")) })?; } @@ -109,14 +109,14 @@ impl NodeClient for HttpClient { error = %e, "node call failed" ); - NodeError::NetworkError(format!("Request failed: {}", e)) + NodeError::NetworkError(format!("Request failed: {e}")) })?; let status = response.status(); let body_text = response .text() .await - .map_err(|e| NodeError::NetworkError(format!("Failed to read response: {}", e)))?; + .map_err(|e| NodeError::NetworkError(format!("Failed to read response: {e}")))?; let duration_ms = start.elapsed().as_millis() as u64; @@ -140,7 +140,7 @@ impl NodeClient for HttpClient { ); serde_json::from_str(&body_text) - .map_err(|e| NodeError::NetworkError(format!("Failed to parse JSON response: {}", e))) + .map_err(|e| NodeError::NetworkError(format!("Failed to parse JSON response: {e}"))) } async fn write_documents( @@ -179,13 +179,13 @@ impl NodeClient for HttpClient { let response = req_builder .send() .await - .map_err(|e| NodeError::NetworkError(format!("Request failed: {}", e)))?; + .map_err(|e| NodeError::NetworkError(format!("Request failed: {e}")))?; let status = response.status(); let body_text = response .text() .await - .map_err(|e| NodeError::NetworkError(format!("Failed to read response: {}", e)))?; + .map_err(|e| NodeError::NetworkError(format!("Failed to read response: {e}")))?; if !status.is_success() { // Try to parse as Meilisearch error @@ -215,7 +215,7 @@ impl NodeClient for HttpClient { // Parse successful response let json: Value = serde_json::from_str(&body_text).map_err(|e| { - NodeError::NetworkError(format!("Failed to parse JSON response: {}", e)) + NodeError::NetworkError(format!("Failed to parse JSON response: {e}")) })?; let duration_ms = start.elapsed().as_millis() as u64; @@ -263,13 +263,13 @@ impl NodeClient for HttpClient { .json(&request.ids) .send() .await - .map_err(|e| NodeError::NetworkError(format!("Request failed: {}", e)))?; + .map_err(|e| NodeError::NetworkError(format!("Request failed: {e}")))?; let status = response.status(); let body_text = response .text() .await - .map_err(|e| NodeError::NetworkError(format!("Failed to read response: {}", e)))?; + .map_err(|e| NodeError::NetworkError(format!("Failed to read response: {e}")))?; let duration_ms = start.elapsed().as_millis() as u64; tracing::debug!( @@ -310,7 +310,7 @@ impl NodeClient for HttpClient { // Parse successful response let json: Value = serde_json::from_str(&body_text).map_err(|e| { - NodeError::NetworkError(format!("Failed to parse JSON response: {}", e)) + NodeError::NetworkError(format!("Failed to parse JSON response: {e}")) })?; Ok(DeleteResponse { @@ -351,13 +351,13 @@ impl NodeClient for HttpClient { .json(&request.filter) .send() .await - .map_err(|e| NodeError::NetworkError(format!("Request failed: {}", e)))?; + .map_err(|e| NodeError::NetworkError(format!("Request failed: {e}")))?; let status = response.status(); let body_text = response .text() .await - .map_err(|e| NodeError::NetworkError(format!("Failed to read response: {}", e)))?; + .map_err(|e| NodeError::NetworkError(format!("Failed to read response: {e}")))?; let duration_ms = start.elapsed().as_millis() as u64; tracing::debug!( @@ -398,7 +398,7 @@ impl NodeClient for HttpClient { // Parse successful response let json: Value = serde_json::from_str(&body_text).map_err(|e| { - NodeError::NetworkError(format!("Failed to parse JSON response: {}", e)) + NodeError::NetworkError(format!("Failed to parse JSON response: {e}")) })?; Ok(DeleteResponse { @@ -437,7 +437,7 @@ impl NodeClient for HttpClient { .header("Authorization", format!("Bearer {}", self.master_key)) .send() .await - .map_err(|e| NodeError::NetworkError(format!("Stats request failed: {}", e)))?; + .map_err(|e| NodeError::NetworkError(format!("Stats request failed: {e}")))?; if !stats_resp.status().is_success() { // Index not found or node unreachable — return empty stats @@ -451,7 +451,7 @@ impl NodeClient for HttpClient { let stats_body: Value = stats_resp .json() .await - .map_err(|e| NodeError::NetworkError(format!("Failed to parse stats: {}", e)))?; + .map_err(|e| NodeError::NetworkError(format!("Failed to parse stats: {e}")))?; let total_docs = stats_body .get("numberOfDocuments") @@ -471,11 +471,11 @@ impl NodeClient for HttpClient { .json(&search_body) .send() .await - .map_err(|e| NodeError::NetworkError(format!("DF search failed: {}", e)))?; + .map_err(|e| NodeError::NetworkError(format!("DF search failed: {e}")))?; if search_resp.status().is_success() { let body: Value = search_resp.json().await.map_err(|e| { - NodeError::NetworkError(format!("Failed to parse DF response: {}", e)) + NodeError::NetworkError(format!("Failed to parse DF response: {e}")) })?; let df = body .get("estimatedTotalHits") @@ -522,16 +522,16 @@ impl NodeClient for HttpClient { async move { let response = client .get(&url) - .header("Authorization", format!("Bearer {}", master_key)) + .header("Authorization", format!("Bearer {master_key}")) .send() .await - .map_err(|e| NodeError::NetworkError(format!("Request failed: {}", e)))?; + .map_err(|e| NodeError::NetworkError(format!("Request failed: {e}")))?; let status = response.status(); let body_text = response .text() .await - .map_err(|e| NodeError::NetworkError(format!("Failed to read response: {}", e)))?; + .map_err(|e| NodeError::NetworkError(format!("Failed to read response: {e}")))?; if !status.is_success() { return Err(NodeError::HttpError { @@ -542,7 +542,7 @@ impl NodeClient for HttpClient { // Parse successful response let json: Value = serde_json::from_str(&body_text).map_err(|e| { - NodeError::NetworkError(format!("Failed to parse JSON response: {}", e)) + NodeError::NetworkError(format!("Failed to parse JSON response: {e}")) })?; Ok(TaskStatusResponse { @@ -576,7 +576,7 @@ impl miroir_core::group_sync_worker::SyncNodeClient for HttpClient { ) -> std::result::Result { let url = self.documents_url(address, &request.index_uid); let filter_json = serde_json::to_string(&request.filter) - .map_err(|e| format!("Failed to serialize filter: {}", e))?; + .map_err(|e| format!("Failed to serialize filter: {e}"))?; let response = self .client @@ -589,19 +589,19 @@ impl miroir_core::group_sync_worker::SyncNodeClient for HttpClient { ]) .send() .await - .map_err(|e| format!("Request failed: {}", e))?; + .map_err(|e| format!("Request failed: {e}"))?; let status = response.status(); let body_text = response .text() .await - .map_err(|e| format!("Failed to read response: {}", e))?; + .map_err(|e| format!("Failed to read response: {e}"))?; if !status.is_success() { return Err(format!("HTTP {}: {}", status.as_u16(), body_text)); } - serde_json::from_str(&body_text).map_err(|e| format!("Failed to parse JSON: {}", e)) + serde_json::from_str(&body_text).map_err(|e| format!("Failed to parse JSON: {e}")) } async fn write_documents( @@ -620,13 +620,13 @@ impl miroir_core::group_sync_worker::SyncNodeClient for HttpClient { .json(&documents) .send() .await - .map_err(|e| format!("Request failed: {}", e))?; + .map_err(|e| format!("Request failed: {e}"))?; let status = response.status(); let body_text = response .text() .await - .map_err(|e| format!("Failed to read response: {}", e))?; + .map_err(|e| format!("Failed to read response: {e}"))?; if !status.is_success() { return Err(format!("HTTP {}: {}", status.as_u16(), body_text)); diff --git a/crates/miroir-proxy/src/middleware.rs b/crates/miroir-proxy/src/middleware.rs index ddc066a..6749732 100644 --- a/crates/miroir-proxy/src/middleware.rs +++ b/crates/miroir-proxy/src/middleware.rs @@ -6,14 +6,12 @@ use async_trait::async_trait; use axum::http::request::Parts; use axum::{ extract::{FromRequestParts, Request, State}, - http::{HeaderMap, HeaderValue, StatusCode}, + http::{HeaderMap, HeaderValue}, middleware::Next, - response::{IntoResponse, Response}, - routing::get, - Extension, Router, + response::Response, + routing::get, Router, }; -use hex; use miroir_core::config::MiroirConfig; use prometheus::{ Counter, CounterVec, Encoder, Gauge, GaugeVec, Histogram, HistogramOpts, HistogramVec, Opts, @@ -31,6 +29,12 @@ use uuid::Uuid; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct RequestId(pub String); +impl Default for RequestId { + fn default() -> Self { + Self::new() + } +} + impl RequestId { /// Create a new RequestId from a UUIDv7. /// @@ -67,13 +71,9 @@ impl RequestId { /// Extracted from the `X-Miroir-Session` header and stored in request extensions. /// Handlers can access it via `Request.extensions().get::()`. #[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Default)] pub struct SessionId(pub String); -impl Default for SessionId { - fn default() -> Self { - Self(String::new()) - } -} impl SessionId { /// Get the inner session ID string. @@ -142,7 +142,7 @@ pub async fn request_id_middleware(mut req: Request, next: Next) -> Response { .get("x-request-id") .and_then(|v| v.to_str().ok()) .and_then(|s| RequestId::parse(s.to_string())) - .unwrap_or_else(RequestId::new); + .unwrap_or_default(); // Store in request extensions for handler access req.extensions_mut().insert(request_id.clone()); @@ -1532,9 +1532,9 @@ impl Metrics { let metric_families = self.registry.gather(); let mut buffer = Vec::new(); encoder.encode(&metric_families, &mut buffer)?; - Ok(String::from_utf8(buffer).map_err(|e| { - prometheus::Error::Msg(format!("failed to convert metrics to UTF-8: {}", e)) - })?) + String::from_utf8(buffer).map_err(|e| { + prometheus::Error::Msg(format!("failed to convert metrics to UTF-8: {e}")) + }) } pub fn admin_session_key_generated(&self) -> Gauge { @@ -1562,7 +1562,7 @@ pub fn generate_request_id() -> String { let hash = hasher.finish(); // Encode as hex (16 chars = 64 bits) - format!("{:016x}", hash) + format!("{hash:016x}") } /// Extension trait to add request ID extraction utilities. @@ -1695,7 +1695,7 @@ pub async fn telemetry_middleware( // Base fields: timestamp (from tracing-subscriber), level, message, duration_ms // Additional fields (index, node_count, estimated_hits, degraded) // are added by request handlers via the tracing span. - let message = format!("{} {}", method, status); + let message = format!("{method} {status}"); if status.is_server_error() { tracing::error!( target: "miroir.request", @@ -1753,7 +1753,7 @@ async fn metrics_handler(State(metrics): State) -> Response { Ok(metrics) => metrics, Err(e) => { tracing::error!(error = %e, "failed to encode metrics"); - format!("# ERROR: failed to encode metrics: {}\n", e) + format!("# ERROR: failed to encode metrics: {e}\n") } }; @@ -2516,7 +2516,7 @@ mod tests { "miroir_rebalance_duration_seconds", ]; for name in &expected_metrics { - assert!(output.contains(name), "missing metric: {}", name); + assert!(output.contains(name), "missing metric: {name}"); } // With defaults (all §13 enabled), advanced metrics should be present @@ -2574,7 +2574,7 @@ mod tests { "miroir_search_ui_p95_ms", ]; for name in &advanced_metrics { - assert!(output.contains(name), "missing advanced metric: {}", name); + assert!(output.contains(name), "missing advanced metric: {name}"); } } @@ -2625,8 +2625,7 @@ mod tests { for name in &advanced_names { assert!( !encoded.contains(name), - "advanced metric should not appear when disabled: {}", - name + "advanced metric should not appear when disabled: {name}" ); } } @@ -2881,7 +2880,7 @@ mod tests { fn test_json_log_format_is_valid() { // Verify that tracing-subscriber's JSON layer produces valid JSON // This test ensures the log format matches plan §10 requirements - use tracing_subscriber::fmt::format::FmtSpan; + // Build a JSON subscriber like the one in main.rs let subscriber = tracing_subscriber::fmt() diff --git a/crates/miroir-proxy/src/routes/admin.rs b/crates/miroir-proxy/src/routes/admin.rs index 0429c47..9032505 100644 --- a/crates/miroir-proxy/src/routes/admin.rs +++ b/crates/miroir-proxy/src/routes/admin.rs @@ -6,7 +6,7 @@ use super::{admin_endpoints, aliases, canary, cdc, dumps, explain, session}; use crate::admin_ui; use axum::{ extract::FromRef, - routing::{delete, get, patch, post, put}, + routing::{delete, get, post, put}, Router, }; diff --git a/crates/miroir-proxy/src/routes/admin_endpoints.rs b/crates/miroir-proxy/src/routes/admin_endpoints.rs index d4ec7ed..76b2453 100644 --- a/crates/miroir-proxy/src/routes/admin_endpoints.rs +++ b/crates/miroir-proxy/src/routes/admin_endpoints.rs @@ -8,21 +8,20 @@ use axum::{ }; use miroir_core::{ config::MiroirConfig, - group_addition::{GroupAdditionCoordinator, GroupAdditionId}, + group_addition::GroupAdditionCoordinator, group_sync_worker::GroupSyncWorker, leader_election::{LeaderElection, LeaderElectionMetricsCallback}, migration::{MigrationConfig, MigrationCoordinator}, mode_a_coordinator::ModeACoordinator, mode_c_worker::{ModeCWorker, ModeCWorkerConfig}, peer_discovery::PeerDiscovery, - rebalancer::{MigrationExecutor, Rebalancer, RebalancerConfig, RebalancerMetrics}, + rebalancer::{Rebalancer, RebalancerConfig, RebalancerMetrics}, rebalancer_worker::{ RebalancerMetricsCallback, RebalancerWorker, RebalancerWorkerConfig, TopologyChangeEvent, }, replica_selection::{ReplicaSelector, SelectionObserver}, reshard::ReshardingRegistry, router, - scatter::{DeleteByFilterRequest, FetchDocumentsRequest, WriteRequest}, task_registry::TaskRegistryImpl, task_store::{NewAdminSession, RedisTaskStore, TaskStore}, topology::{Node, NodeId, Topology}, @@ -114,9 +113,9 @@ impl VersionState { { let cache = self.version_cache.read().await; let last_update = self.last_cache_update.read().await; - if let (Some(ref cached), Some(last)) = (cache.as_ref(), last_update.as_ref()) { + if let (Some(cached), Some(last)) = (cache.as_ref(), last_update.as_ref()) { if last.elapsed().as_secs() < self.cache_ttl_secs { - return Ok((**cached).clone()); + return Ok((*cached).clone()); } } } @@ -967,7 +966,7 @@ impl AppState { // For each replica group, check if we have enough healthy nodes for group in topo.groups() { let healthy = group.healthy_nodes(&node_map); - let required = (topo.rf() + 1) / 2; // Simple majority for quorum + let required = topo.rf().div_ceil(2); // Simple majority for quorum if healthy.len() < required { return false; } @@ -1227,8 +1226,7 @@ pub fn parse_rate_limit(s: &str) -> Result<(u64, u64), String> { let parts: Vec<&str> = s.split('/').collect(); if parts.len() != 2 { return Err(format!( - "invalid rate limit format: '{}', expected 'N/UNIT'", - s + "invalid rate limit format: '{s}', expected 'N/UNIT'" )); } let limit: u64 = parts[0] @@ -1241,8 +1239,7 @@ pub fn parse_rate_limit(s: &str) -> Result<(u64, u64), String> { "day" | "d" => 86400, unit => { return Err(format!( - "invalid time unit: '{}', expected second/minute/hour/day", - unit + "invalid time unit: '{unit}', expected second/minute/hour/day" )) } }; @@ -1253,7 +1250,7 @@ pub fn parse_rate_limit(s: &str) -> Result<(u64, u64), String> { fn generate_session_id() -> String { let mut bytes = [0u8; 24]; rand::rngs::OsRng.fill_bytes(&mut bytes); - hex::encode(&bytes) + hex::encode(bytes) } /// POST /_miroir/admin/login — admin login with rate limiting and exponential backoff. @@ -1330,8 +1327,7 @@ where Json(AdminLoginResponse { success: false, message: Some(format!( - "Too many failed login attempts. Try again in {} seconds.", - ws + "Too many failed login attempts. Try again in {ws} seconds." )), csrf_token: None, }), @@ -1381,8 +1377,7 @@ where success: false, message: if let Some(ws) = wait_seconds { Some(format!( - "Too many failed login attempts. Try again in {} seconds.", - ws + "Too many failed login attempts. Try again in {ws} seconds." )) } else { Some("Too many login attempts. Please try again later.".into()) @@ -1572,8 +1567,7 @@ where [( "Set-Cookie", format!( - "{}=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0", - COOKIE_NAME + "{COOKIE_NAME}=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0" ), )], Json(AdminLogoutResponse { @@ -1591,8 +1585,7 @@ where [( "Set-Cookie", format!( - "{}=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0", - COOKIE_NAME + "{COOKIE_NAME}=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0" ), )], Json(AdminLogoutResponse { @@ -1641,8 +1634,7 @@ where [( "Set-Cookie", format!( - "{}=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0", - COOKIE_NAME + "{COOKIE_NAME}=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0" ), )], Json(AdminLogoutResponse { @@ -1722,7 +1714,7 @@ where if topo.node(&node_id).is_some() { return Err(( StatusCode::BAD_REQUEST, - format!("Node {} already exists", id), + format!("Node {id} already exists"), )); } // Check if replica group exists @@ -1730,7 +1722,7 @@ where if replica_group >= group_count { return Err(( StatusCode::BAD_REQUEST, - format!("Replica group {} does not exist", replica_group), + format!("Replica group {replica_group} does not exist"), )); } let node = Node::new(node_id, address, replica_group); @@ -1749,7 +1741,7 @@ where error!(error = %e, node_id = %id, "failed to send NodeAdded event to rebalancer worker"); return Err(( StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to queue rebalancing: {}", e), + format!("Failed to queue rebalancing: {e}"), )); } } @@ -1803,7 +1795,7 @@ where let topo = app_state.topology.read().await; let node = topo .node(&node_id_obj) - .ok_or_else(|| (StatusCode::NOT_FOUND, format!("Node {} not found", node_id)))?; + .ok_or_else(|| (StatusCode::NOT_FOUND, format!("Node {node_id} not found")))?; // Check if this is the last node in the group let group = topo @@ -1830,10 +1822,8 @@ where return Err(( StatusCode::BAD_REQUEST, format!( - "Node {} is not in draining state (current: {:?}), use force=true to bypass", - node_id, node_status - ) - .into(), + "Node {node_id} is not in draining state (current: {node_status:?}), use force=true to bypass" + ), )); } @@ -1890,7 +1880,7 @@ where let node_id_obj = NodeId::new(node_id.clone()); let node = topo .node(&node_id_obj) - .ok_or_else(|| (StatusCode::NOT_FOUND, format!("Node {} not found", node_id)))?; + .ok_or_else(|| (StatusCode::NOT_FOUND, format!("Node {node_id} not found")))?; // Check if this is the last node in the group let group = topo @@ -1932,7 +1922,7 @@ where error!(error = %e, node_id = %node_id, "failed to send NodeDraining event to rebalancer worker"); return Err(( StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to queue drain: {}", e), + format!("Failed to queue drain: {e}"), )); } @@ -1981,7 +1971,7 @@ where let node_id_obj = NodeId::new(node_id.clone()); let node = topo .node(&node_id_obj) - .ok_or_else(|| (StatusCode::NOT_FOUND, format!("Node {} not found", node_id)))?; + .ok_or_else(|| (StatusCode::NOT_FOUND, format!("Node {node_id} not found")))?; let replica_group = node.replica_group; @@ -2004,7 +1994,7 @@ where error!(error = %e, node_id = %node_id, "failed to send NodeFailed event to rebalancer worker"); return Err(( StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to queue node failure: {}", e), + format!("Failed to queue node failure: {e}"), )); } @@ -2043,7 +2033,7 @@ where let node_id_obj = NodeId::new(node_id.clone()); let node = topo .node(&node_id_obj) - .ok_or_else(|| (StatusCode::NOT_FOUND, format!("Node {} not found", node_id)))?; + .ok_or_else(|| (StatusCode::NOT_FOUND, format!("Node {node_id} not found")))?; let replica_group = node.replica_group; @@ -2066,7 +2056,7 @@ where error!(error = %e, node_id = %node_id, "failed to send NodeRecovered event to rebalancer worker"); return Err(( StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to queue node recovery: {}", e), + format!("Failed to queue node recovery: {e}"), )); } @@ -2181,7 +2171,7 @@ where ); return Err(( StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to trigger rebalance: {}", e), + format!("Failed to trigger rebalance: {e}"), )); } @@ -2259,7 +2249,7 @@ where } for (&shard_id, shard_state) in job.shards.iter() { - let pct_complete = if job.shards.len() > 0 { + let pct_complete = if !job.shards.is_empty() { let completed = job .shards .values() @@ -2434,7 +2424,7 @@ where .map_err(|e| { ( StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to start group addition: {}", e), + format!("Failed to start group addition: {e}"), ) })?; @@ -2442,7 +2432,7 @@ where coord.begin_sync(addition_id).map_err(|e| { ( StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to start sync: {}", e), + format!("Failed to start sync: {e}"), ) })?; @@ -2488,7 +2478,7 @@ where .ok_or_else(|| { ( StatusCode::NOT_FOUND, - format!("No active addition for group {}", group_id), + format!("No active addition for group {group_id}"), ) })?; @@ -2521,7 +2511,7 @@ where "complete": complete, "failed": failed, }, - "started_at": addition.started_at.map(|t| format!("{:?}", t)), + "started_at": addition.started_at.map(|t| format!("{t:?}")), }))) } @@ -2566,8 +2556,7 @@ where ( StatusCode::PRECONDITION_FAILED, format!( - "Group {} is not ready for activation (sync not complete)", - group_id + "Group {group_id} is not ready for activation (sync not complete)" ), ) })?; @@ -2588,13 +2577,13 @@ where let source_group = topo.group(source_group_id).ok_or_else(|| { ( StatusCode::INTERNAL_SERVER_ERROR, - format!("Source group {} not found", source_group_id), + format!("Source group {source_group_id} not found"), ) })?; let new_group = topo.group(group_id).ok_or_else(|| { ( StatusCode::INTERNAL_SERVER_ERROR, - format!("New group {} not found", group_id), + format!("New group {group_id} not found"), ) })?; @@ -2606,13 +2595,13 @@ where if source_nodes.is_empty() { return Err(( StatusCode::PRECONDITION_FAILED, - format!("No healthy nodes in source group {}", source_group_id), + format!("No healthy nodes in source group {source_group_id}"), )); } if new_nodes.is_empty() { return Err(( StatusCode::PRECONDITION_FAILED, - format!("No healthy nodes in new group {}", group_id), + format!("No healthy nodes in new group {group_id}"), )); } @@ -2631,7 +2620,7 @@ where .map_err(|e| { ( StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to create HTTP client: {}", e), + format!("Failed to create HTTP client: {e}"), ) })?; @@ -2660,7 +2649,7 @@ where tracing::error!(error = %e, "Failed to fetch stats from source node"); ( StatusCode::SERVICE_UNAVAILABLE, - format!("Failed to fetch stats from source node: {}", e), + format!("Failed to fetch stats from source node: {e}"), ) })? .json() @@ -2669,7 +2658,7 @@ where tracing::error!(error = %e, "Failed to parse stats from source node"); ( StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to parse stats from source node: {}", e), + format!("Failed to parse stats from source node: {e}"), ) })?; @@ -2686,7 +2675,7 @@ where tracing::error!(error = %e, "Failed to fetch stats from new group node"); ( StatusCode::SERVICE_UNAVAILABLE, - format!("Failed to fetch stats from new group node: {}", e), + format!("Failed to fetch stats from new group node: {e}"), ) })? .json() @@ -2695,7 +2684,7 @@ where tracing::error!(error = %e, "Failed to parse stats from new group node"); ( StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to parse stats from new group node: {}", e), + format!("Failed to parse stats from new group node: {e}"), ) })?; @@ -2711,11 +2700,7 @@ where // Calculate variance percentage (allowing for writes during sync) let variance = if source_count > 0 { - let diff = if source_count > new_count { - source_count - new_count - } else { - new_count - source_count - }; + let diff = source_count.abs_diff(new_count); (diff as f64 / source_count as f64) * 100.0 } else { 0.0 @@ -2727,8 +2712,7 @@ where return Err(( StatusCode::PRECONDITION_FAILED, format!( - "Verification failed: new group has {} docs, source has {} docs (variance: {:.3}%) - must be within {:.1}%", - new_count, source_count, variance, MAX_VARIANCE_PERCENT + "Verification failed: new group has {new_count} docs, source has {source_count} docs (variance: {variance:.3}%) - must be within {MAX_VARIANCE_PERCENT:.1}%" ), )); } @@ -2748,7 +2732,7 @@ where coord.mark_group_active(addition_id).map_err(|e| { ( StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to activate group: {}", e), + format!("Failed to activate group: {e}"), ) })?; } @@ -2926,10 +2910,7 @@ where let mut filtered = diffs; if let Some(target) = ¶ms.target { - filtered = filtered - .into_iter() - .filter(|d| &d.target == target) - .collect(); + filtered.retain(|d| &d.target == target); } Ok(Json(serde_json::json!({ @@ -3049,16 +3030,15 @@ where for key in obj.keys() { let requires_restart = RESTART_REQUIRED_FIELDS .iter() - .any(|field| key.starts_with(&format!("{}.", field)) || key == *field); + .any(|field| key.starts_with(&format!("{field}.")) || key == *field); if requires_restart { return Err(( StatusCode::BAD_REQUEST, format!( - "Cannot modify '{}' at runtime. \ + "Cannot modify '{key}' at runtime. \ This setting requires a pod restart to take effect. \ - Please update the configuration file and restart the pod.", - key + Please update the configuration file and restart the pod." ), )); } @@ -3070,7 +3050,7 @@ where let merged_json = serde_json::to_value(&config).map_err(|e| { ( StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to serialize current config: {}", e), + format!("Failed to serialize current config: {e}"), ) })?; @@ -3078,7 +3058,7 @@ where let updated_config: MiroirConfig = serde_json::from_value(merged_json).map_err(|e| { ( StatusCode::BAD_REQUEST, - format!("Invalid configuration: {}", e), + format!("Invalid configuration: {e}"), ) })?; @@ -3086,7 +3066,7 @@ where if let Err(e) = updated_config.validate() { return Err(( StatusCode::BAD_REQUEST, - format!("Configuration validation failed: {}", e), + format!("Configuration validation failed: {e}"), )); } @@ -3396,7 +3376,7 @@ where }); Ok(Json(ReshardResponse { - operation_id: format!("reshard-{}-{}", index_uid, now), + operation_id: format!("reshard-{index_uid}-{now}"), index_uid, old_shards, new_shards: req.new_shards, diff --git a/crates/miroir-proxy/src/routes/aliases.rs b/crates/miroir-proxy/src/routes/aliases.rs index ae55300..7915af6 100644 --- a/crates/miroir-proxy/src/routes/aliases.rs +++ b/crates/miroir-proxy/src/routes/aliases.rs @@ -7,7 +7,7 @@ use axum::{ Json, }; use miroir_core::{ - alias::{Alias, AliasKind}, + alias::AliasKind, config::MiroirConfig, task_store::TaskStore, }; @@ -152,8 +152,7 @@ where Json(ErrorResponse { code: "alias_exists_ilm_managed".to_string(), message: format!( - "alias '{}' exists and is managed by ILM policy; use ILM API to modify", - name + "alias '{name}' exists and is managed by ILM policy; use ILM API to modify" ), }), )); @@ -179,7 +178,7 @@ where StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { code: "alias_creation_failed".to_string(), - message: format!("failed to create alias: {}", e), + message: format!("failed to create alias: {e}"), }), ) })?; @@ -229,7 +228,7 @@ where StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { code: "alias_lookup_failed".to_string(), - message: format!("failed to lookup alias: {}", e), + message: format!("failed to lookup alias: {e}"), }), ) })?; @@ -259,7 +258,7 @@ where StatusCode::NOT_FOUND, Json(ErrorResponse { code: "alias_not_found".to_string(), - message: format!("alias '{}' not found", name), + message: format!("alias '{name}' not found"), }), )), } @@ -307,7 +306,7 @@ where StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { code: "alias_lookup_failed".to_string(), - message: format!("failed to lookup alias: {}", e), + message: format!("failed to lookup alias: {e}"), }), ) })?; @@ -317,7 +316,7 @@ where StatusCode::NOT_FOUND, Json(ErrorResponse { code: "alias_not_found".to_string(), - message: format!("alias '{}' not found", name), + message: format!("alias '{name}' not found"), }), ) })?; @@ -351,7 +350,7 @@ where StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { code: "alias_flip_failed".to_string(), - message: format!("failed to flip alias: {}", e), + message: format!("failed to flip alias: {e}"), }), ) })?; @@ -367,7 +366,7 @@ where StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { code: "alias_lookup_failed".to_string(), - message: format!("failed to lookup updated alias: {}", e), + message: format!("failed to lookup updated alias: {e}"), }), ) })? @@ -438,7 +437,7 @@ where StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { code: "alias_deletion_failed".to_string(), - message: format!("failed to delete alias: {}", e), + message: format!("failed to delete alias: {e}"), }), ) })?; @@ -450,7 +449,7 @@ where StatusCode::NOT_FOUND, Json(ErrorResponse { code: "alias_not_found".to_string(), - message: format!("alias '{}' not found", name), + message: format!("alias '{name}' not found"), }), )) } @@ -489,7 +488,7 @@ where StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { code: "alias_list_failed".to_string(), - message: format!("failed to list aliases: {}", e), + message: format!("failed to list aliases: {e}"), }), ) })?; @@ -513,9 +512,9 @@ where #[cfg(test)] mod tests { use super::*; - use axum::body::Body; - use axum::http::Request; - use tower::ServiceExt; + + + #[test] fn test_create_alias_request_single() { diff --git a/crates/miroir-proxy/src/routes/canary.rs b/crates/miroir-proxy/src/routes/canary.rs index 35c9ce0..a6dfc6f 100644 --- a/crates/miroir-proxy/src/routes/canary.rs +++ b/crates/miroir-proxy/src/routes/canary.rs @@ -99,7 +99,7 @@ where let assertions: Vec = req .assertions .into_iter() - .map(|v| serde_json::from_value(v)) + .map(serde_json::from_value) .collect::, _>>() .map_err(|e| { tracing::error!(error = %e, "Invalid canary assertion"); @@ -197,7 +197,7 @@ where tracing::error!(error = %e, "Failed to get canary"); StatusCode::INTERNAL_SERVER_ERROR })? - .ok_or_else(|| StatusCode::NOT_FOUND)?; + .ok_or(StatusCode::NOT_FOUND)?; let runs = state.store.get_canary_runs(&id, 100).unwrap_or_default(); @@ -232,7 +232,7 @@ where tracing::error!(error = %e, "Failed to get canary"); StatusCode::INTERNAL_SERVER_ERROR })? - .ok_or_else(|| StatusCode::NOT_FOUND)?; + .ok_or(StatusCode::NOT_FOUND)?; // Parse query let query: SearchQuery = serde_json::from_value(serde_json::json!(req.query)).map_err(|e| { @@ -244,7 +244,7 @@ where let assertions: Vec = req .assertions .into_iter() - .map(|v| serde_json::from_value(v)) + .map(serde_json::from_value) .collect::, _>>() .map_err(|e| { tracing::error!(error = %e, "Invalid canary assertion"); @@ -351,7 +351,7 @@ where let captured = queries .iter() .find(|q| q.index_uid == index_uid) - .ok_or_else(|| StatusCode::NOT_FOUND)?; + .ok_or(StatusCode::NOT_FOUND)?; let id = Uuid::new_v4().to_string(); diff --git a/crates/miroir-proxy/src/routes/cdc.rs b/crates/miroir-proxy/src/routes/cdc.rs index b25ac6a..407d07b 100644 --- a/crates/miroir-proxy/src/routes/cdc.rs +++ b/crates/miroir-proxy/src/routes/cdc.rs @@ -8,9 +8,7 @@ use axum::{ http::StatusCode, Json, }; -use miroir_core::cdc::CdcManager; use serde::{Deserialize, Serialize}; -use std::sync::Arc; /// Query parameters for GET /_miroir/changes. #[derive(Debug, Deserialize)] @@ -103,7 +101,7 @@ where #[cfg(test)] mod tests { use super::*; - use miroir_core::cdc::{CdcConfig, CdcEvent, CdcOperation}; + #[test] fn test_changes_query_params_default_limit() { diff --git a/crates/miroir-proxy/src/routes/documents.rs b/crates/miroir-proxy/src/routes/documents.rs index 5fa301c..f534af4 100644 --- a/crates/miroir-proxy/src/routes/documents.rs +++ b/crates/miroir-proxy/src/routes/documents.rs @@ -30,10 +30,9 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; use std::sync::Arc; -use tracing::{debug, info, instrument}; +use tracing::instrument; use crate::client::HttpClient; -use crate::middleware::SessionId; use crate::routes::admin_endpoints::AppState; /// Document write parameters from query string. @@ -386,8 +385,7 @@ async fn write_documents_impl( return Err(MeilisearchError::new( MiroirCode::MultiAliasNotWritable, format!( - "alias '{}' is a multi-target alias and is read-only (managed by ILM); writes must go to the concrete index or the write alias", - index + "alias '{index}' is a multi-target alias and is read-only (managed by ILM); writes must go to the concrete index or the write alias" ), )); } @@ -404,12 +402,12 @@ async fn write_documents_impl( // 1. Extract primary key from first document if not provided let primary_key = - primary_key.or_else(|| documents.first().and_then(|doc| extract_primary_key(doc))); + primary_key.or_else(|| documents.first().and_then(extract_primary_key)); let primary_key = primary_key.ok_or_else(|| { MeilisearchError::new( MiroirCode::PrimaryKeyRequired, - format!("primary key required for index `{}`", index), + format!("primary key required for index `{index}`"), ) })?; @@ -433,7 +431,7 @@ async fn write_documents_impl( if anti_entropy_enabled && doc.get(updated_at_field).is_some() { return Err(MeilisearchError::new( MiroirCode::ReservedField, - format!("document contains reserved field `{}` (reserved when anti_entropy.enabled: true)", updated_at_field), + format!("document contains reserved field `{updated_at_field}` (reserved when anti_entropy.enabled: true)"), )); } @@ -444,8 +442,7 @@ async fn write_documents_impl( return Err(MeilisearchError::new( MiroirCode::ReservedField, format!( - "document contains reserved field `{}` (reserved when ttl.enabled: true)", - expires_at_field + "document contains reserved field `{expires_at_field}` (reserved when ttl.enabled: true)" ), )); } @@ -454,8 +451,7 @@ async fn write_documents_impl( return Err(MeilisearchError::new( MiroirCode::PrimaryKeyRequired, format!( - "document at index {} missing primary key field `{}`", - i, primary_key + "document at index {i} missing primary key field `{primary_key}`" ), )); } @@ -571,7 +567,7 @@ async fn write_documents_impl( if targets.is_empty() { return Err(MeilisearchError::new( MiroirCode::ShardUnavailable, - format!("no available nodes for shard {}", shard_id), + format!("no available nodes for shard {shard_id}"), )); } @@ -710,7 +706,7 @@ async fn write_documents_impl( .map_err(|e| { MeilisearchError::new( MiroirCode::ShardUnavailable, - format!("failed to register task: {}", e), + format!("failed to register task: {e}"), ) })?; @@ -831,8 +827,7 @@ async fn delete_by_ids_impl( return Err(MeilisearchError::new( MiroirCode::MultiAliasNotWritable, format!( - "alias '{}' is a multi-target alias and is read-only (managed by ILM); deletes must go to the concrete index or the write alias", - index + "alias '{index}' is a multi-target alias and is read-only (managed by ILM); deletes must go to the concrete index or the write alias" ), )); } @@ -872,7 +867,7 @@ async fn delete_by_ids_impl( if targets.is_empty() { return Err(MeilisearchError::new( MiroirCode::ShardUnavailable, - format!("no available nodes for shard {}", shard_id), + format!("no available nodes for shard {shard_id}"), )); } @@ -942,7 +937,7 @@ async fn delete_by_ids_impl( .map_err(|e| { MeilisearchError::new( MiroirCode::ShardUnavailable, - format!("failed to register task: {}", e), + format!("failed to register task: {e}"), ) })?; @@ -1112,7 +1107,7 @@ async fn delete_by_filter_impl( .map_err(|e| { MeilisearchError::new( MiroirCode::ShardUnavailable, - format!("failed to register task: {}", e), + format!("failed to register task: {e}"), ) })?; @@ -1210,7 +1205,7 @@ fn build_response_with_degraded_header( let body = serde_json::to_string(&response).map_err(|e| { MeilisearchError::new( MiroirCode::ShardUnavailable, - format!("failed to serialize response: {}", e), + format!("failed to serialize response: {e}"), ) })?; @@ -1222,16 +1217,16 @@ fn build_response_with_degraded_header( if degraded_groups > 0 { builder = builder.header( HEADER_MIROIR_DEGRADED, - format!("groups={}", degraded_groups), + format!("groups={degraded_groups}"), ); } - Ok(builder.body(axum::body::Body::from(body)).map_err(|e| { + builder.body(axum::body::Body::from(body)).map_err(|e| { MeilisearchError::new( MiroirCode::ShardUnavailable, - format!("failed to build response: {}", e), + format!("failed to build response: {e}"), ) - })?) + }) } /// Build an error response as JSON (for forwarded node errors). @@ -1289,7 +1284,7 @@ mod tests { fn reserved_field_error(field: &str) -> MeilisearchError { MeilisearchError::new( MiroirCode::ReservedField, - format!("document contains reserved field `{}`", field), + format!("document contains reserved field `{field}`"), ) } @@ -1312,8 +1307,7 @@ mod tests { let err = MeilisearchError::new( MiroirCode::ReservedField, format!( - "document contains reserved field `{}` (reserved when anti_entropy.enabled: true)", - field + "document contains reserved field `{field}` (reserved when anti_entropy.enabled: true)" ), ); assert_eq!(err.code, "miroir_reserved_field"); @@ -1327,8 +1321,7 @@ mod tests { let err = MeilisearchError::new( MiroirCode::ReservedField, format!( - "document contains reserved field `{}` (reserved when ttl.enabled: true)", - field + "document contains reserved field `{field}` (reserved when ttl.enabled: true)" ), ); assert_eq!(err.code, "miroir_reserved_field"); diff --git a/crates/miroir-proxy/src/routes/dumps.rs b/crates/miroir-proxy/src/routes/dumps.rs index 826195d..b4b7bd8 100644 --- a/crates/miroir-proxy/src/routes/dumps.rs +++ b/crates/miroir-proxy/src/routes/dumps.rs @@ -5,18 +5,12 @@ //! - `GET /_miroir/dumps/import/{id}/status` — get import status use axum::extract::{Extension, FromRef, Path}; -use axum::http::StatusCode; use axum::routing::{get, post}; use axum::{Json, Router}; use miroir_core::api_error::{MeilisearchError, MiroirCode}; -use miroir_core::config::Config; use miroir_core::dump_import::{DumpImportManager, DumpImportPhase, DumpImportStatus}; -use miroir_core::topology::Topology; -use serde_json::Value; -use std::sync::Arc; use crate::client::HttpClient; -use crate::middleware::Metrics; /// Request body for starting a dump import. #[derive(serde::Deserialize)] @@ -91,7 +85,7 @@ where Err(e) => { return Err(MeilisearchError::new( MiroirCode::InvalidRequest, - format!("invalid base64 dump_data: {}", e), + format!("invalid base64 dump_data: {e}"), )) } } @@ -124,7 +118,7 @@ where .map_err(|e| { MeilisearchError::new( MiroirCode::InternalError, - format!("failed to start import: {}", e), + format!("failed to start import: {e}"), ) })?; @@ -143,7 +137,7 @@ where bytes_read ); - let status_url = format!("/_miroir/dumps/import/{}/status", import_id); + let status_url = format!("/_miroir/dumps/import/{import_id}/status"); Ok(Json(DumpImportResponse { miroir_task_id: import_id, @@ -175,7 +169,7 @@ where let status = manager.get_status(&id).await.ok_or_else(|| { MeilisearchError::new( MiroirCode::NotFound, - format!("import task not found: {}", id), + format!("import task not found: {id}"), ) })?; @@ -222,7 +216,7 @@ fn base64_decode(s: &str) -> Result, String> { use base64::Engine; base64::engine::general_purpose::STANDARD .decode(s) - .map_err(|e| format!("base64 decode failed: {}", e)) + .map_err(|e| format!("base64 decode failed: {e}")) } /// Get current UNIX timestamp in milliseconds. diff --git a/crates/miroir-proxy/src/routes/explain.rs b/crates/miroir-proxy/src/routes/explain.rs index dee89eb..9535de9 100644 --- a/crates/miroir-proxy/src/routes/explain.rs +++ b/crates/miroir-proxy/src/routes/explain.rs @@ -1,12 +1,11 @@ //! Query explain API endpoint (plan §13.20). use axum::{ - extract::{Extension, FromRef, Path, Query}, + extract::{Extension, Path, Query}, http::{HeaderMap, StatusCode}, Json, }; use miroir_core::{ - api_error::{MeilisearchError, MiroirCode}, config::MiroirConfig, explainer::{BroadcastPending, Explainer, SearchQueryExplanation, Warning}, query_planner::QueryPlanner, @@ -15,7 +14,6 @@ use miroir_core::{ topology::Topology, }; use serde::Deserialize; -use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; @@ -55,7 +53,7 @@ pub async fn explain_search( Query(params): Query, Extension(state): Extension>, headers: HeaderMap, - Json(mut query): Json, + Json(query): Json, ) -> Result, StatusCode> where S: Clone + Send + Sync + 'static, @@ -309,8 +307,7 @@ async fn check_unfilterable_attributes( warnings.push(Warning::UnfilterableAttribute { attribute: attr.clone(), suggestion: format!( - "add '{}' to filterableAttributes or remove from filter", - attr + "add '{attr}' to filterableAttributes or remove from filter" ), }); } @@ -336,8 +333,8 @@ fn extract_attributes_from_filter(filter: &str) -> Vec { let known_attrs = vec!["id", "sku", "category", "price", "status", "tenant"]; for attr in known_attrs { - if filter_lower.contains(&format!(r#"{}"#, attr)) - || filter_lower.contains(&format!(r#"{}"#, attr)) + if filter_lower.contains(&attr.to_string()) + || filter_lower.contains(&attr.to_string()) { attrs.push(attr.to_string()); } diff --git a/crates/miroir-proxy/src/routes/indexes.rs b/crates/miroir-proxy/src/routes/indexes.rs index 92d2a4c..8deeb24 100644 --- a/crates/miroir-proxy/src/routes/indexes.rs +++ b/crates/miroir-proxy/src/routes/indexes.rs @@ -11,7 +11,7 @@ //! - `GET /stats` — global stats across all indexes use axum::extract::{Extension, Path}; -use axum::http::{HeaderMap, StatusCode}; +use axum::http::StatusCode; use axum::routing::{get, post}; use axum::{Json, Router}; use futures_util::future::join_all; @@ -19,7 +19,6 @@ use miroir_core::api_error::{MeilisearchError, MiroirCode}; use miroir_core::config::Config; use miroir_core::error::MiroirError; use miroir_core::scatter::{PreflightRequest, PreflightResponse, TermStats}; -use miroir_core::settings::{BroadcastPhase, SettingsBroadcast}; use miroir_core::topology::Topology; use reqwest::Client; use serde_json::Value; @@ -38,14 +37,14 @@ fn convert_miroir_error(e: MiroirError) -> MeilisearchError { "settings divergence detected across nodes", ), MiroirError::NotFound(msg) => { - MeilisearchError::new(MiroirCode::NoQuorum, format!("not found: {}", msg)) + MeilisearchError::new(MiroirCode::NoQuorum, format!("not found: {msg}")) } MiroirError::InvalidState(msg) => { - MeilisearchError::new(MiroirCode::NoQuorum, format!("invalid state: {}", msg)) + MeilisearchError::new(MiroirCode::NoQuorum, format!("invalid state: {msg}")) } _ => MeilisearchError::new( MiroirCode::NoQuorum, - format!("settings broadcast error: {}", e), + format!("settings broadcast error: {e}"), ), } } @@ -86,9 +85,9 @@ impl MeilisearchClient { .json(body) .send() .await - .map_err(|e| format!("request failed: {}", e))?; + .map_err(|e| format!("request failed: {e}"))?; let status = resp.status().as_u16(); - let text = resp.text().await.map_err(|e| format!("read body: {}", e))?; + let text = resp.text().await.map_err(|e| format!("read body: {e}"))?; Ok((status, text)) } @@ -107,9 +106,9 @@ impl MeilisearchClient { .json(body) .send() .await - .map_err(|e| format!("request failed: {}", e))?; + .map_err(|e| format!("request failed: {e}"))?; let status = resp.status().as_u16(); - let text = resp.text().await.map_err(|e| format!("read body: {}", e))?; + let text = resp.text().await.map_err(|e| format!("read body: {e}"))?; Ok((status, text)) } @@ -122,9 +121,9 @@ impl MeilisearchClient { .header(self.auth_header().0, &self.auth_header().1) .send() .await - .map_err(|e| format!("request failed: {}", e))?; + .map_err(|e| format!("request failed: {e}"))?; let status = resp.status().as_u16(); - let text = resp.text().await.map_err(|e| format!("read body: {}", e))?; + let text = resp.text().await.map_err(|e| format!("read body: {e}"))?; Ok((status, text)) } @@ -137,9 +136,9 @@ impl MeilisearchClient { .header(self.auth_header().0, &self.auth_header().1) .send() .await - .map_err(|e| format!("request failed: {}", e))?; + .map_err(|e| format!("request failed: {e}"))?; let status = resp.status().as_u16(); - let text = resp.text().await.map_err(|e| format!("read body: {}", e))?; + let text = resp.text().await.map_err(|e| format!("read body: {e}"))?; Ok((status, text)) } @@ -351,7 +350,7 @@ async fn create_index_handler( // Phase 1: Create index on every node sequentially for address in &nodes { match client.post_raw(address, "/indexes", &body).await { - Ok((status, text)) if status >= 200 && status < 300 => { + Ok((status, text)) if (200..300).contains(&status) => { if first_response.is_none() { first_response = serde_json::from_str(&text).ok(); } @@ -361,8 +360,7 @@ async fn create_index_handler( // Rollback: delete index on all previously created nodes rollback_delete_index(&client, uid, &created_on).await; let msg = format!( - "index creation failed on node {}: HTTP {} — {}", - address, status, text + "index creation failed on node {address}: HTTP {status} — {text}" ); return Err(forward_or_miroir(status, &text, &msg)); } @@ -370,7 +368,7 @@ async fn create_index_handler( rollback_delete_index(&client, uid, &created_on).await; return Err(MeilisearchError::new( MiroirCode::NoQuorum, - format!("index creation failed on node {}: {}", address, e), + format!("index creation failed on node {address}: {e}"), )); } } @@ -383,10 +381,10 @@ async fn create_index_handler( if let Some(first_addr) = nodes.first() { match client - .get_raw(first_addr, &format!("/indexes/{}/settings", uid)) + .get_raw(first_addr, &format!("/indexes/{uid}/settings")) .await { - Ok((status, text)) if status >= 200 && status < 300 => { + Ok((status, text)) if (200..300).contains(&status) => { if let Ok(settings) = serde_json::from_str::(&text) { if let Some(existing) = settings .get("filterableAttributes") @@ -411,9 +409,9 @@ async fn create_index_handler( let mut patch_ok: Vec = Vec::new(); for address in &nodes { - let path = format!("/indexes/{}/settings", uid); + let path = format!("/indexes/{uid}/settings"); match client.patch_raw(address, &path, &filterable_patch).await { - Ok((_status, _text)) if _status >= 200 && _status < 300 => { + Ok((_status, _text)) if (200..300).contains(&_status) => { patch_ok.push(address.clone()); } Ok((status, _text)) => { @@ -454,7 +452,7 @@ async fn create_index_handler( async fn rollback_delete_index(client: &MeilisearchClient, uid: &str, nodes: &[String]) { for address in nodes { - let path = format!("/indexes/{}", uid); + let path = format!("/indexes/{uid}"); match client.delete_raw(address, &path).await { Ok(_) => tracing::info!(node = %address, "rollback: deleted index"), Err(e) => { @@ -483,7 +481,7 @@ async fn list_indexes_handler( tracing::error!(error = %e, "list indexes failed"); StatusCode::INTERNAL_SERVER_ERROR })?; - if status >= 200 && status < 300 { + if (200..300).contains(&status) { let json: Value = serde_json::from_str(&text).unwrap_or(serde_json::json!({"results": []})); Ok(Json(json)) } else { @@ -504,12 +502,12 @@ async fn get_index_handler( .nodes .first() .ok_or(StatusCode::SERVICE_UNAVAILABLE)?; - let path = format!("/indexes/{}", index); + let path = format!("/indexes/{index}"); let (status, text) = client.get_raw(&address.address, &path).await.map_err(|e| { tracing::error!(error = %e, "get index failed"); StatusCode::INTERNAL_SERVER_ERROR })?; - if status >= 200 && status < 300 { + if (200..300).contains(&status) { Ok(Json(serde_json::from_str(&text).unwrap_or(Value::Null))) } else { Err(StatusCode::from_u16(status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)) @@ -528,13 +526,13 @@ async fn update_index_handler( ) -> Result, MeilisearchError> { let client = MeilisearchClient::new(config.node_master_key.clone()); let nodes = all_node_addresses(&config); - let path = format!("/indexes/{}", index); + let path = format!("/indexes/{index}"); // Snapshot current index state from all nodes before applying changes let mut snapshots: Vec<(String, Value)> = Vec::new(); for address in &nodes { match client.get_raw(address, &path).await { - Ok((status, text)) if status >= 200 && status < 300 => { + Ok((status, text)) if (200..300).contains(&status) => { let snapshot: Value = serde_json::from_str(&text).unwrap_or(Value::Null); snapshots.push((address.clone(), snapshot)); } @@ -542,13 +540,13 @@ async fn update_index_handler( return Err(forward_or_miroir( status, &text, - &format!("failed to snapshot index on {}: HTTP {}", address, status), + &format!("failed to snapshot index on {address}: HTTP {status}"), )); } Err(e) => { return Err(MeilisearchError::new( MiroirCode::NoQuorum, - format!("failed to snapshot index on {}: {}", address, e), + format!("failed to snapshot index on {address}: {e}"), )); } } @@ -560,7 +558,7 @@ async fn update_index_handler( for (address, _) in &snapshots { match client.patch_raw(address, &path, &body).await { - Ok((status, text)) if status >= 200 && status < 300 => { + Ok((status, text)) if (200..300).contains(&status) => { if first_response.is_none() { first_response = serde_json::from_str(&text).ok(); } @@ -569,8 +567,7 @@ async fn update_index_handler( Ok((status, text)) => { rollback_index_update(&client, &path, &snapshots, &applied).await; let msg = format!( - "index update failed on {}: HTTP {} — {}", - address, status, text + "index update failed on {address}: HTTP {status} — {text}" ); return Err(forward_or_miroir(status, &text, &msg)); } @@ -578,7 +575,7 @@ async fn update_index_handler( rollback_index_update(&client, &path, &snapshots, &applied).await; return Err(MeilisearchError::new( MiroirCode::NoQuorum, - format!("index update failed on {}: {}", address, e), + format!("index update failed on {address}: {e}"), )); } } @@ -599,7 +596,7 @@ async fn rollback_index_update( for address in applied { if let Some((_, snapshot)) = snapshots.iter().find(|(a, _)| a == address) { match client.patch_raw(address, path, snapshot).await { - Ok((_status, _text)) if _status >= 200 && _status < 300 => { + Ok((_status, _text)) if (200..300).contains(&_status) => { tracing::info!(node = %address, "index update rollback succeeded"); } Ok((status, _text)) => { @@ -632,18 +629,18 @@ async fn delete_index_handler( let mut errors: Vec = Vec::new(); for address in &nodes { - let path = format!("/indexes/{}", index); + let path = format!("/indexes/{index}"); match client.delete_raw(address, &path).await { - Ok((status, text)) if status >= 200 && status < 300 => { + Ok((status, text)) if (200..300).contains(&status) => { if first_response.is_none() { first_response = serde_json::from_str(&text).ok(); } } Ok((status, text)) => { - errors.push(format!("{}: HTTP {} — {}", address, status, text)); + errors.push(format!("{address}: HTTP {status} — {text}")); } Err(e) => { - errors.push(format!("{}: {}", address, e)); + errors.push(format!("{address}: {e}")); } } } @@ -708,7 +705,7 @@ async fn get_index_stats_handler( if success_count == 0 { return Err(MeilisearchError::new( MiroirCode::NoQuorum, - format!("stats unavailable for index `{}`: all nodes failed", index), + format!("stats unavailable for index `{index}`: all nodes failed"), )); } @@ -751,11 +748,11 @@ pub async fn global_stats_handler( .map_err(|e| { MeilisearchError::new( MiroirCode::NoQuorum, - format!("failed to list indexes: {}", e), + format!("failed to list indexes: {e}"), ) })?; - if status < 200 || status >= 300 { + if !(200..300).contains(&status) { return Err(MeilisearchError::new( MiroirCode::NoQuorum, "failed to list indexes", @@ -775,22 +772,19 @@ pub async fn global_stats_handler( for idx in &index_list { if let Some(uid) = idx.get("uid").and_then(|v| v.as_str()) { for address in &nodes { - match client.get_index_stats(address, uid).await { - Ok(stats) => { - if let Some(n) = stats.get("numberOfDocuments").and_then(|v| v.as_u64()) { - total_docs += n; - } - if let Some(fd) = stats.get("fieldDistribution").and_then(|v| v.as_object()) - { - for (field, count) in fd { - if let Some(c) = count.as_u64() { - *total_field_distribution.entry(field.clone()).or_insert(0) += - c; - } + if let Ok(stats) = client.get_index_stats(address, uid).await { + if let Some(n) = stats.get("numberOfDocuments").and_then(|v| v.as_u64()) { + total_docs += n; + } + if let Some(fd) = stats.get("fieldDistribution").and_then(|v| v.as_object()) + { + for (field, count) in fd { + if let Some(c) = count.as_u64() { + *total_field_distribution.entry(field.clone()).or_insert(0) += + c; } } } - Err(_) => {} } } } @@ -833,7 +827,7 @@ async fn update_settings_subpath_handler( Extension(config): Extension>, Json(body): Json, ) -> Result, MeilisearchError> { - let path = format!("/settings/{}", subpath); + let path = format!("/settings/{subpath}"); two_phase_settings_broadcast(&state, &config, &index, &path, &body).await } @@ -853,18 +847,18 @@ async fn two_phase_settings_broadcast( ) -> Result, MeilisearchError> { // Use sequential strategy for rollback compatibility if config.settings_broadcast.strategy == "sequential" { - return update_settings_broadcast_legacy(&config, index, settings_path, body).await; + return update_settings_broadcast_legacy(config, index, settings_path, body).await; } let client = MeilisearchClient::new(config.node_master_key.clone()); let nodes = all_node_addresses(config); - let full_path = format!("/indexes/{}{}", index, settings_path); + let full_path = format!("/indexes/{index}{settings_path}"); // Check if a broadcast is already in flight if state.settings_broadcast.is_in_flight(index).await { return Err(MeilisearchError::new( MiroirCode::IndexAlreadyExists, - format!("settings broadcast already in flight for index '{}'", index), + format!("settings broadcast already in flight for index '{index}'"), )); } @@ -882,7 +876,7 @@ async fn two_phase_settings_broadcast( for address in &nodes { match client.patch_raw(address, &full_path, body).await { - Ok((status, text)) if status >= 200 && status < 300 => { + Ok((status, text)) if (200..300).contains(&status) => { if first_response.is_none() { first_response = serde_json::from_str(&text).ok(); } @@ -894,10 +888,10 @@ async fn two_phase_settings_broadcast( } } Ok((status, text)) => { - errors.push(format!("{}: HTTP {} — {}", address, status, text)); + errors.push(format!("{address}: HTTP {status} — {text}")); } Err(e) => { - errors.push(format!("{}: {}", address, e)); + errors.push(format!("{address}: {e}")); } } } @@ -942,7 +936,7 @@ async fn two_phase_settings_broadcast( .map(|address| { let client = client.clone(); let address = address.clone(); - let path = format!("/indexes/{}{}", index, settings_path); + let path = format!("/indexes/{index}{settings_path}"); async move { (address.clone(), client.get_raw(&address, &path).await) } }) .collect(); @@ -955,17 +949,17 @@ async fn two_phase_settings_broadcast( for (address, result) in results { match result { - Ok((status, text)) if status >= 200 && status < 300 => { + Ok((status, text)) if (200..300).contains(&status) => { if let Ok(settings) = serde_json::from_str::(&text) { let hash = fingerprint_settings(&settings); node_hashes.insert(address, hash); } } Ok((status, text)) => { - verify_errors.push(format!("{}: HTTP {} — {}", address, status, text)); + verify_errors.push(format!("{address}: HTTP {status} — {text}")); } Err(e) => { - verify_errors.push(format!("{}: {}", address, e)); + verify_errors.push(format!("{address}: {e}")); } } } @@ -1019,7 +1013,7 @@ async fn two_phase_settings_broadcast( .settings_broadcast .abort( index, - format!("max repair retries ({}) exceeded", max_retries), + format!("max repair retries ({max_retries}) exceeded"), ) .await .ok(); @@ -1039,7 +1033,7 @@ async fn two_phase_settings_broadcast( return Err(MeilisearchError::new( MiroirCode::NoQuorum, - format!("settings divergence detected after {} retries - writes frozen on index", max_retries), + format!("settings divergence detected after {max_retries} retries - writes frozen on index"), )); } @@ -1065,7 +1059,7 @@ async fn two_phase_settings_broadcast( let mut repair_errors: Vec = Vec::new(); for address in &mismatched_nodes { match client.patch_raw(address, &full_path, body).await { - Ok((status, _text)) if status >= 200 && status < 300 => { + Ok((status, _text)) if (200..300).contains(&status) => { tracing::info!( node = %address, index = %index, @@ -1074,10 +1068,10 @@ async fn two_phase_settings_broadcast( ); } Ok((status, text)) => { - repair_errors.push(format!("{}: HTTP {} — {}", address, status, text)); + repair_errors.push(format!("{address}: HTTP {status} — {text}")); } Err(e) => { - repair_errors.push(format!("{}: {}", address, e)); + repair_errors.push(format!("{address}: {e}")); } } } @@ -1165,13 +1159,13 @@ async fn update_settings_broadcast_legacy( ) -> Result, MeilisearchError> { let client = MeilisearchClient::new(config.node_master_key.clone()); let nodes = all_node_addresses(config); - let full_path = format!("/indexes/{}{}", index, settings_path); + let full_path = format!("/indexes/{index}{settings_path}"); // Snapshot current settings from all nodes before applying changes let mut snapshots: Vec<(String, Value)> = Vec::new(); for address in &nodes { match client.get_raw(address, &full_path).await { - Ok((status, text)) if status >= 200 && status < 300 => { + Ok((status, text)) if (200..300).contains(&status) => { let snapshot: Value = serde_json::from_str(&text).unwrap_or(Value::Null); snapshots.push((address.clone(), snapshot)); } @@ -1180,15 +1174,14 @@ async fn update_settings_broadcast_legacy( status, &text, &format!( - "failed to snapshot settings on {}: HTTP {}", - address, status + "failed to snapshot settings on {address}: HTTP {status}" ), )); } Err(e) => { return Err(MeilisearchError::new( MiroirCode::NoQuorum, - format!("failed to snapshot settings on {}: {}", address, e), + format!("failed to snapshot settings on {address}: {e}"), )); } } @@ -1200,7 +1193,7 @@ async fn update_settings_broadcast_legacy( for (address, _snapshot) in &snapshots { match client.patch_raw(address, &full_path, body).await { - Ok((status, text)) if status >= 200 && status < 300 => { + Ok((status, text)) if (200..300).contains(&status) => { if first_response.is_none() { first_response = serde_json::from_str(&text).ok(); } @@ -1210,8 +1203,7 @@ async fn update_settings_broadcast_legacy( // Rollback all previously applied nodes rollback_settings(&client, &full_path, &snapshots, &applied).await; let msg = format!( - "settings update failed on {}: HTTP {} — {}", - address, status, text + "settings update failed on {address}: HTTP {status} — {text}" ); return Err(forward_or_miroir(status, &text, &msg)); } @@ -1219,7 +1211,7 @@ async fn update_settings_broadcast_legacy( rollback_settings(&client, &full_path, &snapshots, &applied).await; return Err(MeilisearchError::new( MiroirCode::NoQuorum, - format!("settings update failed on {}: {}", address, e), + format!("settings update failed on {address}: {e}"), )); } } @@ -1241,7 +1233,7 @@ async fn rollback_settings( // Find the snapshot for this address if let Some((_, snapshot)) = snapshots.iter().find(|(a, _)| a == address) { match client.patch_raw(address, full_path, snapshot).await { - Ok((_status, _text)) if _status >= 200 && _status < 300 => { + Ok((_status, _text)) if (200..300).contains(&_status) => { tracing::info!(node = %address, "settings rollback succeeded"); } Ok((status, _text)) => { @@ -1272,12 +1264,12 @@ async fn get_settings_handler( .nodes .first() .ok_or(StatusCode::SERVICE_UNAVAILABLE)?; - let path = format!("/indexes/{}/settings", index); + let path = format!("/indexes/{index}/settings"); let (status, text) = client.get_raw(&address.address, &path).await.map_err(|e| { tracing::error!(error = %e, "get settings failed"); StatusCode::INTERNAL_SERVER_ERROR })?; - if status >= 200 && status < 300 { + if (200..300).contains(&status) { Ok(Json(serde_json::from_str(&text).unwrap_or(Value::Null))) } else { Err(StatusCode::from_u16(status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)) @@ -1307,7 +1299,7 @@ async fn preview_settings_handler( .nodes .first() .ok_or(StatusCode::SERVICE_UNAVAILABLE)?; - let current_settings_path = format!("/indexes/{}/settings", index); + let current_settings_path = format!("/indexes/{index}/settings"); let (status, text) = client .get_raw(&first_address.address, ¤t_settings_path) .await @@ -1316,7 +1308,7 @@ async fn preview_settings_handler( StatusCode::INTERNAL_SERVER_ERROR })?; - let current_settings: Value = if status >= 200 && status < 300 { + let current_settings: Value = if (200..300).contains(&status) { serde_json::from_str(&text).unwrap_or(Value::Null) } else { // Index doesn't exist yet - current settings are null @@ -1450,12 +1442,12 @@ async fn get_settings_subpath_handler( .nodes .first() .ok_or(StatusCode::SERVICE_UNAVAILABLE)?; - let path = format!("/indexes/{}/settings/{}", index, subpath); + let path = format!("/indexes/{index}/settings/{subpath}"); let (status, text) = client.get_raw(&address.address, &path).await.map_err(|e| { tracing::error!(error = %e, "get settings subpath failed"); StatusCode::INTERNAL_SERVER_ERROR })?; - if status >= 200 && status < 300 { + if (200..300).contains(&status) { Ok(Json(serde_json::from_str(&text).unwrap_or(Value::Null))) } else { Err(StatusCode::from_u16(status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)) diff --git a/crates/miroir-proxy/src/routes/keys.rs b/crates/miroir-proxy/src/routes/keys.rs index 217edda..09d5d27 100644 --- a/crates/miroir-proxy/src/routes/keys.rs +++ b/crates/miroir-proxy/src/routes/keys.rs @@ -61,7 +61,7 @@ async fn create_key_handler( for address in &nodes { match client.post_raw(address, "/keys", &body).await { - Ok((status, text)) if status >= 200 && status < 300 => { + Ok((status, text)) if (200..300).contains(&status) => { if first_response.is_none() { first_response = serde_json::from_str(&text).ok(); } @@ -71,8 +71,7 @@ async fn create_key_handler( // Rollback: delete key on all previously created nodes rollback_delete_key(&client, &body, &created_on).await; let msg = format!( - "key creation failed on {}: HTTP {} — {}", - address, status, text + "key creation failed on {address}: HTTP {status} — {text}" ); return Err(forward_or_miroir(status, &text, &msg)); } @@ -80,7 +79,7 @@ async fn create_key_handler( rollback_delete_key(&client, &body, &created_on).await; return Err(MeilisearchError::new( MiroirCode::NoQuorum, - format!("key creation failed on {}: {}", address, e), + format!("key creation failed on {address}: {e}"), )); } } @@ -107,7 +106,7 @@ async fn rollback_delete_key(client: &MeilisearchClient, body: &Value, nodes: &[ } for address in nodes { - let path = format!("/keys/{}", key_or_name); + let path = format!("/keys/{key_or_name}"); match client.delete_raw(address, &path).await { Ok(_) => tracing::info!(node = %address, "key rollback: deleted key"), Err(e) => { @@ -128,13 +127,13 @@ async fn update_key_handler( ) -> Result, MeilisearchError> { let client = MeilisearchClient::new(config.node_master_key.clone()); let nodes = all_node_addresses(&config); - let path = format!("/keys/{}", key); + let path = format!("/keys/{key}"); // Snapshot current key state from all nodes let mut snapshots: Vec<(String, Value)> = Vec::new(); for address in &nodes { match client.get_raw(address, &path).await { - Ok((status, text)) if status >= 200 && status < 300 => { + Ok((status, text)) if (200..300).contains(&status) => { let snapshot: Value = serde_json::from_str(&text).unwrap_or(Value::Null); snapshots.push((address.clone(), snapshot)); } @@ -142,13 +141,13 @@ async fn update_key_handler( return Err(forward_or_miroir( status, &text, - &format!("failed to snapshot key on {}: HTTP {}", address, status), + &format!("failed to snapshot key on {address}: HTTP {status}"), )); } Err(e) => { return Err(MeilisearchError::new( MiroirCode::NoQuorum, - format!("failed to snapshot key on {}: {}", address, e), + format!("failed to snapshot key on {address}: {e}"), )); } } @@ -160,7 +159,7 @@ async fn update_key_handler( for (address, _snapshot) in &snapshots { match client.patch_raw(address, &path, &body).await { - Ok((status, text)) if status >= 200 && status < 300 => { + Ok((status, text)) if (200..300).contains(&status) => { if first_response.is_none() { first_response = serde_json::from_str(&text).ok(); } @@ -169,8 +168,7 @@ async fn update_key_handler( Ok((status, text)) => { rollback_key_update(&client, &path, &snapshots, &applied).await; let msg = format!( - "key update failed on {}: HTTP {} — {}", - address, status, text + "key update failed on {address}: HTTP {status} — {text}" ); return Err(forward_or_miroir(status, &text, &msg)); } @@ -178,7 +176,7 @@ async fn update_key_handler( rollback_key_update(&client, &path, &snapshots, &applied).await; return Err(MeilisearchError::new( MiroirCode::NoQuorum, - format!("key update failed on {}: {}", address, e), + format!("key update failed on {address}: {e}"), )); } } @@ -199,7 +197,7 @@ async fn rollback_key_update( for address in applied { if let Some((_, snapshot)) = snapshots.iter().find(|(a, _)| a == address) { match client.patch_raw(address, path, snapshot).await { - Ok((_status, _text)) if _status >= 200 && _status < 300 => { + Ok((_status, _text)) if (200..300).contains(&_status) => { tracing::info!(node = %address, "key rollback succeeded"); } Ok((status, _text)) => { @@ -223,22 +221,22 @@ async fn delete_key_handler( ) -> Result, MeilisearchError> { let client = MeilisearchClient::new(config.node_master_key.clone()); let nodes = all_node_addresses(&config); - let path = format!("/keys/{}", key); + let path = format!("/keys/{key}"); let mut first_response: Option = None; let mut errors: Vec = Vec::new(); for address in &nodes { match client.delete_raw(address, &path).await { - Ok((status, text)) if status >= 200 && status < 300 => { + Ok((status, text)) if (200..300).contains(&status) => { if first_response.is_none() { first_response = serde_json::from_str(&text).ok(); } } Ok((status, text)) => { - errors.push(format!("{}: HTTP {} — {}", address, status, text)); + errors.push(format!("{address}: HTTP {status} — {text}")); } Err(e) => { - errors.push(format!("{}: {}", address, e)); + errors.push(format!("{address}: {e}")); } } } @@ -285,7 +283,7 @@ async fn list_keys_handler( tracing::error!(error = %e, "list keys failed"); StatusCode::INTERNAL_SERVER_ERROR })?; - if status >= 200 && status < 300 { + if (200..300).contains(&status) { Ok(Json(serde_json::from_str(&text).unwrap_or(Value::Null))) } else { Err(StatusCode::from_u16(status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)) @@ -305,12 +303,12 @@ async fn get_key_handler( .nodes .first() .ok_or(StatusCode::SERVICE_UNAVAILABLE)?; - let path = format!("/keys/{}", key); + let path = format!("/keys/{key}"); let (status, text) = client.get_raw(&address.address, &path).await.map_err(|e| { tracing::error!(error = %e, "get key failed"); StatusCode::INTERNAL_SERVER_ERROR })?; - if status >= 200 && status < 300 { + if (200..300).contains(&status) { Ok(Json(serde_json::from_str(&text).unwrap_or(Value::Null))) } else { Err(StatusCode::from_u16(status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)) diff --git a/crates/miroir-proxy/src/routes/multi_search.rs b/crates/miroir-proxy/src/routes/multi_search.rs index 3b4272b..71aeb77 100644 --- a/crates/miroir-proxy/src/routes/multi_search.rs +++ b/crates/miroir-proxy/src/routes/multi_search.rs @@ -9,10 +9,8 @@ use miroir_core::{ config::UnavailableShardPolicy, merger::{MergeStrategy, ScoreMergeStrategy}, multi_search::{MultiSearchExecutor, MultiSearchResponse, SearchResultData}, - query_planner::QueryPlanner, scatter::{ dfs_query_then_fetch_search, plan_search_scatter_with_narrowing, NodeClient, SearchRequest, - VectorMode, }, shadow::ShadowOperation, topology::Topology, @@ -22,9 +20,8 @@ use serde_json::Value; use std::sync::Arc; use std::time::Instant; use tokio::sync::RwLock; -use tracing::{debug, info, instrument}; +use tracing::{debug, instrument}; -use crate::routes::admin_endpoints::AppState; /// Multi-search state. #[derive(Clone)] @@ -299,7 +296,7 @@ where let topology = topology.clone(); let node_client = node_client.clone(); let config = config_for_executor.clone(); - let strategy = strategy.clone(); + let strategy = strategy; let policy = policy; let replica_selector = replica_selector_for_executor.clone(); let query_planner = query_planner_for_executor.clone(); diff --git a/crates/miroir-proxy/src/routes/search.rs b/crates/miroir-proxy/src/routes/search.rs index a097635..a0844ed 100644 --- a/crates/miroir-proxy/src/routes/search.rs +++ b/crates/miroir-proxy/src/routes/search.rs @@ -624,8 +624,7 @@ async fn search_handler( let _err = MeilisearchError::new( MiroirCode::SettingsVersionStale, format!( - "no covering set available for settings version floor {} on index '{}'", - floor, index + "no covering set available for settings version floor {floor} on index '{index}'" ), ); return Response::builder() @@ -848,7 +847,7 @@ async fn search_handler( .map(|id| id.to_string()) .collect::>() .join(","); - response = response.header("X-Miroir-Degraded", format!("shards={}", shard_ids)); + response = response.header("X-Miroir-Degraded", format!("shards={shard_ids}")); } else if result.degraded { response = response.header("X-Miroir-Degraded", "partial"); } @@ -1185,8 +1184,7 @@ async fn search_multi_targets( let _err = MeilisearchError::new( MiroirCode::SettingsVersionStale, format!( - "no covering set available for settings version floor {} on index '{}'", - floor, primary_target + "no covering set available for settings version floor {floor} on index '{primary_target}'" ), ); return Response::builder() @@ -1317,7 +1315,7 @@ async fn search_multi_targets( .map(|id| id.to_string()) .collect::>() .join(","); - response = response.header("X-Miroir-Degraded", format!("shards={}", shard_ids)); + response = response.header("X-Miroir-Degraded", format!("shards={shard_ids}")); } else if result.degraded { response = response.header("X-Miroir-Degraded", "partial"); } @@ -1369,7 +1367,7 @@ mod tests { ranking_score: Some(false), rest: serde_json::json!({}), }; - let debug_output = format!("{:?}", body); + let debug_output = format!("{body:?}"); assert!( !debug_output.contains("sensitive"), diff --git a/crates/miroir-proxy/src/routes/search_ui.rs b/crates/miroir-proxy/src/routes/search_ui.rs index 63a215a..afc0370 100644 --- a/crates/miroir-proxy/src/routes/search_ui.rs +++ b/crates/miroir-proxy/src/routes/search_ui.rs @@ -14,7 +14,7 @@ use axum::{ }; use miroir_core::{ cdc::AnalyticsEvent, - config::advanced::{SearchUiAuthConfig, SearchUiConfig}, + config::advanced::SearchUiConfig, task_store::{SearchUiScopedKey, TaskStore}, }; use sha2::{Digest, Sha256}; @@ -25,8 +25,7 @@ use crate::auth::{ use crate::error_response::ErrorResponse; use rust_embed::RustEmbed as Embed; use serde::{Deserialize, Serialize}; -use std::sync::Arc; -use tracing::{debug, info, warn}; +use tracing::{debug, info}; use crate::routes::indexes::MeilisearchClient; use crate::scoped_key_rotation::mint_scoped_key; @@ -224,7 +223,7 @@ pub async fn mint_session( let now = chrono::Utc::now().timestamp() as u64; let exp = now + auth_config.session_ttl_s; - let mut scope = vec![ + let scope = vec![ "search".to_string(), "multi_search".to_string(), "beacon".to_string(), @@ -286,7 +285,7 @@ pub async fn mint_session( }; let token = jwt_encode(&header, &claims, secret.as_bytes()) - .map_err(|e| ErrorResponse::internal_error(format!("JWT encoding failed: {}", e)))?; + .map_err(|e| ErrorResponse::internal_error(format!("JWT encoding failed: {e}")))?; info!( index = %index_uid, @@ -343,7 +342,7 @@ async fn get_or_create_scoped_key( let client = MeilisearchClient::new(state.config.node_master_key.clone()); let (new_key, new_uid) = mint_scoped_key(&client, &state.config, index_uid) .await - .map_err(|e| ErrorResponse::internal_error(format!("failed to mint scoped key: {}", e)))?; + .map_err(|e| ErrorResponse::internal_error(format!("failed to mint scoped key: {e}")))?; let now_ms = chrono::Utc::now().timestamp_millis(); let scoped_key = SearchUiScopedKey { @@ -359,7 +358,7 @@ async fn get_or_create_scoped_key( // Store in Redis redis_store .set_search_ui_scoped_key(&scoped_key) - .map_err(|e| ErrorResponse::internal_error(format!("failed to store scoped key: {}", e)))?; + .map_err(|e| ErrorResponse::internal_error(format!("failed to store scoped key: {e}")))?; info!( index = %index_uid, @@ -385,7 +384,7 @@ pub async fn get_config( if let Some(row) = task_store.get_search_ui_config(&index_uid)? { // Parse the config JSON let config: SearchUiIndexConfig = serde_json::from_str(&row.config_json) - .map_err(|e| ErrorResponse::internal_error(format!("failed to parse config: {}", e)))?; + .map_err(|e| ErrorResponse::internal_error(format!("failed to parse config: {e}")))?; return Ok(Json(config)); } @@ -416,7 +415,7 @@ pub async fn update_config( // Serialize config to JSON for storage let config_json = serde_json::to_string(&config) - .map_err(|e| ErrorResponse::internal_error(format!("failed to serialize config: {}", e)))?; + .map_err(|e| ErrorResponse::internal_error(format!("failed to serialize config: {e}")))?; // Validate custom template if present (plan §13.21) if let Some(template) = &config.result_template { @@ -490,7 +489,7 @@ pub async fn beacon( // Fallback: generate a session_id from the token itself let hash = Sha256::digest(token.as_bytes()); let hash_hex = hex::encode(&hash[..16]); - format!("anon:{}", hash_hex) + format!("anon:{hash_hex}") } } } else { @@ -701,13 +700,12 @@ fn validate_template(template: &str) -> Result<(), ErrorResponse> { if_stack.push("if"); } // Check for {{/if}} closing - else if tag.starts_with("/if") { - if if_stack.pop() != Some("if") { + else if tag.starts_with("/if") + && if_stack.pop() != Some("if") { return Err(ErrorResponse::invalid_request( "unmatched {{/if}} tag in template".to_string(), )); } - } pos += end + 2; } else { @@ -718,9 +716,7 @@ fn validate_template(template: &str) -> Result<(), ErrorResponse> { } if !if_stack.is_empty() { - return Err(ErrorResponse::invalid_request(format!( - "unclosed {{#if}} tag in template" - ))); + return Err(ErrorResponse::invalid_request("unclosed {#if} tag in template".to_string())); } Ok(()) @@ -780,7 +776,7 @@ pub fn render_custom_template(template: &str, data: &serde_json::Value) -> Strin // Process simple {{field}} tags let mut rendered = result; for (key, value) in obj { - let tag = format!("{{{{{}}}}}", key); + let tag = format!("{{{{{key}}}}}"); let value_str = match value { serde_json::Value::String(s) => s.clone(), serde_json::Value::Number(n) => n.to_string(), diff --git a/crates/miroir-proxy/src/routes/session.rs b/crates/miroir-proxy/src/routes/session.rs index 161c57f..0ae1d9c 100644 --- a/crates/miroir-proxy/src/routes/session.rs +++ b/crates/miroir-proxy/src/routes/session.rs @@ -156,8 +156,7 @@ where return Err(( StatusCode::TOO_MANY_REQUESTS, format!( - "Too many failed login attempts. Try again in {} seconds.", - ws + "Too many failed login attempts. Try again in {ws} seconds." ), )); } else { @@ -414,8 +413,7 @@ where [( "Set-Cookie", format!( - "{}=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0", - COOKIE_NAME + "{COOKIE_NAME}=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0" ), )], Json(serde_json::json!({ diff --git a/crates/miroir-proxy/src/routes/tasks.rs b/crates/miroir-proxy/src/routes/tasks.rs index 210c13a..1710fdd 100644 --- a/crates/miroir-proxy/src/routes/tasks.rs +++ b/crates/miroir-proxy/src/routes/tasks.rs @@ -402,8 +402,8 @@ fn task_to_response(task: MiroirTask) -> TaskResponse { }; let enqueued_at = format_millis_timestamp(task.created_at); - let started_at = task.started_at.map(|t| format_millis_timestamp(t)); - let finished_at = task.finished_at.map(|t| format_millis_timestamp(t)); + let started_at = task.started_at.map(format_millis_timestamp); + let finished_at = task.finished_at.map(format_millis_timestamp); let error = if task.status == TaskStatus::Failed { Some(TaskError { diff --git a/crates/miroir-proxy/src/scoped_key_rotation.rs b/crates/miroir-proxy/src/scoped_key_rotation.rs index 2507bbd..bd6e616 100644 --- a/crates/miroir-proxy/src/scoped_key_rotation.rs +++ b/crates/miroir-proxy/src/scoped_key_rotation.rs @@ -66,7 +66,7 @@ pub async fn run_scoped_key_rotator(state: ScopedKeyRotationState) { // Check each index for rotation need with a per-index leader lease let indexes = discover_scoped_indexes(&state).await; for index_uid in &indexes { - let lease_scope = format!("search_ui_key_rotation:{}", index_uid); + let lease_scope = format!("search_ui_key_rotation:{index_uid}"); let lease_now = now_ms(); let lease_ttl_ms = (state.config.leader_election.lease_ttl_s as i64) * 1000; let expires_at = lease_now + lease_ttl_ms; @@ -111,8 +111,8 @@ pub async fn check_and_rotate( .map_err(|e| format!("redis read failed: {e}"))?; // Timing gate check (skip if force) - if !force { - if !should_rotate(¤t, &state.config) { + if !force + && !should_rotate(¤t, &state.config) { return Ok(RotateScopedKeyResponse { status: "skipped".into(), index_uid: index_uid.into(), @@ -121,7 +121,6 @@ pub async fn check_and_rotate( error: None, }); } - } // Step 1: Mint new scoped key via Meilisearch POST /keys let client = MeilisearchClient::new(state.config.node_master_key.clone()); @@ -258,7 +257,7 @@ pub async fn mint_scoped_key( config: &MiroirConfig, index_uid: &str, ) -> Result<(String, String), String> { - let description = format!("miroir search-ui scoped key for index {}", index_uid); + let description = format!("miroir search-ui scoped key for index {index_uid}"); let body = serde_json::json!({ "description": description, "actions": ["search"], @@ -272,7 +271,7 @@ pub async fn mint_scoped_key( for node in &config.nodes { match client.post_raw(&node.address, "/keys", &body).await { - Ok((status, text)) if status >= 200 && status < 300 => { + Ok((status, text)) if (200..300).contains(&status) => { if created_key.is_none() { let resp: serde_json::Value = serde_json::from_str(&text) .map_err(|e| format!("parse key response: {e}"))?; @@ -302,12 +301,12 @@ pub async fn revoke_previous_key( config: &MiroirConfig, previous_uid: &str, ) -> Result<(), String> { - let path = format!("/keys/{}", previous_uid); + let path = format!("/keys/{previous_uid}"); let mut errors = Vec::new(); for node in &config.nodes { match client.delete_raw(&node.address, &path).await { - Ok((_status, _text)) if _status >= 200 && _status < 300 => {} + Ok((_status, _text)) if (200..300).contains(&_status) => {} Ok((status, _text)) => { // 404 is fine — key was already revoked or never existed if status != 404 {