mockito 0.11.1

HTTP mocking for Rust.
Documentation
use std::thread;
use std::io::Write;
use std::fmt::Display;
use std::net::{TcpListener, TcpStream};
use std::sync::{Arc, Mutex};
use {Matcher, SERVER_ADDRESS, Request, Mock};

impl Mock {
    fn method_matches(&self, request: &Request) -> bool {
        self.method == request.method
    }

    fn path_matches(&self, request: &Request) -> bool {
        self.path == request.path
    }

    fn headers_match(&self, request: &Request) -> bool {
        for &(ref field, ref value) in &self.headers {
            match request.find_header(field) {
                Some(request_header_value) => {
                    if value == request_header_value { continue }

                    return false
                },
                None => {
                    if value == &Matcher::Missing { continue }

                    return false
                },
            }
        }

        true
    }

    fn body_matches(&self, request: &Request) -> bool {
        self.body == String::from_utf8_lossy(&request.body).into_owned()
    }
}

impl<'a> PartialEq<Request> for &'a mut Mock {
    fn eq(&self, other: &Request) -> bool {
        self.method_matches(other)
            && self.path_matches(other)
            && self.headers_match(other)
            && self.body_matches(other)
    }
}

pub struct State {
    pub is_listening: bool,
    pub mocks: Vec<Mock>,
    pub unmatched_requests: Vec<Request>,
}

impl Default for State {
    fn default() -> Self {
        State {
            is_listening: is_listening(),
            mocks: Vec::new(),
            unmatched_requests: Vec::new(),
        }
    }
}

lazy_static! {
    pub static ref STATE: Arc<Mutex<State>> = Arc::new(Mutex::new(State::default()));
}

pub fn try_start() {
    if is_listening() { return }

    start()
}

fn start() {
    let state_mutex = STATE.clone();
    let mut state = state_mutex.lock().unwrap();

    if state.is_listening { return }

    thread::spawn(move || {
        let listener = TcpListener::bind(SERVER_ADDRESS).unwrap();
        debug!("Server is listening");
        for stream in listener.incoming() {
            match stream {
                Ok(mut stream) => {
                    let request = Request::from(&mut stream);
                    debug!("Request received: {}", request);
                    if request.is_ok() {
                        handle_request(request, stream);
                    } else {
                        let message = request.error().map_or("Could not parse the request.", |err| err.as_str());
                        debug!("Could not parse request because: {}", message);
                        respond_with_error(stream, message);
                    }
                },
                Err(_) => {
                    debug!("Could not read from stream");
                },
            }
        }
    });

    while !is_listening() {}

    state.is_listening = true;
}

fn is_listening() -> bool {
    TcpStream::connect(SERVER_ADDRESS).is_ok()
}

fn handle_request(request: Request, stream: TcpStream) {
    handle_match_mock(request, stream);
}

fn handle_match_mock(request: Request, stream: TcpStream) {
    let found;

    let state_mutex = STATE.clone();
    let mut state = state_mutex.lock().unwrap();

    match state.mocks.iter_mut().rev().find(|mock| mock == &request) {
        Some(mock) => {
            debug!("Mock found");
            found = true;
            mock.hits = mock.hits + 1;
            respond_with_mock(stream, &mock);
        },
        None => {
            debug!("Mock not found");
            found = false;
            respond_with_mock_not_found(stream);
        }
    }

    if !found { state.unmatched_requests.push(request); }
}

fn respond<S: Display>(mut stream: TcpStream, status: S, headers: Option<&str>, body: Option<&str>) {
    let mut response = format!("HTTP/1.1 {}\r\n", status);

    if let Some(headers) = headers {
        response.push_str(headers);
    }

    if let Some(body) = body {
        response.push_str(&format!("content-length: {}\r\n\r\n{}", body.len(), body));
    } else {
        response.push_str("\r\n");
    }

    let _ = stream.write(response.as_bytes());
    let _ = stream.flush();
}

fn respond_with_mock(stream: TcpStream, mock: &Mock) {
    let mut headers = String::new();
    for &(ref key, ref value) in &mock.response.headers {
        headers.push_str(key);
        headers.push_str(": ");
        headers.push_str(value);
        headers.push_str("\r\n");
    }

    respond(stream, &mock.response.status, Some(&headers), Some(&mock.response.body));
}

fn respond_with_mock_not_found(stream: TcpStream) {
    respond(stream, "501 Not Implemented", None, None);
}

fn respond_with_error(stream: TcpStream, message: &str) {
    respond(stream, "422 Unprocessable Entity", None, Some(message));
}