use crate::error::JsonRpcError;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
pub const JSONRPC_VERSION: &str = "2.0";
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(untagged)]
pub enum RequestId {
Number(u64),
String(String),
Null,
}
impl RequestId {
#[must_use]
pub const fn number(id: u64) -> Self {
Self::Number(id)
}
#[must_use]
pub fn string(id: impl Into<String>) -> Self {
Self::String(id.into())
}
}
impl From<u64> for RequestId {
fn from(id: u64) -> Self {
Self::Number(id)
}
}
impl From<String> for RequestId {
fn from(id: String) -> Self {
Self::String(id)
}
}
impl From<&str> for RequestId {
fn from(id: &str) -> Self {
Self::String(id.to_string())
}
}
impl std::fmt::Display for RequestId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Number(n) => write!(f, "{n}"),
Self::String(s) => write!(f, "{s}"),
Self::Null => write!(f, "null"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Request {
pub jsonrpc: Cow<'static, str>,
pub id: RequestId,
pub method: Cow<'static, str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<serde_json::Value>,
}
impl Request {
#[must_use]
pub fn new(method: impl Into<Cow<'static, str>>, id: impl Into<RequestId>) -> Self {
Self {
jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
id: id.into(),
method: method.into(),
params: None,
}
}
#[must_use]
pub fn with_params(
method: impl Into<Cow<'static, str>>,
id: impl Into<RequestId>,
params: serde_json::Value,
) -> Self {
Self {
jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
id: id.into(),
method: method.into(),
params: Some(params),
}
}
#[must_use]
pub fn params(mut self, params: serde_json::Value) -> Self {
self.params = Some(params);
self
}
#[must_use]
pub fn method(&self) -> &str {
&self.method
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Response {
pub jsonrpc: Cow<'static, str>,
pub id: RequestId,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<JsonRpcError>,
}
impl Response {
#[must_use]
pub fn success(id: impl Into<RequestId>, result: serde_json::Value) -> Self {
Self {
jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
id: id.into(),
result: Some(result),
error: None,
}
}
#[must_use]
pub fn error(id: impl Into<RequestId>, error: JsonRpcError) -> Self {
Self {
jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
id: id.into(),
result: None,
error: Some(error),
}
}
#[must_use]
pub const fn is_success(&self) -> bool {
self.result.is_some() && self.error.is_none()
}
#[must_use]
pub const fn is_error(&self) -> bool {
self.error.is_some()
}
pub fn into_result(self) -> Result<serde_json::Value, JsonRpcError> {
if let Some(error) = self.error {
Err(error)
} else {
self.result.ok_or_else(|| JsonRpcError {
code: -32603,
message: "Response contained neither result nor error".to_string(),
data: None,
})
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Notification {
pub jsonrpc: Cow<'static, str>,
pub method: Cow<'static, str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<serde_json::Value>,
}
impl Notification {
#[must_use]
pub fn new(method: impl Into<Cow<'static, str>>) -> Self {
Self {
jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
method: method.into(),
params: None,
}
}
#[must_use]
pub fn with_params(method: impl Into<Cow<'static, str>>, params: serde_json::Value) -> Self {
Self {
jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
method: method.into(),
params: Some(params),
}
}
#[must_use]
pub fn params(mut self, params: serde_json::Value) -> Self {
self.params = Some(params);
self
}
#[must_use]
pub fn method(&self) -> &str {
&self.method
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Message {
Request(Request),
Response(Response),
Notification(Notification),
}
impl Message {
#[must_use]
pub fn method(&self) -> Option<&str> {
match self {
Self::Request(r) => Some(&r.method),
Self::Notification(n) => Some(&n.method),
Self::Response(_) => None,
}
}
#[must_use]
pub const fn id(&self) -> Option<&RequestId> {
match self {
Self::Request(r) => Some(&r.id),
Self::Response(r) => Some(&r.id),
Self::Notification(_) => None,
}
}
#[must_use]
pub const fn is_request(&self) -> bool {
matches!(self, Self::Request(_))
}
#[must_use]
pub const fn is_response(&self) -> bool {
matches!(self, Self::Response(_))
}
#[must_use]
pub const fn is_notification(&self) -> bool {
matches!(self, Self::Notification(_))
}
#[must_use]
pub const fn as_request(&self) -> Option<&Request> {
match self {
Self::Request(r) => Some(r),
_ => None,
}
}
#[must_use]
pub const fn as_response(&self) -> Option<&Response> {
match self {
Self::Response(r) => Some(r),
_ => None,
}
}
#[must_use]
pub const fn as_notification(&self) -> Option<&Notification> {
match self {
Self::Notification(n) => Some(n),
_ => None,
}
}
}
impl From<Request> for Message {
fn from(r: Request) -> Self {
Self::Request(r)
}
}
impl From<Response> for Message {
fn from(r: Response) -> Self {
Self::Response(r)
}
}
impl From<Notification> for Message {
fn from(n: Notification) -> Self {
Self::Notification(n)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ProgressToken {
Number(u64),
String(String),
}
impl std::fmt::Display for ProgressToken {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Number(n) => write!(f, "{n}"),
Self::String(s) => write!(f, "{s}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Cursor(pub String);
impl Cursor {
#[must_use]
pub fn new(cursor: impl Into<String>) -> Self {
Self(cursor.into())
}
}
impl std::fmt::Display for Cursor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for Cursor {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for Cursor {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn request_id_null_round_trips() {
assert_eq!(serde_json::to_string(&RequestId::Null).unwrap(), "null");
assert_eq!(
serde_json::from_str::<RequestId>("null").unwrap(),
RequestId::Null
);
assert_eq!(
serde_json::from_str::<RequestId>("7").unwrap(),
RequestId::Number(7)
);
assert_eq!(
serde_json::from_str::<RequestId>("\"abc\"").unwrap(),
RequestId::String("abc".to_string())
);
}
#[test]
fn test_request_serialization() -> Result<(), Box<dyn std::error::Error>> {
let request = Request::new("tools/list", 1u64);
let json = serde_json::to_string(&request)?;
assert!(json.contains("\"jsonrpc\":\"2.0\""));
assert!(json.contains("\"method\":\"tools/list\""));
assert!(json.contains("\"id\":1"));
Ok(())
}
#[test]
fn test_request_with_params() -> Result<(), Box<dyn std::error::Error>> {
let request = Request::with_params(
"tools/call",
1u64,
serde_json::json!({"name": "search", "arguments": {"query": "test"}}),
);
let json = serde_json::to_string(&request)?;
assert!(json.contains("\"params\""));
assert!(json.contains("\"name\":\"search\""));
Ok(())
}
#[test]
fn test_response_success() -> Result<(), Box<dyn std::error::Error>> {
let response = Response::success(1u64, serde_json::json!({"tools": []}));
assert!(response.is_success());
assert!(!response.is_error());
let result = response
.into_result()
.map_err(|e| format!("Error: {}", e.message))?;
assert!(result.get("tools").is_some());
Ok(())
}
#[test]
fn test_response_error() {
let error = JsonRpcError {
code: -32601,
message: "Method not found".to_string(),
data: None,
};
let response = Response::error(1u64, error);
assert!(!response.is_success());
assert!(response.is_error());
let err = response.into_result().unwrap_err();
assert_eq!(err.code, -32601);
}
#[test]
fn test_notification() -> Result<(), Box<dyn std::error::Error>> {
let notification = Notification::with_params(
"notifications/progress",
serde_json::json!({"progress": 50, "total": 100}),
);
let json = serde_json::to_string(¬ification)?;
assert!(json.contains("\"method\":\"notifications/progress\""));
assert!(!json.contains("\"id\"")); Ok(())
}
#[test]
fn test_message_parsing() -> Result<(), Box<dyn std::error::Error>> {
let json = r#"{"jsonrpc":"2.0","id":1,"method":"test"}"#;
let msg: Message = serde_json::from_str(json)?;
assert!(msg.is_request());
assert_eq!(msg.method(), Some("test"));
let json = r#"{"jsonrpc":"2.0","id":1,"result":{}}"#;
let msg: Message = serde_json::from_str(json)?;
assert!(msg.is_response());
let json = r#"{"jsonrpc":"2.0","method":"notify"}"#;
let msg: Message = serde_json::from_str(json)?;
assert!(msg.is_notification());
Ok(())
}
#[test]
fn test_request_id_types() -> Result<(), Box<dyn std::error::Error>> {
let request = Request::new("test", 42u64);
let json = serde_json::to_string(&request)?;
assert!(json.contains("\"id\":42"));
let request = Request::new("test", "req-001");
let json = serde_json::to_string(&request)?;
assert!(json.contains("\"id\":\"req-001\""));
Ok(())
}
}