use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use crate::query::Manifest;
pub type Value = JsonValue;
pub type Row = IndexMap<String, Value>;
pub type Source = IndexMap<String, ModelResult>;
pub fn row_from_form(form: &IndexMap<String, String>) -> Row {
form.iter()
.map(|(k, v)| (k.clone(), Value::String(v.clone())))
.collect()
}
pub fn row_from_value(value: &Value) -> Row {
match value.as_object() {
Some(map) => map.iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
None => IndexMap::new(),
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ModelResult {
pub(crate) result: ModelRows,
pub total: usize,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub(crate) enum ModelRows {
One(Row),
Many(Vec<Row>),
}
impl ModelRows {
pub(crate) fn rows(&self) -> Vec<Row> {
match self {
Self::One(row) => vec![row.clone()],
Self::Many(rows) => rows.clone(),
}
}
}
impl ModelResult {
pub fn one(row: Row) -> Self {
Self {
result: ModelRows::One(row),
total: 1,
}
}
pub fn many(rows: Vec<Row>) -> Self {
let total = rows.len();
Self {
result: ModelRows::Many(rows),
total,
}
}
pub fn rows(&self) -> Vec<Row> {
self.result.rows()
}
}
#[cfg_attr(not(feature = "wasm"), allow(dead_code))]
pub(crate) fn rows_from_outcome(outcome: &Outcome) -> Vec<Row> {
outcome.rows.rows()
}
pub fn is_single(manifest: &Manifest, outcome: &Outcome) -> bool {
manifest.method.as_deref() == Some("one") || matches!(outcome.rows, ModelRows::One(_))
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[doc(hidden)]
pub struct Outcome {
pub(crate) rows: ModelRows,
pub total: usize,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub lookups: IndexMap<String, IndexMap<String, Row>>,
}
impl Outcome {
pub fn rows(&self) -> Vec<Row> {
self.rows.rows()
}
pub fn empty() -> Self {
Self {
rows: ModelRows::Many(vec![]),
total: 0,
lookups: IndexMap::new(),
}
}
}
pub(crate) fn value_to_lookup_key(value: &Value) -> Option<String> {
match value {
Value::String(s) => Some(s.clone()),
Value::Number(n) => Some(n.to_string()),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn row_from_form_converts_strings() {
let mut form = IndexMap::new();
form.insert("name".to_owned(), "Alice".to_owned());
form.insert("active".to_owned(), "true".to_owned());
let row = row_from_form(&form);
assert_eq!(row["name"], json!("Alice"));
assert_eq!(row["active"], json!("true")); }
#[test]
fn row_from_form_empty() {
let row = row_from_form(&IndexMap::new());
assert!(row.is_empty());
}
#[test]
fn row_from_value_object() {
let body = json!({ "name": "Alice", "active": true });
let row = row_from_value(&body);
assert_eq!(row["name"], json!("Alice"));
assert_eq!(row["active"], json!(true)); }
#[test]
fn row_from_value_non_object_returns_empty() {
assert!(row_from_value(&json!(null)).is_empty());
assert!(row_from_value(&json!("string")).is_empty());
assert!(row_from_value(&json!([1, 2])).is_empty());
}
#[test]
fn rows_from_outcome_normalises_single_row() {
let row: Row = IndexMap::from([("id".to_owned(), json!(1))]);
let mr = ModelResult::one(row.clone());
let outcome = Outcome {
rows: mr.result,
total: 1,
lookups: IndexMap::new(),
};
let rows = rows_from_outcome(&outcome);
assert_eq!(rows, vec![row]);
}
}