1mod json;
6mod headers;
7
8pub use json::format_json_response;
9pub use headers::{build_response_headers, ContentRange};
10
11use http::{HeaderMap, HeaderValue, StatusCode};
12use postrust_core::{ActionPlan, ApiRequest, MediaType, PreferRepresentation};
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 = format_json_response(&result.rows)?;
89 let mut response = Response::new(result.status, body);
90 response.set_content_type("application/json; charset=utf-8");
91 add_common_headers(&mut response, request, result);
92 Ok(response)
93 }
94 MediaType::TextCsv => {
95 let body = format_csv_response(&result.rows)?;
97 let mut response = Response::new(result.status, body);
98 response.set_content_type("text/csv; charset=utf-8");
99 add_common_headers(&mut response, request, result);
100 Ok(response)
101 }
102 MediaType::SingularJson { nullable } => {
103 let body = format_singular_json(&result.rows, *nullable)?;
104 let mut response = Response::new(result.status, body);
105 response.set_content_type("application/vnd.pgrst.object+json; charset=utf-8");
106 add_common_headers(&mut response, request, result);
107 Ok(response)
108 }
109 _ => {
110 let body = format_json_response(&result.rows)?;
112 let mut response = Response::new(result.status, body);
113 response.set_content_type("application/json; charset=utf-8");
114 add_common_headers(&mut response, request, result);
115 Ok(response)
116 }
117 }
118}
119
120fn add_common_headers(response: &mut Response, request: &ApiRequest, result: &QueryResult) {
122 if let Some(range) = &result.content_range {
124 response.set_content_range(range);
125 }
126
127 if let Some(location) = &result.location {
129 response.set_location(location);
130 }
131
132 if let Some(applied) = postrust_core::api_request::preferences::preference_applied(&request.preferences) {
134 response.set_header("preference-applied", &applied);
135 }
136
137 if request.negotiated_by_profile {
139 response.set_header("content-profile", &request.schema);
140 }
141}
142
143fn format_singular_json(rows: &[serde_json::Value], nullable: bool) -> Result<bytes::Bytes, FormatError> {
145 match rows.len() {
146 0 if nullable => Ok(bytes::Bytes::from_static(b"null")),
147 0 => Err(FormatError::NotFound),
148 1 => Ok(bytes::Bytes::from(serde_json::to_vec(&rows[0])?)),
149 _ => Err(FormatError::MultipleRows),
150 }
151}
152
153fn format_csv_response(rows: &[serde_json::Value]) -> Result<bytes::Bytes, FormatError> {
155 if rows.is_empty() {
156 return Ok(bytes::Bytes::new());
157 }
158
159 let mut output = Vec::new();
160
161 if let Some(first) = rows.first() {
163 if let serde_json::Value::Object(map) = first {
164 let headers: Vec<&str> = map.keys().map(|s| s.as_str()).collect();
165 output.extend_from_slice(headers.join(",").as_bytes());
166 output.push(b'\n');
167
168 for row in rows {
170 if let serde_json::Value::Object(row_map) = row {
171 let values: Vec<String> = headers
172 .iter()
173 .map(|h| {
174 row_map
175 .get(*h)
176 .map(|v| csv_escape(v))
177 .unwrap_or_default()
178 })
179 .collect();
180 output.extend_from_slice(values.join(",").as_bytes());
181 output.push(b'\n');
182 }
183 }
184 }
185 }
186
187 Ok(bytes::Bytes::from(output))
188}
189
190fn csv_escape(value: &serde_json::Value) -> String {
192 match value {
193 serde_json::Value::String(s) => {
194 if s.contains(',') || s.contains('"') || s.contains('\n') {
195 format!("\"{}\"", s.replace('"', "\"\""))
196 } else {
197 s.clone()
198 }
199 }
200 serde_json::Value::Null => String::new(),
201 other => other.to_string(),
202 }
203}
204
205#[derive(Clone, Debug, Default)]
207pub struct QueryResult {
208 pub status: StatusCode,
210 pub rows: Vec<serde_json::Value>,
212 pub total_count: Option<i64>,
214 pub content_range: Option<ContentRange>,
216 pub location: Option<String>,
218 pub guc_headers: Option<String>,
220 pub guc_status: Option<String>,
222}
223
224#[derive(Debug, thiserror::Error)]
226pub enum FormatError {
227 #[error("JSON serialization error: {0}")]
228 Json(#[from] serde_json::Error),
229
230 #[error("Resource not found")]
231 NotFound,
232
233 #[error("Multiple rows returned for singular response")]
234 MultipleRows,
235}
236
237impl FormatError {
238 pub fn status_code(&self) -> StatusCode {
239 match self {
240 Self::Json(_) => StatusCode::INTERNAL_SERVER_ERROR,
241 Self::NotFound => StatusCode::NOT_FOUND,
242 Self::MultipleRows => StatusCode::NOT_ACCEPTABLE,
243 }
244 }
245}