time/client/
tcp.rs

1//! # TCP client
2//!
3//! This module contains the implementation of the TCP client, based
4//! on [`tokio::net::TcpStream`].
5
6use async_trait::async_trait;
7use std::io::{Error, ErrorKind, Result};
8use tokio::{
9    io::{AsyncBufReadExt, AsyncWriteExt},
10    net::TcpStream,
11};
12
13use crate::{
14    request::{Request, RequestWriter},
15    response::{Response, ResponseReader},
16    tcp::TcpHandler,
17    timer::Timer,
18};
19
20use super::{Client, ClientStream};
21
22/// The TCP client.
23///
24/// This [`Client`] uses the TCP protocol to connect to a listener, to
25/// read responses and write requests.
26pub struct TcpClient {
27    /// The TCP host the client should connect to.
28    pub host: String,
29
30    /// The TCP port the client should connect to.
31    pub port: u16,
32}
33
34impl TcpClient {
35    /// Create a new TCP client using the given host and port.
36    pub fn new(host: impl ToString, port: u16) -> Box<dyn Client> {
37        Box::new(Self {
38            host: host.to_string(),
39            port,
40        })
41    }
42}
43
44#[async_trait]
45impl Client for TcpClient {
46    /// Send the given request to the TCP server.
47    async fn send(&self, req: Request) -> Result<Response> {
48        let stream = TcpStream::connect((self.host.as_str(), self.port)).await?;
49        let mut handler = TcpHandler::from(stream);
50        handler.handle(req).await
51    }
52}
53
54#[async_trait]
55impl RequestWriter for TcpHandler {
56    async fn write(&mut self, req: Request) -> Result<()> {
57        let req = match req {
58            Request::Start => format!("start\n"),
59            Request::Get => format!("get\n"),
60            Request::Set(duration) => format!("set {duration}\n"),
61            Request::Pause => format!("pause\n"),
62            Request::Resume => format!("resume\n"),
63            Request::Stop => format!("stop\n"),
64        };
65
66        self.writer.write_all(req.as_bytes()).await?;
67
68        Ok(())
69    }
70}
71
72#[async_trait]
73impl ResponseReader for TcpHandler {
74    async fn read(&mut self) -> Result<Response> {
75        let mut res = String::new();
76        self.reader.read_line(&mut res).await?;
77
78        let mut tokens = res.split_whitespace();
79        match tokens.next() {
80            Some("ok") => Ok(Response::Ok),
81            Some("timer") => match tokens.next().map(serde_json::from_str::<Timer>) {
82                Some(Ok(timer)) => Ok(Response::Timer(timer)),
83                Some(Err(err)) => Err(Error::new(
84                    ErrorKind::InvalidInput,
85                    format!("invalid timer: {err}"),
86                )),
87                None => Err(Error::new(
88                    ErrorKind::InvalidInput,
89                    "missing timer".to_owned(),
90                )),
91            },
92            Some(res) => Err(Error::new(
93                ErrorKind::InvalidInput,
94                format!("invalid response: {res}"),
95            )),
96            None => Err(Error::new(
97                ErrorKind::InvalidInput,
98                "missing response".to_owned(),
99            )),
100        }
101    }
102}