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