arclutevests 0.1.0

Retrieve secret data from a vault (Hashicorp) instance
Documentation
// Copyright (c) 2022 arclutevests developers
//
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. All files in the project carrying such notice may not be copied,
// modified, or distributed except according to those terms.

use getset::Getters;
use reqwest::header::InvalidHeaderValue;
use serde::{ser::SerializeStruct, Serialize, Serializer};
use std::fmt;

pub(crate) type Result<T> = std::result::Result<T, Error>;

/// An `arclutevests` error
#[derive(Debug, Getters, thiserror::Error)]
#[getset(get = "pub")]
pub struct Error {
    /// The error URI
    uri: String,
    /// The title
    title: String,
    /// The status code
    status: usize,
    /// A detail message
    detail: String,
    /// An optional source error
    source: Option<anyhow::Error>,
}

impl Error {
    fn new<T, U, V>(
        uri: T,
        title: U,
        status: usize,
        detail: V,
        source: Option<anyhow::Error>,
    ) -> Self
    where
        T: Into<String>,
        U: Into<String>,
        V: Into<String>,
    {
        Self {
            uri: uri.into(),
            title: title.into(),
            status,
            detail: detail.into(),
            source,
        }
    }

    pub(crate) fn invalid_body(detail: String) -> Self {
        Self::new("/v1/s/error/invalidbody", "Invalid Body", 400, detail, None)
    }

    #[cfg(feature = "wrapped")]
    pub(crate) fn wrapped_response() -> Self {
        Self::new(
            "/v1/s/error/wrappedresponse",
            "Invalid Wrapped Response",
            500,
            "Invalid wrapped response received from Vault",
            None,
        )
    }

    #[cfg(feature = "wrapped")]
    pub(crate) fn token_wrap_failed(error: anyhow::Error) -> Self {
        Self::new(
            "/v1/s/error/tokenwrapfailed",
            "Token Wrap Failed",
            500,
            "Vault was unable to generate a wrapping token",
            Some(error),
        )
    }

    #[cfg(feature = "check_approle")]
    pub(crate) fn approles_check_failed(error: anyhow::Error) -> Self {
        Self::new(
            "/v1/s/error/approlescheckfailed",
            "Approle Check Failed",
            500,
            "Unable to verify the given approle against Vault",
            Some(error),
        )
    }

    #[cfg(feature = "check_approle")]
    pub(crate) fn approles_response() -> Self {
        Self::new(
            "/v1/s/error/approlecheckfailed",
            "Invalid Approles Response",
            500,
            "Invalid approles response received from toad",
            None,
        )
    }

    pub(crate) fn token_unwrap_failed(error: anyhow::Error) -> Self {
        Self::new(
            "/v1/s/error/tokenunwrapfailed",
            "Token Unwrap Failed",
            500,
            "Vault was unable to unwrap the given wrapped token",
            Some(error),
        )
    }

    pub(crate) fn auth_failed(error: anyhow::Error) -> Self {
        Self::new(
            "/v1/s/error/authfailed",
            "Authentication Failed",
            500,
            "Vault was unable to authenticate the client",
            Some(error),
        )
    }

    pub(crate) fn vault_auth() -> Self {
        Self::new(
            "/v1/s/error/vaultauth",
            "Invalid Vault Auth Response",
            500,
            "Invalid auth response received from Vault",
            None,
        )
    }

    pub(crate) fn data_retrieval_failed(error: anyhow::Error) -> Self {
        Self::new(
            "/v1/s/error/dataretrievefailed",
            "Secret Data Retrieval Failed",
            500,
            "Vault was unable to retrieve and parse the secret data",
            Some(error),
        )
    }

    pub(crate) fn vault_data() -> Self {
        Self::new(
            "/v1/s/error/vaultdata",
            "Invalid Vault Data Response",
            500,
            "Invalid data response received from Vault",
            None,
        )
    }

    pub(crate) fn token_revoke_failed(error: anyhow::Error) -> Self {
        Self::new(
            "/v1/s/error/tokenrevokefailed",
            "Token Revoke Failed",
            500,
            "Vault was unable to revoke the given token",
            Some(error),
        )
    }

    pub(crate) fn reqwest_error(error: reqwest::Error) -> Self {
        Self::new(
            "/v1/s/error/reqwest",
            "reqwest",
            500,
            "An error from the reqwest library",
            Some(error.into()),
        )
    }

    pub(crate) fn invalid_header_error(error: InvalidHeaderValue) -> Self {
        Self::new(
            "/v1/s/error/invalidheadervalue",
            "invalidheadervalue",
            500,
            "An error converting to a 'HeaderValue'",
            Some(error.into()),
        )
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "uri: {}, ", self.uri)?;
        write!(f, "title: {}, ", self.title)?;
        write!(f, "status: {}, ", self.status)?;
        write!(f, "detail: {}", self.detail)?;
        if let Some(ref source) = self.source {
            writeln!(f)?;
            write!(f, "source: {source:?}")?;
        }
        Ok(())
    }
}

impl Serialize for Error {
    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let len = if self.source.is_some() { 6 } else { 5 };
        let mut state = serializer.serialize_struct("Error", len)?;
        state.serialize_field("type", &self.uri)?;
        state.serialize_field("title", &self.title)?;
        state.serialize_field("status", &self.status)?;
        state.serialize_field("detail", &self.detail)?;
        if let Some(source) = self.source() {
            state.serialize_field("source", &format!("{source}"))?;
        }
        state.end()
    }
}