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