use std::fmt;
use polyoxide_ws::{tungstenite::Message, Connection, Correlated, Kind, WsError};
use serde_json::{json, Value};
use super::SocketError;
pub(crate) enum Answer {
Result { id: Option<u64>, result: Value },
Error {
id: Option<u64>,
code: i64,
msg: String,
},
}
impl Answer {
pub(crate) fn parse(text: &str) -> Option<Self> {
let value: Value = serde_json::from_str(text).ok()?;
let object = value.as_object()?;
if object.contains_key("stream") {
return None;
}
let id = object.get("id").and_then(Value::as_u64);
if let Some(error) = object.get("error") {
return Some(Self::Error {
id,
code: error
.get("code")
.and_then(Value::as_i64)
.unwrap_or_default(),
msg: error
.get("msg")
.and_then(Value::as_str)
.unwrap_or_default()
.to_owned(),
});
}
if object.contains_key("result") || object.contains_key("id") {
return Some(Self::Result {
id,
result: object.get("result").cloned().unwrap_or(Value::Null),
});
}
None
}
fn id(&self) -> Option<u64> {
match self {
Self::Result { id, .. } | Self::Error { id, .. } => *id,
}
}
}
pub(crate) fn request_frame<N: fmt::Display>(
method: &str,
params: Option<&[N]>,
id: u64,
) -> Message {
let mut body = json!({ "method": method, "id": id });
if let Some(params) = params {
let names: Vec<String> = params.iter().map(ToString::to_string).collect();
body["params"] = json!(names);
}
Message::Text(body.to_string().into())
}
fn correlate(id: u64, message: &Message) -> Correlated<Answer> {
let Message::Text(text) = message else {
return Correlated::Push;
};
match Answer::parse(text) {
Some(answer) if answer.id() == Some(id) => Correlated::Answer(answer),
Some(other) => {
tracing::debug!(id = ?other.id(), "uncorrelated answer while awaiting {id}");
Correlated::Uncorrelated
}
None => Correlated::Push,
}
}
pub(crate) async fn request<N: fmt::Display, E: SocketError>(
connection: &mut Connection,
method: &str,
params: Option<&[N]>,
) -> Result<Value, E> {
let id = connection.next_id();
let answer = connection
.request(|id| request_frame(method, params, id), correlate)
.await
.map_err(|err| no_answer::<E>(err, id))?;
match answer {
Answer::Result { result, .. } => Ok(result),
Answer::Error { code, msg, .. } => Err(E::refused(code, msg)),
}
}
pub(crate) fn no_answer<E: SocketError>(err: WsError, id: u64) -> E {
match err.kind() {
Kind::NoAnswer { after } => E::no_answer(id, after),
_ => E::from(err),
}
}