neptune-rpc-api 0.14.0

Neptune JSON-RPC API contract: the RpcApi trait and its data-transfer model types
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use serde::Serializer;
use serde::ser::SerializeStruct;
use serde_json::Value;
use thiserror::Error;

use crate::api::rpc::RpcError;

#[derive(Debug, Serialize, Deserialize)]
pub struct JsonRequest {
    #[serde(default)]
    pub jsonrpc: Option<String>,
    pub method: String,
    pub params: Value,
    pub id: Option<Value>,
}

#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum JsonError {
    #[error("Parse error")]
    ParseError,
    #[error("Invalid request")]
    InvalidRequest,
    #[error("Method not found")]
    MethodNotFound,
    #[error("Invalid params")]
    InvalidParams,
    #[error("Internal error")]
    InternalError,
    #[error("connection failed: {message}")]
    ConnectionFailed { message: String },
    #[error("HTTP error: {message}")]
    HttpError { message: String },
    #[error("Request body too big. Max: {max}; got: {got}")]
    RequestBodyTooBig { max: usize, got: usize },
    #[error("Server error")]
    Custom {
        code: i32,
        message: String,
        data: Option<Value>,
    },
}

impl JsonError {
    pub fn code(&self) -> i32 {
        match self {
            Self::ParseError => -32700,
            Self::InvalidRequest => -32600,
            Self::MethodNotFound => -32601,
            Self::InvalidParams => -32602,
            Self::InternalError => -32603,
            Self::ConnectionFailed { .. } => -32604,
            Self::HttpError { .. } => -32605,
            Self::RequestBodyTooBig { .. } => -32606,
            Self::Custom { code, .. } => *code,
        }
    }

    pub fn data(&self) -> Option<&serde_json::Value> {
        match self {
            Self::Custom { data, .. } => data.as_ref(),
            _ => None,
        }
    }
}

impl From<RpcError> for JsonError {
    fn from(err: RpcError) -> Self {
        if let RpcError::Server(error) = err {
            error
        } else {
            JsonError::Custom {
                code: -32000,
                message: "RPC error".to_string(),
                data: Some(serde_json::to_value(err).unwrap()),
            }
        }
    }
}

impl From<JsonError> for RpcError {
    fn from(err: JsonError) -> Self {
        match err {
            JsonError::Custom {
                data: Some(value), ..
            } => serde_json::from_value(value).unwrap_or(RpcError::Server(JsonError::ParseError)),
            err => RpcError::Server(err),
        }
    }
}

impl Serialize for JsonError {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut state = serializer.serialize_struct("JsonError", 3)?;
        state.serialize_field("code", &self.code())?;
        state.serialize_field("message", &self.to_string())?;

        if let Some(data) = self.data() {
            state.serialize_field("data", data)?;
        }

        state.end()
    }
}

impl<'de> Deserialize<'de> for JsonError {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct Envelope {
            code: i32,
            message: String,
            data: Option<serde_json::Value>,
        }

        let err = Envelope::deserialize(deserializer)?;

        match err.code {
            -32700 => Ok(JsonError::ParseError),
            -32600 => Ok(JsonError::InvalidRequest),
            -32601 => Ok(JsonError::MethodNotFound),
            -32602 => Ok(JsonError::InvalidParams),
            -32603 => Ok(JsonError::InternalError),
            code => Ok(JsonError::Custom {
                code,
                message: err.message,
                data: err.data,
            }),
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum JsonResponse {
    Success {
        #[serde(skip_deserializing)]
        jsonrpc: &'static str,
        id: Option<Value>,
        result: Value,
    },
    Error {
        #[serde(skip_deserializing)]
        jsonrpc: &'static str,
        id: Option<Value>,
        error: JsonError,
    },
}

impl JsonResponse {
    pub const VERSION: &'static str = "2.0";

    pub fn success(id: Option<Value>, result: Value) -> Self {
        JsonResponse::Success {
            jsonrpc: Self::VERSION,
            id,
            result,
        }
    }

    pub fn error(id: Option<Value>, error: JsonError) -> Self {
        JsonResponse::Error {
            jsonrpc: Self::VERSION,
            id,
            error,
        }
    }
}

pub type JsonResult<T> = Result<T, JsonError>;

#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
    use serde_json::json;

    use super::*;

    #[test]
    fn json_error_serialization() {
        let error = JsonError::MethodNotFound;
        let serialized = serde_json::to_string(&error).unwrap();
        assert_eq!(
            serialized,
            r#"{"code":-32601,"message":"Method not found"}"#
        );
    }

    #[test]
    fn json_rpc_error_roundtrip() {
        let error = JsonError::MethodNotFound;

        let rpc_error: RpcError = error.clone().into();
        assert_eq!(rpc_error, RpcError::Server(error.clone()));

        let json_error: JsonError = rpc_error.into();
        assert_eq!(json_error, error);
    }

    #[test]
    fn json_response_success() {
        let response = JsonResponse::success(Some(json!(1)), json!({"success": true}));

        let serialized = serde_json::to_string(&response).unwrap();
        assert!(serialized.contains(r#"{"jsonrpc":"2.0","id":1,"result":{"success":true}}"#));
    }

    #[test]
    fn json_response_error() {
        let response = JsonResponse::error(Some(json!("1")), JsonError::InvalidParams);

        let serialized = serde_json::to_string(&response).unwrap();
        assert!(serialized.contains(
            r#"{"jsonrpc":"2.0","id":"1","error":{"code":-32602,"message":"Invalid params"}}"#
        ));
    }

    #[test]
    fn json_request_deserialization() {
        let json_data = r#"{
            "jsonrpc": "2.0",
            "method": "test",
            "params": ["arg1", "arg2"],
            "id": 1
        }"#;

        let request: JsonRequest = serde_json::from_str(json_data).unwrap();

        assert_eq!(request.jsonrpc, Some("2.0".to_string()));
        assert_eq!(request.method, "test");
        assert_eq!(request.params, json!(["arg1", "arg2"]));
        assert_eq!(request.id, Some(json!(1)));
    }

    #[test]
    fn json_response_with_null_id() {
        let success = JsonResponse::success(None, json!(true));
        let error = JsonResponse::error(None, JsonError::InternalError);

        let success_str = serde_json::to_string(&success).unwrap();
        let error_str = serde_json::to_string(&error).unwrap();

        assert!(success_str.contains(r#"{"jsonrpc":"2.0","id":null,"result":true}"#));
        assert!(error_str.contains(
            r#"{"jsonrpc":"2.0","id":null,"error":{"code":-32603,"message":"Internal error"}}"#
        ));
    }
}