1use std::{marker::PhantomData, ops::Deref, str::FromStr};
2
3use compact_str::{CompactString, ToCompactString};
4use enumset::{EnumSet, EnumSetType};
5
6use super::{
7 ValidationError,
8 basin::{BasinName, BasinNamePrefix},
9 stream::{StreamName, StreamNamePrefix},
10 strings::{IdProps, PrefixProps, StartAfterProps, StrProps},
11};
12use crate::{caps, types::resources::ListItemsRequest};
13
14#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
15#[cfg_attr(
16 feature = "rkyv",
17 derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
18)]
19pub struct AccessTokenIdStr<T: StrProps>(CompactString, PhantomData<T>);
20
21impl<T: StrProps> AccessTokenIdStr<T> {
22 fn validate_str(id: &str) -> Result<(), ValidationError> {
23 if !T::IS_PREFIX && id.is_empty() {
24 return Err(format!("access token {} must not be empty", T::FIELD_NAME).into());
25 }
26
27 if id.len() > caps::MAX_ACCESS_TOKEN_ID_LEN {
28 return Err(format!(
29 "access token {} must not exceed {} bytes in length",
30 T::FIELD_NAME,
31 caps::MAX_ACCESS_TOKEN_ID_LEN
32 )
33 .into());
34 }
35
36 Ok(())
37 }
38}
39
40#[cfg(feature = "utoipa")]
41impl<T> utoipa::PartialSchema for AccessTokenIdStr<T>
42where
43 T: StrProps,
44{
45 fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
46 utoipa::openapi::Object::builder()
47 .schema_type(utoipa::openapi::Type::String)
48 .min_length((!T::IS_PREFIX).then_some(1))
49 .max_length(Some(caps::MAX_ACCESS_TOKEN_ID_LEN))
50 .into()
51 }
52}
53
54#[cfg(feature = "utoipa")]
55impl<T> utoipa::ToSchema for AccessTokenIdStr<T> where T: StrProps {}
56
57impl<T: StrProps> serde::Serialize for AccessTokenIdStr<T> {
58 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
59 where
60 S: serde::Serializer,
61 {
62 serializer.serialize_str(&self.0)
63 }
64}
65
66impl<'de, T: StrProps> serde::Deserialize<'de> for AccessTokenIdStr<T> {
67 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
68 where
69 D: serde::Deserializer<'de>,
70 {
71 let s = CompactString::deserialize(deserializer)?;
72 s.try_into().map_err(serde::de::Error::custom)
73 }
74}
75
76impl<T: StrProps> AsRef<str> for AccessTokenIdStr<T> {
77 fn as_ref(&self) -> &str {
78 &self.0
79 }
80}
81
82impl<T: StrProps> Deref for AccessTokenIdStr<T> {
83 type Target = str;
84
85 fn deref(&self) -> &Self::Target {
86 &self.0
87 }
88}
89
90impl<T: StrProps> TryFrom<CompactString> for AccessTokenIdStr<T> {
91 type Error = ValidationError;
92
93 fn try_from(name: CompactString) -> Result<Self, Self::Error> {
94 Self::validate_str(&name)?;
95 Ok(Self(name, PhantomData))
96 }
97}
98
99impl<T: StrProps> FromStr for AccessTokenIdStr<T> {
100 type Err = ValidationError;
101
102 fn from_str(s: &str) -> Result<Self, Self::Err> {
103 Self::validate_str(s)?;
104 Ok(Self(s.to_compact_string(), PhantomData))
105 }
106}
107
108impl<T: StrProps> std::fmt::Debug for AccessTokenIdStr<T> {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 f.write_str(&self.0)
111 }
112}
113
114impl<T: StrProps> std::fmt::Display for AccessTokenIdStr<T> {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 f.write_str(&self.0)
117 }
118}
119
120impl<T: StrProps> From<AccessTokenIdStr<T>> for CompactString {
121 fn from(value: AccessTokenIdStr<T>) -> Self {
122 value.0
123 }
124}
125
126pub type AccessTokenId = AccessTokenIdStr<IdProps>;
127
128pub type AccessTokenIdPrefix = AccessTokenIdStr<PrefixProps>;
129
130impl Default for AccessTokenIdPrefix {
131 fn default() -> Self {
132 AccessTokenIdStr(CompactString::default(), PhantomData)
133 }
134}
135
136impl From<AccessTokenId> for AccessTokenIdPrefix {
137 fn from(value: AccessTokenId) -> Self {
138 Self(value.0, PhantomData)
139 }
140}
141
142pub type AccessTokenIdStartAfter = AccessTokenIdStr<StartAfterProps>;
143
144impl Default for AccessTokenIdStartAfter {
145 fn default() -> Self {
146 AccessTokenIdStr(CompactString::default(), PhantomData)
147 }
148}
149
150impl From<AccessTokenId> for AccessTokenIdStartAfter {
151 fn from(value: AccessTokenId) -> Self {
152 Self(value.0, PhantomData)
153 }
154}
155
156#[derive(Debug, Hash, EnumSetType, strum::EnumCount)]
157pub enum Operation {
158 ListBasins = 1,
159 CreateBasin = 2,
160 DeleteBasin = 3,
161 ReconfigureBasin = 4,
162 GetBasinConfig = 5,
163 IssueAccessToken = 6,
164 RevokeAccessToken = 7,
165 ListAccessTokens = 8,
166 ListStreams = 9,
167 CreateStream = 10,
168 DeleteStream = 11,
169 GetStreamConfig = 12,
170 ReconfigureStream = 13,
171 CheckTail = 14,
172 Append = 15,
173 Read = 16,
174 Trim = 17,
175 Fence = 18,
176 AccountMetrics = 19,
177 BasinMetrics = 20,
178 StreamMetrics = 21,
179}
180
181#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
182#[cfg_attr(
183 feature = "rkyv",
184 derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
185)]
186pub enum ResourceSet<E, P> {
187 #[default]
188 None,
189 Exact(E),
190 Prefix(P),
191}
192
193pub type BasinResourceSet = ResourceSet<BasinName, BasinNamePrefix>;
194pub type StreamResourceSet = ResourceSet<StreamName, StreamNamePrefix>;
195pub type AccessTokenResourceSet = ResourceSet<AccessTokenId, AccessTokenIdPrefix>;
196
197#[derive(Debug, Clone, Copy, Default)]
198pub struct ReadWritePermissions {
199 pub read: bool,
200 pub write: bool,
201}
202
203#[derive(Debug, Clone, Default)]
204pub struct PermittedOperationGroups {
205 pub account: ReadWritePermissions,
206 pub basin: ReadWritePermissions,
207 pub stream: ReadWritePermissions,
208}
209
210#[derive(Debug, Clone, Default)]
211pub struct AccessTokenScope {
212 pub basins: BasinResourceSet,
213 pub streams: StreamResourceSet,
214 pub access_tokens: AccessTokenResourceSet,
215 pub op_groups: PermittedOperationGroups,
216 pub ops: EnumSet<Operation>,
217}
218
219#[derive(Debug, Clone)]
220pub struct AccessTokenInfo {
221 pub id: AccessTokenId,
222 pub expires_at: time::OffsetDateTime,
223 pub auto_prefix_streams: bool,
224 pub scope: AccessTokenScope,
225}
226
227#[derive(Debug, Clone)]
228pub struct IssueAccessTokenRequest {
229 pub id: AccessTokenId,
230 pub expires_at: Option<time::OffsetDateTime>,
231 pub auto_prefix_streams: bool,
232 pub scope: AccessTokenScope,
233}
234
235pub type ListAccessTokensRequest = ListItemsRequest<AccessTokenIdPrefix, AccessTokenIdStartAfter>;