use std::collections::{BTreeMap, VecDeque};
use std::future::Future;
use std::sync::{Arc, Mutex, MutexGuard};
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use serde_json::Value;
use url::Url;
use crate::{Error, Result};
use super::{
BinaryHttpResponse, BoxFuture, HttpMethod, HttpRequest, HttpResponse, MultipartRequest,
OpenApiBinaryTransport, OpenApiMultipartTransport, OpenApiTransport,
};
#[derive(Clone, Debug)]
pub(crate) struct FakeTransport {
state: Arc<Mutex<FakeState>>,
}
impl FakeTransport {
pub(crate) fn new(responses: Vec<HttpResponse>) -> Self {
Self {
state: Arc::new(Mutex::new(FakeState {
responses: responses.into(),
binary_responses: VecDeque::new(),
multipart_responses: VecDeque::new(),
calls: Vec::new(),
multipart_calls: Vec::new(),
})),
}
}
pub(crate) fn with_binary_responses(
responses: Vec<HttpResponse>,
binary_responses: Vec<BinaryHttpResponse>,
) -> Self {
Self {
state: Arc::new(Mutex::new(FakeState {
responses: responses.into(),
binary_responses: binary_responses.into(),
multipart_responses: VecDeque::new(),
calls: Vec::new(),
multipart_calls: Vec::new(),
})),
}
}
pub(crate) fn with_multipart_responses(
responses: Vec<HttpResponse>,
multipart_responses: Vec<HttpResponse>,
) -> Self {
Self {
state: Arc::new(Mutex::new(FakeState {
responses: responses.into(),
binary_responses: VecDeque::new(),
multipart_responses: multipart_responses.into(),
calls: Vec::new(),
multipart_calls: Vec::new(),
})),
}
}
pub(crate) fn calls(&self) -> Vec<FakeCall> {
self.state().calls.clone()
}
pub(crate) fn multipart_calls(&self) -> Vec<MultipartRequest> {
self.state().multipart_calls.clone()
}
fn state(&self) -> MutexGuard<'_, FakeState> {
self.state.lock().expect("fake transport state poisoned")
}
}
impl OpenApiTransport for FakeTransport {
fn send_json(&self, request: HttpRequest) -> BoxFuture<'static, Result<HttpResponse>> {
let response = {
let mut state = self.state();
state.calls.push(FakeCall {
method: request.method,
url: request.url,
headers: request.headers,
body: request.body,
max_response_bytes: None,
});
state.responses.pop_front().expect("fake response")
};
Box::pin(async move { Ok(response) })
}
}
impl OpenApiBinaryTransport for FakeTransport {
fn send_bytes(
&self,
request: HttpRequest,
max_response_bytes: usize,
) -> BoxFuture<'static, Result<BinaryHttpResponse>> {
let response = {
let mut state = self.state();
state.calls.push(FakeCall {
method: request.method,
url: request.url,
headers: request.headers,
body: request.body,
max_response_bytes: Some(max_response_bytes),
});
state
.binary_responses
.pop_front()
.expect("fake binary response")
};
Box::pin(async move {
if !(200..300).contains(&response.status) {
return Ok(BinaryHttpResponse::new(
response.status,
BTreeMap::new(),
Vec::new(),
));
}
if response.body.len() > max_response_bytes {
return Err(Error::Transport(format!(
"binary response exceeds the {max_response_bytes}-byte limit"
)));
}
Ok(response)
})
}
}
impl OpenApiMultipartTransport for FakeTransport {
fn send_multipart(
&self,
request: MultipartRequest,
) -> BoxFuture<'static, Result<HttpResponse>> {
let response = {
let mut state = self.state();
state.multipart_calls.push(request);
state
.multipart_responses
.pop_front()
.expect("fake multipart response")
};
Box::pin(async move { Ok(response) })
}
}
#[derive(Debug)]
struct FakeState {
responses: VecDeque<HttpResponse>,
binary_responses: VecDeque<BinaryHttpResponse>,
multipart_responses: VecDeque<HttpResponse>,
calls: Vec<FakeCall>,
multipart_calls: Vec<MultipartRequest>,
}
#[derive(Clone, Debug)]
pub(crate) struct FakeCall {
pub(crate) method: HttpMethod,
pub(crate) url: Url,
pub(crate) headers: BTreeMap<String, String>,
pub(crate) body: Value,
pub(crate) max_response_bytes: Option<usize>,
}
pub(crate) fn block_on<F>(future: F) -> F::Output
where
F: Future,
{
let waker = noop_waker();
let mut context = Context::from_waker(&waker);
let mut future = Box::pin(future);
match future.as_mut().poll(&mut context) {
Poll::Ready(output) => output,
Poll::Pending => panic!("test future unexpectedly pending"),
}
}
fn noop_waker() -> Waker {
unsafe { Waker::from_raw(noop_raw_waker()) }
}
fn noop_raw_waker() -> RawWaker {
fn clone(_: *const ()) -> RawWaker {
noop_raw_waker()
}
fn wake(_: *const ()) {}
fn wake_by_ref(_: *const ()) {}
fn drop(_: *const ()) {}
RawWaker::new(
std::ptr::null(),
&RawWakerVTable::new(clone, wake, wake_by_ref, drop),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn binary_transport_preserves_error_status_before_body_limits() {
let transport = FakeTransport::with_binary_responses(
Vec::new(),
vec![BinaryHttpResponse::new(
503,
BTreeMap::new(),
b"error body".to_vec(),
)],
);
let response = block_on(transport.send_bytes(
HttpRequest::empty(
HttpMethod::Get,
Url::parse("https://open.feishu.cn/error").expect("test URL"),
),
3,
))
.expect("HTTP status response");
assert_eq!(response.status, 503);
assert!(response.body.is_empty());
}
}