use serde::{Deserialize, Serialize};
pub type RequestId = serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Request {
pub jsonrpc: String,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<RequestId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub correlation_id: Option<String>,
}
impl Request {
pub fn new(method: impl Into<String>) -> Self {
Self {
jsonrpc: "2.0".to_owned(),
method: method.into(),
params: None,
id: None,
correlation_id: Some(uuid::Uuid::new_v4().to_string()),
}
}
#[must_use]
pub fn with_params(mut self, params: serde_json::Value) -> Self {
self.params = Some(params);
self
}
#[must_use]
pub fn with_id(mut self, id: RequestId) -> Self {
self.id = Some(id);
self
}
#[must_use]
pub fn expects_response(&self) -> bool {
self.id.is_some()
}
#[must_use]
pub fn is_notification(&self) -> bool {
self.id.is_none()
}
#[must_use]
pub fn method(&self) -> &str {
&self.method
}
#[must_use]
pub fn params(&self) -> Option<&serde_json::Value> {
self.params.as_ref()
}
#[must_use]
pub fn take_params(self) -> Option<serde_json::Value> {
self.params
}
#[must_use]
pub fn id(&self) -> Option<&RequestId> {
self.id.as_ref()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Response {
pub jsonrpc: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<crate::Error>,
pub id: Option<RequestId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub correlation_id: Option<String>,
}
impl Response {
#[must_use]
pub fn success(result: serde_json::Value, id: Option<RequestId>) -> Self {
Self {
jsonrpc: "2.0".to_owned(),
result: Some(result),
error: None,
id,
correlation_id: None,
}
}
#[must_use]
pub fn error(error: crate::Error, id: Option<RequestId>) -> Self {
Self {
jsonrpc: "2.0".to_owned(),
result: None,
error: Some(error),
id,
correlation_id: None,
}
}
#[must_use]
pub fn is_success(&self) -> bool {
self.error.is_none() && self.result.is_some()
}
#[must_use]
pub fn is_error(&self) -> bool {
self.error.is_some()
}
#[must_use]
pub fn result(&self) -> Option<&serde_json::Value> {
self.result.as_ref()
}
#[must_use]
pub fn take_result(self) -> Option<serde_json::Value> {
self.result
}
#[must_use]
pub fn error_info(&self) -> Option<&crate::Error> {
self.error.as_ref()
}
#[must_use]
pub fn take_error(self) -> Option<crate::Error> {
self.error
}
#[must_use]
pub fn id(&self) -> Option<&RequestId> {
self.id.as_ref()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Error {
pub code: i32,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<serde_json::Value>,
}
impl Error {
pub fn new(code: i32, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
data: None,
}
}
#[must_use]
pub fn with_data(mut self, data: serde_json::Value) -> Self {
self.data = Some(data);
self
}
#[must_use]
pub fn is_parse_error(&self) -> bool {
self.code == crate::error_codes::PARSE_ERROR
}
#[must_use]
pub fn is_invalid_request(&self) -> bool {
self.code == crate::error_codes::INVALID_REQUEST
}
#[must_use]
pub fn is_method_not_found(&self) -> bool {
self.code == crate::error_codes::METHOD_NOT_FOUND
}
#[must_use]
pub fn is_invalid_params(&self) -> bool {
self.code == crate::error_codes::INVALID_PARAMS
}
#[must_use]
pub fn is_internal_error(&self) -> bool {
self.code == crate::error_codes::INTERNAL_ERROR
}
#[must_use]
pub fn is_server_error(&self) -> bool {
self.code >= -32099 && self.code <= -32000
}
#[must_use]
pub fn code(&self) -> i32 {
self.code
}
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
#[must_use]
pub fn data(&self) -> Option<&serde_json::Value> {
self.data.as_ref()
}
#[must_use]
pub fn sanitized_with<F>(&self, transform: F) -> Self
where
F: FnOnce(&Self) -> Self,
{
transform(self)
}
pub fn from_error_logged(error: &dyn std::error::Error) -> Self {
tracing::error!(
error = %error,
error_debug = ?error,
"internal error occurred"
);
Self {
code: crate::error_codes::INTERNAL_ERROR,
message: "Internal server error".to_owned(),
data: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Notification {
pub jsonrpc: String,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<serde_json::Value>,
}
impl Notification {
pub fn new(method: impl Into<String>) -> Self {
Self {
jsonrpc: "2.0".to_owned(),
method: method.into(),
params: None,
}
}
#[must_use]
pub fn with_params(mut self, params: serde_json::Value) -> Self {
self.params = Some(params);
self
}
#[must_use]
pub fn method(&self) -> &str {
&self.method
}
#[must_use]
pub fn params(&self) -> Option<&serde_json::Value> {
self.params.as_ref()
}
#[must_use]
pub fn take_params(self) -> Option<serde_json::Value> {
self.params
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Message {
Request(Request),
Response(Response),
Notification(Notification),
}
impl Message {
#[must_use]
pub fn is_request(&self) -> bool {
matches!(self, Message::Request(_))
}
#[must_use]
pub fn is_response(&self) -> bool {
matches!(self, Message::Response(_))
}
#[must_use]
pub fn is_notification(&self) -> bool {
matches!(self, Message::Notification(_))
}
#[must_use]
pub fn as_request(&self) -> Option<&Request> {
match self {
Message::Request(req) => Some(req),
_ => None,
}
}
#[must_use]
pub fn as_response(&self) -> Option<&Response> {
match self {
Message::Response(resp) => Some(resp),
_ => None,
}
}
#[must_use]
pub fn as_notification(&self) -> Option<&Notification> {
match self {
Message::Notification(notif) => Some(notif),
_ => None,
}
}
#[must_use]
pub fn into_request(self) -> Option<Request> {
match self {
Message::Request(req) => Some(req),
_ => None,
}
}
#[must_use]
pub fn into_response(self) -> Option<Response> {
match self {
Message::Response(resp) => Some(resp),
_ => None,
}
}
#[must_use]
pub fn into_notification(self) -> Option<Notification> {
match self {
Message::Notification(notif) => Some(notif),
_ => None,
}
}
#[must_use]
pub fn method(&self) -> Option<&str> {
match self {
Message::Request(req) => Some(&req.method),
Message::Notification(notif) => Some(¬if.method),
Message::Response(_) => None,
}
}
#[must_use]
pub fn id(&self) -> Option<&RequestId> {
match self {
Message::Request(req) => req.id.as_ref(),
Message::Response(resp) => resp.id.as_ref(),
Message::Notification(_) => None,
}
}
}
pub mod error_codes {
pub const PARSE_ERROR: i32 = -32700;
pub const INVALID_REQUEST: i32 = -32600;
pub const METHOD_NOT_FOUND: i32 = -32601;
pub const INVALID_PARAMS: i32 = -32602;
pub const INTERNAL_ERROR: i32 = -32603;
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_request_creation() {
let request = Request::new("test_method");
assert_eq!(request.jsonrpc, "2.0");
assert_eq!(request.method, "test_method");
assert!(request.params.is_none());
assert!(request.id.is_none());
assert!(request.correlation_id.is_some());
}
#[test]
fn test_request_with_params() {
let params = json!({"key": "value"});
let request = Request::new("method").with_params(params.clone());
assert_eq!(request.params(), Some(¶ms));
}
#[test]
fn test_request_with_id() {
let id = json!(42);
let request = Request::new("method").with_id(id.clone());
assert_eq!(request.id(), Some(&id));
assert!(request.expects_response());
assert!(!request.is_notification());
}
#[test]
fn test_request_notification() {
let request = Request::new("notify");
assert!(!request.expects_response());
assert!(request.is_notification());
}
#[test]
fn test_request_serialization() {
let request = Request::new("test")
.with_params(json!([1, 2, 3]))
.with_id(json!(1));
let serialized = serde_json::to_string(&request).unwrap();
let deserialized: Request = serde_json::from_str(&serialized).unwrap();
assert_eq!(request.method, deserialized.method);
assert_eq!(request.params, deserialized.params);
assert_eq!(request.id, deserialized.id);
}
#[test]
fn test_request_take_params() {
let params = json!([1, 2, 3]);
let request = Request::new("test").with_params(params.clone());
let taken = request.take_params();
assert_eq!(taken, Some(params));
}
#[test]
fn test_response_success() {
let result = json!({"status": "ok"});
let response = Response::success(result.clone(), Some(json!(1)));
assert!(response.is_success());
assert!(!response.is_error());
assert_eq!(response.result(), Some(&result));
assert!(response.error.is_none());
}
#[test]
fn test_response_error() {
let error = crate::ErrorBuilder::new(-32600, "Invalid request").build();
let response = Response::error(error.clone(), Some(json!(1)));
assert!(!response.is_success());
assert!(response.is_error());
assert!(response.result.is_none());
assert_eq!(response.error_info().unwrap().code, error.code);
}
#[test]
fn test_response_serialization() {
let response = Response::success(json!("result"), Some(json!(1)));
let serialized = serde_json::to_string(&response).unwrap();
let deserialized: Response = serde_json::from_str(&serialized).unwrap();
assert_eq!(response.result, deserialized.result);
assert_eq!(response.id, deserialized.id);
}
#[test]
fn test_response_take_result() {
let result = json!({"data": "value"});
let response = Response::success(result.clone(), Some(json!(1)));
let taken = response.take_result();
assert_eq!(taken, Some(result));
}
#[test]
fn test_response_take_error() {
let error = crate::ErrorBuilder::new(-32600, "Error").build();
let response = Response::error(error.clone(), Some(json!(1)));
let taken = response.take_error();
assert!(taken.is_some());
assert_eq!(taken.unwrap().code(), error.code());
}
#[test]
fn test_error_creation() {
let error = crate::ErrorBuilder::new(-32600, "Test error").build();
assert_eq!(error.code(), -32600);
assert_eq!(error.message(), "Test error");
assert!(error.data().is_none());
}
#[test]
fn test_error_with_data() {
let data = json!({"details": "more info"});
let error = crate::ErrorBuilder::new(-32000, "Error")
.data(data.clone())
.build();
assert_eq!(error.data(), Some(&data));
}
#[test]
fn test_error_type_checks() {
assert!(
crate::ErrorBuilder::new(error_codes::PARSE_ERROR, "msg")
.build()
.is_parse_error()
);
assert!(
crate::ErrorBuilder::new(error_codes::INVALID_REQUEST, "msg")
.build()
.is_invalid_request()
);
assert!(
crate::ErrorBuilder::new(error_codes::METHOD_NOT_FOUND, "msg")
.build()
.is_method_not_found()
);
assert!(
crate::ErrorBuilder::new(error_codes::INVALID_PARAMS, "msg")
.build()
.is_invalid_params()
);
assert!(
crate::ErrorBuilder::new(error_codes::INTERNAL_ERROR, "msg")
.build()
.is_internal_error()
);
assert!(
crate::ErrorBuilder::new(-32001, "msg")
.build()
.is_server_error()
);
assert!(
!crate::ErrorBuilder::new(-32700, "msg")
.build()
.is_server_error()
);
}
#[test]
fn test_error_sanitization() {
let error = crate::ErrorBuilder::new(
-32603,
"Internal database connection failed: postgres://user:pass@host",
)
.build();
let sanitized = error
.sanitized_with(|e| crate::ErrorBuilder::new(e.code, "Internal server error").build());
assert_eq!(sanitized.code(), error.code());
assert_eq!(sanitized.message(), "Internal server error");
assert!(!sanitized.message().contains("postgres"));
}
#[test]
fn test_error_from_std_error() {
let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let error = Error::from_error_logged(&io_error);
assert_eq!(error.code(), error_codes::INTERNAL_ERROR);
assert_eq!(error.message(), "Internal server error");
}
#[test]
fn test_notification_creation() {
let notification = Notification::new("notify");
assert_eq!(notification.jsonrpc, "2.0");
assert_eq!(notification.method(), "notify");
assert!(notification.params().is_none());
}
#[test]
fn test_notification_with_params() {
let params = json!({"event": "update"});
let notification = Notification::new("notify").with_params(params.clone());
assert_eq!(notification.params(), Some(¶ms));
}
#[test]
fn test_notification_serialization() {
let notification = Notification::new("event").with_params(json!([1, 2]));
let serialized = serde_json::to_string(¬ification).unwrap();
let deserialized: Notification = serde_json::from_str(&serialized).unwrap();
assert_eq!(notification.method, deserialized.method);
assert_eq!(notification.params, deserialized.params);
}
#[test]
fn test_notification_take_params() {
let params = json!({"event": "data"});
let notification = Notification::new("notify").with_params(params.clone());
let taken = notification.take_params();
assert_eq!(taken, Some(params));
}
#[test]
fn test_message_request_variant() {
let request = Request::new("test");
let message = Message::Request(request);
assert!(message.is_request());
assert!(!message.is_response());
assert!(!message.is_notification());
}
#[test]
fn test_message_response_variant() {
let response = Response::success(json!("ok"), Some(json!(1)));
let message = Message::Response(response);
assert!(!message.is_request());
assert!(message.is_response());
assert!(!message.is_notification());
}
#[test]
fn test_message_notification_variant() {
let notification = Notification::new("event");
let message = Message::Notification(notification);
assert!(!message.is_request());
assert!(!message.is_response());
assert!(message.is_notification());
}
#[test]
fn test_message_serialization_request() {
let request = Request::new("test").with_id(json!(1));
let message = Message::Request(request);
let serialized = serde_json::to_string(&message).unwrap();
let deserialized: Message = serde_json::from_str(&serialized).unwrap();
assert!(deserialized.is_request());
}
#[test]
fn test_message_as_request() {
let request = Request::new("test");
let message = Message::Request(request.clone());
assert!(message.as_request().is_some());
assert_eq!(message.as_request().unwrap().method, "test");
assert!(message.as_response().is_none());
assert!(message.as_notification().is_none());
}
#[test]
fn test_message_as_response() {
let response = Response::success(json!(42), Some(json!(1)));
let message = Message::Response(response);
assert!(message.as_response().is_some());
assert!(message.as_request().is_none());
assert!(message.as_notification().is_none());
}
#[test]
fn test_message_as_notification() {
let notification = Notification::new("event");
let message = Message::Notification(notification);
assert!(message.as_notification().is_some());
assert_eq!(message.as_notification().unwrap().method, "event");
assert!(message.as_request().is_none());
assert!(message.as_response().is_none());
}
#[test]
fn test_message_into_request() {
let request = Request::new("test");
let message = Message::Request(request);
let extracted = message.into_request();
assert!(extracted.is_some());
assert_eq!(extracted.unwrap().method, "test");
}
#[test]
fn test_message_into_response() {
let response = Response::success(json!(true), Some(json!(1)));
let message = Message::Response(response);
let extracted = message.into_response();
assert!(extracted.is_some());
assert!(extracted.unwrap().is_success());
}
#[test]
fn test_message_into_notification() {
let notification = Notification::new("notify");
let message = Message::Notification(notification);
let extracted = message.into_notification();
assert!(extracted.is_some());
assert_eq!(extracted.unwrap().method, "notify");
}
#[test]
fn test_message_into_wrong_type() {
let message = Message::Request(Request::new("test"));
assert!(message.clone().into_response().is_none());
assert!(message.into_notification().is_none());
}
#[test]
fn test_message_method_from_request() {
let request = Request::new("my_method");
let message = Message::Request(request);
assert_eq!(message.method(), Some("my_method"));
}
#[test]
fn test_message_method_from_notification() {
let notification = Notification::new("event_method");
let message = Message::Notification(notification);
assert_eq!(message.method(), Some("event_method"));
}
#[test]
fn test_message_method_from_response() {
let response = Response::success(json!(1), Some(json!(1)));
let message = Message::Response(response);
assert_eq!(message.method(), None);
}
#[test]
fn test_message_id_from_request() {
let request = Request::new("test").with_id(json!(123));
let message = Message::Request(request);
assert_eq!(message.id(), Some(&json!(123)));
}
#[test]
fn test_message_id_from_response() {
let response = Response::success(json!(1), Some(json!("abc")));
let message = Message::Response(response);
assert_eq!(message.id(), Some(&json!("abc")));
}
#[test]
fn test_message_id_from_notification() {
let notification = Notification::new("event");
let message = Message::Notification(notification);
assert_eq!(message.id(), None);
}
#[test]
fn test_message_id_none() {
let request = Request::new("test"); let message = Message::Request(request);
assert_eq!(message.id(), None);
}
#[test]
fn test_request_method_accessor() {
let request = Request::new("get_data");
assert_eq!(request.method(), "get_data");
}
#[test]
fn test_request_params_accessor() {
let params = json!({"key": "value"});
let request = Request::new("test").with_params(params.clone());
assert_eq!(request.params(), Some(¶ms));
}
#[test]
fn test_request_params_none() {
let request = Request::new("test");
assert_eq!(request.params(), None);
}
#[test]
fn test_request_id_accessor() {
let request = Request::new("test").with_id(json!(999));
assert_eq!(request.id(), Some(&json!(999)));
}
#[test]
fn test_response_result_accessor() {
let result = json!({"data": "value"});
let response = Response::success(result.clone(), Some(json!(1)));
assert_eq!(response.result(), Some(&result));
}
#[test]
fn test_response_error_info() {
let error = crate::ErrorBuilder::new(-32600, "Invalid Request").build();
let response = Response::error(error.clone(), Some(json!(1)));
assert!(response.error_info().is_some());
assert_eq!(response.error_info().unwrap().code, -32600);
}
#[test]
fn test_response_id_accessor() {
let response = Response::success(json!(1), Some(json!("req-id")));
assert_eq!(response.id(), Some(&json!("req-id")));
}
#[test]
fn test_error_code_accessor() {
let error = crate::ErrorBuilder::new(-32001, "Custom error").build();
assert_eq!(error.code(), -32001);
}
#[test]
fn test_error_message_accessor() {
let error = crate::ErrorBuilder::new(-32002, "Test message").build();
assert_eq!(error.message(), "Test message");
}
#[test]
fn test_error_data_accessor() {
let data = json!({"detail": "info"});
let error = crate::ErrorBuilder::new(-32003, "Error")
.data(data.clone())
.build();
assert_eq!(error.data(), Some(&data));
}
#[test]
fn test_error_data_none() {
let error = crate::ErrorBuilder::new(-32004, "Error").build();
assert_eq!(error.data(), None);
}
#[test]
fn test_error_is_invalid_params() {
let error = crate::ErrorBuilder::new(error_codes::INVALID_PARAMS, "Invalid").build();
assert!(error.is_invalid_params());
assert!(!error.is_parse_error());
}
#[test]
fn test_error_is_internal_error() {
let error = crate::ErrorBuilder::new(error_codes::INTERNAL_ERROR, "Internal").build();
assert!(error.is_internal_error());
assert!(!error.is_server_error());
}
#[test]
fn test_error_is_server_error() {
let error = crate::ErrorBuilder::new(-32050, "Server error").build();
assert!(error.is_server_error());
assert!(!error.is_internal_error());
let error_min = crate::ErrorBuilder::new(-32099, "Min").build();
assert!(error_min.is_server_error());
let error_max = crate::ErrorBuilder::new(-32000, "Max").build();
assert!(error_max.is_server_error());
let error_out = crate::ErrorBuilder::new(-31999, "Out of range").build();
assert!(!error_out.is_server_error());
}
#[test]
fn test_error_sanitized_with() {
let error =
crate::ErrorBuilder::new(-32603, "Database connection failed: host=db.internal")
.build();
let sanitized = error.sanitized_with(|e| {
crate::ErrorBuilder::new(e.code(), "Internal server error").build()
});
assert_eq!(sanitized.code(), -32603);
assert_eq!(sanitized.message(), "Internal server error");
assert!(!sanitized.message().contains("db.internal"));
}
#[test]
fn test_notification_method_accessor() {
let notification = Notification::new("user_logged_in");
assert_eq!(notification.method(), "user_logged_in");
}
#[test]
fn test_notification_params_accessor() {
let params = json!({"user_id": 123});
let notification = Notification::new("event").with_params(params.clone());
assert_eq!(notification.params(), Some(¶ms));
}
#[test]
fn test_notification_params_none() {
let notification = Notification::new("ping");
assert_eq!(notification.params(), None);
}
#[test]
fn test_error_code_constants() {
assert_eq!(error_codes::PARSE_ERROR, -32700);
assert_eq!(error_codes::INVALID_REQUEST, -32600);
assert_eq!(error_codes::METHOD_NOT_FOUND, -32601);
assert_eq!(error_codes::INVALID_PARAMS, -32602);
assert_eq!(error_codes::INTERNAL_ERROR, -32603);
}
#[test]
fn test_error_from_std_error_logging() {
use std::io;
let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
let error = Error::from_error_logged(&io_error);
assert_eq!(error.code(), error_codes::INTERNAL_ERROR);
assert_eq!(error.message(), "Internal server error");
}
#[test]
fn test_request_correlation_id() {
let request = Request::new("test");
assert!(request.correlation_id.is_some());
}
#[test]
fn test_response_with_correlation_id() {
let mut response = Response::success(json!(1), Some(json!(1)));
response.correlation_id = Some("custom-id".to_string());
assert_eq!(response.correlation_id, Some("custom-id".to_string()));
}
}