Skip to main content

reifydb_sub_server/
wire.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::{collections::HashMap, sync::Arc};
5
6use reifydb_codec::json::{from::parse_json_value, wire_type, wire_type::WireValueType};
7use reifydb_value::{
8	fragment::Fragment,
9	params::Params,
10	value::{
11		Value,
12		constraint::{precision::Precision, scale::Scale},
13		decimal::parse::parse_decimal,
14		duration::Duration,
15		value_type::ValueType,
16	},
17};
18use serde::{Deserialize, Deserializer, Serialize, de::Error as DeError};
19use serde_json::Value as JsonValue;
20
21#[derive(Debug, Serialize)]
22pub struct WireValue {
23	#[serde(rename = "type")]
24	pub r#type: WireValueType,
25	pub value: JsonValue,
26}
27
28pub fn resolve_type(r#type: &JsonValue, value: &JsonValue) -> Result<ValueType, String> {
29	let is_bare_decimal = r#type.as_object().is_some_and(|object| {
30		object.get("id").and_then(JsonValue::as_str) == Some("Decimal") && !object.contains_key("scale")
31	});
32
33	if is_bare_decimal
34		&& let Some(text) = value.as_str()
35		&& let Ok(decimal) = parse_decimal(Fragment::internal(text))
36	{
37		let precision = r#type
38			.as_object()
39			.and_then(|object| object.get("precision"))
40			.and_then(JsonValue::as_u64)
41			.and_then(|precision| u8::try_from(precision).ok())
42			.and_then(|precision| Precision::try_new(precision).ok())
43			.unwrap_or(Precision::MAX);
44		let scale =
45			Scale::try_new_with_precision(decimal.scale(), precision).map_err(|error| error.to_string())?;
46		return Ok(ValueType::decimal(precision, scale));
47	}
48
49	wire_type::from_json(r#type)
50}
51
52impl<'de> Deserialize<'de> for WireValue {
53	fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
54		#[derive(Deserialize)]
55		struct Raw {
56			#[serde(rename = "type")]
57			r#type: JsonValue,
58			value: JsonValue,
59		}
60		let raw = Raw::deserialize(deserializer)?;
61		let ty = resolve_type(&raw.r#type, &raw.value).map_err(D::Error::custom)?;
62		Ok(WireValue {
63			r#type: WireValueType::from(ty),
64			value: raw.value,
65		})
66	}
67}
68
69#[derive(Debug, Serialize, Deserialize)]
70#[serde(untagged)]
71pub enum WireParams {
72	Positional(Vec<WireValue>),
73	Named(HashMap<String, WireValue>),
74}
75
76fn wire_value_to_value(parameter: &str, wire: WireValue) -> Result<Value, String> {
77	parse_json_value(&wire.r#type.0, &wire.value).map_err(|e| format!("parameter {parameter}: {e}"))
78}
79
80impl WireValue {
81	pub fn into_duration(self, option: &str) -> Result<Duration, String> {
82		match parse_json_value(&self.r#type.0, &self.value).map_err(|e| format!("option {option}: {e}"))? {
83			Value::Duration(duration) => Ok(duration),
84			other => Err(format!("option {option}: expected Duration, got {}", other.get_type())),
85		}
86	}
87}
88
89impl WireParams {
90	pub fn into_params(self) -> Result<Params, String> {
91		match self {
92			WireParams::Positional(items) => {
93				let mut values = Vec::with_capacity(items.len());
94				for (index, item) in items.into_iter().enumerate() {
95					values.push(wire_value_to_value(&format!("${}", index + 1), item)?);
96				}
97				Ok(Params::Positional(Arc::new(values)))
98			}
99			WireParams::Named(map) => {
100				let mut result = HashMap::with_capacity(map.len());
101				for (key, wire) in map {
102					let value = wire_value_to_value(&format!("${key}"), wire)?;
103					result.insert(key, value);
104				}
105				Ok(Params::Named(Arc::new(result)))
106			}
107		}
108	}
109}
110
111#[cfg(test)]
112mod tests {
113	use reifydb_codec::json::{NONE_MARKER, none_marker};
114	use reifydb_value::value::value_type::ValueType;
115	use serde_json::from_str;
116
117	use super::*;
118
119	fn positional(json: &str) -> Result<Params, String> {
120		from_str::<WireParams>(json).expect("wire params should deserialise").into_params()
121	}
122
123	fn named(json: &str) -> Result<Params, String> {
124		from_str::<WireParams>(json).expect("wire params should deserialise").into_params()
125	}
126
127	fn option(inner: ValueType) -> ValueType {
128		ValueType::Option(Box::new(inner))
129	}
130
131	fn option_option_int4(value: &str) -> String {
132		format!(
133			r#"{{"type":{{"id":"Option","underlying":{{"id":"Option","underlying":{{"id":"Int4"}}}}}},"value":"{value}"}}"#
134		)
135	}
136
137	#[test]
138	fn a_none_under_one_some_layer_of_option_option_int4_is_a_none_of_int4() {
139		let params = positional(&format!("[{}]", option_option_int4(&none_marker(1)))).unwrap();
140		assert_eq!(params.get_positional(0), Some(&Value::none_of(ValueType::Int4)));
141
142		let params = named(&format!(r#"{{"v":{}}}"#, option_option_int4(&none_marker(1)))).unwrap();
143		assert_eq!(params.get_named("v"), Some(&Value::none_of(ValueType::Int4)));
144	}
145
146	#[test]
147	fn a_bare_marker_on_option_option_int4_is_a_none_of_option_int4() {
148		let params = positional(&format!("[{}]", option_option_int4(NONE_MARKER))).unwrap();
149		assert_eq!(params.get_positional(0), Some(&Value::none_of(option(ValueType::Int4))));
150
151		let params = named(&format!(r#"{{"v":{}}}"#, option_option_int4(NONE_MARKER))).unwrap();
152		assert_eq!(params.get_named("v"), Some(&Value::none_of(option(ValueType::Int4))));
153	}
154
155	#[test]
156	fn a_present_value_on_option_option_int4_is_the_base_value() {
157		let params = positional(&format!("[{}]", option_option_int4("5"))).unwrap();
158		assert_eq!(params.get_positional(0), Some(&Value::Int4(5)));
159
160		let params = named(&format!(r#"{{"v":{}}}"#, option_option_int4("5"))).unwrap();
161		assert_eq!(params.get_named("v"), Some(&Value::Int4(5)));
162	}
163
164	#[test]
165	fn a_scalar_descriptor_is_a_base_type() {
166		let params = positional(r#"[{"type":{"id":"Int4"},"value":"7"}]"#).unwrap();
167		assert_eq!(params.get_positional(0), Some(&Value::Int4(7)));
168	}
169
170	#[test]
171	fn nested_descriptors_carry_the_option_layers() {
172		let params = positional(
173			r#"[{"type":{"id":"Option","underlying":{"id":"Option","underlying":{"id":"Utf8"}}},"value":"⟪none:1⟫"},{"type":{"id":"Option","underlying":{"id":"Utf8"}},"value":"seven"}]"#,
174		)
175		.unwrap();
176		assert_eq!(params.get_positional(0), Some(&Value::none_of(ValueType::Utf8)));
177		assert_eq!(params.get_positional(1), Some(&Value::Utf8("seven".to_string())));
178	}
179
180	#[test]
181	fn the_none_type_name_is_unknown() {
182		let err = from_str::<WireValue>(r#"{"type":{"id":"None"},"value":"⟪none⟫"}"#).unwrap_err().to_string();
183		assert!(err.contains("unknown type id `None`"), "{err}");
184	}
185
186	#[test]
187	fn a_positional_error_names_the_position_and_the_type() {
188		let err = positional(r#"[{"type":{"id":"Int4"},"value":"1"},{"type":{"id":"Int4"},"value":"abc"}]"#)
189			.unwrap_err();
190		assert_eq!(err, "parameter $2: invalid data: cannot parse 'abc' as Int4");
191	}
192
193	#[test]
194	fn a_named_error_names_the_parameter_and_the_type() {
195		let params = from_str::<WireParams>(r#"{"id":{"type":{"id":"Int4"},"value":"⟪none⟫"}}"#).unwrap();
196		let err = params.into_params().unwrap_err();
197		assert_eq!(err, "parameter $id: invalid data: none marker for non-Option type Int4");
198	}
199}