use crate::utils::current_timestamp;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiResponse<T> {
pub data: T,
pub meta: ResponseMeta,
#[serde(skip_serializing_if = "Option::is_none")]
pub links: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponseMeta {
pub timestamp: String,
pub version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub request_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiError {
pub error: ErrorDetails,
pub meta: ResponseMeta,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorDetails {
pub code: String,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub suggestions: Option<Vec<String>>,
}
impl ApiResponse<Value> {
pub fn success(data: Value, request_id: Option<String>) -> Self {
Self {
data,
meta: ResponseMeta {
timestamp: current_timestamp().to_string(),
version: "1.0".to_string(),
request_id,
},
links: None,
}
}
pub fn with_links(mut self, links: HashMap<String, String>) -> Self {
self.links = Some(links);
self
}
}
impl ApiError {
pub fn new(
code: impl Into<String>,
message: impl Into<String>,
details: Option<Value>,
suggestions: Option<Vec<String>>,
request_id: Option<String>,
) -> Self {
Self {
error: ErrorDetails {
code: code.into(),
message: message.into(),
details,
suggestions,
},
meta: ResponseMeta {
timestamp: current_timestamp().to_string(),
version: "1.0".to_string(),
request_id,
},
}
}
}
#[inline]
pub fn rest_error_failed(context: &str, e: impl std::fmt::Display) -> String {
format!("Failed to {}: {}", context, e)
}
#[inline]
pub fn rest_error_invalid(context: &str, e: impl std::fmt::Display) -> String {
format!("Invalid {}: {}", context, e)
}
use bytes::Bytes;
use http_body_util::Full;
use hyper::body::Incoming;
use hyper::{Request, Response, StatusCode};
pub use crate::rpc::server::DEFAULT_MAX_REQUEST_SIZE as MAX_REQUEST_SIZE;
pub async fn read_json_body(req: Request<Incoming>) -> Result<Option<Value>, String> {
use http_body_util::BodyExt;
use http_body_util::Limited;
let (_, body) = req.into_parts();
let limited = Limited::new(body, MAX_REQUEST_SIZE);
let body_bytes = match limited.collect().await {
Ok(collected) => collected.to_bytes(),
Err(e) => return Err(format!("Request body too large or read error: {}", e)),
};
if body_bytes.is_empty() {
Ok(None)
} else {
match serde_json::from_slice::<Value>(&body_bytes) {
Ok(v) => Ok(Some(v)),
Err(_) => Ok(None), }
}
}
pub fn success_response(data: Value, request_id: String) -> Response<Full<Bytes>> {
let response = ApiResponse::success(data, Some(request_id));
let body = serde_json::to_string(&response).unwrap_or_else(|_| "{}".to_string());
Response::builder()
.status(StatusCode::OK)
.header("Content-Type", "application/json")
.header("Content-Length", body.len())
.body(Full::new(Bytes::from(body)))
.unwrap()
}
pub fn error_response(
status: StatusCode,
code: &str,
message: &str,
request_id: String,
) -> Response<Full<Bytes>> {
let error = ApiError::new(code, message, None, None, Some(request_id));
let body = serde_json::to_string(&error).unwrap_or_else(|_| "{}".to_string());
Response::builder()
.status(status)
.header("Content-Type", "application/json")
.header("Content-Length", body.len())
.body(Full::new(Bytes::from(body)))
.unwrap()
}