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
use once_cell::sync::Lazy;
use scraper::{
    Html,
    Selector,
};

/// The error
#[derive(Debug, thiserror::Error)]
pub enum Error {
    /// Reqwest error
    #[error(transparent)]
    Reqwest(#[from] reqwest::Error),

    /// A tokio task failed to join
    #[error(transparent)]
    TokioJoin(#[from] tokio::task::JoinError),

    /// The translate result is missing
    #[error("missing result")]
    MissingResult,
}

/// The client
#[derive(Debug, Clone)]
pub struct Client {
    /// The inner http client
    pub client: reqwest::Client,
}

impl Client {
    /// Make a new client
    pub fn new() -> Self {
        Self {
            client: reqwest::Client::new(),
        }
    }

    /// Translate an input.
    pub async fn translate(&self, data: &str) -> Result<String, Error> {
        let text = self
            .client
            .post("http://www.yodaspeak.co.uk/index.php")
            .form(&[("YodaMe", data), ("go", "Convert to Yoda-Speak!")])
            .send()
            .await?
            .error_for_status()?
            .text()
            .await?;

        tokio::task::spawn_blocking(move || {
            static SELECTOR: Lazy<Selector> = Lazy::new(|| {
                Selector::parse("#result textarea").expect("failed to parse `SELECTOR`")
            });
            let html = Html::parse_document(&text);

            let result = html
                .select(&SELECTOR)
                .next()
                .and_then(|el| el.text().next())
                .ok_or(Error::MissingResult)?;

            Ok(result.to_string())
        })
        .await?
    }
}

impl Default for Client {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[tokio::test]
    async fn it_works() {
        let client = Client::new();
        let input = "this translator works";
        let translated = client.translate(input).await.expect("failed to translate");
        dbg!(translated);
    }
}