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    /// Creates a null [Value].
57    pub fn null() -> Self {
58        Value(ProtoValue {
59            kind: Some(prost_types::value::Kind::NullValue(0)),
60        })
61    }
62
63    /// Safely reinterprets a reference to the inner protobuf value as a reference to Value.
64    /// Logical safety is guaranteed by #[repr(transparent)].
65    pub(crate) fn from_ref(v: &ProtoValue) -> &Self {
66        // Safety: Value is #[repr(transparent)] wrapper around ProtoValue.
67        // This structure guarantees that Value has the exact same memory layout as ProtoValue.
68        // This is the standard Rust pattern for safe zero-cost newtype references.
69        unsafe { &*(v as *const ProtoValue as *const Value) }
70    }
71
72    /// Returns the kind of the value.
73    pub fn kind(&self) -> Kind {
74        match &self.0.kind {
75            Some(prost_types::value::Kind::NullValue(_)) => Kind::Null,
76            Some(prost_types::value::Kind::NumberValue(_)) => Kind::Number,
77            Some(prost_types::value::Kind::StringValue(_)) => Kind::String,
78            Some(prost_types::value::Kind::BoolValue(_)) => Kind::Bool,
79            Some(prost_types::value::Kind::StructValue(_)) => Kind::Struct,
80            Some(prost_types::value::Kind::ListValue(_)) => Kind::List,
81            None => Kind::Null,
82        }
83    }
84
85    /// Returns the underlying string value if the kind is String.
86    pub fn try_as_string(&self) -> Option<&str> {
87        match &self.0.kind {
88            Some(prost_types::value::Kind::StringValue(s)) => Some(s),
89            _ => None,
90        }
91    }
92
93    /// Returns the underlying string value. Panics if the kind is not String.
94    pub fn as_string(&self) -> &str {
95        self.try_as_string().expect("value is not a String")
96    }
97
98    /// Returns the underlying bool value if the kind is Bool.
99    pub fn try_as_bool(&self) -> Option<bool> {
100        match &self.0.kind {
101            Some(prost_types::value::Kind::BoolValue(b)) => Some(*b),
102            _ => None,
103        }
104    }
105
106    /// Returns the underlying bool value. Panics if the kind is not Bool.
107    pub fn as_bool(&self) -> bool {
108        self.try_as_bool().expect("value is not a Bool")
109    }
110
111    /// Returns the underlying number value if the kind is Number.
112    pub fn try_as_f64(&self) -> Option<f64> {
113        match &self.0.kind {
114            Some(prost_types::value::Kind::NumberValue(n)) => Some(*n),
115            _ => None,
116        }
117    }
118
119    /// Returns the underlying number value. Panics if the kind is not Number.
120    pub fn as_f64(&self) -> f64 {
121        self.try_as_f64().expect("value is not a Number")
122    }
123
124    /// Returns the underlying struct value as a map of Values if the kind is Struct.
125    pub fn try_as_struct(&self) -> Option<&Struct> {
126        match &self.0.kind {
127            Some(prost_types::value::Kind::StructValue(s)) => Some(Struct::from_ref(s)),
128            _ => None,
129        }
130    }
131
132    /// Returns the underlying struct value. Panics if the kind is not Struct.
133    pub fn as_struct(&self) -> &Struct {
134        self.try_as_struct().expect("value is not a Struct")
135    }
136
137    /// Returns the underlying list value as a vector of Values if the kind is List.
138    pub fn try_as_list(&self) -> Option<&List> {
139        match &self.0.kind {
140            Some(prost_types::value::Kind::ListValue(l)) => Some(List::from_ref(l)),
141            _ => None,
142        }
143    }
144
145    /// Returns the underlying list value. Panics if the kind is not List.
146    pub fn as_list(&self) -> &List {
147        self.try_as_list().expect("value is not a List")
148    }
149}
150
151impl Value {
152    /// Converts a `prost_types::Value` to a `serde_json::Value`.
153    /// This is needed because the generated gapic client uses `serde_json::Value` instead of `prost_types::Value`.
154    /// It is converted back from `serde_json::Value` to `prost_types::Value` before hitting the wire.
155    pub(crate) fn into_serde_value(self) -> serde_json::Value {
156        match self.0.kind {
157            Some(prost_types::value::Kind::NullValue(_)) => serde_json::Value::Null,
158            Some(prost_types::value::Kind::NumberValue(n)) => {
159                if let Some(num) = serde_json::Number::from_f64(n) {
160                    serde_json::Value::Number(num)
161                } else {
162                    serde_json::Value::Null
163                }
164            }
165            Some(prost_types::value::Kind::StringValue(s)) => serde_json::Value::String(s),
166            Some(prost_types::value::Kind::BoolValue(b)) => serde_json::Value::Bool(b),
167            Some(prost_types::value::Kind::StructValue(s)) => serde_json::Value::Object(
168                s.fields
169                    .into_iter()
170                    .map(|(k, v)| (k, Value(v).into_serde_value()))
171                    .collect(),
172            ),
173            Some(prost_types::value::Kind::ListValue(l)) => serde_json::Value::Array(
174                l.values
175                    .into_iter()
176                    .map(|v| Value(v).into_serde_value())
177                    .collect(),
178            ),
179            None => serde_json::Value::Null,
180        }
181    }
182}
183
184/// A lightweight wrapper around a protobuf Struct.
185#[repr(transparent)]
186#[derive(Clone, Debug, PartialEq, Default)]
187pub struct Struct(pub(crate) prost_types::Struct);
188
189impl Struct {
190    /// Safely reinterprets a reference to the inner protobuf struct as a reference to Struct.
191    pub(crate) fn from_ref(v: &prost_types::Struct) -> &Self {
192        // Safety: Struct is #[repr(transparent)] wrapper around prost_types::Struct.
193        unsafe { &*(v as *const prost_types::Struct as *const Struct) }
194    }
195
196    /// Returns the value for the given key, or `None` if the key is not present.
197    pub fn get(&self, key: &str) -> Option<&Value> {
198        self.0.fields.get(key).map(Value::from_ref)
199    }
200
201    /// Returns the number of fields in the struct.
202    pub fn len(&self) -> usize {
203        self.0.fields.len()
204    }
205
206    /// Returns `true` if the struct has no fields.
207    pub fn is_empty(&self) -> bool {
208        self.0.fields.is_empty()
209    }
210
211    /// Returns an iterator over the fields of the struct.
212    pub fn fields(&self) -> impl Iterator<Item = (&String, &Value)> {
213        self.0.fields.iter().map(|(k, v)| (k, Value::from_ref(v)))
214    }
215}
216
217/// A lightweight wrapper around a protobuf ListValue.
218#[repr(transparent)]
219#[derive(Clone, Debug, PartialEq, Default)]
220pub struct List(pub(crate) prost_types::ListValue);
221
222impl List {
223    /// Safely reinterprets a reference to the inner protobuf list as a reference to List.
224    pub(crate) fn from_ref(v: &prost_types::ListValue) -> &Self {
225        // Safety: List is #[repr(transparent)] wrapper around prost_types::ListValue.
226        unsafe { &*(v as *const prost_types::ListValue as *const List) }
227    }
228
229    /// Returns the value at the given index, or `None` if the index is out of bounds.
230    pub fn get(&self, index: usize) -> Option<&Value> {
231        self.0.values.get(index).map(Value::from_ref)
232    }
233
234    /// Returns the number of values in the list.
235    pub fn len(&self) -> usize {
236        self.0.values.len()
237    }
238
239    /// Returns `true` if the list is empty.
240    pub fn is_empty(&self) -> bool {
241        self.0.values.is_empty()
242    }
243
244    /// Returns an iterator over the values in the list.
245    pub fn iter(&self) -> impl Iterator<Item = &Value> {
246        self.0.values.iter().map(Value::from_ref)
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253    use std::hash::Hash;
254
255    #[test]
256    fn test_value_kind_and_accessors() {
257        let v_null = Value(ProtoValue {
258            kind: Some(prost_types::value::Kind::NullValue(0)),
259        });
260        assert_eq!(v_null.kind(), Kind::Null);
261        assert!(v_null.try_as_string().is_none());
262
263        let v_string = Value(ProtoValue {
264            kind: Some(prost_types::value::Kind::StringValue("foo".to_string())),
265        });
266        assert_eq!(v_string.kind(), Kind::String);
267        assert_eq!(v_string.try_as_string(), Some("foo"));
268        assert_eq!(v_string.as_string(), "foo");
269        assert!(v_string.try_as_bool().is_none());
270
271        let v_bool = Value(ProtoValue {
272            kind: Some(prost_types::value::Kind::BoolValue(true)),
273        });
274        assert_eq!(v_bool.kind(), Kind::Bool);
275        assert_eq!(v_bool.try_as_bool(), Some(true));
276        assert!(v_bool.as_bool());
277
278        let v_number = Value(ProtoValue {
279            kind: Some(prost_types::value::Kind::NumberValue(42.0)),
280        });
281        assert_eq!(v_number.kind(), Kind::Number);
282        assert_eq!(v_number.try_as_f64(), Some(42.0));
283        assert_eq!(v_number.as_f64(), 42.0);
284
285        let v_list = Value(ProtoValue {
286            kind: Some(prost_types::value::Kind::ListValue(
287                prost_types::ListValue {
288                    values: vec![ProtoValue {
289                        kind: Some(prost_types::value::Kind::NumberValue(1.0)),
290                    }],
291                },
292            )),
293        });
294        assert_eq!(v_list.kind(), Kind::List);
295        let list = v_list.try_as_list().unwrap();
296        assert_eq!(list.len(), 1);
297        assert_eq!(list.get(0).unwrap().try_as_f64(), Some(1.0));
298        assert_eq!(v_list.as_list().len(), 1);
299
300        let v_struct = Value(ProtoValue {
301            kind: Some(prost_types::value::Kind::StructValue(prost_types::Struct {
302                fields: std::collections::BTreeMap::from([(
303                    "a".to_string(),
304                    ProtoValue {
305                        kind: Some(prost_types::value::Kind::NumberValue(1.0)),
306                    },
307                )]),
308            })),
309        });
310        assert_eq!(v_struct.kind(), Kind::Struct);
311        let map = v_struct.try_as_struct().unwrap();
312        assert_eq!(map.len(), 1);
313        assert_eq!(map.get("a").unwrap().try_as_f64(), Some(1.0));
314        assert_eq!(v_struct.as_struct().len(), 1);
315    }
316
317    #[test]
318    fn test_auto_traits() {
319        static_assertions::assert_impl_all!(Value: Send, Sync, Clone, std::fmt::Debug);
320        static_assertions::assert_impl_all!(Struct: Send, Sync, Clone, std::fmt::Debug);
321        static_assertions::assert_impl_all!(List: Send, Sync, Clone, std::fmt::Debug);
322        static_assertions::assert_impl_all!(
323            Kind: Send,
324            Sync,
325            Clone,
326            Copy,
327            std::fmt::Debug,
328            PartialEq,
329            Eq,
330            Hash
331        );
332    }
333}