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('/')) {
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")
}
}
}

View File

@@ -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()

View File

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

View File

@@ -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<AppContext>) -> (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<AppContext>) -> (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<AppContext>) -> (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<AppContext>) ->
);
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<AppContext>) ->
}
};
#[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<AppContext>) ->
/// Placeholder for request modification logic.
async fn modify_request(
_request_json: &mut Value,
_request: &mut jsonrpc::Request,
_method: &str,
_ctx: &Arc<AppContext>,
) -> anyhow::Result<Option<InterceptionAction>> {
@@ -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<AppContext>,
) -> anyhow::Result<Option<InterceptionAction>> {
// 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,42 +253,35 @@ 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<AppContext>,
) -> 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<AppContext>) {
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);
}
}
}
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 {
@@ -294,15 +298,12 @@ async fn handle_getcommand_response(
.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);
}
}
Ok(())
}
/// Checks for and applies response interception rules.

View File

@@ -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?;