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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
extern crate reqwest;

use std::collections::HashMap;
use std::time::SystemTime;

use crate::ENDPOINT;
use crate::USER_AGENT;

use reqwest::{Client, Response};
use serde::{Deserialize, Serialize};

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Token {
    value: String,
    created_at: SystemTime,
}

impl Token {
    /// Return the value of the token obtained from the API.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use rarbg_api::token::Token;
    ///
    /// #[tokio::main]
    /// async fn main() {
    ///     let token = Token::new("RustExample").await;
    ///     let value = token.value();
    /// }
    /// ```
    pub fn value(&self) -> &str {
        self.value.as_str()
    }

    /// Returns the time when the token was created.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use rarbg_api::token::Token;
    ///
    /// #[tokio::main]
    /// async fn main() {
    ///     let token = Token::new("RustExample").await;
    ///     let time_of_creation = token.created_at();
    /// }
    /// ```
    pub fn created_at(&self) -> &SystemTime {
        &self.created_at
    }

    /// Create a Token with the value obtained from the API.
    /// This token can be use to make requests to the API.
    ///
    /// # Panics
    ///
    /// Panics if a token cannot be retrieve from the API.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use rarbg_api::token::Token;
    ///
    /// #[tokio::main]
    /// async fn main() {
    ///     let token = Token::new("RustExample").await;
    /// }
    /// ```
    pub async fn new(app_id: &str) -> Self {
        let response = Token::get(app_id).await;
        let content = Token::parse(response).await;
        let value = content.get("token");
        match value {
            Some(token) => Token {
                value: token.clone(),
                created_at: SystemTime::now(),
            },
            None => panic!("Failed to retrieve a token from RARBG API."),
        }
    }

    async fn get(app_id: &str) -> Response {
        let client: Client = Client::builder().user_agent(USER_AGENT).build().unwrap();
        let response = client
            .get(ENDPOINT)
            .query(&[("get_token", "get_token")])
            .query(&[("app_id", app_id)])
            .send()
            .await;
        match response {
            Ok(response) => response,
            Err(reason) => panic!("{}", reason),
        }
    }

    async fn parse(response: Response) -> HashMap<String, String> {
        match response.json().await {
            Ok(json) => json,
            Err(reason) => panic!("{}", reason),
        }
    }

    /// Verifies that the token is still valid to use it with the API.
    /// Officially, a token is valid for 15 minutes but we keep this token valid for 10 minutes.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use rarbg_api::token::Token;
    ///
    /// #[tokio::main]
    /// async fn main() {
    ///     let token = Token::new("RustExample").await;
    ///     assert!(token.is_valid(), "Token should be valid !");
    /// }
    /// ```
    pub fn is_valid(&self) -> bool {
        let sys_time = SystemTime::now();
        let difference = sys_time.duration_since(self.created_at);
        match difference {
            Ok(duration) => {
                duration.as_secs() as f64 + f64::from(duration.subsec_nanos()) * 1e-9 < 600.0
            } // < 10 min
            Err(_) => false,
        }
    }
}