1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//! # TCP client
//!
//! This module contains the implementation of the TCP client, based
//! on [`tokio::net::TcpStream`].

use async_trait::async_trait;
use std::io::{Error, ErrorKind, Result};
use tokio::{
    io::{AsyncBufReadExt, AsyncWriteExt},
    net::TcpStream,
};

use crate::{
    request::{Request, RequestWriter},
    response::{Response, ResponseReader},
    tcp::TcpHandler,
    timer::Timer,
};

use super::{Client, ClientStream};

/// The TCP client.
///
/// This [`Client`] uses the TCP protocol to connect to a listener, to
/// read responses and write requests.
pub struct TcpClient {
    /// The TCP host the client should connect to.
    pub host: String,

    /// The TCP port the client should connect to.
    pub port: u16,
}

impl TcpClient {
    /// Create a new TCP client using the given host and port.
    pub fn new(host: impl ToString, port: u16) -> Box<dyn Client> {
        Box::new(Self {
            host: host.to_string(),
            port,
        })
    }
}

#[async_trait]
impl Client for TcpClient {
    /// Send the given request to the TCP server.
    async fn send(&self, req: Request) -> Result<Response> {
        let stream = TcpStream::connect((self.host.as_str(), self.port)).await?;
        let mut handler = TcpHandler::from(stream);
        handler.handle(req).await
    }
}

#[async_trait]
impl RequestWriter for TcpHandler {
    async fn write(&mut self, req: Request) -> Result<()> {
        let req = match req {
            Request::Start => format!("start\n"),
            Request::Get => format!("get\n"),
            Request::Set(duration) => format!("set {duration}\n"),
            Request::Pause => format!("pause\n"),
            Request::Resume => format!("resume\n"),
            Request::Stop => format!("stop\n"),
        };

        self.writer.write_all(req.as_bytes()).await?;

        Ok(())
    }
}

#[async_trait]
impl ResponseReader for TcpHandler {
    async fn read(&mut self) -> Result<Response> {
        let mut res = String::new();
        self.reader.read_line(&mut res).await?;

        let mut tokens = res.split_whitespace();
        match tokens.next() {
            Some("ok") => Ok(Response::Ok),
            Some("timer") => match tokens.next().map(serde_json::from_str::<Timer>) {
                Some(Ok(timer)) => Ok(Response::Timer(timer)),
                Some(Err(err)) => Err(Error::new(
                    ErrorKind::InvalidInput,
                    format!("invalid timer: {err}"),
                )),
                None => Err(Error::new(
                    ErrorKind::InvalidInput,
                    "missing timer".to_owned(),
                )),
            },
            Some(res) => Err(Error::new(
                ErrorKind::InvalidInput,
                format!("invalid response: {res}"),
            )),
            None => Err(Error::new(
                ErrorKind::InvalidInput,
                "missing response".to_owned(),
            )),
        }
    }
}