use serde::{Deserialize, Serialize};
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Response {
Connected {
id: String,
version: String,
job: String,
},
Pong {
id: String,
},
Exited {
id: String,
},
QueryResult(QueryResult),
PreparedStatement {
id: String,
success: bool,
cont_id: String,
execution_time: f64,
},
SqlClosed {
id: String,
success: bool,
},
ClResult {
id: String,
success: bool,
messages: Vec<ClMessage>,
},
Version {
id: String,
success: bool,
version: String,
},
DbJob {
id: String,
success: bool,
job: String,
},
ConfigSet {
id: String,
success: bool,
},
TraceData {
id: String,
success: bool,
tracedata: String,
},
DoveResult {
id: String,
success: bool,
result: serde_json::Value,
},
Error(ErrorResponse),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QueryResult {
pub id: String,
pub success: bool,
pub has_results: bool,
#[serde(default)]
pub update_count: i64,
#[serde(default)]
pub cont_id: Option<String>,
#[serde(default = "default_true")]
pub is_done: bool,
#[serde(default)]
pub metadata: QueryMetaData,
#[serde(default)]
pub data: Vec<serde_json::Map<String, serde_json::Value>>,
#[serde(default)]
pub execution_time: f64,
}
fn default_true() -> bool {
true
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct QueryMetaData {
#[serde(default)]
pub column_count: u32,
#[serde(default)]
pub columns: Vec<Column>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Column {
pub name: String,
#[serde(default)]
pub label: Option<String>,
#[serde(rename = "type", default)]
pub type_name: Option<String>,
#[serde(default)]
pub display_size: Option<u32>,
#[serde(default)]
pub precision: Option<u32>,
#[serde(default)]
pub scale: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClMessage {
#[serde(default)]
pub id: Option<String>,
#[serde(default, rename = "type")]
pub kind: Option<String>,
#[serde(default)]
pub text: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorResponse {
pub id: String,
pub success: bool,
#[serde(default)]
pub sqlstate: Option<String>,
#[serde(default)]
pub sqlcode: Option<i32>,
#[serde(default)]
pub error: Option<String>,
#[serde(default)]
pub job: Option<String>,
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn pong_round_trips() {
let r = Response::Pong { id: "1".into() };
let json = serde_json::to_string(&r).unwrap();
assert_eq!(json, r#"{"type":"pong","id":"1"}"#);
}
#[test]
fn connected_round_trips() {
let r = Response::Connected {
id: "2".into(),
version: "2.3.5".into(),
job: "QZDASOINIT/QUSER/123456".into(),
};
let json = serde_json::to_string(&r).unwrap();
assert_eq!(
json,
r#"{"type":"connected","id":"2","version":"2.3.5","job":"QZDASOINIT/QUSER/123456"}"#
);
let _: Response = serde_json::from_str(&json).unwrap();
}
#[test]
fn exited_round_trips() {
let r = Response::Exited { id: "3".into() };
let json = serde_json::to_string(&r).unwrap();
assert_eq!(json, r#"{"type":"exited","id":"3"}"#);
}
#[test]
fn query_result_select_round_trips() {
let q = QueryResult {
id: "10".into(),
success: true,
has_results: true,
update_count: -1,
cont_id: Some("cur-1".into()),
is_done: false,
metadata: QueryMetaData {
column_count: 1,
columns: vec![Column {
name: "ID".into(),
label: None,
type_name: Some("INTEGER".into()),
display_size: Some(11),
precision: Some(10),
scale: Some(0),
}],
},
data: vec![{
let mut m = serde_json::Map::new();
m.insert("ID".into(), serde_json::json!(42));
m
}],
execution_time: 1.23,
};
let r = Response::QueryResult(q);
let json = serde_json::to_string(&r).unwrap();
let back: Response = serde_json::from_str(&json).unwrap();
match back {
Response::QueryResult(q2) => {
assert!(q2.has_results);
assert!(!q2.is_done);
assert_eq!(q2.data.len(), 1);
}
_ => panic!("wrong variant"),
}
}
#[test]
fn query_result_dml_round_trips() {
let q = QueryResult {
id: "11".into(),
success: true,
has_results: false,
update_count: 3,
cont_id: None,
is_done: true,
metadata: QueryMetaData::default(),
data: vec![],
execution_time: 0.5,
};
let r = Response::QueryResult(q);
let json = serde_json::to_string(&r).unwrap();
let back: Response = serde_json::from_str(&json).unwrap();
match back {
Response::QueryResult(q2) => {
assert!(!q2.has_results);
assert_eq!(q2.update_count, 3);
}
_ => panic!("wrong variant"),
}
}
#[test]
fn prepared_statement_round_trips() {
let r = Response::PreparedStatement {
id: "20".into(),
success: true,
cont_id: "stmt-7".into(),
execution_time: 0.3,
};
let json = serde_json::to_string(&r).unwrap();
let _: Response = serde_json::from_str(&json).unwrap();
}
#[test]
fn cl_result_round_trips() {
let r = Response::ClResult {
id: "30".into(),
success: true,
messages: vec![ClMessage {
id: Some("CPF1234".into()),
kind: Some("INFO".into()),
text: Some("Job started".into()),
}],
};
let json = serde_json::to_string(&r).unwrap();
let _: Response = serde_json::from_str(&json).unwrap();
}
#[test]
fn error_response_round_trips() {
let r = Response::Error(ErrorResponse {
id: "40".into(),
success: false,
sqlstate: Some("23505".into()),
sqlcode: Some(-803),
error: Some("duplicate key".into()),
job: Some("QZDASOINIT/QUSER/123456".into()),
});
let json = serde_json::to_string(&r).unwrap();
let back: Response = serde_json::from_str(&json).unwrap();
match back {
Response::Error(e) => {
assert_eq!(e.sqlstate.as_deref(), Some("23505"));
assert_eq!(e.sqlcode, Some(-803));
}
_ => panic!("wrong variant"),
}
}
}