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