use std::borrow::Cow as StdCow;
use crate::{
params::{Id, TwoPointZero},
Params,
};
use beef::Cow;
use http::Extensions;
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Request<'a> {
pub jsonrpc: TwoPointZero,
#[serde(borrow)]
pub id: Id<'a>,
#[serde(borrow)]
pub method: Cow<'a, str>,
#[serde(borrow)]
pub params: Option<StdCow<'a, RawValue>>,
#[serde(skip)]
pub extensions: Extensions,
}
impl<'a> Request<'a> {
pub fn new(method: Cow<'a, str>, params: Option<&'a RawValue>, id: Id<'a>) -> Self {
Self { jsonrpc: TwoPointZero, id, method, params: params.map(StdCow::Borrowed), extensions: Extensions::new() }
}
pub fn id(&self) -> Id<'a> {
self.id.clone()
}
pub fn method_name(&self) -> &str {
&self.method
}
pub fn params(&self) -> Params {
Params::new(self.params.as_ref().map(|p| RawValue::get(p)))
}
pub fn extensions(&self) -> &Extensions {
&self.extensions
}
pub fn extensions_mut(&mut self) -> &mut Extensions {
&mut self.extensions
}
}
#[derive(Deserialize, Debug, PartialEq, Eq)]
pub struct InvalidRequest<'a> {
#[serde(borrow)]
pub id: Id<'a>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Notification<'a, T> {
pub jsonrpc: TwoPointZero,
#[serde(borrow)]
pub method: Cow<'a, str>,
pub params: T,
}
impl<'a, T> Notification<'a, T> {
pub fn new(method: Cow<'a, str>, params: T) -> Self {
Self { jsonrpc: TwoPointZero, method, params }
}
}
#[derive(Serialize, Debug, Clone)]
pub struct RequestSer<'a> {
pub jsonrpc: TwoPointZero,
pub id: Id<'a>,
pub method: Cow<'a, str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<StdCow<'a, RawValue>>,
}
impl<'a> RequestSer<'a> {
pub fn borrowed(id: &'a Id<'a>, method: &'a impl AsRef<str>, params: Option<&'a RawValue>) -> Self {
Self {
jsonrpc: TwoPointZero,
id: id.clone(),
method: method.as_ref().into(),
params: params.map(StdCow::Borrowed),
}
}
pub fn owned(id: Id<'a>, method: impl Into<String>, params: Option<Box<RawValue>>) -> Self {
Self { jsonrpc: TwoPointZero, id, method: method.into().into(), params: params.map(StdCow::Owned) }
}
}
#[derive(Serialize, Debug, Clone)]
pub struct NotificationSer<'a> {
pub jsonrpc: TwoPointZero,
pub method: Cow<'a, str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<StdCow<'a, RawValue>>,
}
impl<'a> NotificationSer<'a> {
pub fn borrowed(method: &'a impl AsRef<str>, params: Option<&'a RawValue>) -> Self {
Self { jsonrpc: TwoPointZero, method: method.as_ref().into(), params: params.map(StdCow::Borrowed) }
}
pub fn owned(method: impl Into<String>, params: Option<Box<RawValue>>) -> Self {
Self { jsonrpc: TwoPointZero, method: method.into().into(), params: params.map(StdCow::Owned) }
}
}
#[cfg(test)]
mod test {
use super::{Id, InvalidRequest, Notification, NotificationSer, Request, RequestSer, StdCow, TwoPointZero};
use serde_json::value::RawValue;
fn assert_request<'a>(request: Request<'a>, id: Id<'a>, method: &str, params: Option<&str>) {
assert_eq!(request.jsonrpc, TwoPointZero);
assert_eq!(request.id, id);
assert_eq!(request.method, method);
assert_eq!(request.params.as_ref().map(|p| RawValue::get(p)), params);
}
#[test]
fn deserialize_call() {
let method = "subtract";
let params = "[42, 23]";
let test_vector = vec![
(
r#"{"jsonrpc":"2.0", "method":"subtract", "params":[42, 23], "id":1}"#,
Id::Number(1),
Some(params),
method,
),
(r#"{"jsonrpc":"2.0", "method":"subtract", "id":null}"#, Id::Null, None, method),
(r#"{"jsonrpc":"2.0", "method":"\"m", "id":null}"#, Id::Null, None, "\"m"),
];
for (ser, id, params, method) in test_vector.into_iter() {
let request = serde_json::from_str(ser).unwrap();
assert_request(request, id, method, params);
}
}
#[test]
fn deserialize_call_escaped_method_name() {
let ser = r#"{"jsonrpc":"2.0","id":1,"method":"\"m\""}"#;
let req: Request = serde_json::from_str(ser).unwrap();
assert_request(req, Id::Number(1), "\"m\"", None);
}
#[test]
fn deserialize_valid_notif_works() {
let ser = r#"{"jsonrpc":"2.0","method":"say_hello","params":[]}"#;
let dsr: Notification<&RawValue> = serde_json::from_str(ser).unwrap();
assert_eq!(dsr.method, "say_hello");
assert_eq!(dsr.jsonrpc, TwoPointZero);
}
#[test]
fn deserialize_valid_notif_escaped_method() {
let ser = r#"{"jsonrpc":"2.0","method":"\"m\"","params":[]}"#;
let dsr: Notification<&RawValue> = serde_json::from_str(ser).unwrap();
assert_eq!(dsr.method, "\"m\"");
assert_eq!(dsr.jsonrpc, TwoPointZero);
}
#[test]
fn deserialize_call_bad_id_should_fail() {
let ser = r#"{"jsonrpc":"2.0","method":"say_hello","params":[],"id":{}}"#;
assert!(serde_json::from_str::<Request>(ser).is_err());
}
#[test]
fn deserialize_invalid_request() {
let s = r#"{"id":120,"method":"my_method","params":["foo", "bar"],"extra_field":[]}"#;
let deserialized: InvalidRequest = serde_json::from_str(s).unwrap();
assert_eq!(deserialized, InvalidRequest { id: Id::Number(120) });
}
#[test]
fn serialize_call() {
let method = "subtract";
let id = Id::Number(1); let params = Some(RawValue::from_string("[42,23]".into()).unwrap());
let test_vector: &[(&'static str, Option<_>, Option<_>, &'static str)] = &[
(
r#"{"jsonrpc":"2.0","id":1,"method":"subtract","params":[42,23]}"#,
Some(id.clone()),
params.clone(),
method,
),
(r#"{"jsonrpc":"2.0","id":1,"method":"\"m"}"#, Some(id.clone()), None, "\"m"),
(r#"{"jsonrpc":"2.0","id":null,"method":"subtract","params":[42,23]}"#, None, params, method),
(r#"{"jsonrpc":"2.0","id":1,"method":"subtract"}"#, Some(id), None, method),
(r#"{"jsonrpc":"2.0","id":null,"method":"subtract"}"#, None, None, method),
];
for (ser, id, params, method) in test_vector.iter().cloned() {
let request = serde_json::to_string(&RequestSer {
jsonrpc: TwoPointZero,
method: method.into(),
id: id.unwrap_or(Id::Null),
params: params.map(StdCow::Owned),
})
.unwrap();
assert_eq!(&request, ser);
}
}
#[test]
fn serialize_notif() {
let exp = r#"{"jsonrpc":"2.0","method":"say_hello","params":["hello"]}"#;
let params = Some(RawValue::from_string(r#"["hello"]"#.into()).unwrap());
let req = NotificationSer::owned("say_hello", params);
let ser = serde_json::to_string(&req).unwrap();
assert_eq!(exp, ser);
}
#[test]
fn serialize_notif_escaped_method_name() {
let exp = r#"{"jsonrpc":"2.0","method":"\"method\""}"#;
let req = NotificationSer::owned("\"method\"", None);
let ser = serde_json::to_string(&req).unwrap();
assert_eq!(exp, ser);
}
}