1#![warn(missing_docs)]
6
7mod headers;
8pub use headers::parse_guc_headers;
9mod json;
10
11pub use headers::ContentRange;
12#[allow(deprecated)]
15pub use headers::build_response_headers;
16pub use json::format_json_response;
17
18use http::{HeaderMap, HeaderValue, StatusCode};
19use postrust_core::{ApiRequest, MediaType};
20use serde::Serialize;
21
22#[derive(Clone, Debug)]
24pub struct Response {
25 pub status: StatusCode,
27 pub headers: HeaderMap,
29 pub body: bytes::Bytes,
31}
32
33impl Response {
34 pub fn new(status: StatusCode, body: impl Into<bytes::Bytes>) -> Self {
36 Self {
37 status,
38 headers: HeaderMap::new(),
39 body: body.into(),
40 }
41 }
42
43 pub fn json<T: Serialize>(status: StatusCode, value: &T) -> Result<Self, serde_json::Error> {
45 let body = serde_json::to_vec(value)?;
46 let mut response = Self::new(status, body);
47 response.set_content_type("application/json; charset=utf-8");
48 Ok(response)
49 }
50
51 pub fn empty(status: StatusCode) -> Self {
53 Self::new(status, bytes::Bytes::new())
54 }
55
56 pub fn append_header(&mut self, name: &str, value: &str) {
62 if let (Ok(name), Ok(value)) = (
63 http::header::HeaderName::from_bytes(name.as_bytes()),
64 HeaderValue::from_str(value),
65 ) {
66 self.headers.append(name, value);
67 }
68 }
69
70 pub fn set_header(&mut self, name: &str, value: &str) {
76 if let Ok(v) = HeaderValue::from_str(value) {
77 self.headers.insert(
78 http::header::HeaderName::from_bytes(name.as_bytes()).unwrap(),
79 v,
80 );
81 }
82 }
83
84 pub fn set_content_type(&mut self, content_type: &str) {
86 self.set_header("content-type", content_type);
87 }
88
89 pub fn set_content_range(&mut self, range: &ContentRange) {
91 self.set_header("content-range", &range.to_string());
92 }
93
94 pub fn set_location(&mut self, location: &str) {
96 self.set_header("location", location);
97 }
98}
99
100pub fn format_response(
102 request: &ApiRequest,
103 result: &QueryResult,
104) -> Result<Response, FormatError> {
105 let media_type = request
106 .accept_media_types
107 .first()
108 .cloned()
109 .unwrap_or(MediaType::ApplicationJson);
110
111 if let Some((media_type, body)) = &result.raw_body {
117 let mut response = Response::new(result.status, body.clone());
118 let charset = match media_type.as_str() {
122 "application/octet-stream" => "",
123 already if already.contains("charset=") => "",
124 text if text.starts_with("text/") || text.contains("json") => "; charset=utf-8",
125 _ => "",
126 };
127 response.set_content_type(&format!("{}{}", media_type, charset));
128 add_common_headers(&mut response, request, result);
133 return Ok(response);
134 }
135
136 if result.omit_body {
137 if let MediaType::SingularJson { nullable, .. } = &media_type {
141 match (result.affected, nullable) {
142 (1, _) | (0, true) => {}
143 (0, false) => return Err(FormatError::NotFound),
144 _ => return Err(FormatError::MultipleRows),
145 }
146 }
147 let mut response = Response::new(result.status, bytes::Bytes::new());
148 add_common_headers(&mut response, request, result);
149 return Ok(response);
150 }
151
152 let rows = match &media_type {
155 MediaType::SingularJson {
156 strip_nulls: true, ..
157 }
158 | MediaType::ArrayJson {
159 strip_nulls: true, ..
160 } => result.rows.iter().cloned().map(strip_nulls).collect(),
161 _ => result.rows.clone(),
162 };
163 let result = &QueryResult {
164 rows,
165 ..result.clone()
166 };
167
168 match &media_type {
169 MediaType::ApplicationJson => {
170 let body = if result.singular {
171 format_singular_or_null(&result.rows)?
172 } else {
173 format_json_response(&result.rows)?
174 };
175 let mut response = Response::new(result.status, body);
176 response.set_content_type("application/json; charset=utf-8");
177 add_common_headers(&mut response, request, result);
178 Ok(response)
179 }
180 MediaType::TextCsv => {
181 let body = format_csv_response(&result.rows)?;
183 let mut response = Response::new(result.status, body);
184 response.set_content_type("text/csv; charset=utf-8");
185 add_common_headers(&mut response, request, result);
186 Ok(response)
187 }
188 MediaType::SingularJson { nullable, .. } => {
189 let body = format_singular_json(&result.rows, *nullable)?;
190 let mut response = Response::new(result.status, body);
191 response.set_content_type("application/vnd.pgrst.object+json; charset=utf-8");
192 add_common_headers(&mut response, request, result);
193 Ok(response)
194 }
195 other => {
196 let body = if result.singular {
198 format_singular_or_null(&result.rows)?
199 } else {
200 format_json_response(&result.rows)?
201 };
202 let mut response = Response::new(result.status, body);
203 let content_type = match other {
207 MediaType::ArrayJson { .. } => "application/vnd.pgrst.array+json; charset=utf-8",
208 MediaType::OpenApi => "application/openapi+json; charset=utf-8",
209 _ => "application/json; charset=utf-8",
210 };
211 response.set_content_type(content_type);
212 add_common_headers(&mut response, request, result);
213 Ok(response)
214 }
215 }
216}
217
218pub(crate) fn preference_scope(
220 request: &ApiRequest,
221) -> postrust_core::api_request::preferences::PreferenceScope {
222 use postrust_core::api_request::preferences::PreferenceScope;
223 use postrust_core::api_request::{Action, DbAction, Mutation};
224
225 let Action::Db(action) = &request.action else {
226 return PreferenceScope::read();
227 };
228
229 match action {
230 DbAction::RelationMut { mutation, .. } => PreferenceScope {
231 resolution: matches!(mutation, Mutation::Create),
232 representation: true,
233 missing: matches!(mutation, Mutation::Create | Mutation::Update),
234 max_affected: matches!(mutation, Mutation::Update | Mutation::Delete),
235 },
236 DbAction::Routine { .. } => PreferenceScope {
237 max_affected: true,
238 ..PreferenceScope::read()
239 },
240 _ => PreferenceScope::read(),
241 }
242}
243
244fn strip_nulls(value: serde_json::Value) -> serde_json::Value {
250 match value {
251 serde_json::Value::Object(fields) => serde_json::Value::Object(
252 fields
253 .into_iter()
254 .filter(|(_, value)| !value.is_null())
255 .map(|(key, value)| (key, strip_nulls(value)))
256 .collect(),
257 ),
258 serde_json::Value::Array(items) => {
259 serde_json::Value::Array(items.into_iter().map(strip_nulls).collect())
260 }
261 other => other,
262 }
263}
264
265fn add_common_headers(response: &mut Response, request: &ApiRequest, result: &QueryResult) {
267 if let Some(guc) = &result.guc_headers {
270 if let Some(headers) = crate::headers::parse_guc_headers(guc) {
271 for (name, value) in headers {
272 response.append_header(&name, &value);
273 }
274 }
275 }
276
277 if let Some(range) = &result.content_range {
279 response.set_content_range(range);
280 }
281
282 if let Some(location) = &result.location {
284 response.set_location(location);
285 }
286
287 if let Some(allow) = &result.allow {
289 response.set_header("allow", allow);
290 }
291
292 if let Some(applied) = postrust_core::api_request::preferences::preference_applied(
294 &request.preferences,
295 preference_scope(request),
296 ) {
297 response.set_header("preference-applied", &applied);
298 }
299
300 if request.negotiated_by_profile {
302 response.set_header("content-profile", &request.schema);
303 }
304}
305
306fn format_singular_or_null(rows: &[serde_json::Value]) -> Result<bytes::Bytes, FormatError> {
312 match rows.len() {
313 0 => Ok(bytes::Bytes::from_static(b"null")),
314 1 => Ok(bytes::Bytes::from(serde_json::to_vec(&rows[0])?)),
315 _ => format_json_response(rows),
316 }
317}
318
319fn format_singular_json(
321 rows: &[serde_json::Value],
322 nullable: bool,
323) -> Result<bytes::Bytes, FormatError> {
324 match rows.len() {
325 0 if nullable => Ok(bytes::Bytes::from_static(b"null")),
326 0 => Err(FormatError::NotFound),
327 1 => Ok(bytes::Bytes::from(serde_json::to_vec(&rows[0])?)),
328 _ => Err(FormatError::MultipleRows),
329 }
330}
331
332fn format_csv_response(rows: &[serde_json::Value]) -> Result<bytes::Bytes, FormatError> {
334 if rows.is_empty() {
335 return Ok(bytes::Bytes::new());
336 }
337
338 let mut output = Vec::new();
339
340 if let Some(serde_json::Value::Object(map)) = rows.first() {
342 let headers: Vec<&str> = map.keys().map(|s| s.as_str()).collect();
343 output.extend_from_slice(headers.join(",").as_bytes());
344 output.push(b'\n');
345
346 for row in rows {
348 if let serde_json::Value::Object(row_map) = row {
349 let values: Vec<String> = headers
350 .iter()
351 .map(|h| row_map.get(*h).map(csv_escape).unwrap_or_default())
352 .collect();
353 output.extend_from_slice(values.join(",").as_bytes());
354 output.push(b'\n');
355 }
356 }
357 }
358
359 Ok(bytes::Bytes::from(output))
360}
361
362fn csv_escape(value: &serde_json::Value) -> String {
364 match value {
365 serde_json::Value::String(s) => {
366 if s.contains(',') || s.contains('"') || s.contains('\n') {
367 format!("\"{}\"", s.replace('"', "\"\""))
368 } else {
369 s.clone()
370 }
371 }
372 serde_json::Value::Null => String::new(),
373 other => other.to_string(),
374 }
375}
376
377#[derive(Clone, Debug, Default)]
379pub struct QueryResult {
380 pub status: StatusCode,
382 pub rows: Vec<serde_json::Value>,
384 pub total_count: Option<i64>,
386 pub content_range: Option<ContentRange>,
388 pub location: Option<String>,
390 pub allow: Option<String>,
395 pub guc_headers: Option<String>,
397 pub guc_status: Option<String>,
399 pub omit_body: bool,
405 pub affected: usize,
412 pub singular: bool,
418 pub raw_body: Option<(String, Vec<u8>)>,
424}
425
426#[derive(Debug, thiserror::Error)]
428pub enum FormatError {
429 #[error("JSON serialization error: {0}")]
432 Json(#[from] serde_json::Error),
433
434 #[error("Resource not found")]
436 NotFound,
437
438 #[error("Multiple rows returned for singular response")]
441 MultipleRows,
442}
443
444impl FormatError {
445 pub fn status_code(&self) -> StatusCode {
447 match self {
448 Self::Json(_) => StatusCode::INTERNAL_SERVER_ERROR,
449 Self::NotFound => StatusCode::NOT_FOUND,
450 Self::MultipleRows => StatusCode::NOT_ACCEPTABLE,
451 }
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458 use serde_json::json;
459
460 fn result(rows: Vec<serde_json::Value>, singular: bool) -> QueryResult {
461 QueryResult {
462 status: StatusCode::OK,
463 rows,
464 singular,
465 ..Default::default()
466 }
467 }
468
469 #[test]
470 fn singular_result_renders_bare_object() {
471 let req = ApiRequest::default();
472 let resp = format_response(&req, &result(vec![json!({"ok": true})], true)).unwrap();
473 assert_eq!(&resp.body[..], br#"{"ok":true}"#);
474 }
475
476 #[test]
477 fn singular_empty_result_renders_null() {
478 let req = ApiRequest::default();
479 let resp = format_response(&req, &result(vec![], true)).unwrap();
480 assert_eq!(&resp.body[..], b"null");
481 }
482
483 #[test]
484 fn non_singular_result_renders_array() {
485 let req = ApiRequest::default();
486 let resp = format_response(&req, &result(vec![json!(1), json!(2)], false)).unwrap();
487 assert_eq!(&resp.body[..], b"[1,2]");
488 }
489}