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
12pub mod models {
14 pub use libtad_models::*;
15}
16
17pub use error::ApiError;
19
20pub use error::Error;
22
23pub mod service;
25
26pub 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 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", ×tamp.to_string())
79 .append_pair("version", Self::VERSION);
80
81 Ok(url)
82 }
83}