use crate::RpcRequest;
use crate::rpc_message::rpc_request_parsing_error::RpcRequestParsingError;
use crate::rpc_message::support::{extract_value, parse_method, parse_params, validate_version};
use crate::support::get_json_type;
use serde::ser::SerializeStruct;
use serde::{Deserialize, Serialize, Serializer};
use serde_json::Value;
#[derive(Deserialize, Debug, Clone, PartialEq)]
pub struct RpcNotification {
pub method: String,
pub params: Option<Value>,
}
impl RpcNotification {
pub fn from_value(value: Value) -> Result<RpcNotification, RpcRequestParsingError> {
let value_type = get_json_type(&value);
let Value::Object(mut obj) = value else {
return Err(RpcRequestParsingError::RequestInvalidType {
actual_type: value_type.to_string(),
});
};
let version_val = extract_value(&mut obj, "jsonrpc");
if let Err(version_result) = validate_version(version_val) {
let method_val = extract_value(&mut obj, "method");
let method = method_val.and_then(|v| v.as_str().map(|s| s.to_string()));
return match version_result {
Some(v) => Err(RpcRequestParsingError::VersionInvalid {
id: None, method,
version: v,
}),
None => Err(RpcRequestParsingError::VersionMissing {
id: None, method,
}),
};
}
let method_val = extract_value(&mut obj, "method");
let method = match parse_method(method_val) {
Ok(m) => m,
Err(method_result) => {
return match method_result {
Some(m) => Err(RpcRequestParsingError::MethodInvalidType {
id: None, method: m,
}),
None => Err(RpcRequestParsingError::MethodMissing { id: None }), };
}
};
let params_val = extract_value(&mut obj, "params");
let params = parse_params(params_val)?;
if let Some(id_val) = extract_value(&mut obj, "id") {
return Err(RpcRequestParsingError::NotificationHasId {
method: Some(method),
id: id_val,
});
}
Ok(RpcNotification { method, params })
}
}
impl Serialize for RpcNotification {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut field_count = 2;
if self.params.is_some() {
field_count += 1;
}
let mut state = serializer.serialize_struct("RpcNotification", field_count)?;
state.serialize_field("jsonrpc", "2.0")?;
state.serialize_field("method", &self.method)?;
if let Some(params) = &self.params {
state.serialize_field("params", params)?;
}
state.end()
}
}
impl From<RpcRequest> for RpcNotification {
fn from(request: RpcRequest) -> Self {
RpcNotification {
method: request.method,
params: request.params,
}
}
}
impl TryFrom<Value> for RpcNotification {
type Error = RpcRequestParsingError;
fn try_from(value: Value) -> Result<RpcNotification, RpcRequestParsingError> {
RpcNotification::from_value(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rpc_message::RpcRequestParsingError;
use serde_json::{json, to_value};
type Result<T> = core::result::Result<T, Box<dyn std::error::Error>>;
fn notif_value_ok_params_some() -> Value {
json!({
"jsonrpc": "2.0",
"method": "updateState",
"params": {"value": 123}
})
}
fn notif_value_ok_params_none() -> Value {
json!({
"jsonrpc": "2.0",
"method": "ping"
})
}
fn notif_value_ok_params_arr() -> Value {
json!({
"jsonrpc": "2.0",
"method": "notifyUsers",
"params": ["user1", "user2"]
})
}
fn notif_value_fail_id_present() -> Value {
json!({
"jsonrpc": "2.0",
"id": 888, "method": "updateState",
"params": {"value": 123}
})
}
fn notif_value_fail_version_missing() -> Value {
json!({
"method": "updateState"
})
}
fn notif_value_fail_version_invalid() -> Value {
json!({
"jsonrpc": "1.0", "method": "updateState"
})
}
fn notif_value_fail_method_missing() -> Value {
json!({
"jsonrpc": "2.0"
})
}
fn notif_value_fail_method_invalid() -> Value {
json!({
"jsonrpc": "2.0",
"method": 123 })
}
fn notif_value_fail_params_invalid() -> Value {
json!({
"jsonrpc": "2.0",
"method": "update",
"params": "not-array-or-object" })
}
#[test]
fn test_rpc_notification_serialize_ok_params_some() -> Result<()> {
let notif = RpcNotification {
method: "updateState".to_string(),
params: Some(json!({"value": 123})),
};
let value = to_value(notif)?;
assert_eq!(value, notif_value_ok_params_some());
Ok(())
}
#[test]
fn test_rpc_notification_serialize_ok_params_none() -> Result<()> {
let notif = RpcNotification {
method: "ping".to_string(),
params: None,
};
let value = to_value(notif)?;
assert_eq!(value, notif_value_ok_params_none());
Ok(())
}
#[test]
fn test_rpc_notification_serialize_ok_params_arr() -> Result<()> {
let notif = RpcNotification {
method: "notifyUsers".to_string(),
params: Some(json!(["user1", "user2"])),
};
let value = to_value(notif)?;
assert_eq!(value, notif_value_ok_params_arr());
Ok(())
}
#[test]
fn test_rpc_notification_from_value_ok_params_some() -> Result<()> {
let value = notif_value_ok_params_some();
let expected = RpcNotification {
method: "updateState".to_string(),
params: Some(json!({"value": 123})),
};
let notification = RpcNotification::from_value(value)?;
assert_eq!(notification, expected);
Ok(())
}
#[test]
fn test_rpc_notification_from_value_ok_params_none() -> Result<()> {
let value = notif_value_ok_params_none();
let expected = RpcNotification {
method: "ping".to_string(),
params: None,
};
let notification = RpcNotification::from_value(value)?;
assert_eq!(notification, expected);
Ok(())
}
#[test]
fn test_rpc_notification_from_value_ok_params_arr() -> Result<()> {
let value = notif_value_ok_params_arr();
let expected = RpcNotification {
method: "notifyUsers".to_string(),
params: Some(json!(["user1", "user2"])),
};
let notification = RpcNotification::from_value(value)?;
assert_eq!(notification, expected);
Ok(())
}
#[test]
fn test_rpc_notification_from_value_fail_id_present() -> Result<()> {
let value = notif_value_fail_id_present();
let result = RpcNotification::from_value(value);
assert!(matches!(
result,
Err(RpcRequestParsingError::NotificationHasId { method: Some(_), id: _ })
));
if let Err(RpcRequestParsingError::NotificationHasId { method, id }) = result {
assert_eq!(method.unwrap(), "updateState");
assert_eq!(id, json!(888));
} else {
panic!("Expected NotificationHasId error");
}
Ok(())
}
#[test]
fn test_rpc_notification_from_value_fail_version_missing() -> Result<()> {
let value = notif_value_fail_version_missing();
let result = RpcNotification::from_value(value);
assert!(matches!(
result,
Err(RpcRequestParsingError::VersionMissing {
id: None,
method: Some(_)
})
));
if let Err(RpcRequestParsingError::VersionMissing { id, method }) = result {
assert!(id.is_none());
assert_eq!(method.unwrap(), "updateState");
} else {
panic!("Expected VersionMissing error");
}
Ok(())
}
#[test]
fn test_rpc_notification_from_value_fail_version_invalid() -> Result<()> {
let value = notif_value_fail_version_invalid();
let result = RpcNotification::from_value(value);
assert!(matches!(
result,
Err(RpcRequestParsingError::VersionInvalid {
id: None,
method: Some(_),
version: _
})
));
if let Err(RpcRequestParsingError::VersionInvalid { id, method, version }) = result {
assert!(id.is_none());
assert_eq!(method.unwrap(), "updateState");
assert_eq!(version, json!("1.0"));
} else {
panic!("Expected VersionInvalid error");
}
Ok(())
}
#[test]
fn test_rpc_notification_from_value_fail_method_missing() -> Result<()> {
let value = notif_value_fail_method_missing();
let result = RpcNotification::from_value(value);
assert!(matches!(
result,
Err(RpcRequestParsingError::MethodMissing { id: None })
));
Ok(())
}
#[test]
fn test_rpc_notification_from_value_fail_method_invalid() -> Result<()> {
let value = notif_value_fail_method_invalid();
let result = RpcNotification::from_value(value);
assert!(matches!(
result,
Err(RpcRequestParsingError::MethodInvalidType { id: None, method: _ })
));
if let Err(RpcRequestParsingError::MethodInvalidType { id, method }) = result {
assert!(id.is_none());
assert_eq!(method, json!(123));
} else {
panic!("Expected MethodInvalidType error");
}
Ok(())
}
#[test]
fn test_rpc_notification_from_value_fail_params_invalid() -> Result<()> {
let value = notif_value_fail_params_invalid();
let result = RpcNotification::from_value(value);
assert!(matches!(
result,
Err(RpcRequestParsingError::ParamsInvalidType { actual_type: _ })
));
if let Err(RpcRequestParsingError::ParamsInvalidType { actual_type }) = result {
assert_eq!(actual_type, "String");
} else {
panic!("Expected ParamsInvalidType error");
}
Ok(())
}
#[test]
fn test_rpc_notification_from_value_fail_not_object() -> Result<()> {
let value = json!("not an object");
let result = RpcNotification::from_value(value);
assert!(matches!(
result,
Err(RpcRequestParsingError::RequestInvalidType { actual_type: _ })
));
if let Err(RpcRequestParsingError::RequestInvalidType { actual_type }) = result {
assert_eq!(actual_type, "String");
} else {
panic!("Expected RequestInvalidType error");
}
Ok(())
}
}