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