drive-v3 0.5.1

A library for interacting the Google Drive API
Documentation
use std::io::{prelude::*, BufReader};
use std::net::{TcpListener, TcpStream};

use crate::{Error, ErrorKind};

/// A local server that can be used to listen for requests.
///
/// # Examples
///
/// ```no_run
/// use drive_v3::LocalServer;
///
/// let local_server = LocalServer::new();
/// let received_request = local_server.wait_for_request().expect("failed to listen for requests");
///
/// // The local server is now running and waiting for a request,
/// // run "curl http://localhost:7878/my-request-url" to send one.
///
/// assert_eq!(received_request, "http://localhost:7878/my-request-url");
/// ```
#[derive(Debug, PartialEq, Eq)]
pub struct LocalServer {
    /// URI that a website can make requests to in order for them to be received by the server. (default: "http://localhost:7878")
    pub uri: String,

    /// Address where the server listens for requests. (default: "localhost:7878")
    pub address: String,

    /// Response returned by the server after receiving a request. (default: "request processed!")
    pub response: String,
}

impl Default for LocalServer {
    fn default() -> Self {
        Self {
            uri: String::from("http://localhost:7878"),
            address: String::from("localhost:7878"),
            response: String::from("request processed!"),
        }
    }
}

impl LocalServer {
    /// Creates a new [`LocalServer`] with default values.
    pub fn new() -> Self {
        Self { ..Default::default() }
    }

    /// Sets the `address` and `port` values for a [`LocalServer`].
    ///
    /// # Examples
    ///
    /// ```rust
    /// use drive_v3::LocalServer;
    ///
    /// let local_server = LocalServer::new().at("127.0.0.1", 8080);
    ///
    /// assert_eq!(local_server.uri, "http://127.0.0.1:8080");
    /// assert_eq!(local_server.address, "127.0.0.1:8080");
    /// ```
    pub fn at<T: AsRef<str>> ( mut self, address: T, port: usize ) -> Self {
        self.uri = format!( "http://{}:{}", address.as_ref(), port );
        self.address = format!( "{}:{}", address.as_ref(), port );

        self
    }

    /// Sets the [`response`](LocalServer::response) that the [`LocalServer`] will return after successfully receiving a request.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use drive_v3::LocalServer;
    ///
    /// let local_server = LocalServer::new().with_response("this is my response");
    ///
    /// assert_eq!( local_server.response, String::from("this is my response") )
    /// ```
    pub fn with_response<T: AsRef<str>> ( mut self, response: T ) -> Self {
        self.response = String::from( response.as_ref() );

        self
    }

    /// Returns a [202 Accepted](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202) response, containing the
    /// [`LocalServer`]'s [`response`](LocalServer::response) as the body.
    fn get_accepted_response( &self ) -> String {
        format!(
            "HTTP/1.1 202 Accepted\r\n\
            Content-Type: text/html; charset=utf-8\r\n\
            Connection: Closed\r\n\
            \r\n\
            {}",
            self.response,
        )
    }

    /// Gets the requested URI from the lines of a request.
    fn get_requested_uri( &self, request_lines: &[String] ) -> String {
        let first_line = request_lines[0].clone();
        let first_line_parts: Vec<&str> = first_line.split(' ').collect();

        format!( "{}{}", self.uri, first_line_parts[1] )
    }

    /// Handles a request received in the [`LocalServer`]'s listening [`address`](LocalServer::address) and returns
    /// the URI of the request.
    fn get_request_uri( &self, mut stream: TcpStream ) -> Result<String, Error> {
        let buf_reader = BufReader::new(&mut stream);

        let request_lines: Vec<String> = buf_reader
            .lines()
            .map( |result| result.unwrap() )
            .take_while( |line| !line.is_empty() )
            .collect();

        let response = self.get_accepted_response();
        stream.write_all( response.as_bytes() )?;

        Ok( self.get_requested_uri(&request_lines) )
    }

    /// Runs a [`LocalServer`] and binds a listener to its [`address`](LocalServer::address), after receiving
    /// a request the server will close and return the [URI](https://developer.mozilla.org/en-US/docs/Glossary/URI) of the
    /// received request.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use drive_v3::Error;
    /// use drive_v3::LocalServer;
    ///
    /// let local_server = LocalServer::new();
    /// let received_request = local_server.wait_for_request()?;
    ///
    /// // The local server is now running and waiting for a request,
    /// // run "curl http://localhost:7878/my-request-url" to send one.
    ///
    /// assert_eq!(received_request, "http://localhost:7878/my-request-url");
    /// # Ok::<(), Error>(())
    /// ```
    ///
    /// # Errors
    ///
    /// - an [`IO`](crate::ErrorKind::IO) error, if the server fails to to bind a TCP listener its
    /// [`address`](LocalServer::address) or if the incoming request is invalid.
    /// - a [`LocalServer`](crate::ErrorKind::LocalServer) error, if the [`LocalServer`] failed to listen for
    /// incoming requests.
    pub fn wait_for_request( &self ) -> Result<String, Error> {
        let tcp_listener = TcpListener::bind(&self.address)?;

        if let Some(stream) = tcp_listener.incoming().next() {
            let stream = stream?;

            return self.get_request_uri(stream)
        }

        #[cfg(not(tarpaulin_include))]
        Err(Error {
            kind: ErrorKind::LocalServer,
            message:  String::from("unable to listen for incoming requests"),
        })
    }
}

#[cfg(test)]
mod tests {
    use std::thread;
    use super::LocalServer;
    use crate::utils::test::{LOCAL_SERVER_IN_USE, curl_local_server};

    #[test]
    fn new_test() {
        let server = LocalServer::new();

        assert_eq!( server, LocalServer::default() );
    }

    #[test]
    fn at_test() {
        let server = LocalServer::new().at("test-path", 2024);

        assert_eq!( server.address, String::from("test-path:2024") );
        assert_eq!( server.uri,  String::from("http://test-path:2024") );
    }

    #[test]
    fn with_response_test() {
        let server = LocalServer::new().with_response("test response");

        assert_eq!( server.response, String::from("test response") );
    }

    #[test]
    fn wait_for_request_test() {
        // Only run if no other tests are using the local server
        let _unused = LOCAL_SERVER_IN_USE.lock().unwrap();

        let handle = thread::spawn( || {
            let local_server = LocalServer::new();
            let request = local_server.wait_for_request().unwrap();

            assert_eq!( request, format!("{}/test-request-url", local_server.uri) )
        } );

        let response = curl_local_server("test-request-url").unwrap();
        assert_eq!( response, LocalServer::new().response );

        handle.join().unwrap();
    }
}