libtad_rs/
lib.rs

1#![warn(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4use http::{HTTPBaseClient, HTTPClient};
5use maybe_async::maybe_async;
6use service::Service;
7use url::Url;
8
9mod error;
10mod http;
11
12/// Service-related models.
13pub mod models {
14    pub use libtad_models::*;
15}
16
17/// Error returned from the API.
18pub use error::ApiError;
19
20/// Internal error.
21pub use error::Error;
22
23/// Available Time and Date services.
24pub mod service;
25
26/// Client for accessing the Time and Date APIs.
27pub struct ServiceClient {
28    client: HTTPClient,
29    access_key: String,
30    secret_key: String,
31}
32
33impl ServiceClient {
34    const BASE_URL: &'static str = "https://api.xmltime.com";
35    const VERSION: &'static str = "3";
36
37    /// Initialize a new client with an access and secret key.
38    pub fn new(access_key: String, secret_key: String) -> Self {
39        Self {
40            client: HTTPClient::new(),
41            access_key,
42            secret_key,
43        }
44    }
45
46    #[maybe_async]
47    async fn call<S: Service>(
48        &self,
49        request: &S::Request,
50    ) -> Result<Result<S::Response, ApiError>, Error> {
51        let mut url = Url::parse(Self::BASE_URL).unwrap();
52        let query = S::build_query(request);
53
54        url.set_path(S::PATH);
55        url.set_query(query.as_deref());
56        url = self.authenticate::<S>(url)?;
57
58        self.client.get::<S>(url).await
59    }
60
61    fn authenticate<S: Service>(&self, mut url: Url) -> Result<Url, Error> {
62        use hmac::{Hmac, Mac, NewMac};
63        use sha1::Sha1;
64
65        let timestamp = chrono::Utc::now().format("%FT%T");
66
67        let mut mac = Hmac::<Sha1>::new_from_slice(self.secret_key.as_bytes())?;
68
69        let message = format!("{}{}{}", self.access_key, S::PATH, timestamp);
70        mac.update(message.as_bytes());
71
72        let bytes = mac.finalize().into_bytes();
73        let signature = base64::encode(bytes);
74
75        url.query_pairs_mut()
76            .append_pair("accesskey", &self.access_key)
77            .append_pair("signature", &signature)
78            .append_pair("timestamp", &timestamp.to_string())
79            .append_pair("version", Self::VERSION);
80
81        Ok(url)
82    }
83}