im-core 0.1.0

Rust IM SDK for Awiki clients built on Agent Network Protocol (ANP)
Documentation
use serde::Deserialize;
use serde_json::{json, Value};

pub(crate) const CONTENT_TYPE_JSON: &str = "application/json";
const JSON_RPC_VERSION: &str = "2.0";
const JSON_RPC_ID: &str = "req-1";

#[derive(Debug, Deserialize)]
struct JsonRpcResponseError {
    code: i64,
    message: String,
    #[serde(default)]
    data: Option<Value>,
}

pub(crate) fn build_payload(method: &str, params: Value) -> Value {
    json!({
        "jsonrpc": JSON_RPC_VERSION,
        "id": JSON_RPC_ID,
        "method": method,
        "params": params,
    })
}

pub(crate) fn decode_response(raw: &[u8]) -> crate::ImResult<Value> {
    let envelope: Value =
        serde_json::from_slice(raw).map_err(|err| crate::ImError::Serialization {
            detail: err.to_string(),
        })?;
    if let Some(error) = envelope.get("error").filter(|error| !error.is_null()) {
        let error: JsonRpcResponseError =
            serde_json::from_value(error.clone()).map_err(|err| crate::ImError::Serialization {
                detail: err.to_string(),
            })?;
        return Err(crate::ImError::Service {
            status_code: None,
            code: Some(error.code.to_string()),
            message: error.message,
            data: error.data,
        });
    }
    Ok(envelope.get("result").cloned().unwrap_or(Value::Null))
}

pub(crate) fn decode_plain_response(raw: &[u8]) -> crate::ImResult<Value> {
    serde_json::from_slice(raw).map_err(|err| crate::ImError::Serialization {
        detail: err.to_string(),
    })
}

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

    #[test]
    fn json_rpc_payload_golden_direct_send() {
        assert_eq!(
            build_payload(
                "direct.send",
                json!({
                    "meta": {
                        "profile": "anp.direct.base.v1",
                        "security_profile": "transport-protected",
                        "sender_did": "did:wba:awiki.ai:user:alice:e1",
                        "target": {
                            "kind": "agent",
                            "did": "did:wba:awiki.ai:user:bob:e1",
                        },
                    },
                    "auth": {
                        "scheme": "did-wba",
                    },
                    "body": {
                        "text": "hello",
                    },
                }),
            ),
            json!({
                "jsonrpc": "2.0",
                "id": "req-1",
                "method": "direct.send",
                "params": {
                    "meta": {
                        "profile": "anp.direct.base.v1",
                        "security_profile": "transport-protected",
                        "sender_did": "did:wba:awiki.ai:user:alice:e1",
                        "target": {
                            "kind": "agent",
                            "did": "did:wba:awiki.ai:user:bob:e1",
                        },
                    },
                    "auth": {
                        "scheme": "did-wba",
                    },
                    "body": {
                        "text": "hello",
                    },
                },
            })
        );
    }

    #[test]
    fn json_rpc_payload_golden_group_send() {
        assert_eq!(
            build_payload(
                "group.send",
                json!({
                    "meta": {
                        "profile": "anp.group.base.v1",
                        "security_profile": "transport-protected",
                        "sender_did": "did:wba:awiki.ai:user:alice:e1",
                        "target": {
                            "kind": "group",
                            "did": "did:wba:awiki.ai:groups:demo:e1_group",
                        },
                    },
                    "auth": {
                        "scheme": "did-wba",
                    },
                    "body": {
                        "text": "hello group",
                    },
                }),
            ),
            json!({
                "jsonrpc": "2.0",
                "id": "req-1",
                "method": "group.send",
                "params": {
                    "meta": {
                        "profile": "anp.group.base.v1",
                        "security_profile": "transport-protected",
                        "sender_did": "did:wba:awiki.ai:user:alice:e1",
                        "target": {
                            "kind": "group",
                            "did": "did:wba:awiki.ai:groups:demo:e1_group",
                        },
                    },
                    "auth": {
                        "scheme": "did-wba",
                    },
                    "body": {
                        "text": "hello group",
                    },
                },
            })
        );
    }

    #[test]
    fn json_rpc_payload_golden_read_methods() {
        let inbox = json!({
            "meta": {
                "profile": "anp.inbox.local.v1",
                "sender_did": "did:wba:awiki.ai:user:alice:e1",
            },
            "body": {
                "user_did": "did:wba:awiki.ai:user:alice:e1",
                "limit": 20,
            },
        });
        assert_eq!(
            build_payload("inbox.get", inbox.clone()),
            json!({
                "jsonrpc": "2.0",
                "id": "req-1",
                "method": "inbox.get",
                "params": inbox,
            })
        );

        let history = json!({
            "meta": {
                "profile": "anp.direct.local.v1",
                "sender_did": "did:wba:awiki.ai:user:alice:e1",
            },
            "body": {
                "user_did": "did:wba:awiki.ai:user:alice:e1",
                "peer_did": "did:wba:awiki.ai:user:bob:e1",
                "limit": 50,
                "since_seq": "42",
            },
        });
        assert_eq!(
            build_payload("direct.get_history", history.clone()),
            json!({
                "jsonrpc": "2.0",
                "id": "req-1",
                "method": "direct.get_history",
                "params": history,
            })
        );

        let mark_read = json!({
            "meta": {
                "profile": "anp.inbox.local.v1",
                "sender_did": "did:wba:awiki.ai:user:alice:e1",
            },
            "body": {
                "user_did": "did:wba:awiki.ai:user:alice:e1",
                "message_ids": ["msg-1", "msg-2"],
            },
        });
        assert_eq!(
            build_payload("inbox.mark_read", mark_read.clone()),
            json!({
                "jsonrpc": "2.0",
                "id": "req-1",
                "method": "inbox.mark_read",
                "params": mark_read,
            })
        );
    }

    #[test]
    fn json_rpc_payload_golden_auth_refresh() {
        assert_eq!(
            build_payload("get_me", json!({})),
            json!({
                "jsonrpc": "2.0",
                "id": "req-1",
                "method": "get_me",
                "params": {},
            })
        );
    }
}