From e2d2826d5b555850571f4100bd9e9745f17aa4ec Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Thu, 18 Dec 2025 17:30:10 +0800 Subject: [PATCH] refactor: deserialize response & request to jsonrpc::{Response, Request}; warn unwarp_use & expect_use --- src/admin/static_files.rs | 9 ++-- src/crypto.rs | 1 + src/jsonrpc.rs | 5 +- src/main.rs | 3 ++ src/middleware.rs | 109 +++++++++++++++++++------------------- src/proxy.rs | 9 ++-- 6 files changed, 74 insertions(+), 62 deletions(-) diff --git a/src/admin/static_files.rs b/src/admin/static_files.rs index 8cfc590..8c8abaa 100644 --- a/src/admin/static_files.rs +++ b/src/admin/static_files.rs @@ -23,27 +23,30 @@ pub async fn serve_static(uri: Uri) -> impl IntoResponse { match Assets::get(path.trim_start_matches('/')) { Some(content) => { let mime = mime_guess::from_path(&path).first_or_octet_stream(); + #[allow(clippy::expect_used)] Response::builder() .status(StatusCode::OK) .header(header::CONTENT_TYPE, mime.as_ref()) .body(Body::from(content.data)) - .unwrap() + .expect("always Ok") } None => { // For SPA routing, serve index.html for non-asset paths if !path.contains('.') && let Some(index) = Assets::get("index.html") { + #[allow(clippy::expect_used)] return Response::builder() .status(StatusCode::OK) .header(header::CONTENT_TYPE, "text/html") .body(Body::from(index.data)) - .unwrap(); + .expect("always Ok"); } + #[allow(clippy::expect_used)] Response::builder() .status(StatusCode::NOT_FOUND) .body(Body::from("404 Not Found")) - .unwrap() + .expect("always Ok") } } } diff --git a/src/crypto.rs b/src/crypto.rs index 76b8342..d7a800d 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -37,6 +37,7 @@ impl Cryptor { let len = plaintext.len(); let mut buffer = plaintext.into_bytes(); buffer.extend(std::iter::repeat_n(0, 16 * (len / 16 + 1))); + #[allow(clippy::expect_used)] let ciphertext = self .encryptor .clone() diff --git a/src/jsonrpc.rs b/src/jsonrpc.rs index 7f5610a..a1659f6 100644 --- a/src/jsonrpc.rs +++ b/src/jsonrpc.rs @@ -139,11 +139,12 @@ pub enum ResponseData { #[derive(Serialize, Deserialize, Debug)] pub struct GenericResponseContent { pub r#type: String, - #[serde(default)] - pub data: Option, + pub data: Value, } #[cfg(test)] +#[allow(clippy::expect_used)] +#[allow(clippy::unwrap_used)] mod test { use indoc::indoc; use serde_json::Map; diff --git a/src/main.rs b/src/main.rs index 87d7d13..8948e17 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,6 @@ +#![warn(clippy::unwrap_used)] +#![warn(clippy::expect_used)] + use std::{net::SocketAddr, sync::Arc}; use anyhow::Context; diff --git a/src/middleware.rs b/src/middleware.rs index ece35ff..17bd6e8 100644 --- a/src/middleware.rs +++ b/src/middleware.rs @@ -16,6 +16,7 @@ use crate::{ self, models::{InterceptionAction, InterceptionRule}, }, + jsonrpc, }; struct Processed { @@ -113,12 +114,14 @@ pub async fn middleware( // Build and return the final response let mut response_builder = Response::builder().status(resp_parts.status); + #[allow(clippy::expect_used)] if !resp_parts.headers.is_empty() { - *response_builder.headers_mut().unwrap() = resp_parts.headers; + *response_builder.headers_mut().expect("always Ok") = resp_parts.headers; } + #[allow(clippy::expect_used)] response_builder .body(axum::body::Body::from(processed_resp.encrypted)) - .unwrap() + .expect("always Ok") } /// Processes the incoming request body. @@ -132,6 +135,7 @@ async fn process_request(body: &str, ctx: &Arc) -> (Processed, Strin Value::String("Could not deserialize request".into()) }); decrypt_params(&mut plain_request, ctx); + #[allow(clippy::expect_used)] let original = serde_json::to_string(&plain_request).expect("deserialization succeeded"); let method = plain_request @@ -142,8 +146,9 @@ async fn process_request(body: &str, ctx: &Arc) -> (Processed, Strin "" }) .to_string(); + let mut request: jsonrpc::Request = serde_json::from_value(plain_request).unwrap(); - let action = match modify_request(&mut plain_request, &method, ctx).await { + let action = match modify_request(&mut request, &method, ctx).await { Ok(action) => action, Err(e) => { warn!("Failed to modify request: {}", e); @@ -152,15 +157,18 @@ async fn process_request(body: &str, ctx: &Arc) -> (Processed, Strin }; let final_ = action.map(|action| { ( - serde_json::to_string(&plain_request).expect("deserialization succeeded"), + #[allow(clippy::expect_used)] + serde_json::to_string(&request).expect("deserialization succeeded"), action, ) }); - let mut encrypted_request = plain_request.clone(); + #[allow(clippy::expect_used)] + let mut encrypted_request = serde_json::to_value(request).expect("deserialization succeeded"); encrypt_params(&mut encrypted_request, ctx); ( + #[allow(clippy::expect_used)] Processed { original, final_, @@ -181,14 +189,15 @@ async fn process_response(body: String, method: &str, ctx: &Arc) -> ); body }); + let mut response_value: jsonrpc::Response = serde_json::from_str(&decrypted_body).unwrap(); - let mut response_value: Value = serde_json::from_str(&decrypted_body).unwrap_or_else(|err| { + /* let mut response_value: Value = serde_json::from_str(&decrypted_body).unwrap_or_else(|err| { warn!( "Failed to deserialize response body: {}. Using string value.", err ); Value::String(decrypted_body.clone()) - }); + }); */ let action = match modify_response(&mut response_value, method, ctx).await { Ok(action) => action, @@ -198,6 +207,7 @@ async fn process_response(body: String, method: &str, ctx: &Arc) -> } }; + #[allow(clippy::expect_used)] let modified_body_str = serde_json::to_string(&response_value).expect("serialization succeeded"); let encrypted = ctx.cryptor.encrypt(modified_body_str.clone()); @@ -212,7 +222,7 @@ async fn process_response(body: String, method: &str, ctx: &Arc) -> /// Placeholder for request modification logic. async fn modify_request( - _request_json: &mut Value, + _request: &mut jsonrpc::Request, _method: &str, _ctx: &Arc, ) -> anyhow::Result> { @@ -222,19 +232,20 @@ async fn modify_request( /// Applies modification rules to the response. async fn modify_response( - response_json: &mut Value, + response: &mut jsonrpc::Response, method: &str, ctx: &Arc, ) -> anyhow::Result> { // Check for generic method interception (e.g., replace response from DB) if let Some((intercepted, action)) = intercept_response(method, ctx).await? { debug!("Intercepting response for method: {}", method); - *response_json = serde_json::from_str(&intercepted).unwrap_or_else(|e| { + *response = serde_json::from_str(&intercepted).unwrap_or_else(|e| { warn!( "Failed to parse intercepted response as JSON: {}. Using as string.", e ); - Value::String(intercepted) + // Value::String(intercepted) + todo!() }); return Ok(Some(action)); } @@ -242,67 +253,57 @@ async fn modify_response( // Special handling for getcommand // TODO: Return interception rule if method == "com.linspirer.device.getcommand" - && let Err(e) = handle_getcommand_response(response_json, ctx).await + && let jsonrpc::ResponseData::Generic(jsonrpc::GenericResponseContent { data, .. }) = + &mut response.data { - warn!( - "Failed to handle getcommand response: {}. Responding with empty command list.", - e - ); - if let Some(obj) = response_json.as_object_mut() { - obj.insert("result".to_string(), Value::Array(vec![])); - } + handle_getcommand_response(data, ctx).await; + return Ok(Some(InterceptionAction::Modify)); } Ok(None) } /// Handles the 'getcommand' response by injecting verified commands. -async fn handle_getcommand_response( - response_json: &mut Value, - ctx: &Arc, -) -> anyhow::Result<()> { - if let Some(result) = response_json.get_mut("result") - && let Some(commands) = result.as_array_mut() - && !commands.is_empty() - { - // Persist commands to database - for cmd in commands.iter() { - let cmd_json = serde_json::to_string(cmd)?; +async fn handle_getcommand_response(data: &mut Value, ctx: &Arc) { + if let Some(commands) = data.as_array_mut() { + for cmd in commands.drain(..) { + let Ok(cmd_json) = serde_json::to_string(&cmd).inspect_err(|err| { + warn!("Failed to call serde_json::to_string on {cmd:#?}: {err}"); + }) else { + continue; + }; if let Err(e) = crate::db::repositories::commands::insert(&ctx.db, &cmd_json, "unverified").await { - warn!("Failed to persist command to database: {}", e); + warn!("Failed to add command to database: {}", e); + } else { + debug!("Added command to the queue: {:?}", cmd); } - debug!("Added command to the queue: {:?}", cmd); } } - if let Some(obj) = response_json.as_object_mut() { - // Get verified commands from database - let verified_cmds = - match crate::db::repositories::commands::list_by_status(&ctx.db, "verified").await { - Ok(cmds) => cmds, - Err(e) => { - warn!("Failed to fetch verified commands from database: {}", e); - Vec::new() - } - }; + // Get verified commands from database + let verified_cmds = + match crate::db::repositories::commands::list_by_status(&ctx.db, "verified").await { + Ok(cmds) => cmds, + Err(e) => { + warn!("Failed to fetch verified commands from database: {}", e); + Vec::new() + } + }; - // Convert to JSON values - let verified_values: Vec = verified_cmds - .iter() - .filter_map(|c| serde_json::from_str(&c.command_json).ok()) - .collect(); + // Convert to JSON values + let verified_values: Vec = verified_cmds + .iter() + .filter_map(|c| serde_json::from_str(&c.command_json).ok()) + .collect(); - obj.insert("result".to_string(), Value::Array(verified_values)); + *data = Value::Array(verified_values); - // Clear verified commands from database after sending - if let Err(e) = crate::db::repositories::commands::clear_verified(&ctx.db).await { - warn!("Failed to clear verified commands from database: {}", e); - } + // Clear verified commands from database after sending + if let Err(e) = crate::db::repositories::commands::clear_verified(&ctx.db).await { + warn!("Failed to clear verified commands from database: {}", e); } - - Ok(()) } /// Checks for and applies response interception rules. diff --git a/src/proxy.rs b/src/proxy.rs index ad9221b..f77c835 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -62,12 +62,14 @@ pub async fn proxy_handler( }; let mut response_builder = Response::builder().status(resp_parts.status); + #[allow(clippy::expect_used)] if !resp_parts.headers.is_empty() { - *response_builder.headers_mut().unwrap() = resp_parts.headers; + *response_builder.headers_mut().expect("always Ok") = resp_parts.headers; } + #[allow(clippy::expect_used)] response_builder .body(axum::body::Body::from(resp_body)) - .unwrap() + .expect("always Ok") } async fn clone_response( @@ -77,8 +79,9 @@ async fn clone_response( .status(resp.status()) .version(resp.version()); + #[allow(clippy::expect_used)] if !resp.headers().is_empty() { - *parts_builder.headers_mut().unwrap() = resp.headers().clone(); + *parts_builder.headers_mut().expect("always Ok") = resp.headers().clone(); } let parts = parts_builder.body(())?.into_parts().0; let body_text = resp.text().await?;