mise-client 0.1.6

MIcro SErvice
Documentation
use http::Request;
use http_by_chunks::{ConverterTo, HttpByChunks};
use serde_json::Value;
use std::{
    io::{Read, Write},
    net::{SocketAddr, TcpStream},
};

#[derive(Debug)]
pub enum ClientError {
    StatusCode(u16),
    Io(std::io::Error),
    Http(http::Error),
    Serde(serde_json::Error),
}

impl From<std::io::Error> for ClientError {
    fn from(value: std::io::Error) -> Self {
        Self::Io(value)
    }
}

impl From<http::Error> for ClientError {
    fn from(value: http::Error) -> Self {
        Self::Http(value)
    }
}

impl From<serde_json::Error> for ClientError {
    fn from(value: serde_json::Error) -> Self {
        Self::Serde(value)
    }
}

pub type Result<T> = std::result::Result<T, ClientError>;

/// The client for mise
/// One instance of this will keep one connection open to the server and it
/// will be closed once this instance is deallocated.
///
/// It is possible to batch requests with the plural methods (ex gets instead
/// of get).
pub struct Client {
    _addr: SocketAddr,
    socket: TcpStream,
    static_read_buf: [u8; 4096],
    read_buf: Vec<u8>,
}

struct JsonConverter;
impl ConverterTo<Value> for JsonConverter {
    fn convert(&self, buf: &[u8]) -> Value {
        serde_json::from_slice(buf).unwrap_or(Value::Null)
    }
}

static JSON_CONVERTER: JsonConverter = JsonConverter;

impl Client {
    /// Creates a new client by connecting to a server.
    /// Every client instance is one connection.
    ///
    /// # Errors
    ///
    /// Errors if connection cannot be established.
    pub fn connect(addr: SocketAddr) -> Result<Self> {
        let socket = TcpStream::connect(addr)?;
        Ok(Self {
            _addr: addr,
            socket,
            static_read_buf: [0; 4096],
            read_buf: Vec::new(),
        })
    }

    /// Sends a normal GET request.
    ///
    /// # Errors
    ///
    /// Errors if request fails
    pub fn get(&mut self, uri: &str) -> Result<Value> {
        self.do_no_body("GET", uri)
    }

    /// Sends a batch of GET requests sent all at once.
    ///
    /// First writes all the requests in the pipe before starting to read back
    /// the first response. Effectively this is a batching mode since there is
    /// no waiting between sends.
    ///
    /// When batching, consider that if the send buffer is full this may block
    /// undefinetly as it won't read the first response until all requests are
    /// sent first. This means that the batch number cannot be arbitrarly high
    /// but there is a virtual limit that will trigger this behavior and then
    /// rend this method stuck in wait mode while trying to write to a full
    /// buffer.
    ///
    /// # Errors
    ///
    /// Errors if request fails
    pub fn gets<const X: usize>(&mut self, uris: &[&str; X]) -> Result<[Option<Result<Value>>; X]> {
        self.do_no_bodys("GET", uris)
    }

    /// Sends a DELETE request.
    ///
    /// # Errors
    ///
    /// Errors if request fails
    pub fn delete(&mut self, uri: &str) -> Result<Value> {
        self.do_no_body("DELETE", uri)
    }

    /// Batches a DELETE set of requests. For batching, see [`Self::gets`]
    ///
    /// # Errors
    ///
    /// Errors if request fails
    pub fn deletes<const X: usize>(
        &mut self,
        uris: &[&str; X],
    ) -> Result<[Option<Result<Value>>; X]> {
        self.do_no_bodys("DELETE", uris)
    }

    /// Sends a POST request.
    ///
    /// # Errors
    ///
    /// Errors if request fails
    pub fn post(&mut self, uri: &str, value: Value) -> Result<Value> {
        self.do_body("POST", uri, value)
    }

    /// Batches a POST set of requests. For batching, see [`Self::gets`]
    ///
    /// # Errors
    ///
    /// Errors if request fails
    pub fn posts<const X: usize>(
        &mut self,
        uris: &[&str; X],
        values: &[Value; X],
    ) -> Result<[Option<Result<Value>>; X]> {
        self.do_bodys("POST", uris, values)
    }

