use std::collections::VecDeque;
use std::future::Future;
use std::path::Path;
use std::sync::Mutex;
use std::task::{Context, Poll, Waker};
use async_trait::async_trait;
use jiff::Timestamp;
use crate::error::Error;
use crate::http::{HttpClient, HttpRequest, HttpResponse};
pub const FIXTURE_TIME: Timestamp = Timestamp::constant(1_794_312_000, 0);
pub fn fixture(dir: impl AsRef<Path>, name: &str) -> HttpResponse {
let dir = dir.as_ref();
let read = |ext: &str| {
let path = dir.join(format!("{name}.{ext}"));
std::fs::read(&path).unwrap_or_else(|e| panic!("reading {}: {e}", path.display()))
};
let head = String::from_utf8(read("headers")).expect("headers are UTF-8");
let mut lines = head.lines();
let status = lines
.next()
.and_then(|l| l.split_whitespace().nth(1))
.and_then(|s| s.parse().ok())
.expect("status line like `HTTP/2 200`");
let headers = lines
.filter_map(|l| l.split_once(':'))
.map(|(k, v)| (k.trim().to_owned(), v.trim().to_owned()))
.collect();
HttpResponse::new(status, headers, read("body"), FIXTURE_TIME)
}
pub fn response(status: u16, headers: &[(&str, &str)], body: &str) -> HttpResponse {
let headers = headers
.iter()
.map(|(k, v)| ((*k).to_owned(), (*v).to_owned()))
.collect();
HttpResponse::new(status, headers, body.as_bytes().to_vec(), FIXTURE_TIME)
}
#[derive(Debug, Default)]
pub struct ReplayClient {
responses: Mutex<VecDeque<Result<HttpResponse, Error>>>,
requests: Mutex<Vec<HttpRequest>>,
}
impl ReplayClient {
pub fn new(responses: impl IntoIterator<Item = HttpResponse>) -> Self {
ReplayClient {
responses: Mutex::new(responses.into_iter().map(Ok).collect()),
requests: Mutex::default(),
}
}
pub fn then_fail(self, error: Error) -> Self {
self.responses.lock().unwrap().push_back(Err(error));
self
}
pub fn requests(&self) -> Vec<HttpRequest> {
self.requests.lock().unwrap().clone()
}
}
#[async_trait]
impl HttpClient for ReplayClient {
async fn send(&self, request: HttpRequest) -> Result<HttpResponse, Error> {
let url = request.url.clone();
self.requests.lock().unwrap().push(request);
self.responses
.lock()
.unwrap()
.pop_front()
.unwrap_or_else(|| panic!("ReplayClient: no response queued for {url}"))
}
}
pub fn block_on<F: Future>(fut: F) -> F::Output {
let mut fut = std::pin::pin!(fut);
match fut.as_mut().poll(&mut Context::from_waker(Waker::noop())) {
Poll::Ready(out) => out,
Poll::Pending => panic!("future was not immediately ready"),
}
}