libnoa 0.3.2

AI-native distributed version control
Documentation
use serde::{Deserialize, Serialize};

use crate::error::Result;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcMessage {
    pub jsonrpc: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub method: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub params: Option<serde_json::Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub result: Option<serde_json::Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<JsonRpcError>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcError {
    pub code: i64,
    pub message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<serde_json::Value>,
}

impl JsonRpcMessage {
    #[must_use]
    pub fn request(id: u64, method: &str, params: serde_json::Value) -> Self {
        JsonRpcMessage {
            jsonrpc: "2.0".to_string(),
            id: Some(id),
            method: Some(method.to_string()),
            params: Some(params),
            result: None,
            error: None,
        }
    }

    #[must_use]
    pub fn response(id: u64, result: serde_json::Value) -> Self {
        JsonRpcMessage {
            jsonrpc: "2.0".to_string(),
            id: Some(id),
            method: None,
            params: None,
            result: Some(result),
            error: None,
        }
    }

    #[must_use]
    pub fn error_response(id: u64, code: i64, message: &str) -> Self {
        JsonRpcMessage {
            jsonrpc: "2.0".to_string(),
            id: Some(id),
            method: None,
            params: None,
            result: None,
            error: Some(JsonRpcError {
                code,
                message: message.to_string(),
                data: None,
            }),
        }
    }

    pub fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self)?)
    }

    pub fn from_json(s: &str) -> Result<Self> {
        Ok(serde_json::from_str::<JsonRpcMessage>(s)?)
    }

    pub fn to_frame(&self) -> Result<Vec<u8>> {
        let json = self.to_json()?;
        let len = json.len() as u32;
        let mut frame = Vec::with_capacity(4 + json.len());
        frame.extend_from_slice(&len.to_be_bytes());
        frame.extend_from_slice(json.as_bytes());
        Ok(frame)
    }

    pub fn from_frame(data: &[u8]) -> Result<Self> {
        if data.len() < 4 {
            anyhow::bail!("frame too short");
        }
        let len = u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as usize;
        if data.len() < 4 + len {
            anyhow::bail!("frame incomplete");
        }
        let json = std::str::from_utf8(&data[4..4 + len])?;
        Self::from_json(json)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_request_message() {
        let msg = JsonRpcMessage::request(
            1,
            "noa.handshake",
            serde_json::json!({"workspace_id": "test"}),
        );
        assert_eq!(msg.jsonrpc, "2.0");
        assert_eq!(msg.id, Some(1));
        assert_eq!(msg.method.as_deref(), Some("noa.handshake"));
    }

    #[test]
    fn test_response_message() {
        let msg = JsonRpcMessage::response(1, serde_json::json!({"ok": true}));
        assert_eq!(msg.id, Some(1));
        assert!(msg.result.is_some());
        assert!(msg.method.is_none());
    }

    #[test]
    fn test_error_response_message() {
        let msg = JsonRpcMessage::error_response(1, -32600, "invalid request");
        assert!(msg.error.is_some());
        assert_eq!(msg.error.unwrap().code, -32600);
    }

    #[test]
    fn test_json_roundtrip() {
        let msg = JsonRpcMessage::request(
            42,
            "noa.handshake",
            serde_json::json!({"workspace_id": "ws-1", "remote_name": "origin"}),
        );
        let json = msg.to_json().unwrap();
        let parsed = JsonRpcMessage::from_json(&json).unwrap();
        assert_eq!(parsed.id, msg.id);
        assert_eq!(parsed.method, msg.method);
    }

    #[test]
    fn test_frame_roundtrip() {
        let msg = JsonRpcMessage::request(1, "test.method", serde_json::json!({"key": "value"}));
        let frame = msg.to_frame().unwrap();
        let parsed = JsonRpcMessage::from_frame(&frame).unwrap();
        assert_eq!(parsed.id, msg.id);
        assert_eq!(parsed.method, msg.method);
    }

    #[test]
    fn test_frame_too_short() {
        let result = JsonRpcMessage::from_frame(&[0, 1, 2]);
        assert!(result.is_err());
    }

    #[test]
    fn test_frame_incomplete() {
        let msg = JsonRpcMessage::request(1, "test", serde_json::json!({}));
        let json = msg.to_json().unwrap();
        let len = (json.len() as u32 + 10).to_be_bytes();
        let mut frame = Vec::new();
        frame.extend_from_slice(&len);
        frame.extend_from_slice(json.as_bytes());
        let result = JsonRpcMessage::from_frame(&frame);
        assert!(result.is_err());
    }
}