use std::{
collections::VecDeque,
fmt,
sync::{Mutex, MutexGuard},
time::Duration,
};
use serde_json::Value;
use crate::rest::{DISCORD_API, Method, RawResponse, Request, Transport, TransportError};
type Answer = Box<dyn Fn(&Request) -> Option<RawResponse> + Send + Sync>;
#[derive(Default)]
pub struct MockTransport {
requests: Mutex<Vec<Request>>,
responses: Mutex<VecDeque<RawResponse>>,
sleeps: Mutex<Vec<Duration>>,
answer: Option<Answer>,
}
impl fmt::Debug for MockTransport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MockTransport")
.field("requests", &lock(&self.requests).len())
.field("queued", &lock(&self.responses).len())
.finish_non_exhaustive()
}
}
pub fn json_response(status: u16, body: &Value) -> RawResponse {
RawResponse {
status,
headers: vec![("content-type".into(), "application/json".into())],
body: serde_json::to_vec(body).expect("JSON values serialize"),
}
}
fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
mutex
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
impl MockTransport {
pub fn new() -> Self {
Self::default()
}
pub fn respond(self, status: u16, body: &str) -> Self {
self.respond_raw(RawResponse {
status,
headers: Vec::new(),
body: body.as_bytes().to_vec(),
})
}
pub fn respond_json(self, status: u16, body: &Value) -> Self {
self.respond_raw(json_response(status, body))
}
pub fn respond_raw(self, response: RawResponse) -> Self {
self.queue(response);
self
}
pub fn queue(&self, response: RawResponse) {
lock(&self.responses).push_back(response);
}
pub fn answering(
mut self,
answer: impl Fn(&Request) -> Option<RawResponse> + Send + Sync + 'static,
) -> Self {
self.answer = Some(Box::new(answer));
self
}
pub fn requests(&self) -> Vec<Request> {
lock(&self.requests).clone()
}
pub fn request(&self, index: usize) -> Request {
lock(&self.requests)[index].clone()
}
pub fn sent(&self) -> Vec<(Method, String)> {
lock(&self.requests)
.iter()
.map(|request| (request.method, path(&request.url).to_string()))
.collect()
}
pub fn paths(&self) -> Vec<String> {
self.sent().into_iter().map(|(_, path)| path).collect()
}
pub fn body(&self, index: usize) -> Value {
let requests = lock(&self.requests);
let body = requests[index]
.body
.as_deref()
.expect("the request has a body");
serde_json::from_slice(body).expect("the request body is JSON")
}
pub fn text(&self, index: usize) -> String {
let requests = lock(&self.requests);
String::from_utf8_lossy(requests[index].body.as_deref().unwrap_or_default()).into_owned()
}
pub fn sleeps(&self) -> Vec<Duration> {
lock(&self.sleeps).clone()
}
pub fn clear(&self) {
lock(&self.requests).clear();
}
}
fn path(url: &str) -> &str {
url.strip_prefix(DISCORD_API).unwrap_or(url)
}
impl Transport for MockTransport {
async fn send(&self, request: &Request) -> Result<RawResponse, TransportError> {
lock(&self.requests).push(request.clone());
let queued = lock(&self.responses).pop_front();
Ok(queued
.or_else(|| self.answer.as_ref().and_then(|answer| answer(request)))
.unwrap_or_else(|| RawResponse {
status: 200,
headers: Vec::new(),
body: b"{}".to_vec(),
}))
}
async fn sleep(&self, duration: Duration) {
lock(&self.sleeps).push(duration);
}
}