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::{Value, frame::frame::Frame, r#type::Type};
5use serde::{Deserialize, Serialize};
6use serde_json::{self, Map, Value as JsonValue, to_string as json_to_string};
7
8/// A resolved JSON response for `?format=json` mode.
9pub struct ResolvedResponse {
10	pub content_type: String,
11	pub body: String,
12}
13
14/// Resolve frames into a JSON response body.
15///
16/// If a `body` column exists in the first frame, extracts it:
17/// - Utf8 body: pass through raw (pre-serialized JSON, never re-parsed)
18/// - Structured body (Record/List/etc): serialize via `Value::to_json_value()`, always array
19///
20/// Otherwise, converts all frames to row-oriented JSON objects.
21pub fn resolve_response_json(frames: Vec<Frame>, unwrap: bool) -> Result<ResolvedResponse, String> {
22	if frames.is_empty() {
23		return Ok(ResolvedResponse {
24			content_type: "application/json".to_string(),
25			body: "[]".to_string(),
26		});
27	}
28
29	// Check if the first frame has a "body" column
30	let has_body_col = frames.first().map(|f| f.columns.iter().any(|c| c.name == "body")).unwrap_or(false);
31
32	if has_body_col {
33		// Existing body-column path
34		let frame = frames.into_iter().next().unwrap();
35		let body_col_idx = frame.columns.iter().position(|c| c.name == "body").unwrap();
36		let body_col = &frame.columns[body_col_idx];
37
38		let row_count = body_col.data.len();
39		let body = if body_col.data.is_utf8() {
40			// Utf8: pre-serialized JSON, pass through raw (NEVER re-parse)
41			let values: Vec<String> = (0..row_count).map(|i| body_col.data.as_string(i)).collect();
42			if unwrap || values.len() == 1 {
43				values.into_iter().next().unwrap()
44			} else {
45				format!("[{}]", values.join(", "))
46			}
47		} else {
48			// Structured (Record/List/etc): serialize to JSON
49			let json_values: Vec<JsonValue> =
50				(0..row_count).map(|i| body_col.data.get_value(i).to_json_value()).collect();
51			if unwrap {
52				json_to_string(&json_values[0]).unwrap()
53			} else {
54				json_to_string(&json_values).unwrap()
55			}
56		};
57
58		Ok(ResolvedResponse {
59			content_type: "application/json".to_string(),
60			body,
61		})
62	} else {
63		// Generic path: convert all frames to row-oriented JSON
64		let json_frames = frames_to_json_rows(&frames);
65
66		let body = if unwrap && json_frames.len() == 1 && json_frames[0].len() == 1 {
67			json_to_string(&json_frames[0][0]).unwrap()
68		} else {
69			json_to_string(&json_frames).unwrap()
70		};
71
72		Ok(ResolvedResponse {
73			content_type: "application/json".to_string(),
74			body,
75		})
76	}
77}
78
79/// Convert frames to row-oriented JSON arrays.
80///
81/// Returns one array of JSON objects per frame. Each object maps column names to JSON values.
82fn frames_to_json_rows(frames: &[Frame]) -> Vec<Vec<JsonValue>> {
83	frames.iter()
84		.map(|frame| {
85			let row_count = frame.columns.first().map(|c| c.data.len()).unwrap_or(0);
86			(0..row_count)
87				.map(|i| {
88					let mut obj = Map::new();
89					for col in frame.iter() {
90						obj.insert(col.name.clone(), col.data.get_value(i).to_json_value());
91					}
92					JsonValue::Object(obj)
93				})
94				.collect()
95		})
96		.collect()
97}
98
99/// A response frame containing query/command results.
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct ResponseFrame {
102	pub row_numbers: Vec<u64>,
103	pub columns: Vec<ResponseColumn>,
104}
105
106/// A column in a response frame.
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct ResponseColumn {
109	pub name: String,
110	#[serde(rename = "type")]
111	pub r#type: Type,
112	pub data: Vec<String>,
113}
114
115/// Convert database result frames to response frames.
116///
117/// This function converts the internal `Frame` type to the serializable
118/// `ResponseFrame` type expected by clients.
119pub fn convert_frames(frames: Vec<Frame>) -> Vec<ResponseFrame> {
120	let mut result = Vec::new();
121
122	for frame in frames {
123		let row_numbers: Vec<u64> = frame.row_numbers.iter().map(|rn| rn.value()).collect();
124
125		let mut columns = Vec::new();
126
127		for column in frame.iter() {
128			let column_data: Vec<String> = column
129				.data
130				.iter()
131				.map(|value| match value {
132					Value::None {
133						..
134					} => "⟪none⟫".to_string(),
135					Value::Blob(b) => b.to_hex(),
136					_ => value.to_string(),
137				})
138				.collect();
139
140			columns.push(ResponseColumn {
141				name: column.name.clone(),
142				r#type: column.data.get_type(),
143				data: column_data,
144			});
145		}
146
147		result.push(ResponseFrame {
148			row_numbers,
149			columns,
150		});
151	}
152
153	result
154}