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