use crate::ipc_types::IpcHttpResponse;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use thiserror::Error;
use tracing::{debug, error, trace};
#[derive(Debug, Error)]
pub enum HttpIpcError {
#[error("Invalid request: {0}")]
InvalidRequest(String),
#[error("Not found: {0}")]
NotFound(String),
#[error("Unauthorized: {0}")]
Unauthorized(String),
#[error("Forbidden: {0}")]
Forbidden(String),
#[error("Internal error: {0}")]
Internal(String),
#[error("SDK error: {0}")]
Sdk(#[from] crate::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("Timeout: {0}")]
Timeout(String),
#[error("IPC communication error: {0}")]
IpcCommunication(String),
#[error("Other error: {0}")]
Other(String),
}
impl From<std::io::Error> for HttpIpcError {
fn from(err: std::io::Error) -> Self {
Self::Internal(format!("IO error: {}", err))
}
}
impl From<tokio::time::error::Elapsed> for HttpIpcError {
fn from(err: tokio::time::error::Elapsed) -> Self {
Self::Timeout(format!("Operation timed out: {}", err))
}
}
pub type HttpResult<T> = std::result::Result<T, HttpIpcError>;
pub fn parse_json_body<T: for<'de> Deserialize<'de>>(body: &Option<Vec<u8>>, request_id: &str) -> HttpResult<T> {
match body {
Some(data) => {
trace!("Parsing JSON body (request_id: {}, size: {} bytes)", request_id, data.len());
if data.len() < 1024 {
if let Ok(json_str) = std::str::from_utf8(data) {
trace!("Raw JSON body (request_id: {}): {}", request_id, json_str);
}
}
match serde_json::from_slice(data) {
Ok(parsed) => {
debug!("Successfully parsed JSON body (request_id: {})", request_id);
Ok(parsed)
},
Err(e) => {
error!("Failed to parse JSON body (request_id: {}): {}", request_id, e);
let error_details = if let Ok(partial_json) = std::str::from_utf8(data) {
let truncated = if partial_json.len() > 100 {
format!("{}...", &partial_json[..100])
} else {
partial_json.to_string()
};
format!("Error: {}, JSON: {}", e, truncated)
} else {
format!("Error: {}, non-UTF8 data", e)
};
Err(HttpIpcError::InvalidRequest(format!("Invalid JSON body: {}", error_details)))
}
}
},
None => {
error!("Missing request body (request_id: {})", request_id);
Err(HttpIpcError::InvalidRequest(
"Request body is required".to_string(),
))
}
}
}
pub fn json_response<T: Serialize>(
data: T,
status_code: u16,
request_id: &str
) -> HttpResult<IpcHttpResponse> {
debug!("Creating JSON response (request_id: {}, status_code: {})", request_id, status_code);
let response = super::ApiResponse {
status: "success".to_string(),
data: Some(data),
message: None,
};
let body = match serde_json::to_vec(&response) {
Ok(body) => {
trace!("Serialized JSON response (request_id: {}, size: {} bytes)", request_id, body.len());
body
},
Err(e) => {
error!("Failed to serialize JSON response (request_id: {}): {}", request_id, e);
return Err(HttpIpcError::Json(e));
}
};
let mut headers = HashMap::new();
headers.insert("Content-Type".to_string(), "application/json".to_string());
headers.insert("X-Request-ID".to_string(), request_id.to_string());
Ok(IpcHttpResponse {
request_id: request_id.to_string(),
status_code,
headers,
body: Some(body),
})
}
pub fn success<T: Serialize>(data: T, request_id: &str) -> HttpResult<IpcHttpResponse> {
json_response(data, 200, request_id)
}
pub fn created<T: Serialize>(data: T, request_id: &str) -> HttpResult<IpcHttpResponse> {
json_response(data, 201, request_id)
}
pub fn accepted<T: Serialize>(data: T, request_id: &str) -> HttpResult<IpcHttpResponse> {
json_response(data, 202, request_id)
}
pub fn error_response<T: Serialize>(
message: &str,
status_code: u16,
request_id: &str,
error_code: Option<&str>,
) -> HttpResult<IpcHttpResponse> {
debug!("Creating error response (request_id: {}, status_code: {}, message: {})",
request_id, status_code, message);
let response = super::ApiResponse::<T> {
status: "error".to_string(),
data: None,
message: Some(message.to_string()),
};
let body = match serde_json::to_vec(&response) {
Ok(body) => body,
Err(e) => {
error!("Failed to serialize error response (request_id: {}): {}", request_id, e);
return Err(HttpIpcError::Json(e));
}
};
let mut headers = HashMap::new();
headers.insert("Content-Type".to_string(), "application/json".to_string());
headers.insert("X-Request-ID".to_string(), request_id.to_string());
if let Some(code) = error_code {
headers.insert("X-Error-Code".to_string(), code.to_string());
}
Ok(IpcHttpResponse {
request_id: request_id.to_string(),
status_code,
headers,
body: Some(body),
})
}
pub fn bad_request(message: &str, request_id: &str) -> HttpResult<IpcHttpResponse> {
error_response::<()>(message, 400, request_id, Some("BAD_REQUEST"))
}
pub fn unauthorized(message: &str, request_id: &str) -> HttpResult<IpcHttpResponse> {
error_response::<()>(message, 401, request_id, Some("UNAUTHORIZED"))
}
pub fn forbidden(message: &str, request_id: &str) -> HttpResult<IpcHttpResponse> {
error_response::<()>(message, 403, request_id, Some("FORBIDDEN"))
}
pub fn not_found(path: &str, request_id: &str) -> HttpResult<IpcHttpResponse> {
error_response::<()>(&format!("Endpoint not found: {}", path), 404, request_id, Some("NOT_FOUND"))
}
pub fn method_not_allowed(method: &str, path: &str, request_id: &str) -> HttpResult<IpcHttpResponse> {
error_response::<()>(
&format!("Method {} not allowed for endpoint {}", method, path),
405,
request_id,
Some("METHOD_NOT_ALLOWED")
)
}
pub fn conflict(message: &str, request_id: &str) -> HttpResult<IpcHttpResponse> {
error_response::<()>(message, 409, request_id, Some("CONFLICT"))
}
pub fn timeout(message: &str, request_id: &str) -> HttpResult<IpcHttpResponse> {
error_response::<()>(message, 408, request_id, Some("TIMEOUT"))
}
pub fn internal_error(message: &str, request_id: &str) -> HttpResult<IpcHttpResponse> {
error_response::<()>(message, 500, request_id, Some("INTERNAL_ERROR"))
}
pub(crate) fn error_to_response(error: HttpIpcError, request_id: &str) -> IpcHttpResponse {
let (status_code, error_code) = match &error {
HttpIpcError::InvalidRequest(_) => (400, "BAD_REQUEST"),
HttpIpcError::NotFound(_) => (404, "NOT_FOUND"),
HttpIpcError::Unauthorized(_) => (401, "UNAUTHORIZED"),
HttpIpcError::Forbidden(_) => (403, "FORBIDDEN"),
HttpIpcError::Timeout(_) => (408, "TIMEOUT"),
HttpIpcError::IpcCommunication(_) => (502, "IPC_COMMUNICATION_ERROR"),
_ => (500, "INTERNAL_ERROR"),
};
error!("Converting error to HTTP response (request_id: {}, status: {}, error: {})",
request_id, status_code, error);
let response = super::ApiResponse::<()> {
status: "error".to_string(),
data: None,
message: Some(error.to_string()),
};
let mut headers = HashMap::new();
headers.insert("Content-Type".to_string(), "application/json".to_string());
headers.insert("X-Request-ID".to_string(), request_id.to_string());
headers.insert("X-Error-Code".to_string(), error_code.to_string());
match serde_json::to_vec(&response) {
Ok(body) => IpcHttpResponse {
request_id: request_id.to_string(),
status_code,
headers,
body: Some(body),
},
Err(e) => {
error!("Failed to serialize error response (request_id: {}): {}", request_id, e);
headers.insert("Content-Type".to_string(), "text/plain".to_string());
IpcHttpResponse {
request_id: request_id.to_string(),
status_code,
headers,
body: Some(format!("Error: {}", error).into_bytes()),
}
}
}
}
pub fn diagnostic_response(request_id: &str) -> IpcHttpResponse {
let timestamp = chrono::Utc::now().to_rfc3339();
let message = format!("IPC diagnostic response generated at {}", timestamp);
debug!("Creating diagnostic response (request_id: {})", request_id);
let response = super::ApiResponse::<()> {
status: "diagnostic".to_string(),
data: None,
message: Some(message),
};
let mut headers = HashMap::new();
headers.insert("Content-Type".to_string(), "application/json".to_string());
headers.insert("X-Request-ID".to_string(), request_id.to_string());
headers.insert("X-Response-Type".to_string(), "diagnostic".to_string());
match serde_json::to_vec(&response) {
Ok(body) => IpcHttpResponse {
request_id: request_id.to_string(),
status_code: 200,
headers,
body: Some(body),
},
Err(_) => {
headers.insert("Content-Type".to_string(), "text/plain".to_string());
IpcHttpResponse {
request_id: request_id.to_string(),
status_code: 200,
headers,
body: Some(message.into_bytes()),
}
}
}
}