use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::sync::{Arc, Mutex};
use std::time::Duration;
#[derive(Clone, Debug)]
pub(crate) enum Answer {
Bytes(Vec<u8>),
Silent(Duration),
}
pub(crate) const SILENT_AT_MOST: Duration = Duration::from_secs(60);
pub(crate) struct Mock {
pub(crate) url: String,
pub(crate) seen: Arc<Mutex<Vec<u8>>>,
handle: std::thread::JoinHandle<()>,
}
pub(crate) fn http_reply(status_line: &str, body: &str) -> Vec<u8> {
return format!("HTTP/1.1 {status_line}\r\ncontent-type: application/json\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{body}", body.len()).into_bytes();
}
fn read_request(stream: &mut TcpStream) -> Vec<u8> {
let mut received: Vec<u8> = Vec::new();
let mut chunk: [u8; 4096] = [0; 4096];
loop {
let count: usize = stream.read(&mut chunk).unwrap_or(0);
if count == 0 {
break;
}
received.extend_from_slice(&chunk[..count]);
let text: String = String::from_utf8_lossy(received.as_slice()).into_owned();
if let Some(split) = text.find("\r\n\r\n") {
let length: usize = text[..split]
.to_ascii_lowercase()
.lines()
.find_map(|line| return line.strip_prefix("content-length:").and_then(|value| return value.trim().parse::<usize>().ok()))
.unwrap_or(0);
if received.len() >= split + 4 + length {
break;
}
}
}
return received;
}
impl Mock {
pub(crate) fn start(answer: Answer) -> Mock {
let listener: TcpListener = TcpListener::bind("127.0.0.1:0").expect("bind loopback");
let port: u16 = listener.local_addr().expect("address").port();
let seen: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
let recorded: Arc<Mutex<Vec<u8>>> = Arc::clone(&seen);
let handle: std::thread::JoinHandle<()> = std::thread::spawn(move || {
let Ok((mut stream, _)) = listener.accept() else {
return;
};
let request: Vec<u8> = read_request(&mut stream);
recorded.lock().unwrap_or_else(std::sync::PoisonError::into_inner).extend_from_slice(request.as_slice());
match answer {
Answer::Bytes(bytes) => {
let _ = stream.write_all(bytes.as_slice());
}
Answer::Silent(hold) => {
let _bounded = stream.set_read_timeout(Some(hold));
let mut ignored: [u8; 64] = [0; 64];
while stream.read(&mut ignored).is_ok_and(|count| return count > 0) {}
}
}
});
return Mock { url: format!("http://127.0.0.1:{port}/v1/decisions"), seen, handle };
}
pub(crate) fn finish(self) -> String {
let _ = self.handle.join();
let bytes: Vec<u8> = self.seen.lock().unwrap_or_else(std::sync::PoisonError::into_inner).clone();
return String::from_utf8_lossy(bytes.as_slice()).into_owned();
}
}