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