surrealdb_types/flatbuffers/
value.rs1use 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))))] #[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}