use super::error::{Error, Result};
use serde::Serialize;
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize)]
pub struct Response {
pub status: u16,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub headers: HashMap<String, String>,
pub body: serde_json::Value,
}
impl Response {
#[inline]
pub fn new(status: u16, body: serde_json::Value) -> Self {
Self {
status,
headers: HashMap::new(),
body,
}
}
#[inline]
pub fn json<T: Serialize>(data: &T) -> Result<Self> {
let body = serde_json::to_value(data)?;
Ok(Self::new(200, body))
}
#[inline]
pub fn ok(body: serde_json::Value) -> Self {
Self {
status: 200,
headers: HashMap::new(),
body,
}
}
#[inline]
pub fn created<T: Serialize>(data: &T) -> Result<Self> {
let body = serde_json::to_value(data)?;
Ok(Self::new(201, body))
}
#[inline]
pub fn no_content() -> Self {
Self {
status: 204,
headers: HashMap::new(),
body: serde_json::Value::Null,
}
}
#[inline]
pub fn error(status: u16, message: &str) -> Self {
Self::new(
status,
serde_json::json!({
"error": true,
"message": message
}),
)
}
#[inline]
pub fn bad_request(message: &str) -> Self {
Self::error(400, message)
}
#[inline]
pub fn unauthorized(message: &str) -> Self {
Self::error(401, message)
}
#[inline]
pub fn forbidden(message: &str) -> Self {
Self::error(403, message)
}
#[inline]
pub fn not_found(message: &str) -> Self {
Self::error(404, message)
}
#[inline]
pub fn internal_error(message: &str) -> Self {
Self::error(500, message)
}
#[inline]
pub fn from_error(err: &Error) -> Self {
Self::error(err.status_code(), &err.to_string())
}
#[inline]
pub fn with_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.headers.insert(name.into(), value.into());
self
}
#[inline]
pub fn content_type(self, content_type: &str) -> Self {
self.with_header("Content-Type", content_type)
}
#[inline]
pub fn cache_control(self, value: &str) -> Self {
self.with_header("Cache-Control", value)
}
#[inline]
pub fn no_cache(self) -> Self {
self.cache_control("no-store, no-cache, must-revalidate")
}
#[cfg(target_arch = "wasm32")]
pub fn to_raw(&self) -> Result<i32> {
let json = serde_json::to_vec(self)?;
Ok(super::ffi::return_bytes(&json))
}
#[cfg(not(target_arch = "wasm32"))]
pub fn to_raw(&self) -> Result<i32> {
Err(Error::internal("to_raw only available in WASM"))
}
}
impl From<Error> for Response {
fn from(err: Error) -> Self {
Self::from_error(&err)
}
}
#[derive(Debug, Clone, Serialize)]
pub struct PaginatedResponse<T> {
pub data: Vec<T>,
pub pagination: PaginationMeta,
}
#[derive(Debug, Clone, Serialize)]
pub struct PaginationMeta {
pub page: u32,
pub per_page: u32,
pub total: u64,
pub total_pages: u32,
pub has_next: bool,
pub has_prev: bool,
}
impl<T: Serialize> PaginatedResponse<T> {
pub fn new(data: Vec<T>, page: u32, per_page: u32, total: u64) -> Self {
let total_pages = ((total as f64) / (per_page as f64)).ceil() as u32;
Self {
data,
pagination: PaginationMeta {
page,
per_page,
total,
total_pages,
has_next: page < total_pages,
has_prev: page > 1,
},
}
}
pub fn into_response(self) -> Result<Response> {
Response::json(&self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_response_json() {
let data = serde_json::json!({"name": "Test"});
let resp = Response::json(&data).unwrap();
assert_eq!(resp.status, 200);
assert_eq!(resp.body["name"], "Test");
}
#[test]
fn test_response_error() {
let resp = Response::not_found("User not found");
assert_eq!(resp.status, 404);
assert_eq!(resp.body["error"], true);
assert_eq!(resp.body["message"], "User not found");
}
#[test]
fn test_paginated_response() {
let items = vec![1, 2, 3];
let paginated = PaginatedResponse::new(items, 2, 10, 35);
assert_eq!(paginated.pagination.page, 2);
assert_eq!(paginated.pagination.total_pages, 4);
assert!(paginated.pagination.has_next);
assert!(paginated.pagination.has_prev);
}
}