1use crate::value::{FromValue, Value};
4use rustlavel_core::{Error, Json, Result};
5use std::sync::Arc;
6
7pub type Columns = Arc<Vec<String>>;
9
10#[derive(Debug, Clone)]
11pub struct Row {
12 columns: Columns,
13 values: Vec<Value>,
14}
15
16impl Row {
17 pub fn new(columns: Columns, values: Vec<Value>) -> Self {
18 Row { columns, values }
19 }
20
21 pub fn columns(&self) -> &[String] {
22 &self.columns
23 }
24
25 pub fn len(&self) -> usize {
26 self.values.len()
27 }
28
29 pub fn is_empty(&self) -> bool {
30 self.values.is_empty()
31 }
32
33 pub fn get<T: FromValue>(&self, column: &str) -> Result<T> {
38 let value = self.value(column)?;
39 T::from_value(value).map_err(|e| Error::msg(format!("column `{column}`: {e}")))
40 }
41
42 pub fn get_at<T: FromValue>(&self, index: usize) -> Result<T> {
44 let value = self
45 .values
46 .get(index)
47 .ok_or_else(|| Error::msg(format!("no column at index {index}")))?;
48 T::from_value(value)
49 }
50
51 pub fn get_or<T: FromValue>(&self, column: &str, default: T) -> T {
53 self.value(column).ok().and_then(|v| T::from_value(v).ok()).unwrap_or(default)
54 }
55
56 pub fn value(&self, column: &str) -> Result<&Value> {
57 let index = self
58 .columns
59 .iter()
60 .position(|name| name == column)
61 .ok_or_else(|| self.unknown_column(column))?;
62 Ok(&self.values[index])
63 }
64
65 pub fn value_at(&self, index: usize) -> Result<&Value> {
68 self.values
69 .get(index)
70 .ok_or_else(|| Error::msg(format!("no column at index {index}")))
71 }
72
73 pub fn has(&self, column: &str) -> bool {
74 self.columns.iter().any(|name| name == column)
75 }
76
77 fn unknown_column(&self, column: &str) -> Error {
78 Error::msg(format!(
79 "no column `{column}` in this result. Available: {}",
80 self.columns.join(", ")
81 ))
82 }
83
84 pub fn to_json(&self) -> Json {
86 Json::object(
87 self.columns
88 .iter()
89 .cloned()
90 .zip(self.values.iter().cloned().map(Json::from))
91 .collect::<Vec<_>>(),
92 )
93 }
94}
95
96pub fn rows_to_json(rows: &[Row]) -> Json {
98 Json::Array(rows.iter().map(Row::to_json).collect())
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 fn row() -> Row {
106 let columns = Arc::new(vec!["id".to_string(), "name".to_string(), "note".to_string()]);
107 Row::new(columns, vec![Value::Int(7), Value::Text("Ada".into()), Value::Null])
108 }
109
110 #[test]
111 fn reads_columns_by_name_and_position() {
112 let row = row();
113
114 assert_eq!(row.get::<i64>("id").unwrap(), 7);
115 assert_eq!(row.get::<String>("name").unwrap(), "Ada");
116 assert_eq!(row.get::<Option<String>>("note").unwrap(), None);
117 assert_eq!(row.get_at::<i64>(0).unwrap(), 7);
118 }
119
120 #[test]
121 fn an_unknown_column_lists_what_is_available() {
122 let error = row().get::<i64>("nope").unwrap_err().to_string();
123
124 assert!(error.contains("no column `nope`"));
125 assert!(error.contains("id, name, note"));
126 }
127
128 #[test]
129 fn a_type_error_names_the_column() {
130 let error = row().get::<i64>("name").unwrap_err().to_string();
131 assert!(error.contains("column `name`"));
132 }
133
134 #[test]
135 fn converts_to_json() {
136 assert_eq!(
137 row().to_json().to_string(),
138 r#"{"id":7,"name":"Ada","note":null}"#
139 );
140 }
141}