Skip to main content

surrealdb_types/flatbuffers/
value.rs

1use anyhow::anyhow;
2use chrono::{DateTime, Utc};
3use rust_decimal::Decimal;
4use surrealdb_protocol::fb::v1 as proto_fb;
5
6use super::{FromFlatbuffers, ToFlatbuffers};
7use crate::{
8	Array, Bytes, Datetime, Duration, File, Geometry, Number, Object, Range, RecordId, Regex, Set,
9	Table, Uuid, Value,
10};
11
12impl ToFlatbuffers for Value {
13	type Output<'bldr> = flatbuffers::WIPOffset<proto_fb::Value<'bldr>>;
14
15	#[inline]
16	fn to_fb<'bldr>(
17		&self,
18		builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
19	) -> anyhow::Result<Self::Output<'bldr>> {
20		let args = match self {
21			Self::None => proto_fb::ValueArgs {
22				value_type: proto_fb::ValueType::NONE,
23				value: None,
24			},
25			Self::Null => proto_fb::ValueArgs {
26				value_type: proto_fb::ValueType::Null,
27				value: Some(
28					proto_fb::NullValue::create(builder, &proto_fb::NullValueArgs {})
29						.as_union_value(),
30				),
31			},
32			Self::Bool(b) => proto_fb::ValueArgs {
33				value_type: proto_fb::ValueType::Bool,
34				value: Some(b.to_fb(builder)?.as_union_value()),
35			},
36			Self::Number(n) => match n {
37				crate::Number::Int(i) => proto_fb::ValueArgs {
38					value_type: proto_fb::ValueType::Int64,
39					value: Some(i.to_fb(builder)?.as_union_value()),
40				},
41				crate::Number::Float(f) => proto_fb::ValueArgs {
42					value_type: proto_fb::ValueType::Float64,
43					value: Some(f.to_fb(builder)?.as_union_value()),
44				},
45				crate::Number::Decimal(d) => proto_fb::ValueArgs {
46					value_type: proto_fb::ValueType::Decimal,
47					value: Some(d.to_fb(builder)?.as_union_value()),
48				},
49			},
50			Self::String(s) => proto_fb::ValueArgs {
51				value_type: proto_fb::ValueType::String,
52				value: Some(s.to_fb(builder)?.as_union_value()),
53			},
54			Self::Bytes(b) => proto_fb::ValueArgs {
55				value_type: proto_fb::ValueType::Bytes,
56				value: Some(b.to_fb(builder)?.as_union_value()),
57			},
58			Self::Table(t) => proto_fb::ValueArgs {
59				value_type: proto_fb::ValueType::Table,
60				value: Some(t.to_fb(builder)?.as_union_value()),
61			},
62			Self::RecordId(record_id) => proto_fb::ValueArgs {
63				value_type: proto_fb::ValueType::RecordId,
64				value: Some(record_id.to_fb(builder)?.as_union_value()),
65			},
66			Self::Duration(d) => proto_fb::ValueArgs {
67				value_type: proto_fb::ValueType::Duration,
68				value: Some(d.to_fb(builder)?.as_union_value()),
69			},
70			Self::Datetime(dt) => proto_fb::ValueArgs {
71				value_type: proto_fb::ValueType::Datetime,
72				value: Some(dt.0.to_fb(builder)?.as_union_value()),
73			},
74			Self::Uuid(uuid) => proto_fb::ValueArgs {
75				value_type: proto_fb::ValueType::Uuid,
76				value: Some(uuid.to_fb(builder)?.as_union_value()),
77			},
78			Self::Object(obj) => proto_fb::ValueArgs {
79				value_type: proto_fb::ValueType::Object,
80				value: Some(obj.to_fb(builder)?.as_union_value()),
81			},
82			Self::Array(arr) => proto_fb::ValueArgs {
83				value_type: proto_fb::ValueType::Array,
84				value: Some(arr.to_fb(builder)?.as_union_value()),
85			},
86			Self::Set(set) => proto_fb::ValueArgs {
87				value_type: proto_fb::ValueType::Set,
88				value: Some(set.to_fb(builder)?.as_union_value()),
89			},
90			Self::Geometry(geometry) => proto_fb::ValueArgs {
91				value_type: proto_fb::ValueType::Geometry,
92				value: Some(geometry.to_fb(builder)?.as_union_value()),
93			},
94			Self::File(file) => proto_fb::ValueArgs {
95				value_type: proto_fb::ValueType::File,
96				value: Some(file.to_fb(builder)?.as_union_value()),
97			},
98			Self::Regex(regex) => proto_fb::ValueArgs {
99				value_type: proto_fb::ValueType::Regex,
100				value: Some(regex.to_fb(builder)?.as_union_value()),
101			},
102			Self::Range(range) => proto_fb::ValueArgs {
103				value_type: proto_fb::ValueType::Range,
104				value: Some(range.to_fb(builder)?.as_union_value()),
105			},
106		};
107
108		Ok(proto_fb::Value::create(builder, &args))
109	}
110}
111
112impl FromFlatbuffers for Value {
113	type Input<'a> = proto_fb::Value<'a>;
114
115	#[inline]
116	fn from_fb(input: Self::Input<'_>) -> anyhow::Result<Self> {
117		match input.value_type() {
118			proto_fb::ValueType::NONE => Ok(Value::None),
119			proto_fb::ValueType::Null => Ok(Value::Null),
120			proto_fb::ValueType::Bool => {
121				Ok(Value::Bool(input.value_as_bool().expect("Guaranteed to be a Bool").value()))
122			}
123			proto_fb::ValueType::Int64 => Ok(Value::Number(Number::Int(
124				input.value_as_int_64().expect("Guaranteed to be an Int64").value(),
125			))),
126			proto_fb::ValueType::Float64 => Ok(Value::Number(Number::Float(
127				input.value_as_float_64().expect("Guaranteed to be a Float64").value(),
128			))),
129			proto_fb::ValueType::Decimal => {
130				let decimal_value = input.value_as_decimal().expect("Guaranteed to be a Decimal");
131				Ok(Value::Number(Number::Decimal(Decimal::from_fb(decimal_value)?)))
132			}
133			proto_fb::ValueType::String => {
134				let string_value = input.value_as_string().expect("Guaranteed to be a String");
135				let value = string_value
136					.value()
137					.expect("String value is guaranteed to be present")
138					.to_string();
139				Ok(Value::String(value))
140			}
141			proto_fb::ValueType::Bytes => {
142				let bytes_value = input.value_as_bytes().expect("Guaranteed to be Bytes");
143				Ok(Value::Bytes(Bytes::from_fb(bytes_value)?))
144			}
145			proto_fb::ValueType::Table => {
146				let table_value = input.value_as_table().expect("Guaranteed to be a Table");
147				let table = Table::from_fb(table_value)?;
148				Ok(Value::Table(table))
149			}
150			proto_fb::ValueType::RecordId => {
151				let record_id_value =
152					input.value_as_record_id().expect("Guaranteed to be a RecordId");
153				let thing = RecordId::from_fb(record_id_value)?;
154				Ok(Value::RecordId(thing))
155			}
156			proto_fb::ValueType::Duration => {
157				let duration_value =
158					input.value_as_duration().expect("Guaranteed to be a Duration");
159				let duration = Duration::from_fb(duration_value)?;
160				Ok(Value::Duration(duration))
161			}
162			proto_fb::ValueType::Datetime => {
163				let datetime_value =
164					input.value_as_datetime().expect("Guaranteed to be a Datetime");
165				let dt = DateTime::<Utc>::from_fb(datetime_value)?;
166				Ok(Value::Datetime(Datetime(dt)))
167			}
168			proto_fb::ValueType::Uuid => {
169				let uuid_value = input.value_as_uuid().expect("Guaranteed to be a Uuid");
170				let uuid = Uuid::from_fb(uuid_value)?;
171				Ok(Value::Uuid(uuid))
172			}
173			proto_fb::ValueType::Object => {
174				let object_value = input.value_as_object().expect("Guaranteed to be an Object");
175				let object = Object::from_fb(object_value)?;
176				Ok(Value::Object(object))
177			}
178			proto_fb::ValueType::Array => {
179				let array_value = input.value_as_array().expect("Guaranteed to be an Array");
180				let array = Array::from_fb(array_value)?;
181				Ok(Value::Array(array))
182			}
183			proto_fb::ValueType::Set => {
184				let set_value = input.value_as_set().expect("Guaranteed to be a Set");
185				let set = Set::from_fb(set_value)?;
186				Ok(Value::Set(set))
187			}
188			proto_fb::ValueType::Geometry => {
189				let geometry_value =
190					input.value_as_geometry().expect("Guaranteed to be a Geometry");
191				let geometry = Geometry::from_fb(geometry_value)?;
192				Ok(Value::Geometry(geometry))
193			}
194			proto_fb::ValueType::File => {
195				let file_value = input.value_as_file().expect("Guaranteed to be a File");
196				let file = File::from_fb(file_value)?;
197				Ok(Value::File(file))
198			}
199			proto_fb::ValueType::Regex => {
200				let regex_value = input.value_as_regex().expect("Guaranteed to be a Regex");
201				let regex = Regex::from_fb(regex_value)?;
202				Ok(Value::Regex(regex))
203			}
204			proto_fb::ValueType::Range => {
205				let range_value = input.value_as_range().expect("Guaranteed to be a Range");
206				let range = Range::from_fb(range_value)?;
207				Ok(Value::Range(Box::new(range)))
208			}
209			_ => Err(anyhow!(
210				"Unsupported value type for Flatbuffers deserialization: {:?}",
211				input.value_type()
212			)),
213		}
214	}
215}
216
217#[cfg(test)]
218mod tests {
219	use std::collections::BTreeMap;
220	use std::ops::Bound;
221	use std::str::FromStr;
222
223	use chrono::{DateTime, Utc};
224	use rstest::rstest;
225	use rust_decimal::Decimal;
226	use surrealdb_protocol::fb::v1 as proto_fb;
227
228	use super::*;
229	use crate::*;
230
231	#[rstest]
232	#[case::none(Value::None)]
233	#[case::null(Value::Null)]
234	#[case::bool(Value::Bool(true))]
235	#[case::bool(Value::Bool(false))]
236	#[case::int(Value::Number(Number::Int(42)))]
237	#[case::int(Value::Number(Number::Int(i64::MIN)))]
238	#[case::int(Value::Number(Number::Int(i64::MAX)))]
239	#[case::float(Value::Number(Number::Float(1.23)))]
240	#[case::float(Value::Number(Number::Float(f64::MIN)))]
241	#[case::float(Value::Number(Number::Float(f64::MAX)))]
242	#[case::float(Value::Number(Number::Float(f64::NAN)))]
243	#[case::float(Value::Number(Number::Float(f64::INFINITY)))]
244	#[case::float(Value::Number(Number::Float(f64::NEG_INFINITY)))]
245	#[case::decimal(Value::Number(Number::Decimal(Decimal::new(123, 2))))]
246	#[case::duration(Value::Duration(Duration::default()))]
247	#[case::datetime(Value::Datetime(Datetime(DateTime::<Utc>::from_timestamp(1_000_000_000, 0).unwrap())))]
248	#[case::uuid(Value::Uuid(Uuid::default()))]
249	#[case::string(Value::String("Hello, World!".to_string()))]
250	#[case::bytes(Value::Bytes(Bytes(::bytes::Bytes::from(vec![1_u8, 2, 3, 4, 5]))))]
251	#[case::thing(Value::RecordId(RecordId::new("test_table", RecordIdKey::Number(42))))] // Example Thing
252	#[case::thing_range(Value::RecordId(RecordId::new("test_table", RecordIdKey::Range(Box::new(RecordIdKeyRange { start: Bound::Included(RecordIdKey::String("a".to_string())), end: Bound::Unbounded })))))]
253	#[case::object(Value::Object(Object(BTreeMap::from([("key".to_string(), Value::String("value".to_owned()))]))))]
254	#[case::array(Value::Array(Array::from(vec![Value::Number(Number::Int(1)), Value::Number(Number::Float(2.0))])))]
255	#[case::geometry::point(Value::Geometry(Geometry::Point(geo::Point::new(1.0, 2.0))))]
256	#[case::geometry::line(Value::Geometry(Geometry::Line(geo::LineString(vec![geo::Coord { x: 1.0, y: 2.0 }, geo::Coord { x: 3.0, y: 4.0 }]))))]
257	#[case::geometry::polygon(Value::Geometry(Geometry::Polygon(geo::Polygon::new(
258		geo::LineString(vec![geo::Coord { x: 0.0, y: 0.0 }, geo::Coord { x: 1.0, y: 1.0 }, geo::Coord { x: 0.0, y: 1.0 }]),
259		vec![geo::LineString(vec![geo::Coord { x: 0.5, y: 0.5 }, geo::Coord { x: 0.75, y: 0.75 }])]
260	))))]
261	#[case::geometry::multipoint(Value::Geometry(Geometry::MultiPoint(geo::MultiPoint(vec![geo::Point::new(1.0, 2.0), geo::Point::new(3.0, 4.0)]))))]
262	#[case::geometry::multiline(Value::Geometry(Geometry::MultiLine(geo::MultiLineString(vec![geo::LineString(vec![geo::Coord { x: 1.0, y: 2.0 }, geo::Coord { x: 3.0, y: 4.0 }])] ))))]
263	#[case::geometry::multipolygon(Value::Geometry(Geometry::MultiPolygon(geo::MultiPolygon(vec![geo::Polygon::new(
264		geo::LineString(vec![geo::Coord { x: 0.0, y: 0.0 }, geo::Coord { x: 1.0, y: 1.0 }, geo::Coord { x: 0.0, y: 1.0 }]),
265		vec![geo::LineString(vec![geo::Coord { x: 0.5, y: 0.5 }, geo::Coord { x: 0.75, y: 0.75 }])]
266	)]))))]
267	#[case::file(Value::File(File { bucket: "test_bucket".to_string(), key: "test_key".to_string() }))]
268	#[case::range(Value::Range(Box::new(Range { start: Bound::Included(Value::String("Hello, World!".to_string())), end: Bound::Excluded(Value::String("Hello, World!".to_string())) })))]
269	#[case::range(Value::Range(Box::new(Range { start: Bound::Unbounded, end: Bound::Excluded(Value::String("Hello, World!".to_string())) })))]
270	#[case::range(Value::Range(Box::new(Range { start: Bound::Included(Value::String("Hello, World!".to_string())), end: Bound::Unbounded })))]
271	#[case::regex(Value::Regex(Regex::from_str("/^[a-z]+$/").unwrap()))]
272	fn test_flatbuffers_roundtrip_value(#[case] input: Value) {
273		let mut builder = ::flatbuffers::FlatBufferBuilder::new();
274		let input_fb = input.to_fb(&mut builder).expect("Failed to convert to FlatBuffer");
275		builder.finish_minimal(input_fb);
276		let buf = builder.finished_data();
277		let value_fb =
278			::flatbuffers::root::<proto_fb::Value>(buf).expect("Failed to read FlatBuffer");
279		let value = Value::from_fb(value_fb).expect("Failed to convert from FlatBuffer");
280		assert_eq!(input, value, "Roundtrip conversion failed for input: {:?}", input);
281	}
282}