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        ContentTypeError, EndpointIdentityError, HeaderError, InformationalResponseError,
63        RawResponsePolicyError, RequestPathError, RequestTargetError, ResponseWriterError,
64        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::<RawResponsePolicyError>();
118        assert_error::<InformationalResponseError>();
119        assert_error::<TransportFailure<()>>();
120
121        assert_display(PaginationError::PageZero, "page number must be nonzero");
122        assert_display(RateLimitError::LimitZero, "rate limit must be nonzero");
123        assert_display(ContentTypeError::Empty, "content type is empty");
124        assert_display(HeaderError::DuplicateName, "HTTP header name is duplicated");
125        assert_display(
126            EndpointIdentityError::UnboundTransport,
127            "transport endpoint identity is unbound",
128        );
129        assert_display(
130            RequestTargetError::Path(RequestPathError::Empty),
131            "invalid request path: request path is empty",
132        );
133        assert_display(
134            MethodError::DeniedMethod,
135            "HTTP method is denied by the transport contract",
136        );
137        assert_display(
138            OperationMetadataError::NonIdempotentRetry,
139            "non-idempotent operation cannot be retry eligible",
140        );
141        assert_display(
142            ResponsePolicyError::UnexpectedContentType,
143            "response content type is not accepted",
144        );
145        assert_display(
146            ResponsePolicyError::InvalidContentType,
147            "response content type is invalid",
148        );
149        assert_display(
150            ResponsePolicyValidationError::MissingSuccessStatus,
151            "response policy has no success status",
152        );
153        assert_display(
154            PreparedExecutionError::<()>::Transport(()),
155            "prepared request transport failed",
156        );
157        assert_display(
158            ResponseWriterError::AlreadyCommitted,
159            "response writer is already committed",
160        );
161        assert_display(
162            RawResponsePolicyError::UnsafeAdmittedHeader,
163            "an unsafe response header was admitted",
164        );
165        assert_display(
166            InformationalResponseError::SwitchingProtocols,
167            "switching protocols is forbidden",
168        );
169        assert_display(
170            TransportFailure::unknown("sentinel-secret"),
171            "transport failed with uncertain delivery",
172        );
173        assert_display(
174            ActionPollError::Policy("sentinel-secret"),
175            "action poll policy failed",
176        );
177    }
178
179    fn assert_display(error: impl fmt::Display, expected: &str) {
180        let mut output = DisplayBuffer::new();
181        assert!(write!(&mut output, "{error}").is_ok());
182        assert_eq!(output.as_str(), expected);
183    }
184
185    struct DisplayBuffer {
186        bytes: [u8; 128],
187        len: usize,
188    }
189
190    impl DisplayBuffer {
191        const fn new() -> Self {
192            Self {
193                bytes: [0; 128],
194                len: 0,
195            }
196        }
197
198        fn as_str(&self) -> &str {
199            core::str::from_utf8(self.bytes.get(..self.len).unwrap_or_default()).unwrap_or_default()
200        }
201    }
202
203    impl Write for DisplayBuffer {
204        fn write_str(&mut self, value: &str) -> fmt::Result {
205            let end = self.len.checked_add(value.len()).ok_or(fmt::Error)?;
206            let target = self.bytes.get_mut(self.len..end).ok_or(fmt::Error)?;
207            target.copy_from_slice(value.as_bytes());
208            self.len = end;
209            Ok(())
210        }
211    }
212}