use std::io::Write;
use std::marker::PhantomData;
use http::{Method, Request, Response};
use crate::server::state::{
Cleanup, ProvideResponse, RecvBody, RecvRequest, Send100, SendBody, SendResponse,
};
use crate::server::{RecvRequestResult, Reply, SendResponseResult};
pub struct Scenario {
request: Request<()>,
request_body: Vec<u8>,
response: Response<()>,
response_body: Vec<u8>,
}
impl Scenario {
pub fn builder() -> ScenarioBuilder<()> {
ScenarioBuilder::new()
}
}
impl Scenario {
pub fn to_recv_request(&self) -> Reply<RecvRequest> {
Reply::new().unwrap()
}
pub fn to_send_100(&self) -> Reply<Send100> {
let mut reply = self.to_recv_request();
let input = write_request(&self.request);
let (_, request) = reply.try_request(&input).unwrap();
assert!(request.is_some());
match reply.proceed().unwrap() {
RecvRequestResult::Send100(v) => v,
_ => unreachable!("Incorrect scenario not leading to_send_100()"),
}
}
pub fn to_recv_body(&self) -> Reply<RecvBody> {
let mut reply = self.to_recv_request();
let input = write_request(&self.request);
let (_, request) = reply.try_request(&input).unwrap();
assert!(request.is_some());
if self
.request
.headers()
.get("expect")
.is_some_and(|v| v == "100-continue")
{
match reply.proceed().unwrap() {
RecvRequestResult::Send100(reply) => {
let mut output = vec![0; 1024];
let (_, reply) = reply.accept(&mut output).unwrap();
reply
}
_ => unreachable!("Expect: 100-continue header should lead to Send100"),
}
} else {
match reply.proceed().unwrap() {
RecvRequestResult::RecvBody(reply) => reply,
_ => unreachable!("Request without body should lead to ProvideResponse"),
}
}
}
pub fn to_provide_response(&self) -> Reply<ProvideResponse> {
let mut reply = self.to_recv_request();
let input = write_request(&self.request);
let (_, request) = reply.try_request(&input).unwrap();
assert!(request.is_some());
if self
.request
.headers()
.get("expect")
.is_some_and(|v| v == "100-continue")
{
match reply.proceed().unwrap() {
RecvRequestResult::Send100(reply) => {
let mut output = vec![0; 1024];
let (_, mut reply) = reply.accept(&mut output).unwrap();
let (_, _) = reply.read(&self.request_body, &mut vec![0; 1024]).unwrap();
if !self.request_body.is_empty()
&& !self.request_body.ends_with(b"\r\n0\r\n\r\n")
{
let end_marker = b"0\r\n\r\n";
let (_, _) = reply.read(end_marker, &mut vec![0; 1024]).unwrap();
}
reply.proceed().unwrap()
}
_ => unreachable!("Expect: 100-continue header should lead to Send100"),
}
} else if !self.request_body.is_empty() {
match reply.proceed().unwrap() {
RecvRequestResult::RecvBody(mut reply) => {
let (_, _) = reply.read(&self.request_body, &mut vec![0; 1024]).unwrap();
if !self.request_body.is_empty()
&& !self.request_body.ends_with(b"\r\n0\r\n\r\n")
{
let end_marker = b"0\r\n\r\n";
let (_, _) = reply.read(end_marker, &mut vec![0; 1024]).unwrap();
}
reply.proceed().unwrap()
}
_ => unreachable!("Request with body should lead to RecvBody"),
}
} else {
match reply.proceed().unwrap() {
RecvRequestResult::ProvideResponse(reply) => reply,
_ => unreachable!("Request without body should lead to ProvideResponse"),
}
}
}
pub fn to_send_response(&self) -> Reply<SendResponse> {
let reply = self.to_provide_response();
reply.provide(self.response.clone()).unwrap()
}
pub fn to_send_body(&self) -> Reply<SendBody> {
let mut reply = self.to_send_response();
let mut output = vec![0; 1024];
reply.write(&mut output).unwrap();
match reply.proceed() {
SendResponseResult::SendBody(reply) => reply,
SendResponseResult::Cleanup(_) => {
panic!(
"Expected SendBody variant, got Cleanup. This usually means the response doesn't need a body (e.g., HEAD request or 204 response)"
)
}
}
}
pub fn to_cleanup(&self) -> Reply<Cleanup> {
let mut reply = self.to_send_body();
let mut output = vec![0; 1024];
if !self.response_body.is_empty() {
reply.write(&self.response_body, &mut output).unwrap();
}
reply.write(&[], &mut output).unwrap();
reply.proceed()
}
}
pub fn write_request(r: &Request<()>) -> Vec<u8> {
let mut output = Vec::<u8>::new();
write!(
&mut output,
"{} {} {:?}\r\n",
r.method(),
r.uri().path(),
r.version()
)
.unwrap();
for (k, v) in r.headers().iter() {
write!(&mut output, "{}: {}\r\n", k.as_str(), v.to_str().unwrap()).unwrap();
}
write!(&mut output, "\r\n").unwrap();
output
}
#[derive(Default)]
pub struct ScenarioBuilder<T> {
request: Request<()>,
request_body: Vec<u8>,
response: Response<()>,
response_body: Vec<u8>,
_ph: PhantomData<T>,
}
pub struct WithReq(());
pub struct WithRes(());
#[allow(unused)]
impl ScenarioBuilder<()> {
pub fn new() -> Self {
Default::default()
}
pub fn request(self, request: Request<()>) -> ScenarioBuilder<WithReq> {
ScenarioBuilder {
request,
request_body: vec![],
response: Response::default(),
response_body: vec![],
_ph: PhantomData,
}
}
pub fn method(self, method: Method, uri: &str) -> ScenarioBuilder<WithReq> {
self.request(Request::builder().method(method).uri(uri).body(()).unwrap())
}
pub fn get(self, uri: &str) -> ScenarioBuilder<WithReq> {
self.request(Request::get(uri).body(()).unwrap())
}
pub fn head(self, uri: &str) -> ScenarioBuilder<WithReq> {
self.request(Request::head(uri).body(()).unwrap())
}
pub fn post(self, uri: &str) -> ScenarioBuilder<WithReq> {
self.request(Request::post(uri).body(()).unwrap())
}
pub fn put(self, uri: &str) -> ScenarioBuilder<WithReq> {
self.request(Request::put(uri).body(()).unwrap())
}
pub fn options(self, uri: &str) -> ScenarioBuilder<WithReq> {
self.request(Request::options(uri).body(()).unwrap())
}
pub fn delete(self, uri: &str) -> ScenarioBuilder<WithReq> {
self.request(Request::delete(uri).body(()).unwrap())
}
pub fn trace(self, uri: &str) -> ScenarioBuilder<WithReq> {
self.request(Request::trace(uri).body(()).unwrap())
}
pub fn connect(self, uri: &str) -> ScenarioBuilder<WithReq> {
self.request(Request::connect(uri).body(()).unwrap())
}
pub fn patch(self, uri: &str) -> ScenarioBuilder<WithReq> {
self.request(Request::patch(uri).body(()).unwrap())
}
}
#[allow(unused)]
impl ScenarioBuilder<WithReq> {
pub fn header(mut self, key: &'static str, value: impl ToString) -> Self {
self.request
.headers_mut()
.append(key, value.to_string().try_into().unwrap());
self
}
pub fn request_body<B: AsRef<[u8]>>(mut self, body: B, chunked: bool) -> Self {
let body = body.as_ref().to_vec();
let len = body.len();
if chunked {
let mut chunked_body = Vec::new();
write!(&mut chunked_body, "{:x}\r\n", len).unwrap();
chunked_body.extend_from_slice(&body);
write!(&mut chunked_body, "\r\n0\r\n\r\n").unwrap();
self.request_body = chunked_body;
self.header("transfer-encoding", "chunked")
} else {
self.request_body = body;
self.header("content-length", len.to_string())
}
}
pub fn response(mut self, response: Response<()>) -> ScenarioBuilder<WithRes> {
let ScenarioBuilder {
request,
request_body,
response_body,
..
} = self;
ScenarioBuilder {
request,
request_body,
response,
response_body,
_ph: PhantomData,
}
}
pub fn build(self) -> Scenario {
Scenario {
request: self.request,
request_body: self.request_body,
response: Response::default(),
response_body: vec![],
}
}
}
impl ScenarioBuilder<WithRes> {
pub fn build(self) -> Scenario {
Scenario {
request: self.request,
request_body: self.request_body,
response: self.response,
response_body: self.response_body,
}
}
}