refactor: deserialize response & request to jsonrpc::{Response,

Request}; warn unwarp_use & expect_use
This commit is contained in:
2025-12-18 17:30:10 +08:00
parent d6320061c2
commit e2d2826d5b
6 changed files with 74 additions and 62 deletions

View File

@@ -23,27 +23,30 @@ pub async fn serve_static(uri: Uri) -> impl IntoResponse {
match Assets::get(path.trim_start_matches('/')) { match Assets::get(path.trim_start_matches('/')) {
Some(content) => { Some(content) => {
let mime = mime_guess::from_path(&path).first_or_octet_stream(); let mime = mime_guess::from_path(&path).first_or_octet_stream();
#[allow(clippy::expect_used)]
Response::builder() Response::builder()
.status(StatusCode::OK) .status(StatusCode::OK)
.header(header::CONTENT_TYPE, mime.as_ref()) .header(header::CONTENT_TYPE, mime.as_ref())
.body(Body::from(content.data)) .body(Body::from(content.data))
.unwrap() .expect("always Ok")
} }
None => { None => {
// For SPA routing, serve index.html for non-asset paths // For SPA routing, serve index.html for non-asset paths
if !path.contains('.') if !path.contains('.')
&& let Some(index) = Assets::get("index.html") && let Some(index) = Assets::get("index.html")
{ {
#[allow(clippy::expect_used)]
return Response::builder() return Response::builder()
.status(StatusCode::OK) .status(StatusCode::OK)
.header(header::CONTENT_TYPE, "text/html") .header(header::CONTENT_TYPE, "text/html")
.body(Body::from(index.data)) .body(Body::from(index.data))
.unwrap(); .expect("always Ok");
} }
#[allow(clippy::expect_used)]
Response::builder() Response::builder()
.status(StatusCode::NOT_FOUND) .status(StatusCode::NOT_FOUND)
.body(Body::from("404 Not Found")) .body(Body::from("404 Not Found"))
.unwrap() .expect("always Ok")
} }
} }
} }

View File

