use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use super::{NotificationPayload, Params};
pub const JSON_RPC_VERSION: &str = "2.0";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(bound = "I: Serialize + DeserializeOwned")]
pub struct WsResponseResult<I> {
pub status: String,
#[serde(rename = "subId")]
pub sub_id: I,
}
#[derive(Debug, Clone, Serialize)]
#[serde(bound(serialize = "T: Serialize + DeserializeOwned, I: Serialize"))]
pub struct NotificationInner<T, I>
where
T: Clone,
{
#[serde(rename = "subId")]
pub sub_id: I,
pub payload: NotificationPayload<T>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(bound = "I: Serialize + DeserializeOwned")]
pub struct RawNotificationInner<I> {
#[serde(rename = "subId")]
pub sub_id: I,
pub payload: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(bound = "I: Serialize + DeserializeOwned")]
pub struct WsUnsubscribeRequest<I> {
#[serde(rename = "subId")]
pub sub_id: I,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "method", content = "params")]
#[serde(bound = "I: Serialize + DeserializeOwned")]
pub enum WsMethodRequest<I> {
Subscribe(Params<I>),
Unsubscribe(WsUnsubscribeRequest<I>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(bound = "I: Serialize + DeserializeOwned")]
pub struct WsRequest<I> {
pub jsonrpc: String,
#[serde(flatten)]
pub method: WsMethodRequest<I>,
pub id: usize,
}
impl<I> From<(WsMethodRequest<I>, usize)> for WsRequest<I> {
fn from((method, id): (WsMethodRequest<I>, usize)) -> Self {
WsRequest {
jsonrpc: JSON_RPC_VERSION.to_owned(),
method,
id,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WsNotification<T> {
pub jsonrpc: String,
pub method: String,
pub params: T,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct WsErrorBody {
pub code: i32,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(bound = "I: Serialize + DeserializeOwned")]
pub struct WsResponse<I> {
pub jsonrpc: String,
pub result: WsResponseResult<I>,
pub id: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WsErrorResponse {
pub jsonrpc: String,
pub error: WsErrorBody,
pub id: usize,
}
#[derive(Debug, Clone, Serialize)]
#[serde(bound(serialize = "I: Serialize + DeserializeOwned"))]
#[serde(untagged)]
pub enum WsMessageOrResponse<I> {
Response(WsResponse<I>),
ErrorResponse(WsErrorResponse),
Notification(Box<WsNotification<NotificationInner<String, I>>>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(bound = "I: Serialize + DeserializeOwned")]
#[serde(untagged)]
pub enum RawWsMessageOrResponse<I> {
Response(WsResponse<I>),
ErrorResponse(WsErrorResponse),
Notification(Box<WsNotification<RawNotificationInner<I>>>),
}
impl<I> From<(usize, Result<WsResponseResult<I>, WsErrorBody>)> for WsMessageOrResponse<I> {
fn from((id, result): (usize, Result<WsResponseResult<I>, WsErrorBody>)) -> Self {
match result {
Ok(result) => WsMessageOrResponse::Response(WsResponse {
jsonrpc: JSON_RPC_VERSION.to_owned(),
result,
id,
}),
Err(err) => WsMessageOrResponse::ErrorResponse(WsErrorResponse {
jsonrpc: JSON_RPC_VERSION.to_owned(),
error: err,
id,
}),
}
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn response_result_uses_shared_nut17_acknowledgement_shape() {
let encoded = json!({
"jsonrpc": "2.0",
"result": {
"status": "OK",
"subId": "sub-id"
},
"id": 7
});
let decoded: WsResponse<String> =
serde_json::from_value(encoded.clone()).expect("NUT-17 response");
assert_eq!(decoded.id, 7);
assert_eq!(
decoded.result,
WsResponseResult {
status: "OK".to_string(),
sub_id: "sub-id".to_string(),
}
);
assert_eq!(
serde_json::to_value(decoded).expect("serialized NUT-17 response"),
encoded
);
}
#[test]
fn raw_ws_message_deserializes_notification_payload_as_json() {
let encoded = r#"{
"jsonrpc": "2.0",
"method": "subscribe",
"params": {
"subId": "sub-id",
"payload": {
"quote": "quote-id",
"method": "bolt12"
}
}
}"#;
let decoded: RawWsMessageOrResponse<String> =
serde_json::from_str(encoded).expect("raw websocket notification");
match decoded {
RawWsMessageOrResponse::Notification(notification) => {
assert_eq!(notification.params.sub_id, "sub-id");
assert_eq!(notification.params.payload["quote"], "quote-id");
}
other => panic!("expected notification, got {:?}", other),
}
}
}