use http_body_util::{BodyExt, Full}; use hyper::StatusCode; use hyper::body::{Bytes, Incoming}; use serde::Serialize; use serde::de::DeserializeOwned; use super::model::{ApiFailure, ErrorBody, ErrorResponse, SuccessResponse}; pub(super) fn success_response( status: StatusCode, data: T, revision: String, ) -> hyper::Response> { let payload = SuccessResponse { ok: true, data, revision, }; let body = serde_json::to_vec(&payload).unwrap_or_else(|_| b"{\"ok\":false}".to_vec()); hyper::Response::builder() .status(status) .header("content-type", "application/json; charset=utf-8") .body(Full::new(Bytes::from(body))) .unwrap() } pub(super) fn error_response(request_id: u64, failure: ApiFailure) -> hyper::Response> { let payload = ErrorResponse { ok: false, error: ErrorBody { code: failure.code, message: failure.message, }, request_id, }; let body = serde_json::to_vec(&payload).unwrap_or_else(|_| { format!( "{{\"ok\":false,\"error\":{{\"code\":\"internal_error\",\"message\":\"serialization failed\"}},\"request_id\":{}}}", request_id ) .into_bytes() }); hyper::Response::builder() .status(failure.status) .header("content-type", "application/json; charset=utf-8") .body(Full::new(Bytes::from(body))) .unwrap() } pub(super) async fn read_json( body: Incoming, limit: usize, ) -> Result { let bytes = read_body_with_limit(body, limit).await?; serde_json::from_slice(&bytes).map_err(|_| ApiFailure::bad_request("Invalid JSON body")) } pub(super) async fn read_optional_json( body: Incoming, limit: usize, ) -> Result, ApiFailure> { let bytes = read_body_with_limit(body, limit).await?; if bytes.is_empty() { return Ok(None); } serde_json::from_slice(&bytes) .map(Some) .map_err(|_| ApiFailure::bad_request("Invalid JSON body")) } async fn read_body_with_limit(body: Incoming, limit: usize) -> Result, ApiFailure> { let mut collected = Vec::new(); let mut body = body; while let Some(frame_result) = body.frame().await { let frame = frame_result.map_err(|_| ApiFailure::bad_request("Invalid request body"))?; if let Some(chunk) = frame.data_ref() { if collected.len().saturating_add(chunk.len()) > limit { return Err(ApiFailure::new( StatusCode::PAYLOAD_TOO_LARGE, "payload_too_large", format!("Body exceeds {} bytes", limit), )); } collected.extend_from_slice(chunk); } } Ok(collected) }