synor/crates/synor-gateway/src/response.rs
Gulshan Yadav 03c1664739 feat: Implement standard API response types and health check endpoints
- Added `ApiResponse`, `ResponseMeta`, and related structures for standardized API responses.
- Created health check routes including basic health, liveness, and readiness checks.
- Introduced `AppState` for shared application state across routes.
- Developed wallet API endpoints for wallet management, address operations, balance queries, and transaction signing.
- Implemented RPC API endpoints for blockchain operations including block queries, transaction handling, and network information.
2026-01-28 15:03:36 +05:30

290 lines
6.9 KiB
Rust

//! Standard API response types.
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
/// Standard API response wrapper.
#[derive(Debug, Serialize, Deserialize)]
pub struct ApiResponse<T> {
/// Whether the request succeeded
pub success: bool,
/// Response data (present on success)
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<T>,
/// Response metadata
#[serde(skip_serializing_if = "Option::is_none")]
pub meta: Option<ResponseMeta>,
}
/// Response metadata.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponseMeta {
/// Unique request ID for tracing
pub request_id: String,
/// Response timestamp
pub timestamp: DateTime<Utc>,
/// Request processing time in milliseconds
#[serde(skip_serializing_if = "Option::is_none")]
pub latency_ms: Option<u64>,
/// Pagination info (if applicable)
#[serde(skip_serializing_if = "Option::is_none")]
pub pagination: Option<PaginationMeta>,
}
/// Pagination metadata.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaginationMeta {
/// Total number of items
pub total: u64,
/// Current page (1-indexed)
pub page: u32,
/// Items per page
pub per_page: u32,
/// Total number of pages
pub total_pages: u32,
/// Whether there's a next page
pub has_next: bool,
/// Whether there's a previous page
pub has_prev: bool,
}
impl<T: Serialize> ApiResponse<T> {
/// Create a successful response with data.
pub fn success(data: T) -> Self {
Self {
success: true,
data: Some(data),
meta: Some(ResponseMeta {
request_id: uuid::Uuid::new_v4().to_string(),
timestamp: Utc::now(),
latency_ms: None,
pagination: None,
}),
}
}
/// Create a successful response with data and pagination.
pub fn success_paginated(data: T, pagination: PaginationMeta) -> Self {
Self {
success: true,
data: Some(data),
meta: Some(ResponseMeta {
request_id: uuid::Uuid::new_v4().to_string(),
timestamp: Utc::now(),
latency_ms: None,
pagination: Some(pagination),
}),
}
}
/// Set the request ID.
pub fn with_request_id(mut self, request_id: String) -> Self {
if let Some(ref mut meta) = self.meta {
meta.request_id = request_id;
}
self
}
/// Set the latency.
pub fn with_latency(mut self, latency_ms: u64) -> Self {
if let Some(ref mut meta) = self.meta {
meta.latency_ms = Some(latency_ms);
}
self
}
}
impl<T: Serialize> IntoResponse for ApiResponse<T> {
fn into_response(self) -> Response {
(StatusCode::OK, Json(self)).into_response()
}
}
/// Response for created resources (201).
pub struct Created<T>(pub ApiResponse<T>);
impl<T: Serialize> IntoResponse for Created<T> {
fn into_response(self) -> Response {
(StatusCode::CREATED, Json(self.0)).into_response()
}
}
/// Response for accepted requests (202).
pub struct Accepted<T>(pub ApiResponse<T>);
impl<T: Serialize> IntoResponse for Accepted<T> {
fn into_response(self) -> Response {
(StatusCode::ACCEPTED, Json(self.0)).into_response()
}
}
/// Response for no content (204).
pub struct NoContent;
impl IntoResponse for NoContent {
fn into_response(self) -> Response {
StatusCode::NO_CONTENT.into_response()
}
}
/// Empty success response.
#[derive(Debug, Serialize, Deserialize)]
pub struct EmptyResponse;
impl ApiResponse<EmptyResponse> {
/// Create an empty success response.
pub fn empty() -> Self {
Self {
success: true,
data: None,
meta: Some(ResponseMeta {
request_id: uuid::Uuid::new_v4().to_string(),
timestamp: Utc::now(),
latency_ms: None,
pagination: None,
}),
}
}
}
/// Pagination query parameters.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaginationParams {
/// Page number (1-indexed, default: 1)
#[serde(default = "default_page")]
pub page: u32,
/// Items per page (default: 20, max: 100)
#[serde(default = "default_per_page")]
pub per_page: u32,
}
fn default_page() -> u32 {
1
}
fn default_per_page() -> u32 {
20
}
impl Default for PaginationParams {
fn default() -> Self {
Self {
page: 1,
per_page: 20,
}
}
}
impl PaginationParams {
/// Get the offset for database queries.
pub fn offset(&self) -> u64 {
((self.page.saturating_sub(1)) * self.per_page) as u64
}
/// Get the limit for database queries.
pub fn limit(&self) -> u32 {
self.per_page.min(100)
}
/// Create pagination metadata from results.
pub fn to_meta(&self, total: u64) -> PaginationMeta {
let total_pages = ((total as f64) / (self.per_page as f64)).ceil() as u32;
PaginationMeta {
total,
page: self.page,
per_page: self.per_page,
total_pages,
has_next: self.page < total_pages,
has_prev: self.page > 1,
}
}
}
/// Sorting parameters.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SortParams {
/// Field to sort by
pub sort_by: Option<String>,
/// Sort direction (asc or desc)
#[serde(default = "default_sort_order")]
pub sort_order: SortOrder,
}
fn default_sort_order() -> SortOrder {
SortOrder::Desc
}
/// Sort direction.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum SortOrder {
Asc,
Desc,
}
impl Default for SortOrder {
fn default() -> Self {
Self::Desc
}
}
/// Health check response.
#[derive(Debug, Serialize, Deserialize)]
pub struct HealthResponse {
/// Overall health status
pub status: HealthStatus,
/// Service version
pub version: String,
/// Uptime in seconds
pub uptime_seconds: u64,
/// Individual service health
#[serde(skip_serializing_if = "Option::is_none")]
pub services: Option<Vec<ServiceHealth>>,
}
/// Health status enum.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum HealthStatus {
Healthy,
Degraded,
Unhealthy,
}
/// Individual service health.
#[derive(Debug, Serialize, Deserialize)]
pub struct ServiceHealth {
/// Service name
pub name: String,
/// Health status
pub status: HealthStatus,
/// Optional message
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
/// Response time in milliseconds
#[serde(skip_serializing_if = "Option::is_none")]
pub latency_ms: Option<u64>,
}