ruvector_scipix/api/
responses.rs1use axum::{
2 http::StatusCode,
3 response::{IntoResponse, Response},
4 Json,
5};
6use serde::{Deserialize, Serialize};
7
8use super::jobs::JobStatus;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct TextResponse {
13 pub request_id: String,
15
16 pub text: String,
18
19 pub confidence: f64,
21
22 #[serde(skip_serializing_if = "Option::is_none")]
24 pub latex: Option<String>,
25
26 #[serde(skip_serializing_if = "Option::is_none")]
28 pub mathml: Option<String>,
29
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub html: Option<String>,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct PdfResponse {
38 pub pdf_id: String,
40
41 pub status: JobStatus,
43
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub message: Option<String>,
47
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub result: Option<String>,
51
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub error: Option<String>,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct ErrorResponse {
60 pub error_code: String,
62
63 pub message: String,
65
66 #[serde(skip)]
68 pub status: StatusCode,
69}
70
71impl ErrorResponse {
72 pub fn validation_error(message: impl Into<String>) -> Self {
74 Self {
75 error_code: "VALIDATION_ERROR".to_string(),
76 message: message.into(),
77 status: StatusCode::BAD_REQUEST,
78 }
79 }
80
81 pub fn unauthorized(message: impl Into<String>) -> Self {
83 Self {
84 error_code: "UNAUTHORIZED".to_string(),
85 message: message.into(),
86 status: StatusCode::UNAUTHORIZED,
87 }
88 }
89
90 pub fn not_found(message: impl Into<String>) -> Self {
92 Self {
93 error_code: "NOT_FOUND".to_string(),
94 message: message.into(),
95 status: StatusCode::NOT_FOUND,
96 }
97 }
98
99 pub fn rate_limited(message: impl Into<String>) -> Self {
101 Self {
102 error_code: "RATE_LIMIT_EXCEEDED".to_string(),
103 message: message.into(),
104 status: StatusCode::TOO_MANY_REQUESTS,
105 }
106 }
107
108 pub fn internal_error(message: impl Into<String>) -> Self {
110 Self {
111 error_code: "INTERNAL_ERROR".to_string(),
112 message: message.into(),
113 status: StatusCode::INTERNAL_SERVER_ERROR,
114 }
115 }
116
117 pub fn service_unavailable(message: impl Into<String>) -> Self {
120 Self {
121 error_code: "SERVICE_UNAVAILABLE".to_string(),
122 message: message.into(),
123 status: StatusCode::SERVICE_UNAVAILABLE,
124 }
125 }
126
127 pub fn not_implemented(message: impl Into<String>) -> Self {
129 Self {
130 error_code: "NOT_IMPLEMENTED".to_string(),
131 message: message.into(),
132 status: StatusCode::NOT_IMPLEMENTED,
133 }
134 }
135}
136
137impl IntoResponse for ErrorResponse {
138 fn into_response(self) -> Response {
139 let status = self.status;
140 (status, Json(self)).into_response()
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_text_response_serialization() {
150 let response = TextResponse {
151 request_id: "test-123".to_string(),
152 text: "Hello World".to_string(),
153 confidence: 0.95,
154 latex: Some("x^2".to_string()),
155 mathml: None,
156 html: None,
157 };
158
159 let json = serde_json::to_string(&response).unwrap();
160 assert!(json.contains("request_id"));
161 assert!(json.contains("test-123"));
162 assert!(!json.contains("mathml"));
163 }
164
165 #[test]
166 fn test_error_response_creation() {
167 let error = ErrorResponse::validation_error("Invalid input");
168 assert_eq!(error.status, StatusCode::BAD_REQUEST);
169 assert_eq!(error.error_code, "VALIDATION_ERROR");
170
171 let error = ErrorResponse::unauthorized("Invalid credentials");
172 assert_eq!(error.status, StatusCode::UNAUTHORIZED);
173
174 let error = ErrorResponse::rate_limited("Too many requests");
175 assert_eq!(error.status, StatusCode::TOO_MANY_REQUESTS);
176 }
177}