use std::time::Duration;
use super::mapping::Mapping;
use crate::Error;
pub type Result = std::result::Result<Query, Error>;
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Query {
results: Vec<Mapping>,
#[serde(skip_serializing_if = "Option::is_none")]
sequence_number: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
time: Option<f64>,
}
impl Query {
#[must_use]
pub fn duration(&self) -> Option<Duration> {
self.time.map(Duration::from_secs_f64)
}
pub fn results(&self) -> std::slice::Iter<'_, Mapping> {
self.results.iter()
}
#[must_use]
#[inline]
pub fn sequence_number(&self) -> Option<u64> {
self.sequence_number
}
#[must_use]
#[inline]
pub fn time(&self) -> Option<f64> {
self.time
}
}
#[cfg(feature = "ureq")]
impl TryFrom<ureq::Response> for Query {
type Error = Error;
fn try_from(response: ureq::Response) -> std::result::Result<Self, Self::Error> {
let status = response.status();
if !(200..300).contains(&status) {
return Err(Error::HttpError(status, response.status_text().to_string()));
}
response.into_json::<Self>().map_err(Error::from)
}
}
#[cfg(test)]
mod tests {
use std::{collections::HashMap, time::Duration};
use super::Query;
use crate::response::mapping::{Associative, Mapping, Standard};
use crate::DataType;
#[test]
fn response_associative_json_test() {
let r = Query {
results: vec![Mapping::Associative(Associative {
rows: Vec::new(),
time: None,
types: HashMap::new(),
})],
sequence_number: None,
time: None,
};
let res = serde_json::to_string_pretty(&r);
assert!(res.is_ok(), "error: {}", res.err().unwrap());
let json = res.unwrap();
assert!(json.contains("\"results\": ["));
assert_eq!(
json,
"{\n \"results\": [\n {\n \"rows\": [],\n \"types\": {}\n }\n ]\n}"
);
let res = serde_json::to_string(&r);
assert!(res.is_ok(), "error: {}", res.err().unwrap());
let json = res.unwrap();
assert!(json.contains("\"results\":["));
assert_eq!(json, "{\"results\":[{\"rows\":[],\"types\":{}}]}");
}
#[test]
fn response_standard_json_test() {
let r = Query {
results: vec![Mapping::Standard(Standard {
time: None,
types: Vec::new(),
columns: Vec::new(),
values: None,
})],
sequence_number: None,
time: None,
};
let res = serde_json::to_string_pretty(&r);
assert!(res.is_ok(), "error: {}", res.err().unwrap());
let json = res.unwrap();
assert!(json.contains("\"results\": ["));
assert_eq!(json, "{\n \"results\": [\n {\n \"columns\": [],\n \"types\": []\n }\n ]\n}");
let res = serde_json::to_string(&r);
assert!(res.is_ok(), "error: {}", res.err().unwrap());
let json = res.unwrap();
assert!(json.contains("\"results\":["));
assert_eq!(json, "{\"results\":[{\"columns\":[],\"types\":[]}]}");
}
#[test]
fn response_standard_data_type_json_test() {
let r = Query {
results: vec![Mapping::Standard(Standard {
time: None,
types: vec![DataType::Integer, DataType::Text],
columns: vec!["id".to_string(), "value".to_string()],
values: Some(vec![vec![1.into(), "test".into()]]),
})],
sequence_number: None,
time: None,
};
let res = serde_json::to_string(&r);
assert!(res.is_ok(), "error: {}", res.err().unwrap());
let json = res.unwrap();
assert!(json.contains("\"results\":["));
assert_eq!(
json,
"{\"results\":[{\"columns\":[\"id\",\"value\"],\"types\":[\"integer\",\"text\"],\"values\":[[1,\"test\"]]}]}"
);
}
#[test]
fn deserialize_associative_test() {
let json =
"{\n \"results\": [\n {\n \"rows\": [],\n \"types\": {}\n }\n ]\n}";
let res: Result<Query, serde_json::Error> = serde_json::from_str(json);
assert!(res.is_ok(), "error: {}", res.err().unwrap());
let r = res.unwrap();
assert_eq!(r.results.len(), 1);
match &r.results[0] {
Mapping::Associative(_) => {}
_ => unreachable!(),
}
}
#[test]
fn deserialize_error_test() {
let json = r#"{
"results": [
{
"error": "near \"nonsense\": syntax error"
}
],
"time": 2.478862
}"#;
let res: Result<Query, serde_json::Error> = serde_json::from_str(json);
assert!(res.is_ok(), "error: {}", res.err().unwrap());
let r = res.unwrap();
assert_eq!(r.results.len(), 1);
match &r.results[0] {
Mapping::Error(err) => {
assert!(!err.error.is_empty());
assert_eq!(&err.error, "near \"nonsense\": syntax error");
assert!(r.time.is_some());
#[allow(clippy::float_cmp)]
{
assert!(r.time().unwrap() == 2.478_862_f64);
}
assert_eq!(
r.duration().unwrap(),
Duration::from_secs_f64(2.478_862_f64)
);
}
_ => unreachable!("{:#?}", r),
}
}
#[test]
fn deserialize_standard_test() {
let json = "{\n \"results\": [\n {\n \"columns\": [],\n \"types\": [],\n \"values\": []\n }\n ]\n}";
let res: Result<Query, serde_json::Error> = serde_json::from_str(json);
assert!(res.is_ok(), "error: {}", res.err().unwrap());
let r = res.unwrap();
assert_eq!(r.results.len(), 1);
match &r.results[0] {
Mapping::Standard(_) => {}
_ => unreachable!(),
}
}
#[test]
fn deserialize_multi_test() {
let json = r#"{
"results": [
{
"last_insert_id": 1,
"rows_affected": 1,
"time": 0.000074612,
"rows": null
},
{
"last_insert_id": 2,
"rows_affected": 1,
"time": 0.000044645,
"rows": null
},
{
"types": { "age": "integer", "id": "integer", "name": "text"},
"rows": [
{"age": 20, "id": 1, "name": "fiona"},
{"age": 30, "id": 2, "name": "declan"}
],
"time": 0.000055248
},
{
"error": "no such table: bar"
}
],
"time": 0.010571084
}"#;
let res: Result<Query, serde_json::Error> = serde_json::from_str(json);
assert!(res.is_ok(), "error: {}", res.err().unwrap());
let r = res.unwrap();
assert_eq!(r.results.len(), 4);
let mut results = r.results();
match results.next().unwrap() {
Mapping::Execute(execute) => {
assert_eq!(execute.last_insert_id, 1);
assert!(execute.rows.is_none());
}
_ => unreachable!(),
}
match results.next().unwrap() {
Mapping::Execute(_) => {}
_ => unreachable!(),
}
match results.next().unwrap() {
Mapping::Associative(associative) => {
assert_eq!(associative.types.get("age").unwrap(), &DataType::Integer);
assert_eq!(associative.types.get("name").unwrap(), &DataType::Text);
assert_eq!(associative.rows[0].get("age").unwrap(), 20);
assert_eq!(associative.rows[1].get("name").unwrap(), "declan");
#[allow(clippy::float_cmp)]
{
assert_eq!(associative.time.unwrap(), 0.000_055_248);
}
}
_ => unreachable!(),
}
match results.next().unwrap() {
Mapping::Error(_) => {}
_ => unreachable!(),
}
}
}