rusty-oss 0.2.1

Simple pure Rust Aliyun OSS Client following a Sans-IO approach
Documentation
use std::fmt::{self, Debug, Formatter};
use std::mem;

use serde::{Deserialize, Deserializer};
use time::PrimitiveDateTime;
use zeroize::Zeroize as _;

use crate::time_::ISO8601_EXT;

use super::{Credentials, RotatingCredentials};

/// Parser for responses received from the ECS security-credentials metadata service.
#[derive(Clone, Deserialize)]
pub struct EcsSecurityCredentialsMetadataResponse {
    #[serde(rename = "AccessKeyId")]
    key: String,
    #[serde(rename = "AccessKeySecret")]
    secret: String,
    #[serde(rename = "SecurityToken")]
    token: String,
    #[serde(rename = "Expiration", deserialize_with = "expiration_deserializer")]
    expiration: PrimitiveDateTime,
}

fn expiration_deserializer<'de, D>(deserializer: D) -> Result<PrimitiveDateTime, D::Error>
where
    D: Deserializer<'de>,
{
    let s: &str = Deserialize::deserialize(deserializer)?;

    PrimitiveDateTime::parse(s, &ISO8601_EXT).map_err(serde::de::Error::custom)
}

impl EcsSecurityCredentialsMetadataResponse {
    /// Deserialize a JSON response received from the ECS metadata service.
    ///
    /// Parses the credentials from a response received from
    /// `http://169.254.169.254/latest/meta-data/iam/security-credentials/{name-of-RAM-role}`.
    ///
    /// # Errors
    ///
    /// Returns an error if the JSON is invalid.
    pub fn deserialize(s: &str) -> Result<Self, serde_json::Error> {
        serde_json::from_str(s)
    }

    /// Get the key of this `EcsSecurityCredentialsMetadataResponse`
    #[inline]
    #[must_use]
    pub fn key(&self) -> &str {
        &self.key
    }

    /// Get the secret of this `EcsSecurityCredentialsMetadataResponse`
    #[inline]
    #[must_use]
    pub fn secret(&self) -> &str {
        &self.secret
    }

    /// Get the token of this `EcsSecurityCredentialsMetadataResponse`
    #[inline]
    #[must_use]
    pub fn token(&self) -> &str {
        &self.token
    }

    /// Get the expiration of the credentials of this `EcsSecurityCredentialsMetadataResponse`
    #[inline]
    #[must_use]
    pub const fn expiration(&self) -> PrimitiveDateTime {
        self.expiration
    }

    /// Convert this `EcsSecurityCredentialsMetadataResponse` into [`Credentials`]
    #[inline]
    #[must_use]
    pub fn into_credentials(mut self) -> Credentials {
        let key = mem::take(&mut self.key);
        let secret = mem::take(&mut self.secret);
        let token = mem::take(&mut self.token);
        Credentials::new_with_token(key, secret, token)
    }

    /// Update a [`RotatingCredentials`] with the credentials of this `EcsSecurityCredentialsMetadataResponse`
    #[inline]
    pub fn rotate_credentials(mut self, rotating: &RotatingCredentials) {
        let key = mem::take(&mut self.key);
        let secret = mem::take(&mut self.secret);
        let token = mem::take(&mut self.token);
        rotating.update(key, secret, Some(token));
    }
}

impl Debug for EcsSecurityCredentialsMetadataResponse {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.debug_struct("EcsSecurityCredentialsMetadataResponse")
            .field("key", &self.key)
            .finish_non_exhaustive()
    }
}

impl Drop for EcsSecurityCredentialsMetadataResponse {
    fn drop(&mut self) {
        self.secret.zeroize();
    }
}

#[cfg(test)]
mod tests {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn deserialize() {
        let json = r#"{
    "Code" : "Success",
    "LastUpdated" : "2020-12-28T16:47:50Z",
    "Type" : "OSS-HMAC",
    "AccessKeyId" : "some_access_key",
    "AccessKeySecret" : "some_secret_key",
    "SecurityToken" : "some_token",
    "Expiration" : "2020-12-28T23:10:09Z"
}"#;

        let deserialized = EcsSecurityCredentialsMetadataResponse::deserialize(json).unwrap();
        assert_eq!(deserialized.key(), "some_access_key");
        assert_eq!(deserialized.secret(), "some_secret_key");
        assert_eq!(deserialized.token(), "some_token");
        // 2020-12-28T23:10:09Z
        assert_eq!(
            deserialized.expiration().assume_utc().unix_timestamp(),
            1609197009
        );

        let debug_output = format!("{deserialized:?}");
        assert_eq!(
            debug_output,
            "EcsSecurityCredentialsMetadataResponse { key: \"some_access_key\", .. }"
        );
    }
}