1use std::{fmt::Debug, num::NonZeroUsize, ops::Deref, str::FromStr};
2
3use compact_str::{CompactString, ToCompactString};
4
5#[derive(Debug, Default, Clone, PartialEq, Eq)]
6pub struct Page<T> {
7 pub values: Vec<T>,
8 pub has_more: bool,
9}
10
11impl<T> Page<T> {
12 pub fn new_empty() -> Self {
13 Self {
14 values: Vec::new(),
15 has_more: false,
16 }
17 }
18
19 pub fn new(values: impl Into<Vec<T>>, has_more: bool) -> Self {
20 Self {
21 values: values.into(),
22 has_more,
23 }
24 }
25}
26
27#[derive(Debug, Clone, Copy)]
28pub struct ListLimit(NonZeroUsize);
29
30impl ListLimit {
31 pub const MAX: ListLimit = Self(NonZeroUsize::new(1000).unwrap());
32
33 pub fn get(&self) -> NonZeroUsize {
34 self.0
35 }
36
37 pub fn as_usize(&self) -> usize {
38 self.0.get()
39 }
40}
41
42impl Default for ListLimit {
43 fn default() -> Self {
44 Self::MAX
45 }
46}
47
48impl From<usize> for ListLimit {
49 fn from(value: usize) -> Self {
50 NonZeroUsize::new(value)
51 .and_then(|n| (n <= Self::MAX.0).then_some(Self(n)))
52 .unwrap_or_default()
53 }
54}
55
56impl From<ListLimit> for usize {
57 fn from(value: ListLimit) -> Self {
58 value.as_usize()
59 }
60}
61
62#[derive(Debug, Clone, Default)]
63pub struct ListItemsRequestParts<P, S> {
64 pub prefix: P,
65 pub start_after: S,
66 pub limit: ListLimit,
67}
68
69#[derive(Debug, Clone, Default)]
70pub struct ListItemsRequest<P, S>(ListItemsRequestParts<P, S>)
71where
72 P: Default,
73 S: Default;
74
75impl<P, S> ListItemsRequest<P, S>
76where
77 P: Default,
78 S: Default,
79{
80 pub fn parts(&self) -> &ListItemsRequestParts<P, S> {
81 &self.0
82 }
83}
84
85impl<P, S> From<ListItemsRequest<P, S>> for ListItemsRequestParts<P, S>
86where
87 P: Default,
88 S: Default,
89{
90 fn from(ListItemsRequest(parts): ListItemsRequest<P, S>) -> Self {
91 parts
92 }
93}
94
95#[derive(Debug, Clone, thiserror::Error)]
96#[error("`start_after` must be greater than or equal to the `prefix`")]
97pub struct StartAfterLessThanPrefixError;
98
99impl<P, S> TryFrom<ListItemsRequestParts<P, S>> for ListItemsRequest<P, S>
100where
101 P: Deref<Target = str> + Default,
102 S: Deref<Target = str> + Default,
103{
104 type Error = StartAfterLessThanPrefixError;
105
106 fn try_from(parts: ListItemsRequestParts<P, S>) -> Result<Self, Self::Error> {
107 let start_after: &str = &parts.start_after;
108 let prefix: &str = &parts.prefix;
109
110 if !start_after.is_empty() && !prefix.is_empty() && start_after < prefix {
111 return Err(StartAfterLessThanPrefixError);
112 }
113
114 Ok(Self(parts))
115 }
116}
117
118#[derive(Debug, Clone, PartialEq, Eq)]
123pub enum ProvisionMode {
124 CreateOnly {
129 request_token: Option<RequestToken>,
131 },
132 Ensure,
138}
139
140#[derive(Debug, Clone, PartialEq, Eq)]
145pub enum ProvisionResult<T> {
146 Created(T),
148 Updated(T),
150 Noop(T),
152}
153
154impl<T> ProvisionResult<T> {
155 pub fn inner(&self) -> &T {
157 match self {
158 Self::Created(t) | Self::Updated(t) | Self::Noop(t) => t,
159 }
160 }
161
162 pub fn into_inner(self) -> T {
164 match self {
165 Self::Created(t) | Self::Updated(t) | Self::Noop(t) => t,
166 }
167 }
168
169 pub fn map<U>(self, f: impl FnOnce(T) -> U) -> ProvisionResult<U> {
171 match self {
172 Self::Created(t) => ProvisionResult::Created(f(t)),
173 Self::Updated(t) => ProvisionResult::Updated(f(t)),
174 Self::Noop(t) => ProvisionResult::Noop(f(t)),
175 }
176 }
177
178 pub fn try_map<U, E>(self, f: impl FnOnce(T) -> Result<U, E>) -> Result<ProvisionResult<U>, E> {
180 match self {
181 Self::Created(t) => Ok(ProvisionResult::Created(f(t)?)),
182 Self::Updated(t) => Ok(ProvisionResult::Updated(f(t)?)),
183 Self::Noop(t) => Ok(ProvisionResult::Noop(f(t)?)),
184 }
185 }
186}
187
188pub static REQUEST_TOKEN_HEADER: http::HeaderName =
189 http::HeaderName::from_static("s2-request-token");
190
191pub static PROVISION_RESULT_HEADER: http::HeaderName =
192 http::HeaderName::from_static("s2-provision-result");
193
194pub const MAX_REQUEST_TOKEN_LENGTH: usize = 36;
195
196#[derive(Debug, PartialEq, Eq, thiserror::Error)]
197#[error("request token was longer than {MAX_REQUEST_TOKEN_LENGTH} bytes in length: {0}")]
198pub struct RequestTokenTooLongError(pub usize);
199
200#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
201pub struct RequestToken(CompactString);
202
203#[cfg(feature = "utoipa")]
204impl utoipa::PartialSchema for RequestToken {
205 fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
206 utoipa::openapi::Object::builder()
207 .schema_type(utoipa::openapi::Type::String)
208 .max_length(Some(MAX_REQUEST_TOKEN_LENGTH))
209 .into()
210 }
211}
212
213#[cfg(feature = "utoipa")]
214impl utoipa::ToSchema for RequestToken {}
215
216impl serde::Serialize for RequestToken {
217 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
218 where
219 S: serde::Serializer,
220 {
221 serializer.serialize_str(&self.0)
222 }
223}
224
225impl<'de> serde::Deserialize<'de> for RequestToken {
226 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
227 where
228 D: serde::Deserializer<'de>,
229 {
230 let s = CompactString::deserialize(deserializer)?;
231 RequestToken::try_from(s).map_err(serde::de::Error::custom)
232 }
233}
234
235impl std::fmt::Display for RequestToken {
236 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237 write!(f, "{}", self.0)
238 }
239}
240
241impl TryFrom<CompactString> for RequestToken {
242 type Error = RequestTokenTooLongError;
243
244 fn try_from(input: CompactString) -> Result<Self, Self::Error> {
245 if input.len() > MAX_REQUEST_TOKEN_LENGTH {
246 return Err(RequestTokenTooLongError(input.len()));
247 }
248 Ok(RequestToken(input))
249 }
250}
251
252impl FromStr for RequestToken {
253 type Err = RequestTokenTooLongError;
254
255 fn from_str(s: &str) -> Result<Self, Self::Err> {
256 s.to_compact_string().try_into()
257 }
258}
259
260impl From<RequestToken> for CompactString {
261 fn from(token: RequestToken) -> Self {
262 token.0
263 }
264}
265
266impl AsRef<str> for RequestToken {
267 fn as_ref(&self) -> &str {
268 &self.0
269 }
270}
271
272impl Deref for RequestToken {
273 type Target = str;
274
275 fn deref(&self) -> &Self::Target {
276 &self.0
277 }
278}
279
280impl crate::http::ParseableHeader for RequestToken {
281 fn name() -> &'static http::HeaderName {
282 &REQUEST_TOKEN_HEADER
283 }
284}