1use gcloud_googleapis::spanner::v1::struct_type::Field;
22use gcloud_googleapis::spanner::v1::{Type, TypeCode};
23use gcloud_spanner::row::{Error as RowError, Row, TryFromValue};
24use prost_types::value::Kind;
25use serde_json::Value;
26
27pub struct SpannerJson(pub Value);
29
30impl TryFromValue for SpannerJson {
31 fn try_from(value: &prost_types::Value, field: &Field) -> Result<Self, RowError> {
32 decode_value(value, field.r#type.as_ref(), &field.name).map(SpannerJson)
33 }
34}
35
36pub fn row_to_json(row: &Row, fields: &[Field]) -> Result<Value, RowError> {
39 let mut obj = serde_json::Map::with_capacity(fields.len());
40 for (idx, field) in fields.iter().enumerate() {
41 let SpannerJson(v) = row.column::<SpannerJson>(idx)?;
42 obj.insert(field.name.clone(), v);
43 }
44 Ok(Value::Object(obj))
45}
46
47fn type_code(ty: Option<&Type>) -> TypeCode {
48 ty.and_then(|t| TypeCode::try_from(t.code).ok())
49 .unwrap_or(TypeCode::Unspecified)
50}
51
52pub fn decode_value(
54 value: &prost_types::Value,
55 ty: Option<&Type>,
56 field_name: &str,
57) -> Result<Value, RowError> {
58 let kind = match &value.kind {
59 None | Some(Kind::NullValue(_)) => return Ok(Value::Null),
60 Some(kind) => kind,
61 };
62 let code = type_code(ty);
63 match (code, kind) {
64 (TypeCode::Int64 | TypeCode::Enum, Kind::StringValue(s)) => {
67 let n: i64 = s.parse().map_err(|_| {
68 RowError::CustomParseError(format!("{field_name}: non-integer INT64 `{s}`"))
69 })?;
70 Ok(Value::Number(n.into()))
71 }
72 (TypeCode::Float64 | TypeCode::Float32, Kind::NumberValue(f)) => {
73 Ok(serde_json::Number::from_f64(*f).map_or(Value::Null, Value::Number))
74 }
75 (TypeCode::Float64 | TypeCode::Float32, Kind::StringValue(s)) => {
78 Ok(Value::String(s.clone()))
79 }
80 (TypeCode::Bool, Kind::BoolValue(b)) => Ok(Value::Bool(*b)),
81 (TypeCode::Json, Kind::StringValue(s)) => serde_json::from_str(s).map_err(|e| {
83 RowError::CustomParseError(format!("{field_name}: invalid JSON column value: {e}"))
84 }),
85 (_, Kind::StringValue(s)) => Ok(Value::String(s.clone())),
88 (TypeCode::Array, Kind::ListValue(list)) => {
89 let elem_ty = ty.and_then(|t| t.array_element_type.as_deref());
90 let items: Result<Vec<Value>, RowError> = list
91 .values
92 .iter()
93 .map(|v| decode_value(v, elem_ty, field_name))
94 .collect();
95 Ok(Value::Array(items?))
96 }
97 (TypeCode::Struct, Kind::ListValue(list)) => {
100 let fields = ty
101 .and_then(|t| t.struct_type.as_ref())
102 .map(|st| st.fields.as_slice())
103 .unwrap_or(&[]);
104 let mut obj = serde_json::Map::with_capacity(list.values.len());
105 for (idx, v) in list.values.iter().enumerate() {
106 let (name, elem_ty) = fields
107 .get(idx)
108 .map(|f| (f.name.clone(), f.r#type.as_ref()))
109 .unwrap_or_else(|| (format!("_{idx}"), None));
110 obj.insert(name, decode_value(v, elem_ty, field_name)?);
111 }
112 Ok(Value::Object(obj))
113 }
114 (_, kind) => Ok(kind_to_json(kind)),
117 }
118}
119
120fn kind_to_json(kind: &Kind) -> Value {
124 match kind {
125 Kind::NullValue(_) => Value::Null,
126 Kind::BoolValue(b) => Value::Bool(*b),
127 Kind::NumberValue(f) => serde_json::Number::from_f64(*f).map_or(Value::Null, Value::Number),
128 Kind::StringValue(s) => Value::String(s.clone()),
129 Kind::ListValue(list) => Value::Array(
130 list.values
131 .iter()
132 .map(|v| match &v.kind {
133 None => Value::Null,
134 Some(k) => kind_to_json(k),
135 })
136 .collect(),
137 ),
138 Kind::StructValue(st) => {
139 let mut obj = serde_json::Map::with_capacity(st.fields.len());
140 for (k, v) in &st.fields {
141 let decoded = match &v.kind {
142 None => Value::Null,
143 Some(kind) => kind_to_json(kind),
144 };
145 obj.insert(k.clone(), decoded);
146 }
147 Value::Object(obj)
148 }
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use gcloud_googleapis::spanner::v1::StructType;
156 use serde_json::json;
157
158 fn pv(kind: Kind) -> prost_types::Value {
159 prost_types::Value { kind: Some(kind) }
160 }
161
162 fn ty(code: TypeCode) -> Type {
163 Type {
164 code: code.into(),
165 array_element_type: None,
166 struct_type: None,
167 ..Default::default()
168 }
169 }
170
171 #[test]
172 fn int64_decodes_losslessly_from_string() {
173 let big = 9_007_199_254_740_993_i64; let v = decode_value(
175 &pv(Kind::StringValue(big.to_string())),
176 Some(&ty(TypeCode::Int64)),
177 "n",
178 )
179 .unwrap();
180 assert_eq!(v, json!(big));
181 }
182
183 #[test]
184 fn malformed_int64_is_a_typed_error() {
185 let err = decode_value(
186 &pv(Kind::StringValue("abc".into())),
187 Some(&ty(TypeCode::Int64)),
188 "n",
189 )
190 .unwrap_err();
191 assert!(err.to_string().contains("non-integer INT64"));
192 }
193
194 #[test]
195 fn floats_bools_strings_and_nulls() {
196 assert_eq!(
197 decode_value(
198 &pv(Kind::NumberValue(1.5)),
199 Some(&ty(TypeCode::Float64)),
200 "f"
201 )
202 .unwrap(),
203 json!(1.5)
204 );
205 assert_eq!(
206 decode_value(
207 &pv(Kind::StringValue("NaN".into())),
208 Some(&ty(TypeCode::Float64)),
209 "f"
210 )
211 .unwrap(),
212 json!("NaN")
213 );
214 assert_eq!(
215 decode_value(&pv(Kind::BoolValue(true)), Some(&ty(TypeCode::Bool)), "b").unwrap(),
216 json!(true)
217 );
218 assert_eq!(
219 decode_value(
220 &pv(Kind::StringValue("hi".into())),
221 Some(&ty(TypeCode::String)),
222 "s"
223 )
224 .unwrap(),
225 json!("hi")
226 );
227 assert_eq!(
228 decode_value(&pv(Kind::NullValue(0)), Some(&ty(TypeCode::String)), "s").unwrap(),
229 Value::Null
230 );
231 assert_eq!(
232 decode_value(&prost_types::Value { kind: None }, None, "x").unwrap(),
233 Value::Null
234 );
235 }
236
237 #[test]
238 fn numeric_timestamp_date_bytes_stay_strings() {
239 for code in [
240 TypeCode::Numeric,
241 TypeCode::Timestamp,
242 TypeCode::Date,
243 TypeCode::Bytes,
244 ] {
245 let v =
246 decode_value(&pv(Kind::StringValue("raw".into())), Some(&ty(code)), "c").unwrap();
247 assert_eq!(v, json!("raw"));
248 }
249 }
250
251 #[test]
252 fn json_columns_parse_to_structured_values() {
253 let v = decode_value(
254 &pv(Kind::StringValue(r#"{"a": [1, 2]}"#.into())),
255 Some(&ty(TypeCode::Json)),
256 "j",
257 )
258 .unwrap();
259 assert_eq!(v, json!({"a": [1, 2]}));
260 let err = decode_value(
261 &pv(Kind::StringValue("{not json".into())),
262 Some(&ty(TypeCode::Json)),
263 "j",
264 )
265 .unwrap_err();
266 assert!(err.to_string().contains("invalid JSON column value"));
267 }
268
269 #[test]
270 fn arrays_recurse_with_element_type() {
271 let mut arr_ty = ty(TypeCode::Array);
272 arr_ty.array_element_type = Some(Box::new(ty(TypeCode::Int64)));
273 let v = decode_value(
274 &pv(Kind::ListValue(prost_types::ListValue {
275 values: vec![pv(Kind::StringValue("1".into())), pv(Kind::NullValue(0))],
276 })),
277 Some(&arr_ty),
278 "a",
279 )
280 .unwrap();
281 assert_eq!(v, json!([1, null]));
282 }
283
284 #[test]
285 fn structs_become_objects_keyed_by_field_name() {
286 let mut st_ty = ty(TypeCode::Struct);
287 st_ty.struct_type = Some(StructType {
288 fields: vec![
289 Field {
290 name: "id".into(),
291 r#type: Some(ty(TypeCode::Int64)),
292 },
293 Field {
294 name: "name".into(),
295 r#type: Some(ty(TypeCode::String)),
296 },
297 ],
298 });
299 let v = decode_value(
300 &pv(Kind::ListValue(prost_types::ListValue {
301 values: vec![
302 pv(Kind::StringValue("7".into())),
303 pv(Kind::StringValue("x".into())),
304 ],
305 })),
306 Some(&st_ty),
307 "s",
308 )
309 .unwrap();
310 assert_eq!(v, json!({"id": 7, "name": "x"}));
311 }
312
313 #[test]
314 fn untyped_values_fall_back_to_structural_decode() {
315 let v = decode_value(&pv(Kind::NumberValue(2.0)), None, "u").unwrap();
316 assert_eq!(v, json!(2.0));
317 let v = decode_value(
318 &pv(Kind::ListValue(prost_types::ListValue {
319 values: vec![pv(Kind::BoolValue(false))],
320 })),
321 None,
322 "u",
323 )
324 .unwrap();
325 assert_eq!(v, json!([false]));
326 }
327}