Skip to main content

reifydb_sub_server/
response.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use reifydb_codec::{
5	frame::{encode::encode_frames, options::EncodeOptions},
6	json::{none_marker, wire_type::to_json as type_to_json},
7};
8use reifydb_value::{
9	reifydb_assertions,
10	value::{Value, diff_type::DiffType, frame::frame::Frame, system_columns::SystemColumn, value_type::ValueType},
11};
12use serde_json::{self, Map, Value as JsonValue, to_string as json_to_string};
13
14pub const CONTENT_TYPE_JSON: &str = "application/vnd.reifydb.json";
15pub const CONTENT_TYPE_FRAMES: &str = "application/vnd.reifydb.frames";
16pub const CONTENT_TYPE_RBCF: &str = "application/vnd.reifydb.rbcf";
17pub const CONTENT_TYPE_PROTO: &str = "application/vnd.reifydb.proto";
18
19pub fn encode_frames_rbcf(frames: &[Frame]) -> Result<Vec<u8>, String> {
20	encode_frames(frames, &EncodeOptions::fast()).map_err(|e| e.to_string())
21}
22
23pub struct ResolvedResponse {
24	pub content_type: String,
25	pub body: String,
26}
27
28pub fn resolve_change_json(frames: Vec<Frame>) -> Result<ResolvedResponse, String> {
29	let envelopes: Vec<JsonValue> = frames.iter().map(change_envelope).collect();
30	Ok(json_response(json_to_string(&envelopes).map_err(|e| e.to_string())?))
31}
32
33fn change_envelope(frame: &Frame) -> JsonValue {
34	let row_count = frame.columns.first().map(|c| c.data.len()).unwrap_or(0);
35	let row_numbers = frame.row_numbers();
36	let rows: Vec<JsonValue> = (0..row_count)
37		.map(|i| {
38			let mut obj = Map::new();
39			if let Some(rn) = row_numbers.get(i) {
40				obj.insert(SystemColumn::RowNumbers.name().to_string(), JsonValue::from(rn.value()));
41			}
42			for col in frame.iter() {
43				obj.insert(col.name.clone(), row_value(&col.data.get_type(), col.data.get_value(i)));
44			}
45			JsonValue::Object(obj)
46		})
47		.collect();
48
49	let mut envelope = Map::new();
50	if let Some(op) = frame.op {
51		envelope.insert("op".to_string(), JsonValue::from(DiffType::as_u8(op)));
52	}
53	envelope.insert("types".to_string(), frame_types(frame));
54	envelope.insert("rows".to_string(), JsonValue::Array(rows));
55	JsonValue::Object(envelope)
56}
57
58pub fn resolve_response_json(frames: Vec<Frame>, unwrap: bool) -> Result<ResolvedResponse, String> {
59	if frames.is_empty() {
60		return Ok(json_response("[]".to_string()));
61	}
62
63	if has_body_column(&frames) {
64		let frame = frames.into_iter().next().unwrap();
65		return Ok(json_response(render_body_column(frame, unwrap)));
66	}
67
68	Ok(json_response(render_frame_rows(&frames, unwrap)))
69}
70
71#[inline]
72fn json_response(body: String) -> ResolvedResponse {
73	ResolvedResponse {
74		content_type: CONTENT_TYPE_JSON.to_string(),
75		body,
76	}
77}
78
79#[inline]
80fn has_body_column(frames: &[Frame]) -> bool {
81	frames.first().map(|f| f.columns.iter().any(|c| c.name == "body")).unwrap_or(false)
82}
83
84#[inline]
85fn render_body_column(frame: Frame, unwrap: bool) -> String {
86	let body_col_idx = frame.columns.iter().position(|c| c.name == "body").unwrap();
87	let body_col = &frame.columns[body_col_idx];
88
89	let row_count = body_col.data.len();
90	if body_col.data.is_utf8() {
91		let values: Vec<String> = (0..row_count).map(|i| body_col.data.as_string(i)).collect();
92		if unwrap || values.len() == 1 {
93			values.into_iter().next().unwrap()
94		} else {
95			format!("[{}]", values.join(", "))
96		}
97	} else {
98		let json_values: Vec<JsonValue> =
99			(0..row_count).map(|i| body_col.data.get_value(i).to_json_value()).collect();
100		reifydb_assertions! {
101			let len = json_values.len();
102			assert!(
103				len > 0,
104				"render_body_column reached the non-utf8 branch with an empty body column, but the \
105				 caller already established the frame is non-empty by detecting a body column on it; \
106				 indexing json_values[0] would panic on a row_count={len} column"
107			);
108		}
109		if unwrap {
110			json_to_string(&json_values[0]).unwrap()
111		} else {
112			json_to_string(&json_values).unwrap()
113		}
114	}
115}
116
117fn option_depth(ty: &ValueType) -> u32 {
118	let mut depth = 0;
119	let mut base = ty;
120	while let ValueType::Option(inner) = base {
121		depth += 1;
122		base = inner;
123	}
124	depth
125}
126
127fn row_value(column_type: &ValueType, value: Value) -> JsonValue {
128	match &value {
129		Value::None {
130			inner,
131		} => {
132			let wrapped = option_depth(column_type).saturating_sub(option_depth(inner) + 1);
133			if wrapped == 0 {
134				JsonValue::Null
135			} else {
136				JsonValue::String(none_marker(wrapped))
137			}
138		}
139		_ => value.to_json_value(),
140	}
141}
142
143fn frame_types(frame: &Frame) -> JsonValue {
144	let mut types = Map::new();
145	for col in frame.iter() {
146		types.insert(col.name.clone(), type_to_json(&col.data.get_type()));
147	}
148	JsonValue::Object(types)
149}
150
151#[inline]
152fn render_frame_rows(frames: &[Frame], unwrap: bool) -> String {
153	let json_frames = frames_to_json_rows(frames);
154
155	if unwrap && json_frames.len() == 1 && json_frames[0].len() == 1 {
156		return json_to_string(&json_frames[0][0]).unwrap();
157	}
158
159	let envelopes: Vec<JsonValue> = frames
160		.iter()
161		.zip(json_frames)
162		.map(|(frame, rows)| {
163			let mut envelope = Map::new();
164			envelope.insert("types".to_string(), frame_types(frame));
165			envelope.insert("rows".to_string(), JsonValue::Array(rows));
166			JsonValue::Object(envelope)
167		})
168		.collect();
169	json_to_string(&envelopes).unwrap()
170}
171
172fn frames_to_json_rows(frames: &[Frame]) -> Vec<Vec<JsonValue>> {
173	frames.iter()
174		.map(|frame| {
175			let row_count = frame.columns.first().map(|c| c.data.len()).unwrap_or(0);
176			(0..row_count)
177				.map(|i| {
178					let mut obj = Map::new();
179					for col in frame.iter() {
180						obj.insert(
181							col.name.clone(),
182							row_value(&col.data.get_type(), col.data.get_value(i)),
183						);
184					}
185					JsonValue::Object(obj)
186				})
187				.collect()
188		})
189		.collect()
190}