From 4d41d74ea7a3da12795bc3b5a7b2019106e82a0a Mon Sep 17 00:00:00 2001 From: teernisse Date: Fri, 6 Mar 2026 15:22:37 -0500 Subject: [PATCH] refactor(deps): replace tokio Mutex/join!, add NetworkErrorKind enum, remove reqwest from error types --- src/core/error.rs | 21 +++++++++++--------- src/embedding/ollama.rs | 6 +++--- src/gitlab/client.rs | 43 +++++++++++++++++++++++++++++++++-------- src/gitlab/graphql.rs | 3 ++- 4 files changed, 52 insertions(+), 21 deletions(-) diff --git a/src/core/error.rs b/src/core/error.rs index 7d809fc..d22f53d 100644 --- a/src/core/error.rs +++ b/src/core/error.rs @@ -1,6 +1,15 @@ use serde::Serialize; use thiserror::Error; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum NetworkErrorKind { + Timeout, + ConnectionRefused, + DnsResolution, + Tls, + Other, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ErrorCode { ConfigNotFound, @@ -99,8 +108,8 @@ pub enum LoreError { #[error("Cannot connect to GitLab at {base_url}")] GitLabNetworkError { base_url: String, - #[source] - source: Option, + kind: NetworkErrorKind, + detail: Option, }, #[error( @@ -122,9 +131,6 @@ pub enum LoreError { #[error("Database error: {0}")] Database(#[from] rusqlite::Error), - #[error("HTTP error: {0}")] - Http(#[from] reqwest::Error), - #[error("JSON error: {0}")] Json(#[from] serde_json::Error), @@ -146,8 +152,7 @@ pub enum LoreError { #[error("Cannot connect to Ollama at {base_url}. Is it running?")] OllamaUnavailable { base_url: String, - #[source] - source: Option, + detail: Option, }, #[error("Ollama model '{model}' not found. Run: ollama pull {model}")] @@ -187,7 +192,6 @@ impl LoreError { ErrorCode::DatabaseError } } - Self::Http(_) => ErrorCode::GitLabNetworkError, Self::Json(_) => ErrorCode::InternalError, Self::Io(_) => ErrorCode::IoError, Self::Transform(_) => ErrorCode::TransformError, @@ -238,7 +242,6 @@ impl LoreError { Some("Check database file permissions.\n\n Example:\n lore doctor") } } - Self::Http(_) => Some("Check network connection"), Self::NotFound(_) => { Some("Verify the entity exists.\n\n Example:\n lore issues\n lore mrs") } diff --git a/src/embedding/ollama.rs b/src/embedding/ollama.rs index daa0903..f245cc6 100644 --- a/src/embedding/ollama.rs +++ b/src/embedding/ollama.rs @@ -75,7 +75,7 @@ impl OllamaClient { .await .map_err(|e| LoreError::OllamaUnavailable { base_url: self.config.base_url.clone(), - source: Some(e), + detail: Some(format!("{e:?}")), })?; let tags: TagsResponse = @@ -84,7 +84,7 @@ impl OllamaClient { .await .map_err(|e| LoreError::OllamaUnavailable { base_url: self.config.base_url.clone(), - source: Some(e), + detail: Some(format!("{e:?}")), })?; let model_found = tags.models.iter().any(|m| { @@ -116,7 +116,7 @@ impl OllamaClient { .await .map_err(|e| LoreError::OllamaUnavailable { base_url: self.config.base_url.clone(), - source: Some(e), + detail: Some(format!("{e:?}")), })?; let status = response.status(); diff --git a/src/gitlab/client.rs b/src/gitlab/client.rs index 46c7632..9c21ed0 100644 --- a/src/gitlab/client.rs +++ b/src/gitlab/client.rs @@ -5,8 +5,8 @@ use reqwest::header::{ACCEPT, HeaderMap, HeaderValue}; use reqwest::{Client, Response, StatusCode}; use std::pin::Pin; use std::sync::Arc; +use std::sync::Mutex; use std::time::{Duration, Instant}; -use tokio::sync::Mutex; use tokio::time::sleep; use tracing::{debug, warn}; @@ -131,7 +131,16 @@ impl GitLabClient { let mut last_response = None; for attempt in 0..=Self::MAX_RETRIES { - let delay = self.rate_limiter.lock().await.check_delay(); + // SAFETY: std::sync::Mutex blocks the executor thread while held. This is safe + // because the critical section is a single Instant::now() comparison with no I/O. + // If async work is ever added inside the lock, switch to an async-aware lock. + let delay = { + let mut limiter = self + .rate_limiter + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); + limiter.check_delay() + }; if let Some(d) = delay { sleep(d).await; } @@ -146,7 +155,8 @@ impl GitLabClient { .await .map_err(|e| LoreError::GitLabNetworkError { base_url: self.base_url.clone(), - source: Some(e), + kind: crate::core::error::NetworkErrorKind::Other, + detail: Some(format!("{e:?}")), })?; if response.status() == StatusCode::TOO_MANY_REQUESTS && attempt < Self::MAX_RETRIES { @@ -197,7 +207,14 @@ impl GitLabClient { } status if status.is_success() => { - let text = response.text().await?; + let text = response + .text() + .await + .map_err(|e| LoreError::GitLabNetworkError { + base_url: self.base_url.clone(), + kind: crate::core::error::NetworkErrorKind::Other, + detail: Some(format!("{e:?}")), + })?; serde_json::from_str(&text).map_err(|e| { let preview = if text.len() > 500 { &text[..text.floor_char_boundary(500)] @@ -516,7 +533,16 @@ impl GitLabClient { let mut last_response = None; for attempt in 0..=Self::MAX_RETRIES { - let delay = self.rate_limiter.lock().await.check_delay(); + // SAFETY: std::sync::Mutex blocks the executor thread while held. This is safe + // because the critical section is a single Instant::now() comparison with no I/O. + // If async work is ever added inside the lock, switch to an async-aware lock. + let delay = { + let mut limiter = self + .rate_limiter + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); + limiter.check_delay() + }; if let Some(d) = delay { sleep(d).await; } @@ -532,7 +558,8 @@ impl GitLabClient { .await .map_err(|e| LoreError::GitLabNetworkError { base_url: self.base_url.clone(), - source: Some(e), + kind: crate::core::error::NetworkErrorKind::Other, + detail: Some(format!("{e:?}")), })?; if response.status() == StatusCode::TOO_MANY_REQUESTS && attempt < Self::MAX_RETRIES { @@ -726,14 +753,14 @@ impl GitLabClient { )> { let (state_res, label_res, milestone_res) = match entity_type { "issue" => { - tokio::join!( + futures::join!( self.fetch_issue_state_events(gitlab_project_id, iid), self.fetch_issue_label_events(gitlab_project_id, iid), self.fetch_issue_milestone_events(gitlab_project_id, iid), ) } "merge_request" => { - tokio::join!( + futures::join!( self.fetch_mr_state_events(gitlab_project_id, iid), self.fetch_mr_label_events(gitlab_project_id, iid), self.fetch_mr_milestone_events(gitlab_project_id, iid), diff --git a/src/gitlab/graphql.rs b/src/gitlab/graphql.rs index 980fc02..70bb65e 100644 --- a/src/gitlab/graphql.rs +++ b/src/gitlab/graphql.rs @@ -55,7 +55,8 @@ impl GraphqlClient { .await .map_err(|e| LoreError::GitLabNetworkError { base_url: self.base_url.clone(), - source: Some(e), + kind: crate::core::error::NetworkErrorKind::Other, + detail: Some(format!("{e:?}")), })?; let status = response.status();