dandanapi 0.0.1

Dandanapi http client
Documentation
use std::str::FromStr;

use crate::{
    Error,
    Result,
};

#[derive(Debug, Clone)]
pub struct RequestHeaderGenerator {
    x_appid: Box<str>,
    secret: Box<str>,
}

impl Default for RequestHeaderGenerator {
    fn default() -> Self {
        Self {
            x_appid: "".into(),
            secret: "".into(),
        }
    }
}

impl RequestHeaderGenerator {
    pub fn new(x_appid: String, secret_generator: SecretGenerator) -> Result<Self> {
        let Some(secret) = secret_generator.generate_plaintext() else {
            return Err(Error::SecretGenerationError(
                "Failed to generate secret".to_string(),
            ));
        };

        Ok(Self {
            x_appid: x_appid.into(),
            secret: secret.into(),
        })
    }

    pub fn header(&self, path: &str) -> Result<reqwest::header::HeaderMap> {
        let mut header = reqwest::header::HeaderMap::new();
        header.insert("x-appid", self.x_appid.parse().unwrap());
        header.insert(
            "x-timestamp",
            chrono::Utc::now().timestamp().to_string().parse().unwrap(),
        );
        header.insert(
            "x-signature",
            self.calculate_signature(path)?.parse().unwrap(),
        );
        Ok(header)
    }

    pub fn calculate_signature(&self, path: &str) -> Result<String> {
        use base64::prelude::*;
        use sha2::{
            Digest,
            Sha256,
        };

        let timestamp = chrono::Utc::now().timestamp();
        let sha256 = Sha256::digest(
            format!("{}{}{}{}", self.x_appid, timestamp, path, self.secret).as_bytes(),
        );
        Ok(BASE64_STANDARD.encode(sha256))
    }
}

pub struct SecretGenerator {
    ciphertext: Vec<u8>,
    key: String,
}

impl SecretGenerator {
    pub fn new(ciphertext: Vec<u8>, key: String) -> Self {
        Self { ciphertext, key }
    }

    pub fn generate_plaintext(&self) -> Option<String> {
        let key = age::x25519::Identity::from_str(&self.key).ok()?;
        let pl = age::decrypt(&key, &self.ciphertext).ok()?;
        String::from_utf8(pl).ok()
    }
}

#[cfg(test)]
mod tests {
    use age::secrecy::ExposeSecret;

    use super::*;
    use std::str::FromStr;

    #[test]
    fn test_generate_plaintext() {
        let key = include_str!("../ed25519_key");
        let ciphertext = include_bytes!("../secret");
        let generator = SecretGenerator::new(ciphertext.to_vec(), key.to_string());
        let key = generator.generate_plaintext().unwrap();
        println!("key: {}", key);
    }
}