Skip to main content

reifydb_codec/json/
wire_type.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use reifydb_value::value::{
5	constraint::{precision::Precision, scale::Scale},
6	digest::Digest,
7	value_type::ValueType,
8};
9use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeError, ser::Error as SerError};
10use serde_json::{Map, Value as JsonValue};
11
12#[derive(Debug, Clone, PartialEq)]
13pub struct WireValueType(pub ValueType);
14
15impl From<ValueType> for WireValueType {
16	fn from(ty: ValueType) -> Self {
17		Self(ty)
18	}
19}
20
21#[derive(Debug, Clone, PartialEq)]
22pub struct WireColumnType {
23	pub value_type: ValueType,
24}
25
26impl From<WireValueType> for ValueType {
27	fn from(wire: WireValueType) -> Self {
28		wire.0
29	}
30}
31
32const ID: &str = "id";
33const UNDERLYING: &str = "underlying";
34const NAME: &str = "name";
35const TYPE: &str = "type";
36const ACCURACY: &str = "accuracy";
37const PRECISION: &str = "precision";
38const SCALE: &str = "scale";
39const ENUM: &str = "enum";
40
41fn scalar_id(ty: &ValueType) -> Option<&'static str> {
42	Some(match ty {
43		ValueType::Boolean => "Boolean",
44		ValueType::Float4 => "Float4",
45		ValueType::Float8 => "Float8",
46		ValueType::Int1 => "Int1",
47		ValueType::Int2 => "Int2",
48		ValueType::Int4 => "Int4",
49		ValueType::Int8 => "Int8",
50		ValueType::Int16 => "Int16",
51		ValueType::Utf8 => "Utf8",
52		ValueType::Uint1 => "Uint1",
53		ValueType::Uint2 => "Uint2",
54		ValueType::Uint4 => "Uint4",
55		ValueType::Uint8 => "Uint8",
56		ValueType::Uint16 => "Uint16",
57		ValueType::Date => "Date",
58		ValueType::DateTime => "DateTime",
59		ValueType::Time => "Time",
60		ValueType::Duration => "Duration",
61		ValueType::IdentityId => "IdentityId",
62		ValueType::Uuid4 => "Uuid4",
63		ValueType::Uuid7 => "Uuid7",
64		ValueType::Blob => "Blob",
65		ValueType::Any => "Any",
66		ValueType::DictionaryId => "DictionaryId",
67		ValueType::Decimal {
68			..
69		}
70		| ValueType::Enum(_)
71		| ValueType::Option(_)
72		| ValueType::List(_)
73		| ValueType::Record(_)
74		| ValueType::Tuple(_)
75		| ValueType::Digest {
76			..
77		} => {
78			return None;
79		}
80	})
81}
82
83fn scalar_from_id(id: &str) -> Option<ValueType> {
84	Some(match id {
85		"Boolean" => ValueType::Boolean,
86		"Float4" => ValueType::Float4,
87		"Float8" => ValueType::Float8,
88		"Int1" => ValueType::Int1,
89		"Int2" => ValueType::Int2,
90		"Int4" => ValueType::Int4,
91		"Int8" => ValueType::Int8,
92		"Int16" => ValueType::Int16,
93		"Utf8" => ValueType::Utf8,
94		"Uint1" => ValueType::Uint1,
95		"Uint2" => ValueType::Uint2,
96		"Uint4" => ValueType::Uint4,
97		"Uint8" => ValueType::Uint8,
98		"Uint16" => ValueType::Uint16,
99		"Date" => ValueType::Date,
100		"DateTime" => ValueType::DateTime,
101		"Time" => ValueType::Time,
102		"Duration" => ValueType::Duration,
103		"IdentityId" => ValueType::IdentityId,
104		"Uuid4" => ValueType::Uuid4,
105		"Uuid7" => ValueType::Uuid7,
106		"Blob" => ValueType::Blob,
107		"Any" => ValueType::Any,
108		"DictionaryId" => ValueType::DictionaryId,
109		_ => return None,
110	})
111}
112
113fn family_descriptor(id: &str, precision: Precision, scale: Option<Scale>) -> JsonValue {
114	let mut object = Map::new();
115	object.insert(ID.to_string(), JsonValue::String(id.to_string()));
116	object.insert(PRECISION.to_string(), JsonValue::from(precision.value()));
117	if let Some(scale) = scale {
118		object.insert(SCALE.to_string(), JsonValue::from(scale.value()));
119	}
120	JsonValue::Object(object)
121}
122
123fn family_param(object: &Map<String, JsonValue>, key: &str, default: u8, value: &JsonValue) -> Result<u8, String> {
124	match object.get(key) {
125		None => Ok(default),
126		Some(param) => param
127			.as_u64()
128			.and_then(|param| u8::try_from(param).ok())
129			.ok_or_else(|| format!("`{key}` must be an unsigned 8-bit integer: {value}")),
130	}
131}
132
133fn family_precision(
134	object: &Map<String, JsonValue>,
135	default: Precision,
136	value: &JsonValue,
137) -> Result<Precision, String> {
138	let precision = family_param(object, PRECISION, default.value(), value)?;
139	Precision::try_new(precision).map_err(|error| format!("invalid `{PRECISION}` {precision}: {error}"))
140}
141
142fn descriptor(id: &str, underlying: Option<JsonValue>) -> JsonValue {
143	let mut object = Map::new();
144	object.insert(ID.to_string(), JsonValue::String(id.to_string()));
145	if let Some(underlying) = underlying {
146		object.insert(UNDERLYING.to_string(), underlying);
147	}
148	JsonValue::Object(object)
149}
150
151pub fn to_json(ty: &ValueType) -> JsonValue {
152	if let Some(id) = scalar_id(ty) {
153		return descriptor(id, None);
154	}
155	match ty {
156		ValueType::Decimal {
157			precision,
158			scale,
159		} => family_descriptor("Decimal", *precision, Some(*scale)),
160		ValueType::Option(inner) => descriptor("Option", Some(to_json(inner))),
161		ValueType::Enum(name) => {
162			let mut object = Map::new();
163			object.insert(ID.to_string(), JsonValue::String("Enum".to_string()));
164			object.insert(ENUM.to_string(), JsonValue::String(name.clone()));
165			JsonValue::Object(object)
166		}
167		ValueType::List(inner) => descriptor("List", Some(to_json(inner))),
168		ValueType::Tuple(members) => {
169			descriptor("Tuple", Some(JsonValue::Array(members.iter().map(to_json).collect())))
170		}
171		ValueType::Record(fields) => {
172			let entries = fields
173				.iter()
174				.map(|(name, ty)| {
175					let mut field = Map::new();
176					field.insert(NAME.to_string(), JsonValue::String(name.clone()));
177					field.insert(TYPE.to_string(), to_json(ty));
178					JsonValue::Object(field)
179				})
180				.collect();
181			descriptor("Record", Some(JsonValue::Array(entries)))
182		}
183		ValueType::Digest {
184			inner,
185			accuracy,
186		} => {
187			let mut object = descriptor("Digest", Some(to_json(inner)));
188			if let JsonValue::Object(fields) = &mut object {
189				fields.insert(ACCURACY.to_string(), JsonValue::from(*accuracy));
190			}
191			object
192		}
193		other => descriptor(&other.to_string(), None),
194	}
195}
196
197pub fn from_json(value: &JsonValue) -> Result<ValueType, String> {
198	let object = value.as_object().ok_or_else(|| format!("expected a type descriptor object, got {value}"))?;
199	let id = object
200		.get(ID)
201		.and_then(JsonValue::as_str)
202		.ok_or_else(|| format!("type descriptor is missing a string `{ID}`: {value}"))?;
203	let underlying = object.get(UNDERLYING);
204
205	if let Some(scalar) = scalar_from_id(id) {
206		return Ok(scalar);
207	}
208
209	let child = || -> Result<ValueType, String> {
210		let underlying = underlying.ok_or_else(|| format!("`{id}` needs an `{UNDERLYING}` type: {value}"))?;
211		from_json(underlying)
212	};
213
214	match id {
215		"Enum" => object
216			.get(ENUM)
217			.and_then(JsonValue::as_str)
218			.map(|name| ValueType::Enum(name.to_string()))
219			.ok_or_else(|| format!("`Enum` needs a string `{ENUM}`: {value}")),
220		"Decimal" => {
221			let precision = family_precision(
222				object,
223				ValueType::DECIMAL.precision().expect("decimal carries a precision"),
224				value,
225			)?;
226			let default_scale = ValueType::DECIMAL.scale().expect("decimal carries a scale");
227			let scale = family_param(object, SCALE, default_scale.value(), value)?;
228			let scale = Scale::try_new_with_precision(scale, precision)
229				.map_err(|error| format!("invalid `{SCALE}` {scale}: {error}"))?;
230			Ok(ValueType::decimal(precision, scale))
231		}
232		"Option" => Ok(ValueType::Option(Box::new(child()?))),
233		"List" => Ok(ValueType::List(Box::new(child()?))),
234		"Tuple" => {
235			let members = underlying
236				.and_then(JsonValue::as_array)
237				.ok_or_else(|| format!("`Tuple` needs an `{UNDERLYING}` array: {value}"))?;
238			members.iter().map(from_json).collect::<Result<Vec<_>, _>>().map(ValueType::Tuple)
239		}
240		"Record" => {
241			let fields = underlying
242				.and_then(JsonValue::as_array)
243				.ok_or_else(|| format!("`Record` needs an `{UNDERLYING}` array: {value}"))?;
244			fields.iter()
245				.map(|field| {
246					let name = field
247						.get(NAME)
248						.and_then(JsonValue::as_str)
249						.ok_or_else(|| format!("record field is missing `{NAME}`: {field}"))?;
250					let ty = field
251						.get(TYPE)
252						.ok_or_else(|| format!("record field is missing `{TYPE}`: {field}"))?;
253					Ok((name.to_string(), from_json(ty)?))
254				})
255				.collect::<Result<Vec<_>, String>>()
256				.map(ValueType::Record)
257		}
258		"Digest" => {
259			let inner = child()?;
260			let accuracy = object
261				.get(ACCURACY)
262				.and_then(JsonValue::as_u64)
263				.and_then(|accuracy| u32::try_from(accuracy).ok())
264				.ok_or_else(|| format!("`Digest` needs an unsigned 32-bit `{ACCURACY}`: {value}"))?;
265			Digest::new(inner.clone(), accuracy)
266				.map_err(|error| format!("invalid `Digest` type: {error}"))?;
267			Ok(ValueType::Digest {
268				inner: Box::new(inner),
269				accuracy,
270			})
271		}
272		unknown => Err(format!("unknown type id `{unknown}`")),
273	}
274}
275
276impl Serialize for WireColumnType {
277	fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
278		to_json(&self.value_type).serialize(serializer).map_err(S::Error::custom)
279	}
280}
281
282impl<'de> Deserialize<'de> for WireColumnType {
283	fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
284		let value = JsonValue::deserialize(deserializer)?;
285		from_json(&value)
286			.map(|value_type| WireColumnType {
287				value_type,
288			})
289			.map_err(D::Error::custom)
290	}
291}
292
293impl Serialize for WireValueType {
294	fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
295		to_json(&self.0).serialize(serializer).map_err(S::Error::custom)
296	}
297}
298
299impl<'de> Deserialize<'de> for WireValueType {
300	fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
301		let value = JsonValue::deserialize(deserializer)?;
302		from_json(&value).map(WireValueType).map_err(D::Error::custom)
303	}
304}