1use crate::types::JsonValue;
26
27#[derive(Debug, Clone, PartialEq)]
33pub enum Value {
34 Null,
35 Bool(bool),
36 Int(i64),
37 Float(f64),
38 Text(String),
39 Bytes(Vec<u8>),
40 Vector(Vec<f32>),
42 Json(JsonValue),
44 Timestamp(i64),
46 Uuid([u8; 16]),
48}
49
50impl Value {
51 #[allow(non_snake_case)]
54 pub fn Int64(value: i64) -> Self {
55 Self::Int(value)
56 }
57
58 pub fn into_json_param(self) -> serde_json::Value {
62 match self {
63 Value::Null => serde_json::Value::Null,
64 Value::Bool(b) => serde_json::Value::Bool(b),
65 Value::Int(n) => serde_json::Value::Number(n.into()),
66 Value::Float(n) => serde_json::Number::from_f64(n)
67 .map(serde_json::Value::Number)
68 .unwrap_or(serde_json::Value::Null),
69 Value::Text(s) => serde_json::Value::String(s),
70 Value::Bytes(b) => serde_json::json!({ "$bytes": base64_encode(&b) }),
71 Value::Vector(v) => serde_json::Value::Array(
72 v.into_iter()
73 .map(|f| {
74 serde_json::Number::from_f64(f as f64)
75 .map(serde_json::Value::Number)
76 .unwrap_or(serde_json::Value::Null)
77 })
78 .collect(),
79 ),
80 Value::Json(j) => json_to_serde(&j),
81 Value::Timestamp(secs) => serde_json::json!({ "$ts": secs }),
82 Value::Uuid(bytes) => serde_json::json!({ "$uuid": format_uuid(&bytes) }),
83 }
84 }
85
86 #[cfg(feature = "embedded")]
90 pub fn into_schema_value(self) -> reddb_server::storage::schema::Value {
91 use reddb_server::storage::schema::Value as SV;
92 match self {
93 Value::Null => SV::Null,
94 Value::Bool(b) => SV::Boolean(b),
95 Value::Int(n) => SV::Integer(n),
96 Value::Float(n) => SV::Float(n),
97 Value::Text(s) => SV::Text(std::sync::Arc::from(s.as_str())),
98 Value::Bytes(b) => SV::Blob(b),
99 Value::Vector(v) => SV::Vector(v),
100 Value::Json(j) => SV::Json(j.to_json_string().into_bytes()),
101 Value::Timestamp(secs) => SV::Timestamp(secs),
102 Value::Uuid(bytes) => SV::Uuid(bytes),
103 }
104 }
105}
106
107pub trait IntoValue {
110 fn into_value(self) -> Value;
111}
112
113impl IntoValue for Value {
114 fn into_value(self) -> Value {
115 self
116 }
117}
118
119impl IntoValue for bool {
120 fn into_value(self) -> Value {
121 Value::Bool(self)
122 }
123}
124
125macro_rules! int_into_value {
126 ($($t:ty),*) => {
127 $(
128 impl IntoValue for $t {
129 fn into_value(self) -> Value { Value::Int(self as i64) }
130 }
131 )*
132 };
133}
134int_into_value!(i8, i16, i32, i64, u8, u16, u32);
135
136impl IntoValue for u64 {
137 fn into_value(self) -> Value {
138 Value::Int(i64::try_from(self).expect("u64 param > i64::MAX"))
146 }
147}
148
149impl IntoValue for f32 {
150 fn into_value(self) -> Value {
151 Value::Float(self as f64)
152 }
153}
154
155impl IntoValue for f64 {
156 fn into_value(self) -> Value {
157 Value::Float(self)
158 }
159}
160
161impl IntoValue for &str {
162 fn into_value(self) -> Value {
163 Value::Text(self.to_string())
164 }
165}
166
167impl IntoValue for String {
168 fn into_value(self) -> Value {
169 Value::Text(self)
170 }
171}
172
173impl IntoValue for Vec<u8> {
174 fn into_value(self) -> Value {
175 Value::Bytes(self)
176 }
177}
178
179impl IntoValue for &[u8] {
180 fn into_value(self) -> Value {
181 Value::Bytes(self.to_vec())
182 }
183}
184
185impl IntoValue for Vec<f32> {
186 fn into_value(self) -> Value {
187 Value::Vector(self)
188 }
189}
190
191impl IntoValue for &[f32] {
192 fn into_value(self) -> Value {
193 Value::Vector(self.to_vec())
194 }
195}
196
197impl IntoValue for serde_json::Value {
198 fn into_value(self) -> Value {
199 Value::Json(serde_to_json(&self))
200 }
201}
202
203impl IntoValue for JsonValue {
204 fn into_value(self) -> Value {
205 Value::Json(self)
206 }
207}
208
209impl<T: IntoValue> IntoValue for Option<T> {
210 fn into_value(self) -> Value {
211 match self {
212 Some(v) => v.into_value(),
213 None => Value::Null,
214 }
215 }
216}
217
218pub trait IntoParams: sealed::Sealed {
223 fn into_params(self) -> Vec<Value>;
224}
225
226mod sealed {
227 pub trait Sealed {}
228}
229
230impl sealed::Sealed for () {}
231
232impl IntoParams for () {
233 fn into_params(self) -> Vec<Value> {
234 Vec::new()
235 }
236}
237
238impl<V: IntoValue> sealed::Sealed for Vec<V> {}
239
240impl<V: IntoValue> IntoParams for Vec<V> {
241 fn into_params(self) -> Vec<Value> {
242 self.into_iter().map(IntoValue::into_value).collect()
243 }
244}
245
246impl<V: IntoValue + Clone> sealed::Sealed for &[V] {}
247
248impl<V: IntoValue + Clone> IntoParams for &[V] {
249 fn into_params(self) -> Vec<Value> {
250 self.iter().cloned().map(IntoValue::into_value).collect()
251 }
252}
253
254impl<V: IntoValue + Clone> sealed::Sealed for &Vec<V> {}
255
256impl<V: IntoValue + Clone> IntoParams for &Vec<V> {
257 fn into_params(self) -> Vec<Value> {
258 self.as_slice().into_params()
259 }
260}
261
262impl<V: IntoValue + Clone, const N: usize> sealed::Sealed for &[V; N] {}
263
264impl<V: IntoValue + Clone, const N: usize> IntoParams for &[V; N] {
265 fn into_params(self) -> Vec<Value> {
266 self.as_slice().into_params()
267 }
268}
269
270impl<V: IntoValue, const N: usize> sealed::Sealed for [V; N] {}
271
272impl<V: IntoValue, const N: usize> IntoParams for [V; N] {
273 fn into_params(self) -> Vec<Value> {
274 self.into_iter().map(IntoValue::into_value).collect()
275 }
276}
277
278macro_rules! tuple_into_params {
279 ($($name:ident),+) => {
280 impl<$($name: IntoValue),+> sealed::Sealed for ($($name,)+) {}
281
282 impl<$($name: IntoValue),+> IntoParams for ($($name,)+) {
283 #[allow(non_snake_case)]
284 fn into_params(self) -> Vec<Value> {
285 let ($($name,)+) = self;
286 vec![$($name.into_value()),+]
287 }
288 }
289 };
290}
291
292tuple_into_params!(A);
293tuple_into_params!(A, B);
294tuple_into_params!(A, B, C);
295tuple_into_params!(A, B, C, D);
296tuple_into_params!(A, B, C, D, E);
297tuple_into_params!(A, B, C, D, E, F);
298tuple_into_params!(A, B, C, D, E, F, G);
299tuple_into_params!(A, B, C, D, E, F, G, H);
300
301fn json_to_serde(v: &JsonValue) -> serde_json::Value {
302 match v {
303 JsonValue::Null => serde_json::Value::Null,
304 JsonValue::Bool(b) => serde_json::Value::Bool(*b),
305 JsonValue::Number(n) => serde_json::Number::from_f64(*n)
306 .map(serde_json::Value::Number)
307 .unwrap_or(serde_json::Value::Null),
308 JsonValue::String(s) => serde_json::Value::String(s.clone()),
309 JsonValue::Array(items) => {
310 serde_json::Value::Array(items.iter().map(json_to_serde).collect())
311 }
312 JsonValue::Object(entries) => {
313 let mut map = serde_json::Map::with_capacity(entries.len());
314 for (k, v) in entries {
315 map.insert(k.clone(), json_to_serde(v));
316 }
317 serde_json::Value::Object(map)
318 }
319 }
320}
321
322fn serde_to_json(v: &serde_json::Value) -> JsonValue {
323 match v {
324 serde_json::Value::Null => JsonValue::Null,
325 serde_json::Value::Bool(b) => JsonValue::Bool(*b),
326 serde_json::Value::Number(n) => JsonValue::Number(n.as_f64().unwrap_or(0.0)),
327 serde_json::Value::String(s) => JsonValue::String(s.clone()),
328 serde_json::Value::Array(items) => {
329 JsonValue::Array(items.iter().map(serde_to_json).collect())
330 }
331 serde_json::Value::Object(map) => JsonValue::Object(
332 map.iter()
333 .map(|(k, v)| (k.clone(), serde_to_json(v)))
334 .collect(),
335 ),
336 }
337}
338
339fn base64_encode(bytes: &[u8]) -> String {
340 const TABLE: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
341 let mut out = String::with_capacity((bytes.len() + 2) / 3 * 4);
342 let mut chunks = bytes.chunks_exact(3);
343 for c in chunks.by_ref() {
344 let n = ((c[0] as u32) << 16) | ((c[1] as u32) << 8) | (c[2] as u32);
345 out.push(TABLE[((n >> 18) & 0x3F) as usize] as char);
346 out.push(TABLE[((n >> 12) & 0x3F) as usize] as char);
347 out.push(TABLE[((n >> 6) & 0x3F) as usize] as char);
348 out.push(TABLE[(n & 0x3F) as usize] as char);
349 }
350 let rem = chunks.remainder();
351 match rem.len() {
352 0 => {}
353 1 => {
354 let n = (rem[0] as u32) << 16;
355 out.push(TABLE[((n >> 18) & 0x3F) as usize] as char);
356 out.push(TABLE[((n >> 12) & 0x3F) as usize] as char);
357 out.push('=');
358 out.push('=');
359 }
360 2 => {
361 let n = ((rem[0] as u32) << 16) | ((rem[1] as u32) << 8);
362 out.push(TABLE[((n >> 18) & 0x3F) as usize] as char);
363 out.push(TABLE[((n >> 12) & 0x3F) as usize] as char);
364 out.push(TABLE[((n >> 6) & 0x3F) as usize] as char);
365 out.push('=');
366 }
367 _ => unreachable!(),
368 }
369 out
370}
371
372fn format_uuid(bytes: &[u8; 16]) -> String {
373 let mut out = String::with_capacity(36);
374 for (i, b) in bytes.iter().enumerate() {
375 if matches!(i, 4 | 6 | 8 | 10) {
376 out.push('-');
377 }
378 out.push_str(&format!("{b:02x}"));
379 }
380 out
381}
382
383#[cfg(test)]
384mod tests {
385 use super::*;
386
387 #[test]
388 fn into_value_primitives() {
389 assert_eq!(true.into_value(), Value::Bool(true));
390 assert_eq!(42i32.into_value(), Value::Int(42));
391 assert_eq!((-1i64).into_value(), Value::Int(-1));
392 assert_eq!(2.5f64.into_value(), Value::Float(2.5));
393 assert_eq!("hi".into_value(), Value::Text("hi".to_string()));
394 assert_eq!(String::from("x").into_value(), Value::Text("x".to_string()));
395 }
396
397 #[test]
398 fn into_value_bytes_and_vector() {
399 assert_eq!(vec![1u8, 2, 3].into_value(), Value::Bytes(vec![1, 2, 3]));
400 let slice: &[u8] = &[9, 8];
401 assert_eq!(slice.into_value(), Value::Bytes(vec![9, 8]));
402 assert_eq!(
403 vec![0.1f32, 0.2].into_value(),
404 Value::Vector(vec![0.1, 0.2])
405 );
406 }
407
408 #[test]
409 fn into_value_option_maps_to_null() {
410 let none: Option<i64> = None;
411 assert_eq!(none.into_value(), Value::Null);
412 let some: Option<i64> = Some(7);
413 assert_eq!(some.into_value(), Value::Int(7));
414 }
415
416 #[test]
417 fn json_param_envelope_for_bytes() {
418 let v = Value::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF]);
419 let j = v.into_json_param();
420 assert_eq!(j["$bytes"].as_str().unwrap(), "3q2+7w==");
421 }
422
423 #[test]
424 fn json_param_envelope_for_timestamp() {
425 let j = Value::Timestamp(1_700_000_000).into_json_param();
426 assert_eq!(j["$ts"].as_i64().unwrap(), 1_700_000_000);
427 }
428
429 #[test]
430 fn json_param_envelope_for_uuid() {
431 let bytes = [
432 0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44,
433 0x00, 0x00,
434 ];
435 let j = Value::Uuid(bytes).into_json_param();
436 assert_eq!(
437 j["$uuid"].as_str().unwrap(),
438 "550e8400-e29b-41d4-a716-446655440000"
439 );
440 }
441
442 #[test]
443 fn json_param_vector_is_plain_array() {
444 let j = Value::Vector(vec![0.0, 1.0, -1.5]).into_json_param();
445 let arr = j.as_array().unwrap();
446 assert_eq!(arr.len(), 3);
447 assert!((arr[0].as_f64().unwrap() - 0.0).abs() < 1e-6);
448 assert!((arr[2].as_f64().unwrap() - -1.5).abs() < 1e-6);
449 }
450
451 #[test]
452 fn base64_encode_known_vectors() {
453 assert_eq!(base64_encode(b""), "");
455 assert_eq!(base64_encode(b"f"), "Zg==");
456 assert_eq!(base64_encode(b"fo"), "Zm8=");
457 assert_eq!(base64_encode(b"foo"), "Zm9v");
458 assert_eq!(base64_encode(b"foob"), "Zm9vYg==");
459 assert_eq!(base64_encode(b"fooba"), "Zm9vYmE=");
460 assert_eq!(base64_encode(b"foobar"), "Zm9vYmFy");
461 }
462
463 #[cfg(feature = "embedded")]
464 #[test]
465 fn into_schema_value_covers_all_variants() {
466 use reddb_server::storage::schema::Value as SV;
467 assert!(matches!(Value::Null.into_schema_value(), SV::Null));
468 assert!(matches!(
469 Value::Bool(true).into_schema_value(),
470 SV::Boolean(true)
471 ));
472 assert!(matches!(Value::Int(7).into_schema_value(), SV::Integer(7)));
473 assert!(
474 matches!(Value::Float(1.5).into_schema_value(), SV::Float(f) if (f - 1.5).abs() < 1e-9)
475 );
476 let SV::Text(s) = Value::Text("x".into()).into_schema_value() else {
477 panic!()
478 };
479 assert_eq!(s.as_ref(), "x");
480 assert!(
481 matches!(Value::Bytes(vec![1, 2]).into_schema_value(), SV::Blob(b) if b == vec![1, 2])
482 );
483 assert!(matches!(
484 Value::Vector(vec![0.1, 0.2]).into_schema_value(),
485 SV::Vector(v) if v == vec![0.1f32, 0.2]
486 ));
487 assert!(matches!(
488 Value::Timestamp(99).into_schema_value(),
489 SV::Timestamp(99)
490 ));
491 let SV::Uuid(b) = Value::Uuid([0u8; 16]).into_schema_value() else {
492 panic!()
493 };
494 assert_eq!(b, [0u8; 16]);
495 }
496}