Skip to main content

reifydb_sub_server/
response.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use reifydb_type::value::frame::frame::Frame;
5use serde_json::{self, Map, Value as JsonValue, to_string as json_to_string};
6
7/// A resolved JSON response for `?format=json` mode.
8pub struct ResolvedResponse {
9	pub content_type: String,
10	pub body: String,
11}
12
13/// Resolve frames into a JSON response body.
14///
15/// If a `body` column exists in the first frame, extracts it:
16/// - Utf8 body: pass through raw (pre-serialized JSON, never re-parsed)
17/// - Structured body (Record/List/etc): serialize via `Value::to_json_value()`, always array
18///
19/// Otherwise, converts all frames to row-oriented JSON objects.
20pub fn resolve_response_json(frames: Vec<Frame>, unwrap: bool) -> Result<ResolvedResponse, String> {
21	if frames.is_empty() {
22		return Ok(ResolvedResponse {
23			content_type: "application/json".to_string(),
24			body: "[]".to_string(),
25		});
26	}
27
28	// Check if the first frame has a "body" column
29	let has_body_col = frames.first().map(|f| f.columns.iter().any(|c| c.name == "body")).unwrap_or(false);
30
31	if has_body_col {
32		// Existing body-column path
33		let frame = frames.into_iter().next().unwrap();
34		let body_col_idx = frame.columns.iter().position(|c| c.name == "body").unwrap();
35		let body_col = &frame.columns[body_col_idx];
36
37		let row_count = body_col.data.len();
38		let body = if body_col.data.is_utf8() {
39			// Utf8: pre-serialized JSON, pass through raw (NEVER re-parse)
40			let values: Vec<String> = (0..row_count).map(|i| body_col.data.as_string(i)).collect();
41			if unwrap || values.len() == 1 {
42				values.into_iter().next().unwrap()
43			} else {
44				format!("[{}]", values.join(", "))
45			}
46		} else {
47			// Structured (Record/List/etc): serialize to JSON
48			let json_values: Vec<JsonValue> =
49				(0..row_count).map(|i| body_col.data.get_value(i).to_json_value()).collect();
50			if unwrap {
51				json_to_string(&json_values[0]).unwrap()
52			} else {
53				json_to_string(&json_values).unwrap()
54			}
55		};
56
57		Ok(ResolvedResponse {
58			content_type: "application/json".to_string(),
59			body,
60		})
61	} else {
62		// Generic path: convert all frames to row-oriented JSON
63		let json_frames = frames_to_json_rows(&frames);
64
65		let body = if unwrap && json_frames.len() == 1 && json_frames[0].len() == 1 {
66			json_to_string(&json_frames[0][0]).unwrap()
67		} else {
68			json_to_string(&json_frames).unwrap()
69		};
70
71		Ok(ResolvedResponse {
72			content_type: "application/json".to_string(),
73			body,
74		})
75	}
76}
77
78/// Convert frames to row-oriented JSON arrays.
79///
80/// Returns one array of JSON objects per frame. Each object maps column names to JSON values.
81fn frames_to_json_rows(frames: &[Frame]) -> Vec<Vec<JsonValue>> {
82	frames.iter()
83		.map(|frame| {
84			let row_count = frame.columns.first().map(|c| c.data.len()).unwrap_or(0);
85			(0..row_count)
86				.map(|i| {
87					let mut obj = Map::new();
88					for col in frame.iter() {
89						obj.insert(col.name.clone(), col.data.get_value(i).to_json_value());
90					}
91					JsonValue::Object(obj)
92				})
93				.collect()
94		})
95		.collect()
96}