Skip to main content

cloud_sdk/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3
4#[cfg(feature = "std")]
5extern crate std;
6
7macro_rules! impl_static_error {
8    ($error:ty, $($pattern:pat => $message:literal),+ $(,)?) => {
9        impl core::fmt::Display for $error {
10            fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
11                formatter.write_str(match self {
12                    $($pattern => $message,)+
13                })
14            }
15        }
16
17        impl core::error::Error for $error {}
18    };
19}
20
21pub mod action_polling;
22pub mod buffer;
23mod identity;
24mod method;
25pub mod operation;
26pub mod pagination;
27pub mod rate_limit;
28pub mod transport;
29
30pub use identity::{
31    IdentityError, MAX_PROVIDER_ID_BYTES, MAX_SERVICE_ID_BYTES, ProviderId, ProviderMarker,
32    ServiceId, ServiceMarker,
33};
34pub use method::{MAX_METHOD_BYTES, Method, MethodError};
35
36#[cfg(test)]
37mod tests {
38    use super::{Method, MethodError, ProviderId, ServiceId};
39    use crate::action_polling::ActionPollError;
40    use crate::operation::{
41        OperationMetadataError, PreparedExecutionError, ResponsePolicyError,
42        ResponsePolicyValidationError,
43    };
44    use crate::pagination::PaginationError;
45    use crate::rate_limit::RateLimitError;
46    use crate::transport::{
47        ContentTypeError, EndpointIdentityError, HeaderError, RequestPathError, RequestTargetError,
48        ResponseWriterError,
49    };
50    use core::fmt::{self, Write};
51
52    #[test]
53    fn exposes_provider_neutral_domains() {
54        assert_eq!(
55            ProviderId::new("example").map(ProviderId::as_str),
56            Ok("example")
57        );
58        assert_eq!(
59            ServiceId::new("compute").map(ServiceId::as_str),
60            Ok("compute")
61        );
62        assert_eq!(Method::Get, Method::Get);
63        assert_eq!(Method::Post.as_str(), "POST");
64    }
65
66    #[test]
67    fn public_errors_implement_payload_free_core_error() {
68        fn assert_error<E: core::error::Error>() {}
69
70        assert_error::<PaginationError>();
71        assert_error::<RateLimitError>();
72        assert_error::<ContentTypeError>();
73        assert_error::<HeaderError>();
74        assert_error::<EndpointIdentityError>();
75        assert_error::<RequestTargetError>();
76        assert_error::<MethodError>();
77        assert_error::<ActionPollError<&'static str>>();
78        assert_error::<OperationMetadataError>();
79        assert_error::<ResponsePolicyError>();
80        assert_error::<ResponsePolicyValidationError>();
81        assert_error::<PreparedExecutionError<()>>();
82        assert_error::<ResponseWriterError>();
83
84        assert_display(PaginationError::PageZero, "page number must be nonzero");
85        assert_display(RateLimitError::LimitZero, "rate limit must be nonzero");
86        assert_display(ContentTypeError::Empty, "content type is empty");
87        assert_display(HeaderError::DuplicateName, "HTTP header name is duplicated");
88        assert_display(
89            EndpointIdentityError::UnboundTransport,
90            "transport endpoint identity is unbound",
91        );
92        assert_display(
93            RequestTargetError::Path(RequestPathError::Empty),
94            "invalid request path: request path is empty",
95        );
96        assert_display(
97            MethodError::DeniedMethod,
98            "HTTP method is denied by the transport contract",
99        );
100        assert_display(
101            OperationMetadataError::NonIdempotentRetry,
102            "non-idempotent operation cannot be retry eligible",
103        );
104        assert_display(
105            ResponsePolicyError::UnexpectedContentType,
106            "response content type is not accepted",
107        );
108        assert_display(
109            ResponsePolicyError::InvalidContentType,
110            "response content type is invalid",
111        );
112        assert_display(
113            ResponsePolicyValidationError::MissingSuccessStatus,
114            "response policy has no success status",
115        );
116        assert_display(
117            PreparedExecutionError::<()>::Transport(()),
118            "prepared request transport failed",
119        );
120        assert_display(
121            ResponseWriterError::AlreadyCommitted,
122            "response writer is already committed",
123        );
124        assert_display(
125            ActionPollError::Policy("sentinel-secret"),
126            "action poll policy failed",
127        );
128    }
129
130    fn assert_display(error: impl fmt::Display, expected: &str) {
131        let mut output = DisplayBuffer::new();
132        assert!(write!(&mut output, "{error}").is_ok());
133        assert_eq!(output.as_str(), expected);
134    }
135
136    struct DisplayBuffer {
137        bytes: [u8; 128],
138        len: usize,
139    }
140
141    impl DisplayBuffer {
142        const fn new() -> Self {
143            Self {
144                bytes: [0; 128],
145                len: 0,
146            }
147        }
148
149        fn as_str(&self) -> &str {
150            core::str::from_utf8(self.bytes.get(..self.len).unwrap_or_default()).unwrap_or_default()
151        }
152    }
153
154    impl Write for DisplayBuffer {
155        fn write_str(&mut self, value: &str) -> fmt::Result {
156            let end = self.len.checked_add(value.len()).ok_or(fmt::Error)?;
157            let target = self.bytes.get_mut(self.len..end).ok_or(fmt::Error)?;
158            target.copy_from_slice(value.as_bytes());
159            self.len = end;
160            Ok(())
161        }
162    }
163}