Response Serialization

Guide to serializing data as JSON or other formats in responses.

Table of Contents


JSON Responses

Using .with_json()

Return JSON responses using Response::with_json().

use reinhardt::Response;
use serde::Serialize;

#[derive(Serialize)]
struct User {
    id: u32,
    username: String,
    email: String,
}

async fn get_user() -> Response {
    let user = User {
        id: 1,
        username: "alice".to_string(),
        email: "[email protected]".to_string(),
    };

    Response::ok()
        .with_json(&user)
        .unwrap()
}

Using serde_json::json! Macro

Use macro for simple JSON responses.

use reinhardt::Response;
use serde_json::json;

async fn health_check() -> Response {
    Response::ok()
        .with_json(&json!({
            "status": "healthy",
            "version": "1.0.0"
        }))
        .unwrap()
}

Status Code Responses

Created (201 Created)

use reinhardt::Response;

async fn create_user() -> Response {
    Response::created()
        .with_json(&serde_json::json!({
            "id": 123,
            "username": "alice"
        }))
        .unwrap()
}

Error Response (400 Bad Request)

use reinhardt::Response;

async fn invalid_request() -> Response {
    Response::bad_request()
        .with_json(&serde_json::json!({
            "error": "Invalid username",
            "code": "INVALID_USERNAME"
        }))
        .unwrap()
}

Not Found (404 Not Found)

use reinhardt::Response;

async fn not_found() -> Response {
    Response::not_found()
        .with_json(&serde_json::json!({
            "error": "Resource not found",
            "code": "NOT_FOUND"
        }))
        .unwrap()
}

Streaming Responses

StreamingResponse

Stream large or chunked data.

use reinhardt::StreamingResponse;
use futures::stream;
use bytes::Bytes;

async fn stream_data() -> StreamingResponse<impl Stream<Item = Result<Bytes, Box<dyn std::error::Error + Send + Sync>>>> {
    let data = vec![
        Ok(Bytes::from("chunk1")),
        Ok(Bytes::from("chunk2")),
        Ok(Bytes::from("chunk3")),
    ];

    StreamingResponse::new(stream::iter(data))
        .media_type("text/plain")
}

Server-Sent Events

use reinhardt::StreamingResponse;
use futures::stream;
use std::time::Duration;

async fn sse_events() -> StreamingResponse<impl Stream<Item = Result<Bytes, Box<dyn std::error::Error + Send + Sync>>>> {
    let events = vec![
        Ok(Bytes::from("data: message 1\n\n")),
        Ok(Bytes::from("data: message 2\n\n")),
    ];

    StreamingResponse::new(stream::iter(events))
        .header(
            hyper::header::CONTENT_TYPE,
            "text/event-stream"
        )
}

Error Responses

Conversion from Error

Use From<Error> implementation.

use reinhardt::{Error, Response};

async fn handle_result() -> Response {
    let result = fetch_data().await;

    match result {
        Ok(data) => Response::ok().with_json(&data).unwrap(),
        Err(e) => Response::from(e), // Automatically converts to JSON error response
    }
}

Custom Error Responses

use reinhardt::{Error, Response};

#[derive(Debug)]
enum ApiError {
    UserNotFound,
    InvalidInput(String),
}

impl From<ApiError> for Response {
    fn from(err: ApiError) -> Self {
        match err {
            ApiError::UserNotFound => Response::not_found()
                .with_json(&serde_json::json!({
                    "error": "User not found"
                }))
                .unwrap(),
            ApiError::InvalidInput(msg) => Response::bad_request()
                .with_json(&serde_json::json!({
                    "error": msg
                }))
                .unwrap(),
        }
    }
}

Paginated Responses

PaginatedResponse

Return paginated data.

use reinhardt::pagination::{PaginatedResponse, PaginationMetadata, Page};
use reinhardt::Response;

async fn list_users(page: usize) -> Response {
    let page_data = fetch_users_page(page).await;

    let metadata = PaginationMetadata {
        count: page_data.count,
        next: if page_data.has_next() {
            Some(format!("/api/users?page={}", page + 1))
        } else {
            None
        },
        previous: if page > 1 {
            Some(format!("/api/users?page={}", page - 1))
        } else {
            None
        },
    };

    let response = PaginatedResponse::new(page_data.object_list, metadata);
    Response::ok().with_json(&response).unwrap()
}

Page Struct

use reinhardt::pagination::Page;

let page = Page::new(
    vec!["item1", "item2", "item3"], // results
    2,                                  // current page number
    5,                                  // total pages
    15,                                 // total items
    3,                                  // items per page
);

assert_eq!(page.start_index(), 4);  // (2-1) * 3 + 1
assert_eq!(page.end_index(), 6);    // 4 + 3 - 1
assert!(page.has_next());

Header Customization

Adding Custom Headers

use reinhardt::Response;

async fn with_custom_headers() -> Response {
    Response::ok()
        .with_header("X-Custom-Header", "custom-value")
        .with_header("X-Request-ID", "12345")
        .with_json(&serde_json::json!({"data": "value"}))
        .unwrap()
}

Explicit Content-Type

use reinhardt::Response;

async fn custom_content_type() -> Response {
    Response::ok()
        .with_header("Content-Type", "application/vnd.api+json")
        .with_body(r#"{"data": {"type": "users", "id": "1"}}"#)
}

Empty Responses

204 No Content

use reinhardt::Response;

async fn delete_resource() -> Response {
    // Delete resource...
    Response::no_content()
}

See Also