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;
23pub mod pagination;
24pub mod rate_limit;
25pub mod transport;
26
27/// Cloud provider namespace.
28#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
29pub enum Provider {
30    /// Hetzner Cloud and DNS APIs.
31    Hetzner,
32}
33
34/// Provider API family.
35#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
36pub enum ApiFamily {
37    /// Cloud infrastructure API.
38    Cloud,
39    /// DNS API.
40    Dns,
41    /// Security-related API resources.
42    Security,
43    /// Storage-related API resources.
44    Storage,
45    /// Provider-specific post-1.0 API surface.
46    Extended,
47}
48
49/// HTTP method for a provider operation.
50#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
51pub enum Method {
52    /// GET request.
53    Get,
54    /// POST request.
55    Post,
56    /// PUT request.
57    Put,
58    /// DELETE request.
59    Delete,
60}
61
62impl Method {
63    /// Returns the HTTP method token.
64    #[must_use]
65    pub const fn as_str(self) -> &'static str {
66        match self {
67            Self::Get => "GET",
68            Self::Post => "POST",
69            Self::Put => "PUT",
70            Self::Delete => "DELETE",
71        }
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::{ApiFamily, Method, Provider};
78    use crate::action_polling::ActionPollError;
79    use crate::pagination::PaginationError;
80    use crate::rate_limit::RateLimitError;
81    use crate::transport::{ContentTypeError, RequestTargetError};
82    use core::fmt::{self, Write};
83
84    #[test]
85    fn exposes_provider_neutral_domains() {
86        assert_eq!(Provider::Hetzner, Provider::Hetzner);
87        assert_eq!(ApiFamily::Cloud, ApiFamily::Cloud);
88        assert_eq!(Method::Get, Method::Get);
89        assert_eq!(Method::Post.as_str(), "POST");
90    }
91
92    #[test]
93    fn public_errors_implement_payload_free_core_error() {
94        fn assert_error<E: core::error::Error>() {}
95
96        assert_error::<PaginationError>();
97        assert_error::<RateLimitError>();
98        assert_error::<ContentTypeError>();
99        assert_error::<RequestTargetError>();
100        assert_error::<ActionPollError<&'static str>>();
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(RequestTargetError::Empty, "request target is empty");
106        assert_display(
107            ActionPollError::Policy("sentinel-secret"),
108            "action poll policy failed",
109        );
110    }
111
112    fn assert_display(error: impl fmt::Display, expected: &str) {
113        let mut output = DisplayBuffer::new();
114        assert!(write!(&mut output, "{error}").is_ok());
115        assert_eq!(output.as_str(), expected);
116    }
117
118    struct DisplayBuffer {
119        bytes: [u8; 128],
120        len: usize,
121    }
122
123    impl DisplayBuffer {
124        const fn new() -> Self {
125            Self {
126                bytes: [0; 128],
127                len: 0,
128            }
129        }
130
131        fn as_str(&self) -> &str {
132            core::str::from_utf8(self.bytes.get(..self.len).unwrap_or_default()).unwrap_or_default()
133        }
134    }
135
136    impl Write for DisplayBuffer {
137        fn write_str(&mut self, value: &str) -> fmt::Result {
138            let end = self.len.checked_add(value.len()).ok_or(fmt::Error)?;
139            let target = self.bytes.get_mut(self.len..end).ok_or(fmt::Error)?;
140            target.copy_from_slice(value.as_bytes());
141            self.len = end;
142            Ok(())
143        }
144    }
145}