use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::error::ControlError;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum RequestId {
Number(i64),
String(String),
Null,
}
impl From<i64> for RequestId {
fn from(n: i64) -> Self {
RequestId::Number(n)
}
}
impl From<&str> for RequestId {
fn from(s: &str) -> Self {
RequestId::String(s.to_string())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct JsonRpcRequest {
pub jsonrpc: String,
pub id: RequestId,
pub method: String,
pub params: Value,
}
impl JsonRpcRequest {
pub fn new(id: RequestId, method: impl Into<String>, params: Value) -> JsonRpcRequest {
JsonRpcRequest {
jsonrpc: "2.0".to_string(),
id,
method: method.into(),
params,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct JsonRpcResponse {
pub jsonrpc: String,
pub id: RequestId,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub result: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub error: Option<ControlError>,
}
impl JsonRpcResponse {
pub fn success(id: RequestId, result: Value) -> JsonRpcResponse {
JsonRpcResponse {
jsonrpc: "2.0".to_string(),
id,
result: Some(result),
error: None,
}
}
pub fn error(id: RequestId, error: ControlError) -> JsonRpcResponse {
JsonRpcResponse {
jsonrpc: "2.0".to_string(),
id,
result: None,
error: Some(error),
}
}
pub fn into_result(self) -> Result<Value, ControlError> {
match (self.result, self.error) {
(Some(v), _) => Ok(v),
(None, Some(e)) => Err(e),
(None, None) => Err(ControlError::of(
crate::error::ControlErrorCode::ControlError,
"malformed JSON-RPC response: neither result nor error present",
)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::ControlErrorCode;
use serde_json::json;
#[test]
fn request_serializes_to_the_canonical_shape() {
let req = JsonRpcRequest::new(1.into(), "control.status", json!({}));
let v = serde_json::to_value(&req).unwrap();
assert_eq!(
v,
json!({"jsonrpc":"2.0","id":1,"method":"control.status","params":{}})
);
}
#[test]
fn request_id_supports_number_string_and_null() {
assert_eq!(serde_json::to_value(RequestId::from(7)).unwrap(), json!(7));
assert_eq!(
serde_json::to_value(RequestId::from("abc")).unwrap(),
json!("abc")
);
assert_eq!(serde_json::to_value(RequestId::Null).unwrap(), json!(null));
assert_eq!(
serde_json::from_value::<RequestId>(json!("z")).unwrap(),
RequestId::String("z".into())
);
}
#[test]
fn success_response_omits_the_error_field() {
let resp = JsonRpcResponse::success(1.into(), json!({"ok":true}));
let v = serde_json::to_value(&resp).unwrap();
assert_eq!(v, json!({"jsonrpc":"2.0","id":1,"result":{"ok":true}}));
assert_eq!(resp.into_result().unwrap(), json!({"ok":true}));
}
#[test]
fn error_response_omits_the_result_field_and_unwraps_to_err() {
let resp = JsonRpcResponse::error(
1.into(),
ControlError::of(ControlErrorCode::Unauthorized, "no token"),
);
let v = serde_json::to_value(&resp).unwrap();
assert_eq!(v["error"]["data"]["code"], "UNAUTHORIZED");
assert!(v.get("result").is_none());
assert!(resp.into_result().is_err());
}
#[test]
fn a_response_with_neither_field_is_a_control_error() {
let resp = JsonRpcResponse {
jsonrpc: "2.0".into(),
id: RequestId::Number(1),
result: None,
error: None,
};
let err = resp.into_result().unwrap_err();
assert_eq!(err.code_enum(), Some(ControlErrorCode::ControlError));
}
}