use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
#[cfg(feature = "direct-sql")]
use sqlx::Row as SqlxRow;
use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct Row(pub HashMap<String, JsonValue>);
impl Row {
pub fn new() -> Self {
Self(HashMap::new())
}
pub fn with_capacity(capacity: usize) -> Self {
Self(HashMap::with_capacity(capacity))
}
pub fn set(&mut self, key: impl Into<String>, value: impl Into<JsonValue>) -> &mut Self {
self.0.insert(key.into(), value.into());
self
}
pub fn get_value(&self, key: &str) -> Option<&JsonValue> {
self.0.get(key)
}
pub fn contains(&self, key: &str) -> bool {
self.0.contains_key(key)
}
pub fn get_as<T: serde::de::DeserializeOwned>(&self, key: &str) -> Option<T> {
self.0
.get(key)
.and_then(|v| serde_json::from_value(v.clone()).ok())
}
pub fn columns(&self) -> Vec<&str> {
self.0.keys().map(|k| k.as_str()).collect()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn into_inner(self) -> HashMap<String, JsonValue> {
self.0
}
}
impl Deref for Row {
type Target = HashMap<String, JsonValue>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Row {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<K: Into<String>, V: Into<JsonValue>> FromIterator<(K, V)> for Row {
fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
let map = iter
.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.collect();
Self(map)
}
}
impl<K: Into<String>, V: Into<JsonValue>, const N: usize> From<[(K, V); N]> for Row {
fn from(arr: [(K, V); N]) -> Self {
arr.into_iter().collect()
}
}
#[cfg(feature = "direct-sql")]
impl<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow> for Row {
fn from_row(row: &'r sqlx::postgres::PgRow) -> Result<Self, sqlx::Error> {
use sqlx::Column;
let mut map = Row::new();
for col in row.columns() {
let name = col.name();
if let Ok(v) = row.try_get::<JsonValue, _>(name) {
map.set(name, v);
} else if let Ok(v) = row.try_get::<String, _>(name) {
map.set(name, JsonValue::String(v));
} else if let Ok(v) = row.try_get::<i64, _>(name) {
map.set(name, JsonValue::Number(v.into()));
} else if let Ok(v) = row.try_get::<i32, _>(name) {
map.set(name, JsonValue::Number(v.into()));
} else if let Ok(v) = row.try_get::<f64, _>(name) {
if let Some(n) = serde_json::Number::from_f64(v) {
map.set(name, JsonValue::Number(n));
} else {
map.set(name, JsonValue::Null);
}
} else if let Ok(v) = row.try_get::<bool, _>(name) {
map.set(name, JsonValue::Bool(v));
} else if let Ok(v) = row.try_get::<uuid::Uuid, _>(name) {
map.set(name, JsonValue::String(v.to_string()));
} else if let Ok(v) = row.try_get::<chrono::NaiveDateTime, _>(name) {
map.set(name, JsonValue::String(v.to_string()));
} else if let Ok(v) = row.try_get::<chrono::DateTime<chrono::Utc>, _>(name) {
map.set(name, JsonValue::String(v.to_rfc3339()));
} else {
map.set(name, JsonValue::Null);
}
}
Ok(map)
}
}
#[macro_export]
macro_rules! row {
() => {
$crate::Row::new()
};
($(($key:expr, $val:expr)),+ $(,)?) => {{
let mut row = $crate::Row::new();
$(
row.set($key, serde_json::json!($val));
)+
row
}};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_row_new() {
let row = Row::new();
assert!(row.is_empty());
}
#[test]
fn test_row_set_get() {
let mut row = Row::new();
row.set("name", JsonValue::String("Auckland".to_string()));
assert_eq!(
row.get_value("name"),
Some(&JsonValue::String("Auckland".to_string()))
);
assert!(row.contains("name"));
assert!(!row.contains("missing"));
}
#[test]
fn test_row_macro() {
let row = row![("name", "Auckland"), ("id", 1)];
assert_eq!(row.len(), 2);
assert!(row.contains("name"));
assert!(row.contains("id"));
}
#[test]
fn test_row_get_as() {
let row = row![("count", 42)];
assert_eq!(row.get_as::<i64>("count"), Some(42));
assert_eq!(row.get_as::<String>("count"), None);
}
#[test]
fn test_get_as_wrong_type_returns_none() {
let row = row![("flag", true)];
assert_eq!(row.get_as::<i64>("flag"), None);
assert_eq!(row.get_as::<Vec<String>>("flag"), None);
}
#[test]
fn test_columns_returns_all_column_names() {
let row = row![("a", 1), ("b", 2), ("c", 3)];
let mut cols = row.columns();
cols.sort();
assert_eq!(cols, vec!["a", "b", "c"]);
}
#[test]
fn test_is_empty_on_empty_row() {
let row = Row::new();
assert!(row.is_empty());
}
#[test]
fn test_is_empty_on_non_empty_row() {
let row = row![("key", "value")];
assert!(!row.is_empty());
}
#[test]
fn test_len_counts_columns() {
let row = row![("x", 1), ("y", 2)];
assert_eq!(row.len(), 2);
}
#[test]
fn test_len_empty_row() {
let row = Row::new();
assert_eq!(row.len(), 0);
}
#[test]
fn test_get_value_missing_key() {
let row = Row::new();
assert_eq!(row.get_value("nonexistent"), None);
}
#[test]
fn test_with_capacity_creates_row() {
let mut row = Row::with_capacity(10);
assert!(row.is_empty());
row.set("key", "val");
assert_eq!(row.len(), 1);
}
#[test]
fn test_into_inner_returns_hashmap() {
let row = row![("name", "Alice"), ("age", 30)];
let map = row.into_inner();
assert_eq!(map.len(), 2);
assert_eq!(map.get("name"), Some(&serde_json::json!("Alice")));
assert_eq!(map.get("age"), Some(&serde_json::json!(30)));
}
#[test]
fn test_deref_access() {
let row = row![("city", "Auckland")];
assert!(row.contains_key("city"));
assert!(!row.contains_key("country"));
assert_eq!(row.get("city"), Some(&serde_json::json!("Auckland")));
}
#[test]
fn test_from_array_conversion() {
let row = Row::from([
("name".to_string(), JsonValue::String("Bob".to_string())),
("score".to_string(), JsonValue::from(100)),
]);
assert_eq!(row.len(), 2);
assert_eq!(
row.get_value("name"),
Some(&JsonValue::String("Bob".to_string()))
);
assert_eq!(row.get_value("score"), Some(&JsonValue::from(100)));
}
#[test]
fn test_from_iterator_conversion() {
let pairs = vec![
("alpha".to_string(), JsonValue::from(1)),
("beta".to_string(), JsonValue::from(2)),
("gamma".to_string(), JsonValue::from(3)),
];
let row: Row = pairs.into_iter().collect();
assert_eq!(row.len(), 3);
assert_eq!(row.get_as::<i64>("alpha"), Some(1));
assert_eq!(row.get_as::<i64>("beta"), Some(2));
assert_eq!(row.get_as::<i64>("gamma"), Some(3));
}
#[test]
fn test_deref_mut_access() {
let mut row = Row::new();
row.insert("direct".to_string(), JsonValue::Bool(true));
assert_eq!(row.get_value("direct"), Some(&JsonValue::Bool(true)));
}
#[test]
fn test_row_serialize_deserialize() {
let row = row![("key", "value"), ("num", 42)];
let json = serde_json::to_string(&row).unwrap();
let deserialized: Row = serde_json::from_str(&json).unwrap();
assert_eq!(row, deserialized);
}
}