diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..38957264 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ +[env] +# Force use of system OpenSSL instead of vendored OpenSSL source builds. +OPENSSL_NO_VENDOR = "1" diff --git a/src-tauri/.cargo/config.toml b/src-tauri/.cargo/config.toml index ec6d3760..28287212 100644 --- a/src-tauri/.cargo/config.toml +++ b/src-tauri/.cargo/config.toml @@ -4,3 +4,8 @@ # error. The desktop binary links against rlib (static), so cdylib exports # are unused at runtime. rustflags = ["-C", "link-arg=-Wl,--exclude-all-symbols"] + +[env] +# Use system OpenSSL instead of vendoring from source (which requires Perl modules +# unavailable on some environments and breaks clippy/check). +OPENSSL_NO_VENDOR = "1" diff --git a/src-tauri/src/ai/mistral.rs b/src-tauri/src/ai/mistral.rs index 4f62c915..3fc2192d 100644 --- a/src-tauri/src/ai/mistral.rs +++ b/src-tauri/src/ai/mistral.rs @@ -47,7 +47,10 @@ impl Provider for MistralProvider { let resp = client .post(&url) - .header("Authorization", format!("Bearer {}", config.api_key)) + .header( + "Authorization", + format!("Bearer {api_key}", api_key = config.api_key), + ) .header("Content-Type", "application/json") .json(&body) .send() diff --git a/src-tauri/src/ai/openai.rs b/src-tauri/src/ai/openai.rs index cd2270d4..99fade4d 100644 --- a/src-tauri/src/ai/openai.rs +++ b/src-tauri/src/ai/openai.rs @@ -54,7 +54,8 @@ impl OpenAiProvider { .custom_endpoint_path .as_deref() .unwrap_or("/chat/completions"); - let url = format!("{}{}", config.api_url.trim_end_matches('/'), endpoint_path); + let api_url = config.api_url.trim_end_matches('/'); + let url = format!("{api_url}{endpoint_path}"); let mut body = serde_json::json!({ "model": config.model, @@ -75,7 +76,7 @@ impl OpenAiProvider { .as_deref() .unwrap_or("Authorization"); let auth_prefix = config.custom_auth_prefix.as_deref().unwrap_or("Bearer "); - let auth_value = format!("{}{}", auth_prefix, config.api_key); + let auth_value = format!("{auth_prefix}{api_key}", api_key = config.api_key); let resp = client .post(&url) @@ -122,7 +123,8 @@ impl OpenAiProvider { // Use custom endpoint path, default to empty (API URL already includes /api/v2/chat) let endpoint_path = config.custom_endpoint_path.as_deref().unwrap_or(""); - let url = format!("{}{}", config.api_url.trim_end_matches('/'), endpoint_path); + let api_url = config.api_url.trim_end_matches('/'); + let url = format!("{api_url}{endpoint_path}"); // Extract system message if present let system_message = messages @@ -177,7 +179,7 @@ impl OpenAiProvider { .as_deref() .unwrap_or("x-msi-genai-api-key"); let auth_prefix = config.custom_auth_prefix.as_deref().unwrap_or(""); - let auth_value = format!("{}{}", auth_prefix, config.api_key); + let auth_value = format!("{auth_prefix}{api_key}", api_key = config.api_key); let resp = client .post(&url) diff --git a/src-tauri/src/commands/ai.rs b/src-tauri/src/commands/ai.rs index d9812462..bc9fc657 100644 --- a/src-tauri/src/commands/ai.rs +++ b/src-tauri/src/commands/ai.rs @@ -246,7 +246,7 @@ pub async fn chat_message( "api_url": provider_config.api_url, "user_message": user_msg.content, "response_preview": if response.content.len() > 200 { - format!("{}...", &response.content[..200]) + format!("{preview}...", preview = &response.content[..200]) } else { response.content.clone() }, diff --git a/src-tauri/src/commands/db.rs b/src-tauri/src/commands/db.rs index fbe5a77a..1fc98837 100644 --- a/src-tauri/src/commands/db.rs +++ b/src-tauri/src/commands/db.rs @@ -295,19 +295,19 @@ pub async fn list_issues( let mut params: Vec> = vec![]; if let Some(ref status) = filter.status { - sql.push_str(&format!(" AND i.status = ?{}", params.len() + 1)); + sql.push_str(&format!(" AND i.status = ?{index}", index = params.len() + 1)); params.push(Box::new(status.clone())); } if let Some(ref severity) = filter.severity { - sql.push_str(&format!(" AND i.severity = ?{}", params.len() + 1)); + sql.push_str(&format!(" AND i.severity = ?{index}", index = params.len() + 1)); params.push(Box::new(severity.clone())); } if let Some(ref category) = filter.category { - sql.push_str(&format!(" AND i.category = ?{}", params.len() + 1)); + sql.push_str(&format!(" AND i.category = ?{index}", index = params.len() + 1)); params.push(Box::new(category.clone())); } if let Some(ref domain) = filter.domain { - sql.push_str(&format!(" AND i.category = ?{}", params.len() + 1)); + sql.push_str(&format!(" AND i.category = ?{index}", index = params.len() + 1)); params.push(Box::new(domain.clone())); } if let Some(ref search) = filter.search { @@ -321,9 +321,9 @@ pub async fn list_issues( sql.push_str(" ORDER BY i.updated_at DESC"); sql.push_str(&format!( - " LIMIT ?{} OFFSET ?{}", - params.len() + 1, - params.len() + 2 + " LIMIT ?{limit_index} OFFSET ?{offset_index}", + limit_index = params.len() + 1, + offset_index = params.len() + 2 )); params.push(Box::new(limit)); params.push(Box::new(offset)); diff --git a/src-tauri/src/commands/docs.rs b/src-tauri/src/commands/docs.rs index 11e6dc18..25b51d7a 100644 --- a/src-tauri/src/commands/docs.rs +++ b/src-tauri/src/commands/docs.rs @@ -34,7 +34,7 @@ pub async fn generate_rca( id: doc_id.clone(), issue_id: issue_id.clone(), doc_type: "rca".to_string(), - title: format!("RCA: {}", issue_detail.issue.title), + title: format!("RCA: {title}", title = issue_detail.issue.title), content_md: content_md.clone(), created_at: now.clone(), updated_at: now, @@ -49,7 +49,7 @@ pub async fn generate_rca( "doc_title": document.title, "content_length": content_md.len(), "content_preview": if content_md.len() > 300 { - format!("{}...", &content_md[..300]) + format!("{preview}...", preview = &content_md[..300]) } else { content_md.clone() }, @@ -93,7 +93,7 @@ pub async fn generate_postmortem( id: doc_id.clone(), issue_id: issue_id.clone(), doc_type: "postmortem".to_string(), - title: format!("Post-Mortem: {}", issue_detail.issue.title), + title: format!("Post-Mortem: {title}", title = issue_detail.issue.title), content_md: content_md.clone(), created_at: now.clone(), updated_at: now, @@ -108,7 +108,7 @@ pub async fn generate_postmortem( "doc_title": document.title, "content_length": content_md.len(), "content_preview": if content_md.len() > 300 { - format!("{}...", &content_md[..300]) + format!("{preview}...", preview = &content_md[..300]) } else { content_md.clone() }, diff --git a/src-tauri/src/commands/integrations.rs b/src-tauri/src/commands/integrations.rs index d7e04c3b..85988838 100644 --- a/src-tauri/src/commands/integrations.rs +++ b/src-tauri/src/commands/integrations.rs @@ -514,7 +514,7 @@ pub async fn authenticate_with_webview( app_handle: tauri::AppHandle, app_state: State<'_, AppState>, ) -> Result { - let webview_id = format!("{}-auth", service); + let webview_id = format!("{service}-auth"); // Check if window already exists if let Some(existing_label) = app_state @@ -526,10 +526,7 @@ pub async fn authenticate_with_webview( if app_handle.get_webview_window(existing_label).is_some() { return Ok(WebviewAuthResponse { success: true, - message: format!( - "{} browser window is already open. Switch to it to log in.", - service - ), + message: format!("{service} browser window is already open. Switch to it to log in."), webview_id: existing_label.clone(), }); } @@ -551,8 +548,7 @@ pub async fn authenticate_with_webview( Ok(WebviewAuthResponse { success: true, message: format!( - "{} browser window opened. This window will stay open - use it to browse and authenticate. Cookies will be extracted automatically for API calls.", - service + "{service} browser window opened. This window will stay open - use it to browse and authenticate. Cookies will be extracted automatically for API calls." ), webview_id, }) @@ -622,7 +618,7 @@ pub async fn extract_cookies_from_webview( Ok(ConnectionResult { success: true, - message: format!("{} authentication saved successfully", service), + message: format!("{service} authentication saved successfully"), }) } @@ -669,7 +665,7 @@ pub async fn save_manual_token( }; crate::integrations::servicenow::test_connection(&config).await } - _ => return Err(format!("Unknown service: {}", request.service)), + _ => return Err(format!("Unknown service: {service}", service = request.service)), }; // If test fails, don't save the token @@ -736,7 +732,10 @@ pub async fn save_manual_token( Ok(ConnectionResult { success: true, - message: format!("{} token saved and validated successfully", request.service), + message: format!( + "{service} token saved and validated successfully", + service = request.service + ), }) } diff --git a/src-tauri/src/commands/system.rs b/src-tauri/src/commands/system.rs index 7baf15a2..12ec3abc 100644 --- a/src-tauri/src/commands/system.rs +++ b/src-tauri/src/commands/system.rs @@ -98,20 +98,23 @@ pub async fn get_audit_log( let mut params: Vec> = vec![]; if let Some(ref action) = filter.action { - sql.push_str(&format!(" AND action = ?{}", params.len() + 1)); + sql.push_str(&format!(" AND action = ?{index}", index = params.len() + 1)); params.push(Box::new(action.clone())); } if let Some(ref entity_type) = filter.entity_type { - sql.push_str(&format!(" AND entity_type = ?{}", params.len() + 1)); + sql.push_str(&format!( + " AND entity_type = ?{index}", + index = params.len() + 1 + )); params.push(Box::new(entity_type.clone())); } if let Some(ref entity_id) = filter.entity_id { - sql.push_str(&format!(" AND entity_id = ?{}", params.len() + 1)); + sql.push_str(&format!(" AND entity_id = ?{index}", index = params.len() + 1)); params.push(Box::new(entity_id.clone())); } sql.push_str(" ORDER BY timestamp DESC"); - sql.push_str(&format!(" LIMIT ?{}", params.len() + 1)); + sql.push_str(&format!(" LIMIT ?{index}", index = params.len() + 1)); params.push(Box::new(limit)); let param_refs: Vec<&dyn rusqlite::types::ToSql> = params.iter().map(|p| p.as_ref()).collect(); diff --git a/src-tauri/src/docs/postmortem.rs b/src-tauri/src/docs/postmortem.rs index 1a5631ff..cc6085c8 100644 --- a/src-tauri/src/docs/postmortem.rs +++ b/src-tauri/src/docs/postmortem.rs @@ -5,15 +5,21 @@ pub fn generate_postmortem_markdown(detail: &IssueDetail) -> String { let mut md = String::new(); - md.push_str(&format!("# Blameless Post-Mortem: {}\n\n", issue.title)); + md.push_str(&format!( + "# Blameless Post-Mortem: {title}\n\n", + title = issue.title + )); // Header metadata md.push_str("## Metadata\n\n"); - md.push_str(&format!("- **Date:** {}\n", issue.created_at)); - md.push_str(&format!("- **Severity:** {}\n", issue.severity)); - md.push_str(&format!("- **Category:** {}\n", issue.category)); - md.push_str(&format!("- **Status:** {}\n", issue.status)); - md.push_str(&format!("- **Last Updated:** {}\n", issue.updated_at)); + md.push_str(&format!("- **Date:** {created_at}\n", created_at = issue.created_at)); + md.push_str(&format!("- **Severity:** {severity}\n", severity = issue.severity)); + md.push_str(&format!("- **Category:** {category}\n", category = issue.category)); + md.push_str(&format!("- **Status:** {status}\n", status = issue.status)); + md.push_str(&format!( + "- **Last Updated:** {updated_at}\n", + updated_at = issue.updated_at + )); md.push_str(&format!( "- **Assigned To:** {}\n", if issue.assigned_to.is_empty() { @@ -45,7 +51,7 @@ pub fn generate_postmortem_markdown(detail: &IssueDetail) -> String { md.push_str("## Timeline\n\n"); md.push_str("| Time (UTC) | Event |\n"); md.push_str("|------------|-------|\n"); - md.push_str(&format!("| {} | Issue created |\n", issue.created_at)); + md.push_str(&format!("| {created_at} | Issue created |\n", created_at = issue.created_at)); if let Some(ref resolved) = issue.resolved_at { md.push_str(&format!("| {resolved} | Issue resolved |\n")); } @@ -77,7 +83,7 @@ pub fn generate_postmortem_markdown(detail: &IssueDetail) -> String { if let Some(last) = detail.resolution_steps.last() { if !last.answer.is_empty() { - md.push_str(&format!("**Root Cause:** {}\n\n", last.answer)); + md.push_str(&format!("**Root Cause:** {answer}\n\n", answer = last.answer)); } } } diff --git a/src-tauri/src/docs/rca.rs b/src-tauri/src/docs/rca.rs index cff36be7..a6b7658c 100644 --- a/src-tauri/src/docs/rca.rs +++ b/src-tauri/src/docs/rca.rs @@ -5,16 +5,16 @@ pub fn generate_rca_markdown(detail: &IssueDetail) -> String { let mut md = String::new(); - md.push_str(&format!("# Root Cause Analysis: {}\n\n", issue.title)); + md.push_str(&format!("# Root Cause Analysis: {title}\n\n", title = issue.title)); md.push_str("## Issue Summary\n\n"); md.push_str("| Field | Value |\n"); md.push_str("|-------|-------|\n"); - md.push_str(&format!("| **Issue ID** | {} |\n", issue.id)); - md.push_str(&format!("| **Category** | {} |\n", issue.category)); - md.push_str(&format!("| **Status** | {} |\n", issue.status)); - md.push_str(&format!("| **Severity** | {} |\n", issue.severity)); - md.push_str(&format!("| **Source** | {} |\n", issue.source)); + md.push_str(&format!("| **Issue ID** | {id} |\n", id = issue.id)); + md.push_str(&format!("| **Category** | {category} |\n", category = issue.category)); + md.push_str(&format!("| **Status** | {status} |\n", status = issue.status)); + md.push_str(&format!("| **Severity** | {severity} |\n", severity = issue.severity)); + md.push_str(&format!("| **Source** | {source} |\n", source = issue.source)); md.push_str(&format!( "| **Assigned To** | {} |\n", if issue.assigned_to.is_empty() { @@ -23,8 +23,11 @@ pub fn generate_rca_markdown(detail: &IssueDetail) -> String { &issue.assigned_to } )); - md.push_str(&format!("| **Created** | {} |\n", issue.created_at)); - md.push_str(&format!("| **Last Updated** | {} |\n", issue.updated_at)); + md.push_str(&format!("| **Created** | {created_at} |\n", created_at = issue.created_at)); + md.push_str(&format!( + "| **Last Updated** | {updated_at} |\n", + updated_at = issue.updated_at + )); if let Some(ref resolved) = issue.resolved_at { md.push_str(&format!("| **Resolved** | {resolved} |\n")); } @@ -47,12 +50,12 @@ pub fn generate_rca_markdown(detail: &IssueDetail) -> String { step.step_order, step.why_question )); if !step.answer.is_empty() { - md.push_str(&format!("**Answer:** {}\n\n", step.answer)); + md.push_str(&format!("**Answer:** {answer}\n\n", answer = step.answer)); } else { md.push_str("_Awaiting answer._\n\n"); } if !step.evidence.is_empty() { - md.push_str(&format!("**Evidence:** {}\n\n", step.evidence)); + md.push_str(&format!("**Evidence:** {evidence}\n\n", evidence = step.evidence)); } } } diff --git a/src-tauri/src/integrations/auth.rs b/src-tauri/src/integrations/auth.rs index 506e209a..ed6ad7a1 100644 --- a/src-tauri/src/integrations/auth.rs +++ b/src-tauri/src/integrations/auth.rs @@ -365,7 +365,7 @@ mod tests { .create_async() .await; - let token_endpoint = format!("{}/oauth/token", server.url()); + let token_endpoint = format!("{server_url}/oauth/token", server_url = server.url()); let result = exchange_code( &token_endpoint, "test-client-id", @@ -397,7 +397,7 @@ mod tests { .create_async() .await; - let token_endpoint = format!("{}/oauth/token", server.url()); + let token_endpoint = format!("{server_url}/oauth/token", server_url = server.url()); let result = exchange_code( &token_endpoint, "test-client-id", @@ -421,7 +421,7 @@ mod tests { .create_async() .await; - let token_endpoint = format!("{}/oauth/token", server.url()); + let token_endpoint = format!("{server_url}/oauth/token", server_url = server.url()); let result = exchange_code( &token_endpoint, "test-client-id", diff --git a/src-tauri/src/integrations/azuredevops.rs b/src-tauri/src/integrations/azuredevops.rs index e05e6ab7..76f04589 100644 --- a/src-tauri/src/integrations/azuredevops.rs +++ b/src-tauri/src/integrations/azuredevops.rs @@ -40,9 +40,10 @@ pub async fn test_connection(config: &AzureDevOpsConfig) -> Result Result Result Result, String> { let client = reqwest::Client::new(); - let url = format!("{}/rest/api/space", config.base_url.trim_end_matches('/')); + let base_url = config.base_url.trim_end_matches('/'); + let url = format!("{base_url}/rest/api/space"); let resp = client .get(&url) @@ -103,9 +105,9 @@ pub async fn search_pages( config.base_url.trim_end_matches('/') ); - let mut cql = format!("text ~ \"{}\"", query); + let mut cql = format!("text ~ \"{query}\""); if let Some(space) = space_key { - cql = format!("{} AND space = {}", cql, space); + cql = format!("{cql} AND space = {space}"); } let resp = client @@ -140,7 +142,7 @@ pub async fn search_pages( id: page_id.to_string(), title: p["title"].as_str()?.to_string(), space_key: p["space"]["key"].as_str()?.to_string(), - url: format!("{}/pages/viewpage.action?pageId={}", base_url, page_id), + url: format!("{base_url}/pages/viewpage.action?pageId={page_id}"), }) }) .collect(); @@ -157,7 +159,8 @@ pub async fn publish_page( parent_page_id: Option<&str>, ) -> Result { let client = reqwest::Client::new(); - let url = format!("{}/rest/api/content", config.base_url.trim_end_matches('/')); + let base_url = config.base_url.trim_end_matches('/'); + let url = format!("{base_url}/rest/api/content"); let mut body = serde_json::json!({ "type": "page", diff --git a/src-tauri/src/integrations/servicenow.rs b/src-tauri/src/integrations/servicenow.rs index 9e0bd072..a67f5c80 100644 --- a/src-tauri/src/integrations/servicenow.rs +++ b/src-tauri/src/integrations/servicenow.rs @@ -42,9 +42,10 @@ pub async fn test_connection(config: &ServiceNowConfig) -> Result Result { + let trimmed_base_url = base_url.trim_end_matches('/'); let login_url = match service { - "confluence" => format!("{}/login.action", base_url.trim_end_matches('/')), + "confluence" => format!("{trimmed_base_url}/login.action"), "azuredevops" => { // Azure DevOps login - user will be redirected through Microsoft SSO - format!("{}/_signin", base_url.trim_end_matches('/')) + format!("{trimmed_base_url}/_signin") } - "servicenow" => format!("{}/login.do", base_url.trim_end_matches('/')), + "servicenow" => format!("{trimmed_base_url}/login.do"), _ => return Err(format!("Unknown service: {service}")), }; @@ -42,13 +43,13 @@ pub async fn authenticate_with_webview( ); // Create persistent browser window (stays open for browsing and fresh cookie extraction) - let webview_label = format!("{}-auth", service); + let webview_label = format!("{service}-auth"); let webview = WebviewWindowBuilder::new( &app_handle, &webview_label, WebviewUrl::External(login_url.parse().map_err(|e| format!("Invalid URL: {e}"))?), ) - .title(format!("{} Browser (TFTSR)", service)) + .title(format!("{service} Browser (TFTSR)")) .inner_size(1000.0, 800.0) .min_inner_size(800.0, 600.0) .resizable(true) @@ -195,7 +196,7 @@ pub async fn extract_cookies_via_ipc( pub fn cookies_to_header(cookies: &[Cookie]) -> String { cookies .iter() - .map(|c| format!("{}={}", c.name, c.value)) + .map(|c| format!("{name}={value}", name = c.name.as_str(), value = c.value.as_str())) .collect::>() .join("; ") }