1mod headers;
6mod json;
7
8pub use headers::{build_response_headers, ContentRange};
9pub use json::format_json_response;
10
11use http::{HeaderMap, HeaderValue, StatusCode};
12use postrust_core::{ApiRequest, MediaType};
13use serde::Serialize;
14
15#[derive(Clone, Debug)]
17pub struct Response {
18 pub status: StatusCode,
20 pub headers: HeaderMap,
22 pub body: bytes::Bytes,
24}
25
26impl Response {
27 pub fn new(status: StatusCode, body: impl Into<bytes::Bytes>) -> Self {
29 Self {
30 status,
31 headers: HeaderMap::new(),
32 body: body.into(),
33 }
34 }
35
36 pub fn json<T: Serialize>(status: StatusCode, value: &T) -> Result<Self, serde_json::Error> {
38 let body = serde_json::to_vec(value)?;
39 let mut response = Self::new(status, body);
40 response.set_content_type("application/json; charset=utf-8");
41 Ok(response)
42 }
43
44 pub fn empty(status: StatusCode) -> Self {
46 Self::new(status, bytes::Bytes::new())
47 }
48
49 pub fn set_header(&mut self, name: &str, value: &str) {
51 if let Ok(v) = HeaderValue::from_str(value) {
52 self.headers.insert(
53 http::header::HeaderName::from_bytes(name.as_bytes()).unwrap(),
54 v,
55 );
56 }
57 }
58
59 pub fn set_content_type(&mut self, content_type: &str) {
61 self.set_header("content-type", content_type);
62 }
63
64 pub fn set_content_range(&mut self, range: &ContentRange) {
66 self.set_header("content-range", &range.to_string());
67 }
68
69 pub fn set_location(&mut self, location: &str) {
71 self.set_header("location", location);
72 }
73}
74
75pub fn format_response(
77 request: &ApiRequest,
78 result: &QueryResult,
79) -> Result<Response, FormatError> {
80 let media_type = request
81 .accept_media_types
82 .first()
83 .cloned()
84 .unwrap_or(MediaType::ApplicationJson);
85
86 match &media_type {
87 MediaType::ApplicationJson => {
88 let body = if result.singular {
89 format_singular_or_null(&result.rows)?
90 } else {
91 format_json_response(&result.rows)?
92 };
93 let mut response = Response::new(result.status, body);
94 response.set_content_type("application/json; charset=utf-8");
95 add_common_headers(&mut response, request, result);
96 Ok(response)
97 }
98 MediaType::TextCsv => {
99 let body = format_csv_response(&result.rows)?;
101 let mut response = Response::new(result.status, body);
102 response.set_content_type("text/csv; charset=utf-8");
103 add_common_headers(&mut response, request, result);
104 Ok(response)
105 }
106 MediaType::SingularJson { nullable } => {
107 let body = format_singular_json(&result.rows, *nullable)?;
108 let mut response = Response::new(result.status, body);
109 response.set_content_type("application/vnd.pgrst.object+json; charset=utf-8");
110 add_common_headers(&mut response, request, result);
111 Ok(response)
112 }
113 _ => {
114 let body = if result.singular {
116 format_singular_or_null(&result.rows)?
117 } else {
118 format_json_response(&result.rows)?
119 };
120 let mut response = Response::new(result.status, body);
121 response.set_content_type("application/json; charset=utf-8");
122 add_common_headers(&mut response, request, result);
123 Ok(response)
124 }
125 }
126}
127
128fn add_common_headers(response: &mut Response, request: &ApiRequest, result: &QueryResult) {
130 if let Some(range) = &result.content_range {
132 response.set_content_range(range);
133 }
134
135 if let Some(location) = &result.location {
137 response.set_location(location);
138 }
139
140 if let Some(applied) =
142 postrust_core::api_request::preferences::preference_applied(&request.preferences)
143 {
144 response.set_header("preference-applied", &applied);
145 }
146
147 if request.negotiated_by_profile {
149 response.set_header("content-profile", &request.schema);
150 }
151}
152
153fn format_singular_or_null(rows: &[serde_json::Value]) -> Result<bytes::Bytes, FormatError> {
159 match rows.len() {
160 0 => Ok(bytes::Bytes::from_static(b"null")),
161 1 => Ok(bytes::Bytes::from(serde_json::to_vec(&rows[0])?)),
162 _ => format_json_response(rows),
163 }
164}
165
166fn format_singular_json(
168 rows: &[serde_json::Value],
169 nullable: bool,
170) -> Result<bytes::Bytes, FormatError> {
171 match rows.len() {
172 0 if nullable => Ok(bytes::Bytes::from_static(b"null")),
173 0 => Err(FormatError::NotFound),
174 1 => Ok(bytes::Bytes::from(serde_json::to_vec(&rows[0])?)),
175 _ => Err(FormatError::MultipleRows),
176 }
177}
178
179fn format_csv_response(rows: &[serde_json::Value]) -> Result<bytes::Bytes, FormatError> {
181 if rows.is_empty() {
182 return Ok(bytes::Bytes::new());
183 }
184
185 let mut output = Vec::new();
186
187 if let Some(serde_json::Value::Object(map)) = rows.first() {
189 let headers: Vec<&str> = map.keys().map(|s| s.as_str()).collect();
190 output.extend_from_slice(headers.join(",").as_bytes());
191 output.push(b'\n');
192
193 for row in rows {
195 if let serde_json::Value::Object(row_map) = row {
196 let values: Vec<String> = headers
197 .iter()
198 .map(|h| row_map.get(*h).map(csv_escape).unwrap_or_default())
199 .collect();
200 output.extend_from_slice(values.join(",").as_bytes());
201 output.push(b'\n');
202 }
203 }
204 }
205
206 Ok(bytes::Bytes::from(output))
207}
208
209fn csv_escape(value: &serde_json::Value) -> String {
211 match value {
212 serde_json::Value::String(s) => {
213 if s.contains(',') || s.contains('"') || s.contains('\n') {
214 format!("\"{}\"", s.replace('"', "\"\""))
215 } else {
216 s.clone()
217 }
218 }
219 serde_json::Value::Null => String::new(),
220 other => other.to_string(),
221 }
222}
223
224#[derive(Clone, Debug, Default)]
226pub struct QueryResult {
227 pub status: StatusCode,
229 pub rows: Vec<serde_json::Value>,
231 pub total_count: Option<i64>,
233 pub content_range: Option<ContentRange>,
235 pub location: Option<String>,
237 pub guc_headers: Option<String>,
239 pub guc_status: Option<String>,
241 pub singular: bool,
247}
248
249#[derive(Debug, thiserror::Error)]
251pub enum FormatError {
252 #[error("JSON serialization error: {0}")]
253 Json(#[from] serde_json::Error),
254
255 #[error("Resource not found")]
256 NotFound,
257
258 #[error("Multiple rows returned for singular response")]
259 MultipleRows,
260}
261
262impl FormatError {
263 pub fn status_code(&self) -> StatusCode {
264 match self {
265 Self::Json(_) => StatusCode::INTERNAL_SERVER_ERROR,
266 Self::NotFound => StatusCode::NOT_FOUND,
267 Self::MultipleRows => StatusCode::NOT_ACCEPTABLE,
268 }
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275 use serde_json::json;
276
277 fn result(rows: Vec<serde_json::Value>, singular: bool) -> QueryResult {
278 QueryResult {
279 status: StatusCode::OK,
280 rows,
281 singular,
282 ..Default::default()
283 }
284 }
285
286 #[test]
287 fn singular_result_renders_bare_object() {
288 let req = ApiRequest::default();
289 let resp = format_response(&req, &result(vec![json!({"ok": true})], true)).unwrap();
290 assert_eq!(&resp.body[..], br#"{"ok":true}"#);
291 }
292
293 #[test]
294 fn singular_empty_result_renders_null() {
295 let req = ApiRequest::default();
296 let resp = format_response(&req, &result(vec![], true)).unwrap();
297 assert_eq!(&resp.body[..], b"null");
298 }
299
300 #[test]
301 fn non_singular_result_renders_array() {
302 let req = ApiRequest::default();
303 let resp = format_response(&req, &result(vec![json!(1), json!(2)], false)).unwrap();
304 assert_eq!(&resp.body[..], b"[1,2]");
305 }
306}