Skip to main content

toolu_orm_core/
relational_row.rs

1//! JSON-backed deserialization for relational SELECT results.
2//!
3//! # Public API
4//!
5//! - [`FromRelationalRow`], [`RelationDeserializer`]
6//! - [`parse_json_array_of_arrays`], [`parse_json_single_array`], [`from_json_object_slice`]
7
8use serde::de::DeserializeOwned;
9
10use crate::error::DbCoreError;
11
12/// Parse a JSON string containing an array of arrays (one inner array per row).
13///
14/// # Errors
15///
16/// Returns [`DbCoreError::RowMapping`] when JSON is invalid or not an array of arrays.
17pub fn parse_json_array_of_arrays(json: &str) -> Result<Vec<Vec<serde_json::Value>>, DbCoreError> {
18  let parsed: serde_json::Value =
19    serde_json::from_str(json).map_err(|e| DbCoreError::RowMapping(e.to_string()))?;
20
21  let outer = parsed
22    .as_array()
23    .ok_or_else(|| DbCoreError::RowMapping("expected JSON array".to_owned()))?;
24
25  let mut rows = Vec::with_capacity(outer.len());
26  for item in outer {
27    let inner = item
28      .as_array()
29      .ok_or_else(|| DbCoreError::RowMapping("expected inner array in JSON row".to_owned()))?
30      .clone();
31    rows.push(inner);
32  }
33
34  Ok(rows)
35}
36
37/// Parse a JSON string containing a single array of values (one row).
38///
39/// For `"null"` input returns an empty vec.
40///
41/// # Errors
42///
43/// Returns [`DbCoreError::RowMapping`] when JSON is invalid or the top-level value is neither
44/// `null` nor an array.
45pub fn parse_json_single_array(json: &str) -> Result<Vec<serde_json::Value>, DbCoreError> {
46  let parsed: serde_json::Value =
47    serde_json::from_str(json).map_err(|e| DbCoreError::RowMapping(e.to_string()))?;
48
49  if parsed.is_null() {
50    return Ok(Vec::new());
51  }
52
53  parsed
54    .as_array()
55    .cloned()
56    .ok_or_else(|| DbCoreError::RowMapping("expected JSON array or null".to_owned()))
57}
58
59/// Build a JSON object from positional `json_build_array` / `json_array` values and `T: Deserialize`.
60///
61/// # Errors
62///
63/// Returns [`DbCoreError::RowMapping`] when `values` and `keys` lengths differ or deserialization
64/// fails.
65pub fn from_json_object_slice<T: DeserializeOwned>(
66  values: &[serde_json::Value],
67  keys: &[&str],
68) -> Result<T, DbCoreError> {
69  if values.len() != keys.len() {
70    return Err(DbCoreError::RowMapping(format!(
71      "column count mismatch: got {} values for {} keys",
72      values.len(),
73      keys.len()
74    )));
75  }
76  let mut map = serde_json::Map::new();
77  for (k, v) in keys.iter().zip(values.iter()) {
78    map.insert((*k).to_owned(), v.clone());
79  }
80  serde_json::from_value(serde_json::Value::Object(map))
81    .map_err(|e| DbCoreError::RowMapping(e.to_string()))
82}
83
84/// Extract typed values from a parsed JSON array row.
85pub struct RelationDeserializer<'a> {
86  values: &'a [serde_json::Value],
87}
88
89impl<'a> RelationDeserializer<'a> {
90  /// Create a deserializer over a slice of JSON values.
91  pub fn new(values: &'a [serde_json::Value]) -> Self {
92    Self { values }
93  }
94
95  /// Extract a typed value at the given index.
96  ///
97  /// # Errors
98  ///
99  /// Returns [`DbCoreError::RowMapping`] when `index` is out of range or the value cannot be
100  /// deserialized to `T`.
101  pub fn get<T: DeserializeOwned>(&self, index: usize) -> Result<T, DbCoreError> {
102    let val = self.values.get(index).ok_or_else(|| {
103      DbCoreError::RowMapping(format!(
104        "relation column index {index} out of bounds (len {})",
105        self.values.len()
106      ))
107    })?;
108    serde_json::from_value(val.clone())
109      .map_err(|e| DbCoreError::RowMapping(format!("column {index}: {e}")))
110  }
111
112  /// Number of values in this row.
113  pub fn len(&self) -> usize {
114    self.values.len()
115  }
116
117  /// Whether this row has no values.
118  pub fn is_empty(&self) -> bool {
119    self.values.is_empty()
120  }
121}
122
123/// Types deserialized from relational query rows (scalar + JSON relation columns).
124pub trait FromRelationalRow: Sized {
125  /// Column names for scalar (non-relation) fields, in order.
126  const SCALAR_COLUMNS: &'static [&'static str];
127
128  /// Construct `Self` from JSON values (scalars then relation columns).
129  ///
130  /// # Errors
131  ///
132  /// Returns [`DbCoreError::RowMapping`] when any scalar or relation column fails to deserialize.
133  fn from_relational_values(values: &[serde_json::Value]) -> Result<Self, DbCoreError>;
134}