    /// Sends a PUT request.
    ///
    /// # Errors
    ///
    /// Errors if request fails
    pub fn put(&mut self, uri: &str, value: Value) -> Result<Value> {
        self.do_body("PUT", uri, value)
    }

    /// Batches a PUT set of requests. For batching, see [`Self::gets`]
    ///
    /// # Errors
    ///
    /// Errors if request fails
    pub fn puts<const X: usize>(
        &mut self,
        uris: &[&str; X],
        values: &[Value; X],
    ) -> Result<[Option<Result<Value>>; X]> {
        self.do_bodys("PUT", uris, values)
    }

    /// Sends a PATCH request.
    ///
    /// # Errors
    ///
    /// Errors if request fails
    pub fn patch(&mut self, uri: &str, value: Value) -> Result<Value> {
        self.do_body("PATCH", uri, value)
    }

    /// Batches a PATCH set of requests. For batching, see [`Self::gets`]
    ///
    /// # Errors
    ///
    /// Errors if request fails
    pub fn patches<const X: usize>(
        &mut self,
        uris: &[&str; X],
        values: &[Value; X],
    ) -> Result<[Option<Result<Value>>; X]> {
        self.do_bodys("PATCH", uris, values)
    }

    fn do_no_body(&mut self, method: &str, uri: &str) -> Result<Value> {
        let req = Request::builder()
            .method(method)
            .uri(uri)
            .body(Value::Null)?;
        self.send_request(&req)?;
        self.read_response()
    }

    fn do_no_bodys<const X: usize>(
        &mut self,
        method: &str,
        uris: &[&str; X],
    ) -> Result<[Option<Result<Value>>; X]> {
        let mut res = [const { None }; X];
        for uri in uris {
            let req = Request::builder()
                .method(method)
                .uri(*uri)
                .body(Value::Null)?;
            self.send_request(&req)?;
        }
        (0..uris.len()).for_each(|x| {
            res[x] = Some(self.read_response());
        });
        Ok(res)
    }

    fn do_body(&mut self, method: &str, uri: &str, value: Value) -> Result<Value> {
        let req = Request::builder().method(method).uri(uri).body(value)?;
        self.send_request(&req)?;
        self.read_response()
    }

    fn do_bodys<const X: usize>(
        &mut self,
        method: &str,
        uris: &[&str; X],
        values: &[Value; X],
    ) -> Result<[Option<Result<Value>>; X]> {
        let mut res = [const { None }; X];
        for i in 0..uris.len() {
            let req = Request::builder()
                .method(method)
                .uri(uris[i])
                .body(values[i].clone())?;
            self.send_request(&req)?;
        }
        (0..uris.len()).for_each(|x| {
            res[x] = Some(self.read_response());
        });
        Ok(res)
    }

    fn send_request(&mut self, req: &Request<Value>) -> Result<()> {
        self.socket.write_all(&req_to_bytes(req))?;
        Ok(())
    }

    fn read_response(&mut self) -> Result<Value> {
        let mut c = HttpByChunks::<Value>::new(&JSON_CONVERTER);
        loop {
            let n = self.socket.read(&mut self.static_read_buf)?;
            self.read_buf.extend_from_slice(&self.static_read_buf[..n]);
            self.read_buf = c.append(&self.read_buf).to_vec();
            if let Some(resp) = c.build_response() {
                let mut resp = resp?;
                if !resp.status().is_success() {
                    return Err(ClientError::StatusCode(resp.status().as_u16()));
                }
                return Ok(resp.body_mut().take());
            }
        }
    }
}

fn req_to_bytes(req: &Request<Value>) -> Vec<u8> {
    let mut res = vec![];
    res.extend_from_slice(format!("{} {} HTTP/1.1\r\n", req.method(), req.uri()).as_bytes());
    if *req.body() == Value::Null {
        res.extend_from_slice("\r\n".to_string().as_bytes());
    } else if let Ok(b) = serde_json::to_vec(req.body()) {
        res.extend_from_slice(format!("Content-Length: {}\r\n\r\n", b.len()).as_bytes());
        res.extend_from_slice(&b);
    }
    res
}