cloud-sdk 0.34.0

no_std-first provider-neutral cloud SDK foundations.
Documentation
#![no_std]
#![doc = include_str!("../README.md")]

#[cfg(feature = "std")]
extern crate std;

macro_rules! impl_static_error {
    ($error:ty, $($pattern:pat => $message:literal),+ $(,)?) => {
        impl core::fmt::Display for $error {
            fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
                formatter.write_str(match self {
                    $($pattern => $message,)+
                })
            }
        }

        impl core::error::Error for $error {}
    };
}

pub mod action_polling;
pub mod buffer;
mod identity;
mod method;
pub mod operation;
pub mod pagination;
pub mod rate_limit;
pub mod transport;

pub use identity::{
    IdentityError, MAX_PROVIDER_ID_BYTES, MAX_SERVICE_ID_BYTES, ProviderId, ProviderMarker,
    ServiceId, ServiceMarker,
};
pub use method::{MAX_METHOD_BYTES, Method, MethodError};

#[cfg(test)]
mod tests {
    use super::{Method, MethodError, ProviderId, ServiceId};
    use crate::action_polling::ActionPollError;
    use crate::operation::{
        OperationMetadataError, PreparedExecutionError, ResponsePolicyError,
        ResponsePolicyValidationError,
    };
    use crate::pagination::PaginationError;
    use crate::rate_limit::RateLimitError;
    use crate::transport::{ContentTypeError, EndpointIdentityError, RequestTargetError};
    use core::fmt::{self, Write};

    #[test]
    fn exposes_provider_neutral_domains() {
        assert_eq!(
            ProviderId::new("example").map(ProviderId::as_str),
            Ok("example")
        );
        assert_eq!(
            ServiceId::new("compute").map(ServiceId::as_str),
            Ok("compute")
        );
        assert_eq!(Method::Get, Method::Get);
        assert_eq!(Method::Post.as_str(), "POST");
    }

    #[test]
    fn public_errors_implement_payload_free_core_error() {
        fn assert_error<E: core::error::Error>() {}

        assert_error::<PaginationError>();
        assert_error::<RateLimitError>();
        assert_error::<ContentTypeError>();
        assert_error::<EndpointIdentityError>();
        assert_error::<RequestTargetError>();
        assert_error::<MethodError>();
        assert_error::<ActionPollError<&'static str>>();
        assert_error::<OperationMetadataError>();
        assert_error::<ResponsePolicyError>();
        assert_error::<ResponsePolicyValidationError>();
        assert_error::<PreparedExecutionError<()>>();

        assert_display(PaginationError::PageZero, "page number must be nonzero");
        assert_display(RateLimitError::LimitZero, "rate limit must be nonzero");
        assert_display(ContentTypeError::Empty, "content type is empty");
        assert_display(
            EndpointIdentityError::UnboundTransport,
            "transport endpoint identity is unbound",
        );
        assert_display(RequestTargetError::Empty, "request target is empty");
        assert_display(
            MethodError::DeniedMethod,
            "HTTP method is denied by the transport contract",
        );
        assert_display(
            OperationMetadataError::NonIdempotentRetry,
            "non-idempotent operation cannot be retry eligible",
        );
        assert_display(
            ResponsePolicyError::UnexpectedContentType,
            "response content type is not accepted",
        );
        assert_display(
            ResponsePolicyValidationError::MissingSuccessStatus,
            "response policy has no success status",
        );
        assert_display(
            PreparedExecutionError::<()>::Transport(()),
            "prepared request transport failed",
        );
        assert_display(
            ActionPollError::Policy("sentinel-secret"),
            "action poll policy failed",
        );
    }

    fn assert_display(error: impl fmt::Display, expected: &str) {
        let mut output = DisplayBuffer::new();
        assert!(write!(&mut output, "{error}").is_ok());
        assert_eq!(output.as_str(), expected);
    }

    struct DisplayBuffer {
        bytes: [u8; 128],
        len: usize,
    }

    impl DisplayBuffer {
        const fn new() -> Self {
            Self {
                bytes: [0; 128],
                len: 0,
            }
        }

        fn as_str(&self) -> &str {
            core::str::from_utf8(self.bytes.get(..self.len).unwrap_or_default()).unwrap_or_default()
        }
    }

    impl Write for DisplayBuffer {
        fn write_str(&mut self, value: &str) -> fmt::Result {
            let end = self.len.checked_add(value.len()).ok_or(fmt::Error)?;
            let target = self.bytes.get_mut(self.len..end).ok_or(fmt::Error)?;
            target.copy_from_slice(value.as_bytes());
            self.len = end;
            Ok(())
        }
    }
}