pub mod router;
pub mod client;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;
use crate::registration::RegisteredModule;
pub use self::router::{HttpTcpRouter, Route, start_http_server};
pub use self::client::HttpTcpClient;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HttpTcpRequest {
pub request_id: String,
pub method: String,
pub uri: String,
pub headers: HashMap<String, String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body: Option<Vec<u8>>,
}
impl HttpTcpRequest {
pub fn new<S: Into<String>>(method: S, uri: S) -> Self {
Self {
request_id: Uuid::new_v4().to_string(),
method: method.into(),
uri: uri.into(),
headers: HashMap::new(),
body: None,
}
}
pub fn with_header<S: Into<String>>(mut self, key: S, value: S) -> Self {
self.headers.insert(key.into(), value.into());
self
}
pub fn with_headers(mut self, headers: HashMap<String, String>) -> Self {
self.headers.extend(headers);
self
}
pub fn with_body<B: Into<Vec<u8>>>(mut self, body: B) -> Self {
self.body = Some(body.into());
self
}
pub fn with_request_id<S: Into<String>>(mut self, request_id: S) -> Self {
self.request_id = request_id.into();
self
}
pub fn header(&self, key: &str) -> Option<&String> {
self.headers.get(key)
}
pub fn query_params(&self) -> HashMap<String, String> {
let mut params = HashMap::new();
if let Some(query_str) = self.uri.split('?').nth(1) {
for pair in query_str.split('&') {
let mut parts = pair.split('=');
if let (Some(key), Some(value)) = (parts.next(), parts.next()) {
params.insert(key.to_string(), value.to_string());
}
}
}
params
}
pub fn query_param(&self, key: &str) -> Option<String> {
self.query_params().get(key).cloned()
}
pub fn path(&self) -> &str {
self.uri.split('?').next().unwrap_or(&self.uri)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HttpTcpResponse {
pub request_id: String,
pub status_code: u16,
pub headers: HashMap<String, String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body: Option<Vec<u8>>,
}
impl HttpTcpResponse {
pub fn new(request_id: &str, status_code: u16) -> Self {
Self {
request_id: request_id.to_string(),
status_code,
headers: HashMap::new(),
body: None,
}
}
pub fn with_header<S: Into<String>>(mut self, key: S, value: S) -> Self {
self.headers.insert(key.into(), value.into());
self
}
pub fn with_headers(mut self, headers: HashMap<String, String>) -> Self {
self.headers.extend(headers);
self
}
pub fn with_body<B: Into<Vec<u8>>>(mut self, body: B) -> Self {
self.body = Some(body.into());
self
}
pub fn header(&self, key: &str) -> Option<&String> {
self.headers.get(key)
}
pub fn body_as_string(&self) -> Option<Result<String, std::string::FromUtf8Error>> {
self.body.as_ref().map(|b| String::from_utf8(b.clone()))
}
pub fn body_as_json<T: for<'de> Deserialize<'de>>(&self) -> Option<Result<T, serde_json::Error>> {
self.body.as_ref().map(|b| serde_json::from_slice(b))
}
pub fn is_success(&self) -> bool {
self.status_code >= 200 && self.status_code < 300
}
pub fn is_client_error(&self) -> bool {
self.status_code >= 400 && self.status_code < 500
}
pub fn is_server_error(&self) -> bool {
self.status_code >= 500
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ApiResponse<T> {
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<T>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
pub fn json_response<T: Serialize>(
request_id: &str,
status_code: u16,
data: Option<T>,
message: Option<String>,
) -> HttpTcpResponse {
let api_response = ApiResponse {
status: if status_code < 400 { "success" } else { "error" }.to_string(),
data,
message,
};
let body = serde_json::to_vec(&api_response).ok();
let mut headers = HashMap::new();
headers.insert("Content-Type".to_string(), "application/json".to_string());
HttpTcpResponse {
request_id: request_id.to_string(),
status_code,
headers,
body,
}
}
pub fn success<T: Serialize>(request_id: &str, data: T) -> HttpTcpResponse {
json_response(request_id, 200, Some(data), None)
}
pub fn created<T: Serialize>(request_id: &str, data: T) -> HttpTcpResponse {
json_response(request_id, 201, Some(data), None)
}
pub fn error_response(request_id: &str, status_code: u16, message: &str) -> HttpTcpResponse {
json_response::<()>(request_id, status_code, None, Some(message.to_string()))
}
pub fn not_found(request_id: &str, path: &str) -> HttpTcpResponse {
error_response(request_id, 404, &format!("Not found: {}", path))
}
pub fn bad_request(request_id: &str, message: &str) -> HttpTcpResponse {
error_response(request_id, 400, message)
}
pub fn internal_error(request_id: &str, message: &str) -> HttpTcpResponse {
error_response(request_id, 500, message)
}
pub fn parse_json_body<T: for<'de> Deserialize<'de>>(
request: &HttpTcpRequest,
) -> Result<T, String> {
match &request.body {
Some(body) => serde_json::from_slice(body)
.map_err(|e| format!("Invalid JSON body: {}", e)),
None => Err("Request body is required".to_string()),
}
}
pub async fn serve<S: Send + Sync + Clone + 'static>(
router: HttpTcpRouter<S>,
module: RegisteredModule,
state: S,
shutdown_signal: Option<tokio::sync::broadcast::Receiver<()>>,
) -> Result<tokio::task::JoinHandle<()>, crate::error::Error> {
start_http_server(router, module, state, shutdown_signal).await
}