use std::cell::RefCell;
use std::collections::HashMap;
use crate::error::{Error, Result};
pub trait Transport {
fn post_json(&self, url: &str, body: &str) -> Result<String>;
}
impl<T: Transport + ?Sized> Transport for &T {
fn post_json(&self, url: &str, body: &str) -> Result<String> {
(**self).post_json(url, body)
}
}
impl<T: Transport + ?Sized> Transport for Box<T> {
fn post_json(&self, url: &str, body: &str) -> Result<String> {
(**self).post_json(url, body)
}
}
#[cfg(target_family = "wasm")]
mod waki_transport {
use super::{Error, Result, Transport};
use std::time::Duration;
pub struct WakiTransport {
connect_timeout: Duration,
}
impl WakiTransport {
pub fn new() -> Self {
Self {
connect_timeout: Duration::from_secs(10),
}
}
pub fn with_timeout(secs: u64) -> Self {
Self {
connect_timeout: Duration::from_secs(secs),
}
}
}
impl Default for WakiTransport {
fn default() -> Self {
Self::new()
}
}
impl Transport for WakiTransport {
fn post_json(&self, url: &str, body: &str) -> Result<String> {
let resp = waki::Client::new()
.post(url)
.connect_timeout(self.connect_timeout)
.header("Content-Type", "application/json")
.body(body.as_bytes().to_vec())
.send()
.map_err(|e| Error::Transport(e.to_string()))?;
let status = resp.status_code();
let bytes = resp.body().map_err(|e| Error::Transport(e.to_string()))?;
if !(200..300).contains(&status) {
return Err(Error::Transport(format!("HTTP {status}")));
}
String::from_utf8(bytes).map_err(|_| Error::Transport("response was not UTF-8".into()))
}
}
}
#[cfg(target_family = "wasm")]
pub use waki_transport::WakiTransport;
#[derive(Default)]
pub struct MockTransport {
results: HashMap<String, serde_json::Value>,
errors: HashMap<String, (i64, String)>,
requests: RefCell<Vec<serde_json::Value>>,
fail_with: Option<String>,
}
impl MockTransport {
pub fn new() -> Self {
Self::default()
}
pub fn on(mut self, method: &str, result: serde_json::Value) -> Self {
self.results.insert(method.to_string(), result);
self
}
pub fn on_error(mut self, method: &str, code: i64, message: &str) -> Self {
self.errors
.insert(method.to_string(), (code, message.to_string()));
self
}
pub fn failing(mut self, message: &str) -> Self {
self.fail_with = Some(message.to_string());
self
}
pub fn requests(&self) -> Vec<serde_json::Value> {
self.requests.borrow().clone()
}
pub fn call_count(&self) -> usize {
self.requests.borrow().len()
}
pub fn last_params(&self, method: &str) -> Option<serde_json::Value> {
self.requests
.borrow()
.iter()
.rev()
.find(|r| r.get("method").and_then(|m| m.as_str()) == Some(method))
.and_then(|r| r.get("params").cloned())
}
}
impl Transport for MockTransport {
fn post_json(&self, _url: &str, body: &str) -> Result<String> {
let parsed: serde_json::Value = serde_json::from_str(body)
.map_err(|e| Error::Transport(format!("mock got invalid JSON: {e}")))?;
self.requests.borrow_mut().push(parsed.clone());
if let Some(msg) = &self.fail_with {
return Err(Error::Transport(msg.clone()));
}
let method = parsed
.get("method")
.and_then(|m| m.as_str())
.unwrap_or_default()
.to_string();
let id = parsed.get("id").cloned().unwrap_or(serde_json::json!(1));
if let Some((code, message)) = self.errors.get(&method) {
return Ok(serde_json::json!({
"jsonrpc": "2.0",
"id": id,
"error": { "code": code, "message": message }
})
.to_string());
}
match self.results.get(&method) {
Some(result) => Ok(serde_json::json!({
"jsonrpc": "2.0",
"id": id,
"result": result
})
.to_string()),
None => Err(Error::Transport(format!(
"mock has no response registered for `{method}`"
))),
}
}
}