im-core 0.1.0

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

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IncomingWsMessage {
    Response { request_id: String },
    Notification,
}

pub fn request_id_from_value(value: &Value) -> String {
    match value {
        Value::String(value) => value.clone(),
        Value::Number(number) => {
            if let Some(value) = number.as_i64() {
                value.to_string()
            } else if let Some(value) = number.as_u64() {
                value.to_string()
            } else if let Some(value) = number.as_f64() {
                format!("{value:.0}")
            } else {
                String::new()
            }
        }
        _ => String::new(),
    }
}

pub fn int64_from_value(value: &Value) -> i64 {
    match value {
        Value::Number(number) => number
            .as_i64()
            .or_else(|| number.as_u64().and_then(|value| i64::try_from(value).ok()))
            .or_else(|| number.as_f64().map(|value| value as i64))
            .unwrap_or_default(),
        _ => 0,
    }
}

pub fn next_ws_rpc_request_id(next_id: &mut i64) -> String {
    *next_id = next_id.wrapping_add(1);
    format!("req-{}", *next_id)
}

pub fn build_ws_rpc_request(
    request_id: &str,
    method: &str,
    params: Option<Map<String, Value>>,
) -> Map<String, Value> {
    let mut request = Map::new();
    request.insert("jsonrpc".to_string(), Value::String("2.0".to_string()));
    request.insert("id".to_string(), Value::String(request_id.to_string()));
    request.insert("method".to_string(), Value::String(method.to_string()));
    if let Some(params) = params {
        request.insert("params".to_string(), Value::Object(params));
    }
    request
}

pub fn decode_ws_rpc_result(response: &Map<String, Value>) -> Result<Map<String, Value>, String> {
    if let Some(Value::Object(error)) = response.get("error") {
        return Err(format!(
            "json-rpc error {}: {}",
            go_fmt_value(error.get("code")),
            go_fmt_value(error.get("message"))
        ));
    }
    match response.get("result") {
        Some(Value::Object(result)) => Ok(result.clone()),
        _ => Ok(Map::new()),
    }
}

pub fn pending_failure_response(request_id: &str, error: &str) -> Map<String, Value> {
    Map::from_iter([
        (
            "error".to_string(),
            Value::Object(Map::from_iter([(
                "message".to_string(),
                Value::String(error.to_string()),
            )])),
        ),
        ("id".to_string(), Value::String(request_id.to_string())),
    ])
}

pub fn classify_incoming_message(message: &Map<String, Value>) -> IncomingWsMessage {
    match message.get("id") {
        Some(id) => IncomingWsMessage::Response {
            request_id: request_id_from_value(id),
        },
        None => IncomingWsMessage::Notification,
    }
}

fn go_fmt_value(value: Option<&Value>) -> String {
    match value {
        Some(Value::Null) | None => "<nil>".to_string(),
        Some(Value::String(value)) => value.clone(),
        Some(Value::Number(value)) => value.to_string(),
        Some(Value::Bool(value)) => value.to_string(),
        Some(Value::Array(value)) => format!("{value:?}"),
        Some(Value::Object(value)) => format!("{value:?}"),
    }
}