use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct HealthResponse {
pub status: &'static str,
pub version: String,
pub uptime_seconds: u64,
pub timestamp: DateTime<Utc>,
}
impl HealthResponse {
pub fn new(version: &str, uptime_seconds: u64) -> Self {
Self {
status: "ok",
version: version.to_string(),
uptime_seconds,
timestamp: Utc::now(),
}
}
pub fn from_uptime(uptime_seconds: u64) -> Self {
Self::new(env!("CARGO_PKG_VERSION"), uptime_seconds)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ReadyResponse {
pub ready: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<ModelInfo>,
pub timestamp: DateTime<Utc>,
}
impl ReadyResponse {
pub fn not_ready() -> Self {
Self {
ready: false,
model: None,
timestamp: Utc::now(),
}
}
pub fn ready_with_model(model: ModelInfo) -> Self {
Self {
ready: true,
model: Some(model),
timestamp: Utc::now(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ModelInfo {
pub id: String,
pub loaded_at: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub memory_bytes: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context_length: Option<u32>,
}
impl ModelInfo {
pub fn new(id: impl Into<String>) -> Self {
Self {
id: id.into(),
loaded_at: Utc::now(),
memory_bytes: None,
context_length: None,
}
}
pub fn with_memory(mut self, bytes: u64) -> Self {
self.memory_bytes = Some(bytes);
self
}
pub fn with_context_length(mut self, length: u32) -> Self {
self.context_length = Some(length);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricsResponse {
pub queue_depth: u64,
pub queue_capacity: usize,
pub concurrent_requests_limit: usize,
pub active_requests: u64,
pub total_requests_served: u64,
pub failed_requests: u64,
pub uptime_seconds: u64,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponseMeta {
pub request_id: String,
pub timestamp: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub processing_time_ms: Option<u64>,
}
impl ResponseMeta {
pub fn new(request_id: impl Into<String>) -> Self {
Self {
request_id: request_id.into(),
timestamp: Utc::now(),
processing_time_ms: None,
}
}
pub fn with_processing_time(mut self, ms: u64) -> Self {
self.processing_time_ms = Some(ms);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiResponse<T> {
pub success: bool,
pub data: T,
#[serde(skip_serializing_if = "Option::is_none")]
pub meta: Option<ResponseMeta>,
}
impl<T> ApiResponse<T> {
pub fn ok(data: T) -> Self {
Self {
success: true,
data,
meta: None,
}
}
pub fn with_meta(mut self, meta: ResponseMeta) -> Self {
self.meta = Some(meta);
self
}
pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
self.meta = Some(ResponseMeta::new(request_id));
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_health_response() {
let response = HealthResponse::new("1.0.0", 3600);
assert_eq!(response.status, "ok");
assert_eq!(response.version, "1.0.0");
assert_eq!(response.uptime_seconds, 3600);
}
#[test]
fn test_ready_response_not_ready() {
let response = ReadyResponse::not_ready();
assert!(!response.ready);
assert!(response.model.is_none());
}
#[test]
fn test_ready_response_with_model() {
let model = ModelInfo::new("test-model")
.with_memory(1024 * 1024 * 1024)
.with_context_length(8192);
let response = ReadyResponse::ready_with_model(model);
assert!(response.ready);
assert!(response.model.is_some());
let m = response.model.unwrap();
assert_eq!(m.id, "test-model");
assert_eq!(m.memory_bytes, Some(1024 * 1024 * 1024));
assert_eq!(m.context_length, Some(8192));
}
#[test]
fn test_api_response_wrapper() {
let inner = HealthResponse::new("1.0.0", 100);
let response = ApiResponse::ok(inner).with_request_id("req-123");
assert!(response.success);
assert!(response.meta.is_some());
assert_eq!(response.meta.unwrap().request_id, "req-123");
}
#[test]
fn test_response_meta() {
let meta = ResponseMeta::new("req-456").with_processing_time(42);
assert_eq!(meta.request_id, "req-456");
assert_eq!(meta.processing_time_ms, Some(42));
}
#[test]
fn test_model_info() {
let info = ModelInfo::new("llama-3b");
assert_eq!(info.id, "llama-3b");
assert!(info.memory_bytes.is_none());
}
#[test]
fn test_health_response_serialization() {
let response = HealthResponse::new("1.0.0", 100);
let json = serde_json::to_string(&response).unwrap();
assert!(json.contains("\"status\":\"ok\""));
assert!(json.contains("\"version\":\"1.0.0\""));
}
}