1use console::style;
2use hyper::HeaderMap;
3use serde_json::{Map, Value};
4use tokio::task;
5
6use std::{collections::HashMap, fs};
7
8use crate::{
9 constant::CSV_RECORDS_DEFAULT_KEY,
10 json_path_util::resolve_with_json_compatible_extensions,
11 response::{error_response::not_found_response, json_response::json_response},
12 response_handler::ResponseHandler,
13 types::BoxBody,
14};
15
16use super::{
17 error_response::internal_server_error_response,
18 text_response::text_response,
19 util::{
20 binary_content_type, file_extension, json_value_with_jsonpath_key, text_file_content_type,
21 },
22};
23
24pub struct FileResponse {
25 file_path: String,
26 csv_records_key: Option<String>,
27 text_content: Option<String>,
28 binary_content: Option<Vec<u8>>,
29 custom_headers: Option<HashMap<String, Option<String>>>,
30 request_headers: HeaderMap,
31}
32
33impl FileResponse {
34 pub fn new(
36 file_path: &str,
37 custom_headers: Option<&HashMap<String, Option<String>>>,
38 request_headers: &HeaderMap,
39 ) -> Self {
40 FileResponse {
41 file_path: file_path.to_owned(),
42 csv_records_key: None,
43 text_content: None,
44 binary_content: None,
45 custom_headers: custom_headers.cloned(),
46 request_headers: request_headers.clone(),
47 }
48 }
49
50 pub fn new_with_csv_records_jsonpath(
52 file_path: &str,
53 custom_headers: Option<&HashMap<String, Option<String>>>,
54 csv_records_key: Option<String>,
55 request_headers: &HeaderMap,
56 ) -> Self {
57 let mut ret = FileResponse::new(file_path, custom_headers, request_headers);
58 ret.csv_records_key = csv_records_key;
59 ret
60 }
61
62 pub async fn file_content_response(
64 &mut self,
65 ) -> Result<hyper::Response<BoxBody>, hyper::http::Error> {
66 let file_path = match resolve_with_json_compatible_extensions(self.file_path.as_str()) {
67 Some(x) => x,
68 None => {
69 log::warn!(
70 "{}:\n{} (missing or a directory)",
71 style("file not found").red(),
72 self.file_path
73 );
74 return not_found_response(&self.request_headers);
75 }
76 };
77 self.file_path = file_path.clone();
78
79 let file_path_to_read_text_file = file_path.clone();
81 let content =
82 task::spawn_blocking(move || fs::read_to_string(file_path_to_read_text_file)).await;
83
84 let response = match content {
85 Ok(Ok(content)) => {
86 self.text_content = Some(content);
87 self.text_file_content_response()
88 }
89 Ok(Err(_)) => {
90 let file_path_to_read_binary = file_path.clone();
92 let content =
93 task::spawn_blocking(move || fs::read(file_path_to_read_binary)).await;
94 match content {
95 Ok(Ok(content)) => {
96 self.binary_content = Some(content);
97 self.binary_content_type_response()
98 }
99 Ok(Err(err)) => {
100 return internal_server_error_response(
101 &format!("{}: failed to read file - {}", self.file_path, err),
102 &self.request_headers,
103 )
104 }
105 Err(err) => {
106 return internal_server_error_response(
107 &format!("{}: async task failed - {}", self.file_path, err),
108 &self.request_headers,
109 )
110 }
111 }
112 }
113 Err(err) => {
114 return internal_server_error_response(
115 &format!("{}: async task failed - {}", self.file_path, err),
116 &self.request_headers,
117 )
118 }
119 };
120
121 response
122 }
123
124 fn text_file_content_response(&self) -> Result<hyper::Response<BoxBody>, hyper::http::Error> {
126 match file_extension(self.file_path.as_str()) {
127 Some(ext) => match ext.as_str() {
128 "json" | "json5" => self.json_file_content_response(),
129 "csv" => self.csv_file_content_response(),
130 _ => text_response(
131 self.text_content.clone().unwrap_or_default().as_str(),
132 Some(text_file_content_type(ext).as_str()),
133 None,
134 &self.request_headers,
135 ),
136 },
137 None => text_response(
138 self.text_content.clone().unwrap_or_default().as_str(),
139 None,
140 None,
141 &self.request_headers,
142 ),
143 }
144 }
145
146 fn json_file_content_response(&self) -> Result<hyper::Response<BoxBody>, hyper::http::Error> {
148 let json_str = self.text_content.clone().unwrap_or_default();
149 json_response(
150 json_str.as_str(),
151 self.custom_headers.as_ref(),
152 &self.request_headers,
153 self.file_path.as_str(),
154 )
155 }
156
157 fn csv_file_content_response(&self) -> Result<hyper::Response<BoxBody>, hyper::http::Error> {
159 let text_content = self.text_content.clone().unwrap_or_default();
160 let mut rdr = csv::ReaderBuilder::new()
161 .has_headers(true)
162 .from_reader(text_content.as_bytes());
163
164 let csv_headers = if let Ok(csv_headers) = rdr.headers() {
165 csv_headers.clone()
166 } else {
167 return internal_server_error_response(
168 &format!("{}: failed to analyze csv headers", self.file_path.as_str()),
169 &self.request_headers,
170 );
171 };
172
173 let rows = rdr
174 .records()
175 .map(|result| {
176 let record = result?;
177 let obj = csv_headers
178 .iter()
179 .zip(record.iter())
180 .map(|(k, v)| (k.to_string(), Value::String(v.to_string())))
181 .collect::<Map<_, _>>();
182 Ok(Value::Object(obj))
183 })
184 .collect::<Result<Vec<Value>, csv::Error>>();
185
186 match rows {
187 Ok(rows) => {
188 let jsonpath_key = if let Some(csv_records_key) = self.csv_records_key.as_ref() {
189 csv_records_key.as_str()
190 } else {
191 CSV_RECORDS_DEFAULT_KEY
192 };
193 let json_value = json_value_with_jsonpath_key(jsonpath_key, Value::from(rows));
194
195 let body = serde_json::to_string(&json_value);
196 match body {
197 Ok(body) => json_response(
198 body.as_str(),
199 self.custom_headers.as_ref(),
200 &self.request_headers,
201 self.file_path.as_str(),
202 ),
203 Err(err) => internal_server_error_response(
204 &format!(
205 "{}: failed to convert csv records to json response - {}",
206 self.file_path.as_str(),
207 err
208 ),
209 &self.request_headers,
210 ),
211 }
212 }
213 Err(err) => internal_server_error_response(
214 &format!(
215 "{}: failed to analyze csv records - {}",
216 self.file_path.as_str(),
217 err
218 ),
219 &self.request_headers,
220 ),
221 }
222 }
223
224 fn binary_content_type_response(&self) -> Result<hyper::Response<BoxBody>, hyper::http::Error> {
226 let mut response_handler = ResponseHandler::default();
227
228 if let Some(custom_headers) = self.custom_headers.clone() {
229 response_handler = response_handler.with_headers(custom_headers);
230 }
231
232 let content = self.binary_content.clone().unwrap_or_default().to_owned();
233 let content_type = binary_content_type(self.file_path.as_str());
234 response_handler
235 .with_binary_body(content, Some(content_type))
236 .into_response(&self.request_headers)
237 }
238}