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