Skip to main content

google_cloud_spanner/
value.rs

1// Copyright 2026 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15pub(crate) const SPANNER_TIMESTAMP_FORMAT: &[time::format_description::FormatItem<'static>] = time::macros::format_description!(
16    "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:9]Z"
17);
18pub(crate) const SPANNER_DATE_FORMAT: &[time::format_description::FormatItem<'static>] =
19    time::macros::format_description!("[year]-[month]-[day]");
20
21pub use crate::from_value::FromValue;
22pub use crate::to_value::ToValue;
23pub use crate::types::{Type, TypeCode};
24
25use prost_types::Value as ProtoValue;
26
27/// Kind indicates the type of the value.
28///
29/// This enum maps 1-to-1 with the frozen specification of JSON/Protobuf types
30/// in `google.protobuf.Value`, and is guaranteed not to grow.
31#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
32#[allow(clippy::exhaustive_enums, reason = "Value kinds are frozen JSON types")]
33pub enum Kind {
34    /// Represents a null value of any data type.
35    Null,
36    /// Represents a floating point value.
37    Number,
38    /// Represents a UTF-8 string value or encoded representations of other data types,
39    /// such as base64-encoded bytes, decimals, dates, timestamps, and integers.
40    String,
41    /// Represents a boolean value.
42    Bool,
43    /// Represents a structured object containing a collection of key-value pairs.
44    Struct,
45    /// Represents an ordered list of values.
46    List,
47}
48
49/// Value is a transparent wrapper around a protobuf value.
50/// It adds helper methods for accessing the underlying value.
51#[repr(transparent)]
52#[derive(Clone, Debug, PartialEq, Default)]
53pub struct Value(pub(crate) ProtoValue);
54
55impl Value {
56    /// Safely reinterprets a reference to the inner protobuf value as a reference to Value.
57    /// Logical safety is guaranteed by #[repr(transparent)].
58    pub(crate) fn from_ref(v: &ProtoValue) -> &Self {
59        // Safety: Value is #[repr(transparent)] wrapper around ProtoValue.
60        // This structure guarantees that Value has the exact same memory layout as ProtoValue.
61        // This is the standard Rust pattern for safe zero-cost newtype references.
62        unsafe { &*(v as *const ProtoValue as *const Value) }
63    }
64
65    /// Returns the kind of the value.
66    pub fn kind(&self) -> Kind {
67        match &self.0.kind {
68            Some(prost_types::value::Kind::NullValue(_)) => Kind::Null,
69            Some(prost_types::value::Kind::NumberValue(_)) => Kind::Number,
70            Some(prost_types::value::Kind::StringValue(_)) => Kind::String,
71            Some(prost_types::value::Kind::BoolValue(_)) => Kind::Bool,
72            Some(prost_types::value::Kind::StructValue(_)) => Kind::Struct,
73            Some(prost_types::value::Kind::ListValue(_)) => Kind::List,
74            None => Kind::Null,
75        }
76    }
77
78    /// Returns the underlying string value if the kind is String.
79    pub fn try_as_string(&self) -> Option<&str> {
80        match &self.0.kind {
81            Some(prost_types::value::Kind::StringValue(s)) => Some(s),
82            _ => None,
83        }
84    }
85
86    /// Returns the underlying string value. Panics if the kind is not String.
87    pub fn as_string(&self) -> &str {
88        self.try_as_string().expect("value is not a String")
89    }
90
91    /// Returns the underlying bool value if the kind is Bool.
92    pub fn try_as_bool(&self) -> Option<bool> {
93        match &self.0.kind {
94            Some(prost_types::value::Kind::BoolValue(b)) => Some(*b),
95            _ => None,
96        }
97    }
98
99    /// Returns the underlying bool value. Panics if the kind is not Bool.
100    pub fn as_bool(&self) -> bool {
101        self.try_as_bool().expect("value is not a Bool")
102    }
103
104    /// Returns the underlying number value if the kind is Number.
105    pub fn try_as_f64(&self) -> Option<f64> {
106        match &self.0.kind {
107            Some(prost_types::value::Kind::NumberValue(n)) => Some(*n),
108            _ => None,
109        }
110    }
111
112    /// Returns the underlying number value. Panics if the kind is not Number.
113    pub fn as_f64(&self) -> f64 {
114        self.try_as_f64().expect("value is not a Number")
115    }
116
117    /// Returns the underlying struct value as a map of Values if the kind is Struct.
118    pub fn try_as_struct(&self) -> Option<&Struct> {
119        match &self.0.kind {
120            Some(prost_types::value::Kind::StructValue(s)) => Some(Struct::from_ref(s)),
121            _ => None,
122        }
123    }
124
125    /// Returns the underlying struct value. Panics if the kind is not Struct.
126    pub fn as_struct(&self) -> &Struct {
127        self.try_as_struct().expect("value is not a Struct")
128    }
129
130    /// Returns the underlying list value as a vector of Values if the kind is List.
131    pub fn try_as_list(&self) -> Option<&List> {
132        match &self.0.kind {
133            Some(prost_types::value::Kind::ListValue(l)) => Some(List::from_ref(l)),
134            _ => None,
135        }
136    }
137
138    /// Returns the underlying list value. Panics if the kind is not List.
139    pub fn as_list(&self) -> &List {
140        self.try_as_list().expect("value is not a List")
141    }
142}
143
144impl Value {
145    /// Converts a `prost_types::Value` to a `serde_json::Value`.
146    /// This is needed because the generated gapic client uses `serde_json::Value` instead of `prost_types::Value`.
147    /// It is converted back from `serde_json::Value` to `prost_types::Value` before hitting the wire.
148    pub(crate) fn into_serde_value(self) -> serde_json::Value {
149        match self.0.kind {
150            Some(prost_types::value::Kind::NullValue(_)) => serde_json::Value::Null,
151            Some(prost_types::value::Kind::NumberValue(n)) => {
152                if let Some(num) = serde_json::Number::from_f64(n) {
153                    serde_json::Value::Number(num)
154                } else {
155                    serde_json::Value::Null
156                }
157            }
158            Some(prost_types::value::Kind::StringValue(s)) => serde_json::Value::String(s),
159            Some(prost_types::value::Kind::BoolValue(b)) => serde_json::Value::Bool(b),
160            Some(prost_types::value::Kind::StructValue(s)) => serde_json::Value::Object(
161                s.fields
162                    .into_iter()
163                    .map(|(k, v)| (k, Value(v).into_serde_value()))
164                    .collect(),
165            ),
166            Some(prost_types::value::Kind::ListValue(l)) => serde_json::Value::Array(
167                l.values
168                    .into_iter()
169                    .map(|v| Value(v).into_serde_value())
170                    .collect(),
171            ),
172            None => serde_json::Value::Null,
173        }
174    }
175}
176
177/// A lightweight wrapper around a protobuf Struct.
178#[repr(transparent)]
179#[derive(Clone, Debug, PartialEq, Default)]
180pub struct Struct(pub(crate) prost_types::Struct);
181
182impl Struct {
183    /// Safely reinterprets a reference to the inner protobuf struct as a reference to Struct.
184    pub(crate) fn from_ref(v: &prost_types::Struct) -> &Self {
185        // Safety: Struct is #[repr(transparent)] wrapper around prost_types::Struct.
186        unsafe { &*(v as *const prost_types::Struct as *const Struct) }
187    }
188
189    /// Returns the value for the given key, or `None` if the key is not present.
190    pub fn get(&self, key: &str) -> Option<&Value> {
191        self.0.fields.get(key).map(Value::from_ref)
192    }
193
194    /// Returns the number of fields in the struct.
195    pub fn len(&self) -> usize {
196        self.0.fields.len()
197    }
198
199    /// Returns `true` if the struct has no fields.
200    pub fn is_empty(&self) -> bool {
201        self.0.fields.is_empty()
202    }
203
204    /// Returns an iterator over the fields of the struct.
205    pub fn fields(&self) -> impl Iterator<Item = (&String, &Value)> {
206        self.0.fields.iter().map(|(k, v)| (k, Value::from_ref(v)))
207    }
208}
209
210/// A lightweight wrapper around a protobuf ListValue.
211#[repr(transparent)]
212#[derive(Clone, Debug, PartialEq, Default)]
213pub struct List(pub(crate) prost_types::ListValue);
214
215impl List {
216    /// Safely reinterprets a reference to the inner protobuf list as a reference to List.
217    pub(crate) fn from_ref(v: &prost_types::ListValue) -> &Self {
218        // Safety: List is #[repr(transparent)] wrapper around prost_types::ListValue.
219        unsafe { &*(v as *const prost_types::ListValue as *const List) }
220    }
221
222    /// Returns the value at the given index, or `None` if the index is out of bounds.
223    pub fn get(&self, index: usize) -> Option<&Value> {
224        self.0.values.get(index).map(Value::from_ref)
225    }
226
227    /// Returns the number of values in the list.
228    pub fn len(&self) -> usize {
229        self.0.values.len()
230    }
231
232    /// Returns `true` if the list is empty.
233    pub fn is_empty(&self) -> bool {
234        self.0.values.is_empty()
235    }
236
237    /// Returns an iterator over the values in the list.
238    pub fn iter(&self) -> impl Iterator<Item = &Value> {
239        self.0.values.iter().map(Value::from_ref)
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246    use std::hash::Hash;
247
248    #[test]
249    fn test_value_kind_and_accessors() {
250        let v_null = Value(ProtoValue {
251            kind: Some(prost_types::value::Kind::NullValue(0)),
252        });
253        assert_eq!(v_null.kind(), Kind::Null);
254        assert!(v_null.try_as_string().is_none());
255
256        let v_string = Value(ProtoValue {
257            kind: Some(prost_types::value::Kind::StringValue("foo".to_string())),
258        });
259        assert_eq!(v_string.kind(), Kind::String);
260        assert_eq!(v_string.try_as_string(), Some("foo"));
261        assert_eq!(v_string.as_string(), "foo");
262        assert!(v_string.try_as_bool().is_none());
263
264        let v_bool = Value(ProtoValue {
265            kind: Some(prost_types::value::Kind::BoolValue(true)),
266        });
267        assert_eq!(v_bool.kind(), Kind::Bool);
268        assert_eq!(v_bool.try_as_bool(), Some(true));
269        assert!(v_bool.as_bool());
270
271        let v_number = Value(ProtoValue {
272            kind: Some(prost_types::value::Kind::NumberValue(42.0)),
273        });
274        assert_eq!(v_number.kind(), Kind::Number);
275        assert_eq!(v_number.try_as_f64(), Some(42.0));
276        assert_eq!(v_number.as_f64(), 42.0);
277
278        let v_list = Value(ProtoValue {
279            kind: Some(prost_types::value::Kind::ListValue(
280                prost_types::ListValue {
281                    values: vec![ProtoValue {
282                        kind: Some(prost_types::value::Kind::NumberValue(1.0)),
283                    }],
284                },
285            )),
286        });
287        assert_eq!(v_list.kind(), Kind::List);
288        let list = v_list.try_as_list().unwrap();
289        assert_eq!(list.len(), 1);
290        assert_eq!(list.get(0).unwrap().try_as_f64(), Some(1.0));
291        assert_eq!(v_list.as_list().len(), 1);
292
293        let v_struct = Value(ProtoValue {
294            kind: Some(prost_types::value::Kind::StructValue(prost_types::Struct {
295                fields: std::collections::BTreeMap::from([(
296                    "a".to_string(),
297                    ProtoValue {
298                        kind: Some(prost_types::value::Kind::NumberValue(1.0)),
299                    },
300                )]),
301            })),
302        });
303        assert_eq!(v_struct.kind(), Kind::Struct);
304        let map = v_struct.try_as_struct().unwrap();
305        assert_eq!(map.len(), 1);
306        assert_eq!(map.get("a").unwrap().try_as_f64(), Some(1.0));
307        assert_eq!(v_struct.as_struct().len(), 1);
308    }
309
310    #[test]
311    fn test_auto_traits() {
312        static_assertions::assert_impl_all!(Value: Send, Sync, Clone, std::fmt::Debug);
313        static_assertions::assert_impl_all!(Struct: Send, Sync, Clone, std::fmt::Debug);
314        static_assertions::assert_impl_all!(List: Send, Sync, Clone, std::fmt::Debug);
315        static_assertions::assert_impl_all!(
316            Kind: Send,
317            Sync,
318            Clone,
319            Copy,
320            std::fmt::Debug,
321            PartialEq,
322            Eq,
323            Hash
324        );
325    }
326}