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 column_is_numeric(fields: &[Field], name: &str) -> bool {
61 fields
62 .iter()
63 .find(|f| f.name == name)
64 .map(|f| type_code(f.r#type.as_ref()) == TypeCode::Numeric)
65 .unwrap_or(false)
66}
67
68pub fn decode_value(
70 value: &prost_types::Value,
71 ty: Option<&Type>,
72 field_name: &str,
73) -> Result<Value, RowError> {
74 let kind = match &value.kind {
75 None | Some(Kind::NullValue(_)) => return Ok(Value::Null),
76 Some(kind) => kind,
77 };
78 let code = type_code(ty);
79 match (code, kind) {
80 (TypeCode::Int64 | TypeCode::Enum, Kind::StringValue(s)) => {
83 let n: i64 = s.parse().map_err(|_| {
84 RowError::CustomParseError(format!("{field_name}: non-integer INT64 `{s}`"))
85 })?;
86 Ok(Value::Number(n.into()))
87 }
88 (TypeCode::Float64 | TypeCode::Float32, Kind::NumberValue(f)) => {
89 Ok(serde_json::Number::from_f64(*f).map_or(Value::Null, Value::Number))
90 }
91 (TypeCode::Float64 | TypeCode::Float32, Kind::StringValue(s)) => {
94 Ok(Value::String(s.clone()))
95 }
96 (TypeCode::Bool, Kind::BoolValue(b)) => Ok(Value::Bool(*b)),
97 (TypeCode::Json, Kind::StringValue(s)) => serde_json::from_str(s).map_err(|e| {
99 RowError::CustomParseError(format!("{field_name}: invalid JSON column value: {e}"))
100 }),
101 (_, Kind::StringValue(s)) => Ok(Value::String(s.clone())),
104 (TypeCode::Array, Kind::ListValue(list)) => {
105 let elem_ty = ty.and_then(|t| t.array_element_type.as_deref());
106 let items: Result<Vec<Value>, RowError> = list
107 .values
108 .iter()
109 .map(|v| decode_value(v, elem_ty, field_name))
110 .collect();
111 Ok(Value::Array(items?))
112 }
113 (TypeCode::Struct, Kind::ListValue(list)) => {
116 let fields = ty
117 .and_then(|t| t.struct_type.as_ref())
118 .map(|st| st.fields.as_slice())
119 .unwrap_or(&[]);
120 let mut obj = serde_json::Map::with_capacity(list.values.len());
121 for (idx, v) in list.values.iter().enumerate() {
122 let (name, elem_ty) = fields
123 .get(idx)
124 .map(|f| (f.name.clone(), f.r#type.as_ref()))
125 .unwrap_or_else(|| (format!("_{idx}"), None));
126 obj.insert(name, decode_value(v, elem_ty, field_name)?);
127 }
128 Ok(Value::Object(obj))
129 }
130 (_, kind) => Ok(kind_to_json(kind)),
133 }
134}
135
136fn kind_to_json(kind: &Kind) -> Value {
140 match kind {
141 Kind::NullValue(_) => Value::Null,
142 Kind::BoolValue(b) => Value::Bool(*b),
143 Kind::NumberValue(f) => serde_json::Number::from_f64(*f).map_or(Value::Null, Value::Number),
144 Kind::StringValue(s) => Value::String(s.clone()),
145 Kind::ListValue(list) => Value::Array(
146 list.values
147 .iter()
148 .map(|v| match &v.kind {
149 None => Value::Null,
150 Some(k) => kind_to_json(k),
151 })
152 .collect(),
153 ),
154 Kind::StructValue(st) => {
155 let mut obj = serde_json::Map::with_capacity(st.fields.len());
156 for (k, v) in &st.fields {
157 let decoded = match &v.kind {
158 None => Value::Null,
159 Some(kind) => kind_to_json(kind),
160 };
161 obj.insert(k.clone(), decoded);
162 }
163 Value::Object(obj)
164 }
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use gcloud_googleapis::spanner::v1::StructType;
172 use serde_json::json;
173
174 fn pv(kind: Kind) -> prost_types::Value {
175 prost_types::Value { kind: Some(kind) }
176 }
177
178 fn ty(code: TypeCode) -> Type {
179 Type {
180 code: code.into(),
181 array_element_type: None,
182 struct_type: None,
183 ..Default::default()
184 }
185 }
186
187 #[test]
188 fn column_is_numeric_only_flags_numeric_columns() {
189 let field = |name: &str, code: TypeCode| Field {
190 name: name.to_string(),
191 r#type: Some(ty(code)),
192 };
193 let fields = vec![
194 field("id", TypeCode::Int64),
195 field("amount", TypeCode::Numeric),
196 field("updated_at", TypeCode::Timestamp),
197 ];
198 assert!(column_is_numeric(&fields, "amount"));
200 assert!(!column_is_numeric(&fields, "id"));
201 assert!(!column_is_numeric(&fields, "updated_at"));
202 assert!(!column_is_numeric(&fields, "nope"));
204 }
205
206 #[test]
207 fn int64_decodes_losslessly_from_string() {
208 let big = 9_007_199_254_740_993_i64; let v = decode_value(
210 &pv(Kind::StringValue(big.to_string())),
211 Some(&ty(TypeCode::Int64)),
212 "n",
213 )
214 .unwrap();
215 assert_eq!(v, json!(big));
216 }
217
218 #[test]
219 fn malformed_int64_is_a_typed_error() {
220 let err = decode_value(
221 &pv(Kind::StringValue("abc".into())),
222 Some(&ty(TypeCode::Int64)),
223 "n",
224 )
225 .unwrap_err();
226 assert!(err.to_string().contains("non-integer INT64"));
227 }
228
229 #[test]
230 fn floats_bools_strings_and_nulls() {
231 assert_eq!(
232 decode_value(
233 &pv(Kind::NumberValue(1.5)),
234 Some(&ty(TypeCode::Float64)),
235 "f"
236 )
237 .unwrap(),
238 json!(1.5)
239 );
240 assert_eq!(
241 decode_value(
242 &pv(Kind::StringValue("NaN".into())),
243 Some(&ty(TypeCode::Float64)),
244 "f"
245 )
246 .unwrap(),
247 json!("NaN")
248 );
249 assert_eq!(
250 decode_value(&pv(Kind::BoolValue(true)), Some(&ty(TypeCode::Bool)), "b").unwrap(),
251 json!(true)
252 );
253 assert_eq!(
254 decode_value(
255 &pv(Kind::StringValue("hi".into())),
256 Some(&ty(TypeCode::String)),
257 "s"
258 )
259 .unwrap(),
260 json!("hi")
261 );
262 assert_eq!(
263 decode_value(&pv(Kind::NullValue(0)), Some(&ty(TypeCode::String)), "s").unwrap(),
264 Value::Null
265 );
266 assert_eq!(
267 decode_value(&prost_types::Value { kind: None }, None, "x").unwrap(),
268 Value::Null
269 );
270 }
271
272 #[test]
273 fn numeric_timestamp_date_bytes_stay_strings() {
274 for code in [
275 TypeCode::Numeric,
276 TypeCode::Timestamp,
277 TypeCode::Date,
278 TypeCode::Bytes,
279 ] {
280 let v =
281 decode_value(&pv(Kind::StringValue("raw".into())), Some(&ty(code)), "c").unwrap();
282 assert_eq!(v, json!("raw"));
283 }
284 }
285
286 #[test]
287 fn json_columns_parse_to_structured_values() {
288 let v = decode_value(
289 &pv(Kind::StringValue(r#"{"a": [1, 2]}"#.into())),
290 Some(&ty(TypeCode::Json)),
291 "j",
292 )
293 .unwrap();
294 assert_eq!(v, json!({"a": [1, 2]}));
295 let err = decode_value(
296 &pv(Kind::StringValue("{not json".into())),
297 Some(&ty(TypeCode::Json)),
298 "j",
299 )
300 .unwrap_err();
301 assert!(err.to_string().contains("invalid JSON column value"));
302 }
303
304 #[test]
305 fn arrays_recurse_with_element_type() {
306 let mut arr_ty = ty(TypeCode::Array);
307 arr_ty.array_element_type = Some(Box::new(ty(TypeCode::Int64)));
308 let v = decode_value(
309 &pv(Kind::ListValue(prost_types::ListValue {
310 values: vec![pv(Kind::StringValue("1".into())), pv(Kind::NullValue(0))],
311 })),
312 Some(&arr_ty),
313 "a",
314 )
315 .unwrap();
316 assert_eq!(v, json!([1, null]));
317 }
318
319 #[test]
320 fn structs_become_objects_keyed_by_field_name() {
321 let mut st_ty = ty(TypeCode::Struct);
322 st_ty.struct_type = Some(StructType {
323 fields: vec![
324 Field {
325 name: "id".into(),
326 r#type: Some(ty(TypeCode::Int64)),
327 },
328 Field {
329 name: "name".into(),
330 r#type: Some(ty(TypeCode::String)),
331 },
332 ],
333 });
334 let v = decode_value(
335 &pv(Kind::ListValue(prost_types::ListValue {
336 values: vec![
337 pv(Kind::StringValue("7".into())),
338 pv(Kind::StringValue("x".into())),
339 ],
340 })),
341 Some(&st_ty),
342 "s",
343 )
344 .unwrap();
345 assert_eq!(v, json!({"id": 7, "name": "x"}));
346 }
347
348 #[test]
349 fn untyped_values_fall_back_to_structural_decode() {
350 let v = decode_value(&pv(Kind::NumberValue(2.0)), None, "u").unwrap();
351 assert_eq!(v, json!(2.0));
352 let v = decode_value(
353 &pv(Kind::ListValue(prost_types::ListValue {
354 values: vec![pv(Kind::BoolValue(false))],
355 })),
356 None,
357 "u",
358 )
359 .unwrap();
360 assert_eq!(v, json!([false]));
361 }
362}