@@ -37,6 +37,7 @@ impl Cryptor {
let len = plaintext.len(); let len = plaintext.len();
let mut buffer = plaintext.into_bytes(); let mut buffer = plaintext.into_bytes();
buffer.extend(std::iter::repeat_n(0, 16 * (len / 16 + 1))); buffer.extend(std::iter::repeat_n(0, 16 * (len / 16 + 1)));
#[allow(clippy::expect_used)]
let ciphertext = self let ciphertext = self
.encryptor .encryptor
.clone() .clone()

View File

@@ -139,11 +139,12 @@ pub enum ResponseData {
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct GenericResponseContent { pub struct GenericResponseContent {
pub r#type: String, pub r#type: String,
#[serde(default)] pub data: Value,
pub data: Option<Value>,
} }
#[cfg(test)] #[cfg(test)]
#[allow(clippy::expect_used)]
#[allow(clippy::unwrap_used)]
mod test { mod test {
use indoc::indoc; use indoc::indoc;
use serde_json::Map; use serde_json::Map;

View File

@@ -1,3 +1,6 @@
#![warn(clippy::unwrap_used)]
#![warn(clippy::expect_used)]
use std::{net::SocketAddr, sync::Arc}; use std::{net::SocketAddr, sync::Arc};
use anyhow::Context; use anyhow::Context;

View File

@@ -16,6 +16,7 @@ use crate::{
self, self,
models::{InterceptionAction, InterceptionRule}, models::{InterceptionAction, InterceptionRule},
}, },
jsonrpc,
}; };
struct Processed { struct Processed {
@@ -113,12 +114,14 @@ pub async fn middleware(
// Build and return the final response // Build and return the final response
let mut response_builder = Response::builder().status(resp_parts.status); let mut response_builder = Response::builder().status(resp_parts.status);
#[allow(clippy::expect_used)]
if !resp_parts.headers.is_empty() { 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 response_builder
.body(axum::body::Body::from(processed_resp.encrypted)) .body(axum::body::Body::from(processed_resp.encrypted))
.unwrap() .expect("always Ok")
} }
/// Processes the incoming request body. /// Processes the incoming request body.
@@ -132,6 +135,7 @@ async fn process_request(body: &str, ctx: &Arc<AppContext>) -> (Processed, Strin
Value::String("Could not deserialize request".into()) Value::String("Could not deserialize request".into())
}); });
decrypt_params(&mut plain_request, ctx); decrypt_params(&mut plain_request, ctx);
#[allow(clippy::expect_used)]
let original = serde_json::to_string(&plain_request).expect("deserialization succeeded"); let original = serde_json::to_string(&plain_request).expect("deserialization succeeded");
let method = plain_request let method = plain_request
@@ -142,8 +146,9 @@ async fn process_request(body: &str, ctx: &Arc<AppContext>) -> (Processed, Strin
"" ""
}) })
.to_string(); .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, Ok(action) => action,
Err(e) => { Err(e) => {
warn!("Failed to modify request: {}", e); warn!("Failed to modify request: {}", e);
@@ -152,15 +157,18 @@ async fn process_request(body: &str, ctx: &Arc<AppContext>) -> (Processed, Strin
}; };
let final_ = action.map(|action| { 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, 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); encrypt_params(&mut encrypted_request, ctx);
( (
#[allow(clippy::expect_used)]
Processed { Processed {
original, original,
final_, final_,
@@ -181,14 +189,15 @@ async fn process_response(body: String, method: &str, ctx: &Arc<AppContext>) ->
); );
body 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!( warn!(
"Failed to deserialize response body: {}. Using string value.", "Failed to deserialize response body: {}. Using string value.",
err err
); );
Value::String(decrypted_body.clone()) Value::String(decrypted_body.clone())
}); }); */
let action = match modify_response(&mut response_value, method, ctx).await { let action = match modify_response(&mut response_value, method, ctx).await {
Ok(action) => action, Ok(action) => action,
@@ -198,6 +207,7 @@ async fn process_response(body: String, method: &str, ctx: &Arc<AppContext>) ->
} }
}; };
#[allow(clippy::expect_used)]
let modified_body_str = let modified_body_str =
serde_json::to_string(&response_value).expect("serialization succeeded"); serde_json::to_string(&response_value).expect("serialization succeeded");
let encrypted = ctx.cryptor.encrypt(modified_body_str.clone()); let encrypted = ctx.cryptor.encrypt(modified_body_str.clone());
@@ -212,7 +222,7 @@ async fn process_response(body: String, method: &str, ctx: &Arc<AppContext>) ->
/// Placeholder for request modification logic. /// Placeholder for request modification logic.
async fn modify_request( async fn modify_request(
_request_json: &mut Value, _request: &mut jsonrpc::Request,
_method: &str, _method: &str,
_ctx: &Arc<AppContext>, _ctx: &Arc<AppContext>,
) -> anyhow::Result<Option<InterceptionAction>> { ) -> anyhow::Result<Option<InterceptionAction>> {
@@ -222,19 +232,20 @@ async fn modify_request(
/// Applies modification rules to the response. /// Applies modification rules to the response.
async fn modify_response( async fn modify_response(
response_json: &mut Value, response: &mut jsonrpc::Response,
method: &str, method: &str,
ctx: &Arc<AppContext>, ctx: &Arc<AppContext>,
) -> anyhow::Result<Option<InterceptionAction>> { ) -> anyhow::Result<Option<InterceptionAction>> {
// Check for generic method interception (e.g., replace response from DB) // Check for generic method interception (e.g., replace response from DB)
if let Some((intercepted, action)) = intercept_response(method, ctx).await? { if let Some((intercepted, action)) = intercept_response(method, ctx).await? {
debug!("Intercepting response for method: {}", method); 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!( warn!(
"Failed to parse intercepted response as JSON: {}. Using as string.", "Failed to parse intercepted response as JSON: {}. Using as string.",
e e
); );
Value::String(intercepted) // Value::String(intercepted)
todo!()
}); });
return Ok(Some(action)); return Ok(Some(action));
} }
@@ -242,42 +253,35 @@ async fn modify_response(
// Special handling for getcommand // Special handling for getcommand
// TODO: Return interception rule // TODO: Return interception rule
if method == "com.linspirer.device.getcommand" 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!( handle_getcommand_response(data, ctx).await;
"Failed to handle getcommand response: {}. Responding with empty command list.", return Ok(Some(InterceptionAction::Modify));
e
);
if let Some(obj) = response_json.as_object_mut() {
obj.insert("result".to_string(), Value::Array(vec![]));
}
} }
Ok(None) Ok(None)
} }
/// Handles the 'getcommand' response by injecting verified commands. /// Handles the 'getcommand' response by injecting verified commands.
async fn handle_getcommand_response( async fn handle_getcommand_response(data: &mut Value, ctx: &Arc<AppContext>) {
response_json: &mut Value, if let Some(commands) = data.as_array_mut() {
ctx: &Arc<AppContext>, for cmd in commands.drain(..) {
) -> anyhow::Result<()> { let Ok(cmd_json) = serde_json::to_string(&cmd).inspect_err(|err| {
if let Some(result) = response_json.get_mut("result") warn!("Failed to call serde_json::to_string on {cmd:#?}: {err}");
&& let Some(commands) = result.as_array_mut() }) else {
&& !commands.is_empty() continue;
{ };
// Persist commands to database
for cmd in commands.iter() {
let cmd_json = serde_json::to_string(cmd)?;
if let Err(e) = if let Err(e) =
crate::db::repositories::commands::insert(&ctx.db, &cmd_json, "unverified").await 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 // Get verified commands from database
let verified_cmds = let verified_cmds =
match crate::db::repositories::commands::list_by_status(&ctx.db, "verified").await { match crate::db::repositories::commands::list_by_status(&ctx.db, "verified").await {
@@ -294,7 +298,7 @@ async fn handle_getcommand_response(
.filter_map(|c| serde_json::from_str(&c.command_json).ok()) .filter_map(|c| serde_json::from_str(&c.command_json).ok())
.collect(); .collect();
obj.insert("result".to_string(), Value::Array(verified_values)); *data = Value::Array(verified_values);
// Clear verified commands from database after sending // Clear verified commands from database after sending
if let Err(e) = crate::db::repositories::commands::clear_verified(&ctx.db).await { if let Err(e) = crate::db::repositories::commands::clear_verified(&ctx.db).await {
@@ -302,9 +306,6 @@ async fn handle_getcommand_response(
} }
} }
Ok(())
}
/// Checks for and applies response interception rules. /// Checks for and applies response interception rules.
async fn intercept_response( async fn intercept_response(
method: &str, method: &str,

View File

@@ -62,12 +62,14 @@ pub async fn proxy_handler(
}; };
let mut response_builder = Response::builder().status(resp_parts.status); let mut response_builder = Response::builder().status(resp_parts.status);
#[allow(clippy::expect_used)]
if !resp_parts.headers.is_empty() { 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 response_builder
.body(axum::body::Body::from(resp_body)) .body(axum::body::Body::from(resp_body))
.unwrap() .expect("always Ok")
} }
async fn clone_response( async fn clone_response(
@@ -77,8 +79,9 @@ async fn clone_response(
.status(resp.status()) .status(resp.status())
.version(resp.version()); .version(resp.version());
#[allow(clippy::expect_used)]
if !resp.headers().is_empty() { 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 parts = parts_builder.body(())?.into_parts().0;
let body_text = resp.text().await?; let body_text = resp.text().await?;