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