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;
24pub mod operation;
25pub mod pagination;
26pub mod rate_limit;
27pub mod transport;
28
29pub use identity::{
30    IdentityError, MAX_PROVIDER_ID_BYTES, MAX_SERVICE_ID_BYTES, ProviderId, ProviderMarker,
31    ServiceId, ServiceMarker,
32};
33
34/// HTTP method for a provider operation.
35#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
36pub enum Method {
37    /// GET request.
38    Get,
39    /// POST request.
40    Post,
41    /// PUT request.
42    Put,
43    /// DELETE request.
44    Delete,
45}
46
47impl Method {
48    /// Returns the HTTP method token.
49    #[must_use]
50    pub const fn as_str(self) -> &'static str {
51        match self {
52            Self::Get => "GET",
53            Self::Post => "POST",
54            Self::Put => "PUT",
55            Self::Delete => "DELETE",
56        }
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::{Method, ProviderId, ServiceId};
63    use crate::action_polling::ActionPollError;
64    use crate::operation::{
65        OperationMetadataError, PreparedExecutionError, ResponsePolicyError,
66        ResponsePolicyValidationError,
67    };
68    use crate::pagination::PaginationError;
69    use crate::rate_limit::RateLimitError;
70    use crate::transport::{ContentTypeError, EndpointIdentityError, RequestTargetError};
71    use core::fmt::{self, Write};
72
73    #[test]
74    fn exposes_provider_neutral_domains() {
75        assert_eq!(
76            ProviderId::new("example").map(ProviderId::as_str),
77            Ok("example")
78        );
79        assert_eq!(
80            ServiceId::new("compute").map(ServiceId::as_str),
81            Ok("compute")
82        );
83        assert_eq!(Method::Get, Method::Get);
84        assert_eq!(Method::Post.as_str(), "POST");
85    }
86
87    #[test]
88    fn public_errors_implement_payload_free_core_error() {
89        fn assert_error<E: core::error::Error>() {}
90
91        assert_error::<PaginationError>();
92        assert_error::<RateLimitError>();
93        assert_error::<ContentTypeError>();
94        assert_error::<EndpointIdentityError>();
95        assert_error::<RequestTargetError>();
96        assert_error::<ActionPollError<&'static str>>();
97        assert_error::<OperationMetadataError>();
98        assert_error::<ResponsePolicyError>();
99        assert_error::<ResponsePolicyValidationError>();
100        assert_error::<PreparedExecutionError<()>>();
101
102        assert_display(PaginationError::PageZero, "page number must be nonzero");
103        assert_display(RateLimitError::LimitZero, "rate limit must be nonzero");
104        assert_display(ContentTypeError::Empty, "content type is empty");
105        assert_display(
106            EndpointIdentityError::UnboundTransport,
107            "transport endpoint identity is unbound",
108        );
109        assert_display(RequestTargetError::Empty, "request target is empty");
110        assert_display(
111            OperationMetadataError::NonIdempotentRetry,
112            "non-idempotent operation cannot be retry eligible",
113        );
114        assert_display(
115            ResponsePolicyError::UnexpectedContentType,
116            "response content type is not accepted",
117        );
118        assert_display(
119            ResponsePolicyValidationError::MissingSuccessStatus,
120            "response policy has no success status",
121        );
122        assert_display(
123            PreparedExecutionError::<()>::Transport(()),
124            "prepared request transport failed",
125        );
126        assert_display(
127            ActionPollError::Policy("sentinel-secret"),
128            "action poll policy failed",
129        );
130    }
131
132    fn assert_display(error: impl fmt::Display, expected: &str) {
133        let mut output = DisplayBuffer::new();
134        assert!(write!(&mut output, "{error}").is_ok());
135        assert_eq!(output.as_str(), expected);
136    }
137
138    struct DisplayBuffer {
139        bytes: [u8; 128],
140        len: usize,
141    }
142
143    impl DisplayBuffer {
144        const fn new() -> Self {
145            Self {
146                bytes: [0; 128],
147                len: 0,
148            }
149        }
150
151        fn as_str(&self) -> &str {
152            core::str::from_utf8(self.bytes.get(..self.len).unwrap_or_default()).unwrap_or_default()
153        }
154    }
155
156    impl Write for DisplayBuffer {
157        fn write_str(&mut self, value: &str) -> fmt::Result {
158            let end = self.len.checked_add(value.len()).ok_or(fmt::Error)?;
159            let target = self.bytes.get_mut(self.len..end).ok_or(fmt::Error)?;
160            target.copy_from_slice(value.as_bytes());
161            self.len = end;
162            Ok(())
163        }
164    }
165}