pub(crate) const SPANNER_TIMESTAMP_FORMAT: &[time::format_description::FormatItem<'static>] = time::macros::format_description!(
"[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:9]Z"
);
pub(crate) const SPANNER_DATE_FORMAT: &[time::format_description::FormatItem<'static>] =
time::macros::format_description!("[year]-[month]-[day]");
pub use crate::from_value::FromValue;
pub use crate::to_value::ToValue;
pub use crate::types::{Type, TypeCode};
use prost_types::Value as ProtoValue;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[allow(clippy::exhaustive_enums, reason = "Value kinds are frozen JSON types")]
pub enum Kind {
Null,
Number,
String,
Bool,
Struct,
List,
}
#[repr(transparent)]
#[derive(Clone, Debug, PartialEq, Default)]
pub struct Value(pub(crate) ProtoValue);
impl Value {
pub(crate) fn from_ref(v: &ProtoValue) -> &Self {
unsafe { &*(v as *const ProtoValue as *const Value) }
}
pub fn kind(&self) -> Kind {
match &self.0.kind {
Some(prost_types::value::Kind::NullValue(_)) => Kind::Null,
Some(prost_types::value::Kind::NumberValue(_)) => Kind::Number,
Some(prost_types::value::Kind::StringValue(_)) => Kind::String,
Some(prost_types::value::Kind::BoolValue(_)) => Kind::Bool,
Some(prost_types::value::Kind::StructValue(_)) => Kind::Struct,
Some(prost_types::value::Kind::ListValue(_)) => Kind::List,
None => Kind::Null,
}
}
pub fn try_as_string(&self) -> Option<&str> {
match &self.0.kind {
Some(prost_types::value::Kind::StringValue(s)) => Some(s),
_ => None,
}
}
pub fn as_string(&self) -> &str {
self.try_as_string().expect("value is not a String")
}
pub fn try_as_bool(&self) -> Option<bool> {
match &self.0.kind {
Some(prost_types::value::Kind::BoolValue(b)) => Some(*b),
_ => None,
}
}
pub fn as_bool(&self) -> bool {
self.try_as_bool().expect("value is not a Bool")
}
pub fn try_as_f64(&self) -> Option<f64> {
match &self.0.kind {
Some(prost_types::value::Kind::NumberValue(n)) => Some(*n),
_ => None,
}
}
pub fn as_f64(&self) -> f64 {
self.try_as_f64().expect("value is not a Number")
}
pub fn try_as_struct(&self) -> Option<&Struct> {
match &self.0.kind {
Some(prost_types::value::Kind::StructValue(s)) => Some(Struct::from_ref(s)),
_ => None,
}
}
pub fn as_struct(&self) -> &Struct {
self.try_as_struct().expect("value is not a Struct")
}
pub fn try_as_list(&self) -> Option<&List> {
match &self.0.kind {
Some(prost_types::value::Kind::ListValue(l)) => Some(List::from_ref(l)),
_ => None,
}
}
pub fn as_list(&self) -> &List {
self.try_as_list().expect("value is not a List")
}
}
impl Value {
pub(crate) fn into_serde_value(self) -> serde_json::Value {
match self.0.kind {
Some(prost_types::value::Kind::NullValue(_)) => serde_json::Value::Null,
Some(prost_types::value::Kind::NumberValue(n)) => {
if let Some(num) = serde_json::Number::from_f64(n) {
serde_json::Value::Number(num)
} else {
serde_json::Value::Null
}
}
Some(prost_types::value::Kind::StringValue(s)) => serde_json::Value::String(s),
Some(prost_types::value::Kind::BoolValue(b)) => serde_json::Value::Bool(b),
Some(prost_types::value::Kind::StructValue(s)) => serde_json::Value::Object(
s.fields
.into_iter()
.map(|(k, v)| (k, Value(v).into_serde_value()))
.collect(),
),
Some(prost_types::value::Kind::ListValue(l)) => serde_json::Value::Array(
l.values
.into_iter()
.map(|v| Value(v).into_serde_value())
.collect(),
),
None => serde_json::Value::Null,
}
}
}
#[repr(transparent)]
#[derive(Clone, Debug, PartialEq, Default)]
pub struct Struct(pub(crate) prost_types::Struct);
impl Struct {
pub(crate) fn from_ref(v: &prost_types::Struct) -> &Self {
unsafe { &*(v as *const prost_types::Struct as *const Struct) }
}
pub fn get(&self, key: &str) -> Option<&Value> {
self.0.fields.get(key).map(Value::from_ref)
}
pub fn len(&self) -> usize {
self.0.fields.len()
}
pub fn is_empty(&self) -> bool {
self.0.fields.is_empty()
}
pub fn fields(&self) -> impl Iterator<Item = (&String, &Value)> {
self.0.fields.iter().map(|(k, v)| (k, Value::from_ref(v)))
}
}
#[repr(transparent)]
#[derive(Clone, Debug, PartialEq, Default)]
pub struct List(pub(crate) prost_types::ListValue);
impl List {
pub(crate) fn from_ref(v: &prost_types::ListValue) -> &Self {
unsafe { &*(v as *const prost_types::ListValue as *const List) }
}
pub fn get(&self, index: usize) -> Option<&Value> {
self.0.values.get(index).map(Value::from_ref)
}
pub fn len(&self) -> usize {
self.0.values.len()
}
pub fn is_empty(&self) -> bool {
self.0.values.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &Value> {
self.0.values.iter().map(Value::from_ref)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::hash::Hash;
#[test]
fn test_value_kind_and_accessors() {
let v_null = Value(ProtoValue {
kind: Some(prost_types::value::Kind::NullValue(0)),
});
assert_eq!(v_null.kind(), Kind::Null);
assert!(v_null.try_as_string().is_none());
let v_string = Value(ProtoValue {
kind: Some(prost_types::value::Kind::StringValue("foo".to_string())),
});
assert_eq!(v_string.kind(), Kind::String);
assert_eq!(v_string.try_as_string(), Some("foo"));
assert_eq!(v_string.as_string(), "foo");
assert!(v_string.try_as_bool().is_none());
let v_bool = Value(ProtoValue {
kind: Some(prost_types::value::Kind::BoolValue(true)),
});
assert_eq!(v_bool.kind(), Kind::Bool);
assert_eq!(v_bool.try_as_bool(), Some(true));
assert!(v_bool.as_bool());
let v_number = Value(ProtoValue {
kind: Some(prost_types::value::Kind::NumberValue(42.0)),
});
assert_eq!(v_number.kind(), Kind::Number);
assert_eq!(v_number.try_as_f64(), Some(42.0));
assert_eq!(v_number.as_f64(), 42.0);
let v_list = Value(ProtoValue {
kind: Some(prost_types::value::Kind::ListValue(
prost_types::ListValue {
values: vec![ProtoValue {
kind: Some(prost_types::value::Kind::NumberValue(1.0)),
}],
},
)),
});
assert_eq!(v_list.kind(), Kind::List);
let list = v_list.try_as_list().unwrap();
assert_eq!(list.len(), 1);
assert_eq!(list.get(0).unwrap().try_as_f64(), Some(1.0));
assert_eq!(v_list.as_list().len(), 1);
let v_struct = Value(ProtoValue {
kind: Some(prost_types::value::Kind::StructValue(prost_types::Struct {
fields: std::collections::BTreeMap::from([(
"a".to_string(),
ProtoValue {
kind: Some(prost_types::value::Kind::NumberValue(1.0)),
},
)]),
})),
});
assert_eq!(v_struct.kind(), Kind::Struct);
let map = v_struct.try_as_struct().unwrap();
assert_eq!(map.len(), 1);
assert_eq!(map.get("a").unwrap().try_as_f64(), Some(1.0));
assert_eq!(v_struct.as_struct().len(), 1);
}
#[test]
fn test_auto_traits() {
static_assertions::assert_impl_all!(Value: Send, Sync, Clone, std::fmt::Debug);
static_assertions::assert_impl_all!(Struct: Send, Sync, Clone, std::fmt::Debug);
static_assertions::assert_impl_all!(List: Send, Sync, Clone, std::fmt::Debug);
static_assertions::assert_impl_all!(
Kind: Send,
Sync,
Clone,
Copy,
std::fmt::Debug,
PartialEq,
Eq,
Hash
);
}
}