1mod headers;
6pub use headers::parse_guc_headers;
7mod json;
8
9pub use headers::ContentRange;
10#[allow(deprecated)]
13pub use headers::build_response_headers;
14pub use json::format_json_response;
15
16use http::{HeaderMap, HeaderValue, StatusCode};
17use postrust_core::{ApiRequest, MediaType};
18use serde::Serialize;
19
20#[derive(Clone, Debug)]
22pub struct Response {
23 pub status: StatusCode,
25 pub headers: HeaderMap,
27 pub body: bytes::Bytes,
29}
30
31impl Response {
32 pub fn new(status: StatusCode, body: impl Into<bytes::Bytes>) -> Self {
34 Self {
35 status,
36 headers: HeaderMap::new(),
37 body: body.into(),
38 }
39 }
40
41 pub fn json<T: Serialize>(status: StatusCode, value: &T) -> Result<Self, serde_json::Error> {
43 let body = serde_json::to_vec(value)?;
44 let mut response = Self::new(status, body);
45 response.set_content_type("application/json; charset=utf-8");
46 Ok(response)
47 }
48
49 pub fn empty(status: StatusCode) -> Self {
51 Self::new(status, bytes::Bytes::new())
52 }
53
54 pub fn append_header(&mut self, name: &str, value: &str) {
60 if let (Ok(name), Ok(value)) = (
61 http::header::HeaderName::from_bytes(name.as_bytes()),
62 HeaderValue::from_str(value),
63 ) {
64 self.headers.append(name, value);
65 }
66 }
67
68 pub fn set_header(&mut self, name: &str, value: &str) {
69 if let Ok(v) = HeaderValue::from_str(value) {
70 self.headers.insert(
71 http::header::HeaderName::from_bytes(name.as_bytes()).unwrap(),
72 v,
73 );
74 }
75 }
76
77 pub fn set_content_type(&mut self, content_type: &str) {
79 self.set_header("content-type", content_type);
80 }
81
82 pub fn set_content_range(&mut self, range: &ContentRange) {
84 self.set_header("content-range", &range.to_string());
85 }
86
87 pub fn set_location(&mut self, location: &str) {
89 self.set_header("location", location);
90 }
91}
92
93pub fn format_response(
95 request: &ApiRequest,
96 result: &QueryResult,
97) -> Result<Response, FormatError> {
98 let media_type = request
99 .accept_media_types
100 .first()
101 .cloned()
102 .unwrap_or(MediaType::ApplicationJson);
103
104 if let Some((media_type, body)) = &result.raw_body {
110 let mut response = Response::new(result.status, body.clone());
111 let charset = match media_type.as_str() {
115 "application/octet-stream" => "",
116 already if already.contains("charset=") => "",
117 text if text.starts_with("text/") || text.contains("json") => "; charset=utf-8",
118 _ => "",
119 };
120 response.set_content_type(&format!("{}{}", media_type, charset));
121 add_common_headers(&mut response, request, result);
126 return Ok(response);
127 }
128
129 if result.omit_body {
130 if let MediaType::SingularJson { nullable, .. } = &media_type {
134 match (result.affected, nullable) {
135 (1, _) | (0, true) => {}
136 (0, false) => return Err(FormatError::NotFound),
137 _ => return Err(FormatError::MultipleRows),
138 }
139 }
140 let mut response = Response::new(result.status, bytes::Bytes::new());
141 add_common_headers(&mut response, request, result);
142 return Ok(response);
143 }
144
145 let rows = match &media_type {
148 MediaType::SingularJson {
149 strip_nulls: true, ..
150 }
151 | MediaType::ArrayJson {
152 strip_nulls: true, ..
153 } => result.rows.iter().cloned().map(strip_nulls).collect(),
154 _ => result.rows.clone(),
155 };
156 let result = &QueryResult {
157 rows,
158 ..result.clone()
159 };
160
161 match &media_type {
162 MediaType::ApplicationJson => {
163 let body = if result.singular {
164 format_singular_or_null(&result.rows)?
165 } else {
166 format_json_response(&result.rows)?
167 };
168 let mut response = Response::new(result.status, body);
169 response.set_content_type("application/json; charset=utf-8");
170 add_common_headers(&mut response, request, result);
171 Ok(response)
172 }
173 MediaType::TextCsv => {
174 let body = format_csv_response(&result.rows)?;
176 let mut response = Response::new(result.status, body);
177 response.set_content_type("text/csv; charset=utf-8");
178 add_common_headers(&mut response, request, result);
179 Ok(response)
180 }
181 MediaType::SingularJson { nullable, .. } => {
182 let body = format_singular_json(&result.rows, *nullable)?;
183 let mut response = Response::new(result.status, body);
184 response.set_content_type("application/vnd.pgrst.object+json; charset=utf-8");
185 add_common_headers(&mut response, request, result);
186 Ok(response)
187 }
188 other => {
189 let body = if result.singular {
191 format_singular_or_null(&result.rows)?
192 } else {
193 format_json_response(&result.rows)?
194 };
195 let mut response = Response::new(result.status, body);
196 let content_type = match other {
200 MediaType::ArrayJson { .. } => "application/vnd.pgrst.array+json; charset=utf-8",
201 MediaType::OpenApi => "application/openapi+json; charset=utf-8",
202 _ => "application/json; charset=utf-8",
203 };
204 response.set_content_type(content_type);
205 add_common_headers(&mut response, request, result);
206 Ok(response)
207 }
208 }
209}
210
211pub(crate) fn preference_scope(
213 request: &ApiRequest,
214) -> postrust_core::api_request::preferences::PreferenceScope {
215 use postrust_core::api_request::preferences::PreferenceScope;
216 use postrust_core::api_request::{Action, DbAction, Mutation};
217
218 let Action::Db(action) = &request.action else {
219 return PreferenceScope::read();
220 };
221
222 match action {
223 DbAction::RelationMut { mutation, .. } => PreferenceScope {
224 resolution: matches!(mutation, Mutation::Create),
225 representation: true,
226 missing: matches!(mutation, Mutation::Create | Mutation::Update),
227 max_affected: matches!(mutation, Mutation::Update | Mutation::Delete),
228 },
229 DbAction::Routine { .. } => PreferenceScope {
230 max_affected: true,
231 ..PreferenceScope::read()
232 },
233 _ => PreferenceScope::read(),
234 }
235}
236
237fn strip_nulls(value: serde_json::Value) -> serde_json::Value {
243 match value {
244 serde_json::Value::Object(fields) => serde_json::Value::Object(
245 fields
246 .into_iter()
247 .filter(|(_, value)| !value.is_null())
248 .map(|(key, value)| (key, strip_nulls(value)))
249 .collect(),
250 ),
251 serde_json::Value::Array(items) => {
252 serde_json::Value::Array(items.into_iter().map(strip_nulls).collect())
253 }
254 other => other,
255 }
256}
257
258fn add_common_headers(response: &mut Response, request: &ApiRequest, result: &QueryResult) {
260 if let Some(guc) = &result.guc_headers {
263 if let Some(headers) = crate::headers::parse_guc_headers(guc) {
264 for (name, value) in headers {
265 response.append_header(&name, &value);
266 }
267 }
268 }
269
270 if let Some(range) = &result.content_range {
272 response.set_content_range(range);
273 }
274
275 if let Some(location) = &result.location {
277 response.set_location(location);
278 }
279
280 if let Some(allow) = &result.allow {
282 response.set_header("allow", allow);
283 }
284
285 if let Some(applied) = postrust_core::api_request::preferences::preference_applied(
287 &request.preferences,
288 preference_scope(request),
289 ) {
290 response.set_header("preference-applied", &applied);
291 }
292
293 if request.negotiated_by_profile {
295 response.set_header("content-profile", &request.schema);
296 }
297}
298
299fn format_singular_or_null(rows: &[serde_json::Value]) -> Result<bytes::Bytes, FormatError> {
305 match rows.len() {
306 0 => Ok(bytes::Bytes::from_static(b"null")),
307 1 => Ok(bytes::Bytes::from(serde_json::to_vec(&rows[0])?)),
308 _ => format_json_response(rows),
309 }
310}
311
312fn format_singular_json(
314 rows: &[serde_json::Value],
315 nullable: bool,
316) -> Result<bytes::Bytes, FormatError> {
317 match rows.len() {
318 0 if nullable => Ok(bytes::Bytes::from_static(b"null")),
319 0 => Err(FormatError::NotFound),
320 1 => Ok(bytes::Bytes::from(serde_json::to_vec(&rows[0])?)),
321 _ => Err(FormatError::MultipleRows),
322 }
323}
324
325fn format_csv_response(rows: &[serde_json::Value]) -> Result<bytes::Bytes, FormatError> {
327 if rows.is_empty() {
328 return Ok(bytes::Bytes::new());
329 }
330
331 let mut output = Vec::new();
332
333 if let Some(serde_json::Value::Object(map)) = rows.first() {
335 let headers: Vec<&str> = map.keys().map(|s| s.as_str()).collect();
336 output.extend_from_slice(headers.join(",").as_bytes());
337 output.push(b'\n');
338
339 for row in rows {
341 if let serde_json::Value::Object(row_map) = row {
342 let values: Vec<String> = headers
343 .iter()
344 .map(|h| row_map.get(*h).map(csv_escape).unwrap_or_default())
345 .collect();
346 output.extend_from_slice(values.join(",").as_bytes());
347 output.push(b'\n');
348 }
349 }
350 }
351
352 Ok(bytes::Bytes::from(output))
353}
354
355fn csv_escape(value: &serde_json::Value) -> String {
357 match value {
358 serde_json::Value::String(s) => {
359 if s.contains(',') || s.contains('"') || s.contains('\n') {
360 format!("\"{}\"", s.replace('"', "\"\""))
361 } else {
362 s.clone()
363 }
364 }
365 serde_json::Value::Null => String::new(),
366 other => other.to_string(),
367 }
368}
369
370#[derive(Clone, Debug, Default)]
372pub struct QueryResult {
373 pub status: StatusCode,
375 pub rows: Vec<serde_json::Value>,
377 pub total_count: Option<i64>,
379 pub content_range: Option<ContentRange>,
381 pub location: Option<String>,
383 pub allow: Option<String>,
388 pub guc_headers: Option<String>,
390 pub guc_status: Option<String>,
392 pub omit_body: bool,
398 pub affected: usize,
405 pub singular: bool,
411 pub raw_body: Option<(String, Vec<u8>)>,
417}
418
419#[derive(Debug, thiserror::Error)]
421pub enum FormatError {
422 #[error("JSON serialization error: {0}")]
423 Json(#[from] serde_json::Error),
424
425 #[error("Resource not found")]
426 NotFound,
427
428 #[error("Multiple rows returned for singular response")]
429 MultipleRows,
430}
431
432impl FormatError {
433 pub fn status_code(&self) -> StatusCode {
434 match self {
435 Self::Json(_) => StatusCode::INTERNAL_SERVER_ERROR,
436 Self::NotFound => StatusCode::NOT_FOUND,
437 Self::MultipleRows => StatusCode::NOT_ACCEPTABLE,
438 }
439 }
440}
441
442#[cfg(test)]
443mod tests {
444 use super::*;
445 use serde_json::json;
446
447 fn result(rows: Vec<serde_json::Value>, singular: bool) -> QueryResult {
448 QueryResult {
449 status: StatusCode::OK,
450 rows,
451 singular,
452 ..Default::default()
453 }
454 }
455
456 #[test]
457 fn singular_result_renders_bare_object() {
458 let req = ApiRequest::default();
459 let resp = format_response(&req, &result(vec![json!({"ok": true})], true)).unwrap();
460 assert_eq!(&resp.body[..], br#"{"ok":true}"#);
461 }
462
463 #[test]
464 fn singular_empty_result_renders_null() {
465 let req = ApiRequest::default();
466 let resp = format_response(&req, &result(vec![], true)).unwrap();
467 assert_eq!(&resp.body[..], b"null");
468 }
469
470 #[test]
471 fn non_singular_result_renders_array() {
472 let req = ApiRequest::default();
473 let resp = format_response(&req, &result(vec![json!(1), json!(2)], false)).unwrap();
474 assert_eq!(&resp.body[..], b"[1,2]");
475 }
476}