use std::sync::Arc;
pub(crate) mod iam;
pub(crate) mod mds;
pub(crate) mod service_account;
pub type Result<T> = std::result::Result<T, SigningError>;
#[derive(Clone, Debug)]
pub struct Signer {
pub(crate) inner: Arc<dyn dynamic::SigningProvider>,
}
impl<T> std::convert::From<T> for Signer
where
T: SigningProvider + Send + Sync + 'static,
{
fn from(value: T) -> Self {
Self {
inner: Arc::new(value),
}
}
}
impl Signer {
pub async fn client_email(&self) -> Result<String> {
self.inner.client_email().await
}
pub async fn sign<T>(&self, content: T) -> Result<bytes::Bytes>
where
T: AsRef<[u8]> + Send + Sync,
{
self.inner.sign(content.as_ref()).await
}
}
pub trait SigningProvider: std::fmt::Debug {
fn client_email(&self) -> impl Future<Output = Result<String>> + Send;
fn sign(&self, content: &[u8]) -> impl Future<Output = Result<bytes::Bytes>> + Send;
}
pub(crate) mod dynamic {
use super::Result;
#[async_trait::async_trait]
pub trait SigningProvider: Send + Sync + std::fmt::Debug {
async fn client_email(&self) -> Result<String>;
async fn sign(&self, content: &[u8]) -> Result<bytes::Bytes>;
}
#[async_trait::async_trait]
impl<T> SigningProvider for T
where
T: super::SigningProvider + Send + Sync,
{
async fn client_email(&self) -> Result<String> {
T::client_email(self).await
}
async fn sign(&self, content: &[u8]) -> Result<bytes::Bytes> {
T::sign(self, content).await
}
}
}
type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
#[derive(thiserror::Error, Debug)]
#[error(transparent)]
pub struct SigningError(SigningErrorKind);
impl SigningError {
pub fn is_transport(&self) -> bool {
matches!(self.0, SigningErrorKind::Transport(_))
}
pub fn is_parsing(&self) -> bool {
matches!(self.0, SigningErrorKind::Parsing(_))
}
pub fn is_sign(&self) -> bool {
matches!(self.0, SigningErrorKind::Sign(_))
}
pub(crate) fn parsing<T>(source: T) -> SigningError
where
T: Into<BoxError>,
{
SigningError(SigningErrorKind::Parsing(source.into()))
}
pub(crate) fn transport<T>(source: T) -> SigningError
where
T: Into<BoxError>,
{
SigningError(SigningErrorKind::Transport(source.into()))
}
pub(crate) fn sign<T>(source: T) -> SigningError
where
T: Into<BoxError>,
{
SigningError(SigningErrorKind::Sign(source.into()))
}
#[doc(hidden)]
pub fn from_msg<T>(message: T) -> SigningError
where
T: Into<BoxError>,
{
SigningError(SigningErrorKind::Sign(message.into()))
}
}
#[derive(thiserror::Error, Debug)]
enum SigningErrorKind {
#[error("failed to generate signature via IAM API: {0}")]
Transport(#[source] BoxError),
#[error("failed to parse private key: {0}")]
Parsing(#[source] BoxError),
#[error("failed to sign content: {0}")]
Sign(#[source] BoxError),
}
#[cfg(test)]
mod tests {
use super::*;
type TestResult = anyhow::Result<()>;
mockall::mock! {
#[derive(Debug)]
Signer{}
impl SigningProvider for Signer {
async fn client_email(&self) -> Result<String>;
async fn sign(&self, content: &[u8]) -> Result<bytes::Bytes>;
}
}
#[tokio::test]
async fn test_signer_success() -> TestResult {
let mut mock = MockSigner::new();
mock.expect_client_email()
.returning(|| Ok("test".to_string()));
mock.expect_sign()
.returning(|_| Ok(bytes::Bytes::from("test")));
let signer = Signer::from(mock);
let result = signer.client_email().await?;
assert_eq!(result, "test");
let result = signer.sign("test").await?;
assert_eq!(result, "test");
Ok(())
}
#[tokio::test]
async fn test_signer_error() -> TestResult {
let mut mock = MockSigner::new();
mock.expect_client_email()
.returning(|| Err(SigningError::transport("test")));
mock.expect_sign()
.returning(|_| Err(SigningError::sign("test")));
let signer = Signer::from(mock);
let result = signer.client_email().await;
assert!(result.is_err(), "{result:?}");
assert!(result.unwrap_err().is_transport());
let result = signer.sign("test").await;
assert!(result.is_err(), "{result:?}");
assert!(result.unwrap_err().is_sign());
Ok(())
}
}