1use chrono::{DateTime, Utc};
2use hmac::digest::InvalidLength;
3use hmac::{Hmac, Mac};
4use sha2::Sha256;
5
6#[derive(Debug)]
7pub struct SignRequestParams<'a, T>
8where
9 T: serde::Serialize,
10{
11 pub time: DateTime<Utc>,
12 pub api_key: &'a str,
13 pub secret_key: &'a str,
14 pub params_kind: SignRequestParamsKind,
15 pub params: &'a T,
16}
17
18#[derive(Debug)]
19pub enum SignRequestParamsKind {
20 Query,
21 Body,
22}
23
24#[derive(Debug)]
25pub struct SignRequestOutput {
26 pub signature: String,
27}
28
29#[derive(Debug, thiserror::Error)]
30pub enum SignRequestError {
31 #[error("Serde url encoded error: {0}")]
32 SerdeUrlEncoded(#[from] serde_urlencoded::ser::Error),
33
34 #[error("Serde json error: {0}")]
35 SerdeJson(#[from] serde_json::Error),
36
37 #[error("Secret key has invalid length for hmac sha 265")]
38 SecretKeyInvalidLength(#[from] InvalidLength),
39}
40
41pub fn sign_request<T>(
42 params: SignRequestParams<'_, T>,
43) -> Result<SignRequestOutput, SignRequestError>
44where
45 T: serde::Serialize,
46{
47 let data_string = match params.params_kind {
48 SignRequestParamsKind::Query => serde_urlencoded::to_string(params.params)?,
49 SignRequestParamsKind::Body => serde_json::to_string(params.params)?,
50 };
51 let mut mac = Hmac::<Sha256>::new_from_slice(params.secret_key.as_bytes())?;
52
53 let string_to_sign = format!(
54 "{}{}{}",
55 params.api_key,
56 params.time.timestamp_millis(),
57 data_string
58 );
59
60 mac.update(string_to_sign.as_bytes());
61 let mac_result = mac.finalize();
62 let mac_bytes = mac_result.into_bytes();
63 let signature = hex::encode(mac_bytes);
64
65 Ok(SignRequestOutput { signature })
66}