use serde::de::DeserializeOwned;
use serde_json::Value;
use crate::error::{Error, Result};
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct Response {
pub request_id: String,
pub status: String,
pub kind: String,
pub data: Value,
pub route: Option<String>,
pub elapsed_ms: Option<i64>,
pub warnings: Vec<String>,
pub error_code: Option<String>,
pub leader_hint: Option<String>,
}
impl Response {
pub fn json<T: DeserializeOwned>(&self) -> Result<T> {
serde_json::from_value(self.data.clone())
.map_err(|e| Error::protocol(format!("unexpected {} payload: {e}", self.describe())))
}
pub(crate) fn expect(&self, kind: &str, what: &str) -> Result<&Value> {
if self.kind == kind {
Ok(&self.data)
} else {
Err(Error::protocol(format!(
"expected {kind} from {what}, got {}",
self.describe()
)))
}
}
pub(crate) fn decode<T: DeserializeOwned>(&self, what: &str) -> Result<T> {
let data = self.expect("Json", what)?;
serde_json::from_value(data.clone())
.map_err(|e| Error::protocol(format!("malformed {what} payload: {e}")))
}
fn describe(&self) -> String {
if self.kind.is_empty() {
"no data".to_string()
} else {
self.kind.clone()
}
}
pub(crate) fn failure_message(&self) -> String {
match self.kind.as_str() {
"Message" | "Toon" => match self.data.as_str() {
Some(text) if !text.is_empty() => text.to_string(),
_ => self.data.to_string(),
},
"Json" => self.data.to_string(),
_ if !self.status.is_empty() => format!("the server answered `{}`", self.status),
_ => "the request failed".to_string(),
}
}
}
pub(crate) fn decode_variant(data: Value) -> (String, Value) {
match data {
Value::Null => (String::new(), Value::Null),
Value::String(name) => (name, Value::Null),
Value::Object(map) => match map.into_iter().next() {
Some((name, payload)) => (name, payload),
None => (String::new(), Value::Null),
},
other => (String::new(), other),
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn an_externally_tagged_variant_splits_into_name_and_payload() {
let (kind, data) = decode_variant(json!({"Rows": {"columns": ["a"], "rows": []}}));
assert_eq!(kind, "Rows");
assert_eq!(data["columns"][0], "a");
}
#[test]
fn a_unit_variant_is_a_bare_string() {
let (kind, data) = decode_variant(json!("Empty"));
assert_eq!(kind, "Empty");
assert_eq!(data, Value::Null);
}
#[test]
fn absent_data_is_no_kind_at_all() {
let (kind, _) = decode_variant(Value::Null);
assert_eq!(kind, "");
}
#[test]
fn the_wrong_variant_is_named_in_the_error() {
let response = Response {
request_id: "r1".into(),
status: "ok".into(),
kind: "Message".into(),
data: json!("hi"),
route: None,
elapsed_ms: None,
warnings: vec![],
error_code: None,
leader_hint: None,
};
let err = response.expect("Rows", "query").unwrap_err();
assert!(err.message.contains("expected Rows"), "{}", err.message);
assert!(err.message.contains("Message"), "{}", err.message);
}
#[test]
fn a_failure_reports_the_servers_own_message() {
let mut response = Response {
request_id: "r1".into(),
status: "error".into(),
kind: "Message".into(),
data: json!("no such table"),
route: None,
elapsed_ms: None,
warnings: vec![],
error_code: Some("sql.unknown_table".into()),
leader_hint: None,
};
assert_eq!(response.failure_message(), "no such table");
response.kind = "Empty".into();
assert!(response.failure_message().contains("error"));
}
}