1use crate::error::FireboltError;
2use crate::result::ResultSet;
3use crate::types::{Column, Type};
4use regex::Regex;
5
6fn parse_type(type_str: &str) -> Result<(Type, bool, Option<i32>, Option<i32>), FireboltError> {
7 let is_nullable = type_str.ends_with(" null");
8 let clean_type = if is_nullable {
9 &type_str[..type_str.len() - 5]
10 } else {
11 type_str
12 };
13
14 if let Ok(decimal_regex) = Regex::new(r"(?i)decimal\((\d+),\s*(\d+)\)") {
15 if let Some(captures) = decimal_regex.captures(clean_type) {
16 let precision = captures[1]
17 .parse()
18 .map_err(|_| FireboltError::Query("Invalid decimal precision".to_string()))?;
19 let scale = captures[2]
20 .parse()
21 .map_err(|_| FireboltError::Query("Invalid decimal scale".to_string()))?;
22 return Ok((Type::Decimal, is_nullable, Some(precision), Some(scale)));
23 }
24 }
25
26 if clean_type.starts_with("array") {
27 return Ok((Type::Array, is_nullable, None, None));
28 }
29
30 let base_type = match clean_type {
31 "int" => Type::Int,
32 "bigint" | "long" => Type::Long,
33 "float4" | "float" => Type::Float,
34 "double" | "float8" => Type::Double,
35 "decimal" => Type::Decimal,
36 "text" | "string" => Type::Text,
37 "date" => Type::Date,
38 "timestamp" => Type::Timestamp,
39 "timestamptz" => Type::TimestampTZ,
40 "bool" | "boolean" => Type::Boolean,
41 "bytea" => Type::Bytes,
42 "geography" => Type::Geography,
43 _ if clean_type.starts_with("struct") => Type::Struct,
44 _ => {
45 return Err(FireboltError::Query(format!(
46 "Unsupported type: {clean_type}"
47 )))
48 }
49 };
50
51 Ok((base_type, is_nullable, None, None))
52}
53
54pub fn parse_columns(json: &serde_json::Value) -> Result<Vec<Column>, FireboltError> {
55 let meta = json.get("meta").and_then(|m| m.as_array()).ok_or_else(|| {
56 FireboltError::Query("Missing or invalid 'meta' field in response".to_string())
57 })?;
58
59 meta.iter()
60 .map(|col| {
61 let name = col
62 .get("name")
63 .and_then(|n| n.as_str())
64 .ok_or_else(|| FireboltError::Query("Missing column name".to_string()))?
65 .to_string();
66
67 let type_str = col
68 .get("type")
69 .and_then(|t| t.as_str())
70 .ok_or_else(|| FireboltError::Query("Missing column type".to_string()))?;
71
72 let (r#type, is_nullable, precision, scale) = parse_type(type_str)?;
73
74 Ok(Column {
75 name,
76 r#type,
77 precision,
78 scale,
79 is_nullable,
80 })
81 })
82 .collect()
83}
84
85pub fn parse_data(
86 json: &serde_json::Value,
87 columns: &[Column],
88) -> Result<Vec<crate::result::Row>, FireboltError> {
89 let data = json.get("data").and_then(|d| d.as_array()).ok_or_else(|| {
90 FireboltError::Query("Missing or invalid 'data' field in response".to_string())
91 })?;
92
93 data.iter()
94 .map(|row_array| {
95 let row_values: Vec<serde_json::Value> = row_array
96 .as_array()
97 .ok_or_else(|| FireboltError::Query("Row data is not an array".to_string()))?
98 .to_vec();
99
100 Ok(crate::result::Row::new(row_values, columns.to_vec()))
101 })
102 .collect()
103}
104
105pub fn parse_response(body: String) -> Result<ResultSet, FireboltError> {
106 let json: serde_json::Value = serde_json::from_str(&body)
107 .map_err(|e| FireboltError::Serialization(format!("Failed to parse JSON: {e}")))?;
108
109 let columns = parse_columns(&json)?;
110 let rows = parse_data(&json, &columns)?;
111
112 Ok(ResultSet { columns, rows })
113}
114
115pub fn parse_server_error(body: String) -> FireboltError {
116 FireboltError::Query(format!("Server error: {body}"))
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use crate::types::Type;
123
124 #[test]
125 fn test_parse_type_basic_types() {
126 assert_eq!(parse_type("int").unwrap(), (Type::Int, false, None, None));
127 assert_eq!(
128 parse_type("bigint").unwrap(),
129 (Type::Long, false, None, None)
130 );
131 assert_eq!(parse_type("long").unwrap(), (Type::Long, false, None, None));
132 assert_eq!(
133 parse_type("float").unwrap(),
134 (Type::Float, false, None, None)
135 );
136 assert_eq!(
137 parse_type("float4").unwrap(),
138 (Type::Float, false, None, None)
139 );
140 assert_eq!(
141 parse_type("double").unwrap(),
142 (Type::Double, false, None, None)
143 );
144 assert_eq!(
145 parse_type("float8").unwrap(),
146 (Type::Double, false, None, None)
147 );
148 assert_eq!(parse_type("text").unwrap(), (Type::Text, false, None, None));
149 assert_eq!(
150 parse_type("string").unwrap(),
151 (Type::Text, false, None, None)
152 );
153 assert_eq!(parse_type("date").unwrap(), (Type::Date, false, None, None));
154 assert_eq!(
155 parse_type("timestamp").unwrap(),
156 (Type::Timestamp, false, None, None)
157 );
158 assert_eq!(
159 parse_type("timestamptz").unwrap(),
160 (Type::TimestampTZ, false, None, None)
161 );
162 assert_eq!(
163 parse_type("bool").unwrap(),
164 (Type::Boolean, false, None, None)
165 );
166 assert_eq!(
167 parse_type("boolean").unwrap(),
168 (Type::Boolean, false, None, None)
169 );
170 assert_eq!(
171 parse_type("bytea").unwrap(),
172 (Type::Bytes, false, None, None)
173 );
174 assert_eq!(
175 parse_type("geography").unwrap(),
176 (Type::Geography, false, None, None)
177 );
178 assert_eq!(
179 parse_type("array(int)").unwrap(),
180 (Type::Array, false, None, None)
181 );
182 }
183
184 #[test]
185 fn test_parse_type_nullable() {
186 assert_eq!(
187 parse_type("int null").unwrap(),
188 (Type::Int, true, None, None)
189 );
190 assert_eq!(
191 parse_type("text null").unwrap(),
192 (Type::Text, true, None, None)
193 );
194 assert_eq!(
195 parse_type("array(int) null").unwrap(),
196 (Type::Array, true, None, None)
197 );
198 }
199
200 #[test]
201 fn test_parse_type_decimal_with_precision_scale() {
202 assert_eq!(
203 parse_type("decimal(38, 30)").unwrap(),
204 (Type::Decimal, false, Some(38), Some(30))
205 );
206 assert_eq!(
207 parse_type("decimal(10, 2) null").unwrap(),
208 (Type::Decimal, true, Some(10), Some(2))
209 );
210 assert_eq!(
211 parse_type("Decimal(38, 30)").unwrap(),
212 (Type::Decimal, false, Some(38), Some(30))
213 );
214 assert_eq!(
215 parse_type("Decimal(10, 2) null").unwrap(),
216 (Type::Decimal, true, Some(10), Some(2))
217 );
218 }
219
220 #[test]
221 fn test_parse_type_unsupported() {
222 assert!(parse_type("unsupported_type").is_err());
223 }
224
225 #[test]
226 fn test_parse_columns() {
227 let json = serde_json::json!({
228 "meta": [
229 {"name": "id", "type": "int"},
230 {"name": "name", "type": "text"},
231 {"name": "price", "type": "decimal(10, 2)"},
232 {"name": "nullable_field", "type": "text null"}
233 ]
234 });
235
236 let columns = parse_columns(&json).unwrap();
237 assert_eq!(columns.len(), 4);
238
239 assert_eq!(columns[0].name, "id");
240 assert_eq!(columns[0].r#type, Type::Int);
241 assert!(!columns[0].is_nullable);
242
243 assert_eq!(columns[1].name, "name");
244 assert_eq!(columns[1].r#type, Type::Text);
245 assert!(!columns[1].is_nullable);
246
247 assert_eq!(columns[2].name, "price");
248 assert_eq!(columns[2].r#type, Type::Decimal);
249 assert_eq!(columns[2].precision, Some(10));
250 assert_eq!(columns[2].scale, Some(2));
251 assert!(!columns[2].is_nullable);
252
253 assert_eq!(columns[3].name, "nullable_field");
254 assert_eq!(columns[3].r#type, Type::Text);
255 assert!(columns[3].is_nullable);
256 }
257
258 #[test]
259 fn test_parse_data() {
260 let columns = vec![
261 Column {
262 name: "id".to_string(),
263 r#type: Type::Int,
264 precision: None,
265 scale: None,
266 is_nullable: false,
267 },
268 Column {
269 name: "name".to_string(),
270 r#type: Type::Text,
271 precision: None,
272 scale: None,
273 is_nullable: false,
274 },
275 ];
276
277 let json = serde_json::json!({
278 "data": [
279 [1, "test"],
280 [2, "example"]
281 ]
282 });
283
284 let rows = parse_data(&json, &columns).unwrap();
285 assert_eq!(rows.len(), 2);
286 }
287
288 #[test]
289 fn test_parse_response_success() {
290 let json_response = r#"{
291 "meta": [
292 {"name": "id", "type": "int"},
293 {"name": "name", "type": "text"}
294 ],
295 "data": [
296 [1, "test"],
297 [2, "example"]
298 ],
299 "rows": 2,
300 "statistics": {"elapsed": 0.006947, "rows_read": 2, "bytes_read": 10}
301 }"#;
302
303 let result = parse_response(json_response.to_string());
304 assert!(result.is_ok());
305
306 let result_set = result.unwrap();
307 assert_eq!(result_set.columns.len(), 2);
308 assert_eq!(result_set.rows.len(), 2);
309 assert_eq!(result_set.columns[0].name, "id");
310 assert_eq!(result_set.columns[0].r#type, Type::Int);
311 assert_eq!(result_set.columns[1].name, "name");
312 assert_eq!(result_set.columns[1].r#type, Type::Text);
313 }
314
315 #[test]
316 fn test_parse_response_invalid_json() {
317 let invalid_json = "invalid json";
318
319 let result = parse_response(invalid_json.to_string());
320 assert!(result.is_err());
321 assert!(matches!(
322 result.unwrap_err(),
323 FireboltError::Serialization(_)
324 ));
325 }
326
327 #[test]
328 fn test_parse_response_missing_meta() {
329 let json_response = r#"{"data": []}"#;
330
331 let result = parse_response(json_response.to_string());
332 assert!(result.is_err());
333 assert!(matches!(result.unwrap_err(), FireboltError::Query(_)));
334 }
335
336 #[test]
337 fn test_parse_response_missing_data() {
338 let json_response = r#"{"meta": []}"#;
339
340 let result = parse_response(json_response.to_string());
341 assert!(result.is_err());
342 assert!(matches!(result.unwrap_err(), FireboltError::Query(_)));
343 }
344
345 #[test]
346 fn test_parse_server_error() {
347 let error_body = "Internal Server Error".to_string();
348 let result = parse_server_error(error_body);
349 assert!(matches!(result, FireboltError::Query(_)));
350 assert!(format!("{result:?}").contains("Server error: Internal Server Error"));
351 }
352}