diff --git a/cli/build.rs b/cli/build.rs index c3b06dc..6e234d9 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -175,7 +175,7 @@ fn to_snake_case(s: &str) -> String { // Only insert underscore at transitions from lowercase to uppercase, // or when an uppercase sequence ends (e.g. "DOM" -> "dom", not "d_o_m") let prev_upper = chars[i - 1].is_uppercase(); - let next_lower = chars.get(i + 1).map_or(false, |n| n.is_lowercase()); + let next_lower = chars.get(i + 1).is_some_and(|n| n.is_lowercase()); if !prev_upper || next_lower { result.push('_'); } @@ -202,7 +202,7 @@ fn resolve_ref( // Check if this type actually exists in the referenced domain if domain_types .get(ref_domain) - .map_or(false, |t| t.contains(ref_type)) + .is_some_and(|t| t.contains(ref_type)) { format!( "super::cdp_{}::{}", @@ -339,7 +339,7 @@ fn generate_domain( if variant == "Self" { variant = "SelfValue".to_string(); } - if variant.chars().next().map_or(false, |c| c.is_ascii_digit()) { + if variant.chars().next().is_some_and(|c| c.is_ascii_digit()) { variant = format!("V{}", variant); } if seen_variants.insert(variant.clone()) { diff --git a/cli/src/commands.rs b/cli/src/commands.rs index ed8ea7c..2e707ee 100644 --- a/cli/src/commands.rs +++ b/cli/src/commands.rs @@ -137,7 +137,7 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result { - let new_tab = rest.iter().any(|arg| *arg == "--new-tab"); + let new_tab = rest.contains(&"--new-tab"); let sel = rest .iter() .find(|arg| **arg != "--new-tab") @@ -588,7 +588,7 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result { url = rest.get(j + 1).cloned(); j += 1; diff --git a/cli/src/native/actions.rs b/cli/src/native/actions.rs index 706cc58..77d5b73 100644 --- a/cli/src/native/actions.rs +++ b/cli/src/native/actions.rs @@ -173,7 +173,7 @@ impl DaemonState { let already_tracked = self .browser .as_ref() - .map_or(true, |b| b.has_target(&te.target_info.target_id)); + .is_none_or(|b| b.has_target(&te.target_info.target_id)); if !already_tracked { new_targets.push(te); } @@ -549,16 +549,16 @@ pub async fn execute_command(cmd: &Value, state: &mut DaemonState) -> Value { } // WebDriver backend: reject unsupported CDP-only actions - if matches!(state.backend_type, BackendType::WebDriver) { - if WEBDRIVER_UNSUPPORTED_ACTIONS.contains(&action) { - return error_response( - &id, - &format!( - "Action '{}' is not supported on the WebDriver backend", - action - ), - ); - } + if matches!(state.backend_type, BackendType::WebDriver) + && WEBDRIVER_UNSUPPORTED_ACTIONS.contains(&action) + { + return error_response( + &id, + &format!( + "Action '{}' is not supported on the WebDriver backend", + action + ), + ); } let result = match action { @@ -841,15 +841,7 @@ async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result Result, - name: Option<&str>, - url: Option<&str>, - ) -> Option { + fn find_frame(tree: &Value, name: Option<&str>, url: Option<&str>) -> Option { let frame = tree.get("frame")?; let frame_name = frame.get("name").and_then(|v| v.as_str()).unwrap_or(""); let frame_url = frame.get("url").and_then(|v| v.as_str()).unwrap_or(""); @@ -3256,7 +3243,7 @@ async fn handle_frame(cmd: &Value, state: &mut DaemonState) -> Result Result Result { if event.method == "Page.downloadProgress" && event.session_id.as_deref() == Some(&session_id) + && event.params.get("state").and_then(|v| v.as_str()) == Some("completed") { - if event.params.get("state").and_then(|v| v.as_str()) == Some("completed") { - let path = cmd - .get("path") - .and_then(|v| v.as_str()) - .unwrap_or("download"); - return Ok(json!({ "path": path })); - } + let path = cmd + .get("path") + .and_then(|v| v.as_str()) + .unwrap_or("download"); + return Ok(json!({ "path": path })); } } Ok(Err(_)) => return Err("Event stream closed".to_string()), diff --git a/cli/src/native/auth.rs b/cli/src/native/auth.rs index f130585..20af27c 100644 --- a/cli/src/native/auth.rs +++ b/cli/src/native/auth.rs @@ -215,16 +215,15 @@ fn decrypt_profile(data: &[u8]) -> Result { combined.extend_from_slice(&ciphertext); combined.extend_from_slice(&auth_tag); - let cipher = Aes256Gcm::new_from_slice(&key) - .map_err(|e| format!("Decryption key error: {}", e))?; + let cipher = + Aes256Gcm::new_from_slice(&key).map_err(|e| format!("Decryption key error: {}", e))?; let plaintext = cipher .decrypt(aes_gcm::Nonce::from_slice(&iv), combined.as_slice()) .map_err(|e| format!("Decryption failed: {}", e))?; let json_str = String::from_utf8(plaintext) .map_err(|e| format!("Decrypted data is not valid UTF-8: {}", e))?; - return serde_json::from_str(&json_str) - .map_err(|e| format!("Invalid profile data: {}", e)); + return serde_json::from_str(&json_str).map_err(|e| format!("Invalid profile data: {}", e)); } // Fallback: try as plain unencrypted JSON profile diff --git a/cli/src/native/browser.rs b/cli/src/native/browser.rs index b027919..39863b2 100644 --- a/cli/src/native/browser.rs +++ b/cli/src/native/browser.rs @@ -79,7 +79,9 @@ fn validate_lightpanda_options(options: &LaunchOptions) -> Result<(), String> { return Err("Headed mode is not supported with Lightpanda (headless only)".to_string()); } if !options.args.is_empty() { - return Err("Custom Chrome arguments (--args) are not supported with Lightpanda".to_string()); + return Err( + "Custom Chrome arguments (--args) are not supported with Lightpanda".to_string(), + ); } Ok(()) } diff --git a/cli/src/native/cdp/lightpanda.rs b/cli/src/native/cdp/lightpanda.rs index a36a2ba..3f2e1f4 100644 --- a/cli/src/native/cdp/lightpanda.rs +++ b/cli/src/native/cdp/lightpanda.rs @@ -23,22 +23,13 @@ impl Drop for LightpandaProcess { } } +#[derive(Default)] pub struct LightpandaLaunchOptions { pub executable_path: Option, pub proxy: Option, pub port: Option, } -impl Default for LightpandaLaunchOptions { - fn default() -> Self { - Self { - executable_path: None, - proxy: None, - port: None, - } - } -} - pub fn find_lightpanda() -> Option { // Check PATH via `which` #[cfg(unix)] @@ -89,9 +80,7 @@ pub fn find_lightpanda() -> Option { None } -pub fn launch_lightpanda( - options: &LightpandaLaunchOptions, -) -> Result { +pub fn launch_lightpanda(options: &LightpandaLaunchOptions) -> Result { let binary_path = match &options.executable_path { Some(p) => PathBuf::from(p), None => find_lightpanda().ok_or( diff --git a/cli/src/native/cdp/types.rs b/cli/src/native/cdp/types.rs index 416f781..282cf06 100644 --- a/cli/src/native/cdp/types.rs +++ b/cli/src/native/cdp/types.rs @@ -532,6 +532,7 @@ pub struct BrowserVersionInfo { /// Chromium source) into `cli/cdp-protocol/` and rebuild. /// /// Usage: `use super::cdp::types::generated::cdp_page::*;` +#[allow(clippy::upper_case_acronyms)] pub mod generated { include!(concat!(env!("OUT_DIR"), "/cdp_generated.rs")); } diff --git a/cli/src/native/recording.rs b/cli/src/native/recording.rs index 769b8e2..f9614cd 100644 --- a/cli/src/native/recording.rs +++ b/cli/src/native/recording.rs @@ -112,6 +112,25 @@ pub fn recording_stop(state: &mut RecordingState) -> Result { } } +pub fn recording_restart(state: &mut RecordingState, path: &str) -> Result { + let previous = if state.active { + let stop_result = recording_stop(state); + stop_result + .ok() + .and_then(|v| v.get("path").and_then(|p| p.as_str()).map(String::from)) + } else { + None + }; + + recording_start(state, path)?; + + Ok(json!({ + "restarted": true, + "previousPath": previous, + "path": path, + })) +} + #[cfg(test)] mod tests { use super::*; @@ -182,22 +201,3 @@ mod tests { let _ = std::fs::remove_dir_all(&state.temp_dir); } } - -pub fn recording_restart(state: &mut RecordingState, path: &str) -> Result { - let previous = if state.active { - let stop_result = recording_stop(state); - stop_result - .ok() - .and_then(|v| v.get("path").and_then(|p| p.as_str()).map(String::from)) - } else { - None - }; - - recording_start(state, path)?; - - Ok(json!({ - "restarted": true, - "previousPath": previous, - "path": path, - })) -} diff --git a/cli/src/native/snapshot.rs b/cli/src/native/snapshot.rs index 0d1cb78..7d91808 100644 --- a/cli/src/native/snapshot.rs +++ b/cli/src/native/snapshot.rs @@ -65,6 +65,7 @@ const STRUCTURAL_ROLES: &[&str] = &[ "RootWebArea", ]; +#[derive(Default)] pub struct SnapshotOptions { pub selector: Option, pub interactive: bool, @@ -73,18 +74,6 @@ pub struct SnapshotOptions { pub cursor: bool, } -impl Default for SnapshotOptions { - fn default() -> Self { - Self { - selector: None, - interactive: false, - compact: false, - depth: None, - cursor: false, - } - } -} - struct TreeNode { role: String, name: String, @@ -364,8 +353,7 @@ async fn find_cursor_interactive_elements( let escaped = text .replace('\\', "\\\\") .replace('"', "\\\"") - .replace('\n', " ") - .replace('\r', " "); + .replace(['\n', '\r'], " "); lines.push(format!("[ref={}] ({}) \"{}\"", ref_id, kind, escaped)); } diff --git a/cli/src/native/state.rs b/cli/src/native/state.rs index 00edf0d..9821f9d 100644 --- a/cli/src/native/state.rs +++ b/cli/src/native/state.rs @@ -467,7 +467,7 @@ pub fn find_auto_state_file(session_name: &str) -> Option { .ok() .and_then(|m| m.modified().ok()) .unwrap_or(std::time::UNIX_EPOCH); - if best_path.as_ref().map_or(true, |(_, t)| modified > *t) { + if best_path.as_ref().is_none_or(|(_, t)| modified > *t) { best_path = Some((path.to_string_lossy().to_string(), modified)); } } diff --git a/cli/src/native/stream.rs b/cli/src/native/stream.rs index e0a71a9..b5f428c 100644 --- a/cli/src/native/stream.rs +++ b/cli/src/native/stream.rs @@ -155,6 +155,7 @@ async fn accept_loop( } } +#[allow(clippy::result_large_err)] async fn handle_ws_client( stream: tokio::net::TcpStream, _addr: SocketAddr, diff --git a/cli/src/native/webdriver/client.rs b/cli/src/native/webdriver/client.rs index 8d61259..e7cbc35 100644 --- a/cli/src/native/webdriver/client.rs +++ b/cli/src/native/webdriver/client.rs @@ -212,32 +212,6 @@ impl WebDriverClient { } } -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_client_new() { - let client = WebDriverClient::new(4444); - assert_eq!(client.base_url, "http://127.0.0.1:4444"); - assert!(client.session_id.is_none()); - } - - #[test] - fn test_session_id_none() { - let client = WebDriverClient::new(4444); - let result = client.session_id(); - assert!(result.is_err()); - assert!(result.unwrap_err().contains("No active WebDriver session")); - } - - #[test] - fn test_client_custom_port() { - let client = WebDriverClient::new(9515); - assert_eq!(client.base_url, "http://127.0.0.1:9515"); - } -} - async fn http_request(method: &str, url: &str, body: Option<&Value>) -> Result { let parsed = url::Url::parse(url).map_err(|e| format!("Invalid URL: {}", e))?; let host = parsed.host_str().unwrap_or("127.0.0.1"); @@ -316,3 +290,29 @@ async fn http_request(method: &str, url: &str, body: Option<&Value>) -> Result