1use crate::{Error, MAX_APP, MAX_DEPTH, MAX_ID, MAX_SEGMENT, base64};
2
3#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5#[non_exhaustive]
6pub enum State {
7 Idle,
9 Working,
11 Done,
13 Blocked,
15 Error,
17}
18
19impl State {
20 #[must_use]
22 pub const fn as_str(self) -> &'static str {
23 match self {
24 Self::Idle => "idle",
25 Self::Working => "working",
26 Self::Done => "done",
27 Self::Blocked => "blocked",
28 Self::Error => "error",
29 }
30 }
31
32 #[must_use]
34 pub fn parse(value: &str) -> Option<Self> {
35 match value {
36 "idle" => Some(Self::Idle),
37 "working" => Some(Self::Working),
38 "done" => Some(Self::Done),
39 "blocked" => Some(Self::Blocked),
40 "error" => Some(Self::Error),
41 _ => None,
42 }
43 }
44}
45
46#[derive(Clone, Copy, Debug, Eq, PartialEq)]
48#[non_exhaustive]
49pub enum Kind {
50 Permission,
52 Question,
54 Auth,
56}
57
58impl Kind {
59 #[must_use]
61 pub const fn as_str(self) -> &'static str {
62 match self {
63 Self::Permission => "permission",
64 Self::Question => "question",
65 Self::Auth => "auth",
66 }
67 }
68
69 #[must_use]
71 pub fn parse(value: &str) -> Option<Self> {
72 match value {
73 "permission" => Some(Self::Permission),
74 "question" => Some(Self::Question),
75 "auth" => Some(Self::Auth),
76 _ => None,
77 }
78 }
79}
80
81#[derive(Clone, Copy, Debug, Eq, PartialEq)]
83pub enum Action {
84 Set(State),
86 Clear,
88}
89
90impl Action {
91 #[must_use]
93 pub const fn as_str(self) -> &'static str {
94 match self {
95 Self::Set(state) => state.as_str(),
96 Self::Clear => "clear",
97 }
98 }
99}
100
101#[derive(Clone, Copy, Debug, Eq, PartialEq)]
103pub struct Progress(u8);
104
105impl Progress {
106 pub const fn new(value: u8) -> Result<Self, Error> {
111 if value <= 100 {
112 Ok(Self(value))
113 } else {
114 Err(Error::InvalidProgress)
115 }
116 }
117
118 #[must_use]
120 pub const fn get(self) -> u8 {
121 self.0
122 }
123}
124
125impl TryFrom<u8> for Progress {
126 type Error = Error;
127
128 fn try_from(value: u8) -> Result<Self, Self::Error> {
129 Self::new(value)
130 }
131}
132
133pub(crate) fn segment_byte(byte: u8) -> bool {
134 byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'.' | b'+' | b'-')
135}
136
137#[derive(Clone, Copy, Debug, Eq, PartialEq)]
139pub struct RecordId<'a>(&'a str);
140
141impl<'a> RecordId<'a> {
142 pub fn new(value: &'a str) -> Result<Self, Error> {
149 if value.is_empty() || value.len() > MAX_ID {
150 return Err(Error::InvalidRecordId);
151 }
152 let mut depth = 0;
153 for segment in value.split('/') {
154 depth += 1;
155 if depth > MAX_DEPTH
156 || segment.is_empty()
157 || segment.len() > MAX_SEGMENT
158 || !segment.bytes().all(segment_byte)
159 {
160 return Err(Error::InvalidRecordId);
161 }
162 }
163 Ok(Self(value))
164 }
165
166 #[must_use]
168 pub const fn as_str(self) -> &'a str {
169 self.0
170 }
171}
172
173impl<'a> TryFrom<&'a str> for RecordId<'a> {
174 type Error = Error;
175
176 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
177 Self::new(value)
178 }
179}
180
181#[derive(Clone, Copy, Debug, Eq, PartialEq)]
183pub struct AppId<'a>(&'a str);
184
185impl<'a> AppId<'a> {
186 pub fn new(value: &'a str) -> Result<Self, Error> {
193 if value.is_empty() || value.len() > MAX_APP || !value.bytes().all(segment_byte) {
194 Err(Error::InvalidAppId)
195 } else {
196 Ok(Self(value))
197 }
198 }
199
200 #[must_use]
202 pub const fn as_str(self) -> &'a str {
203 self.0
204 }
205}
206
207impl<'a> TryFrom<&'a str> for AppId<'a> {
208 type Error = Error;
209
210 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
211 Self::new(value)
212 }
213}
214
215#[derive(Clone, Copy, Debug, Eq, PartialEq)]
219pub struct EncodedText<'a> {
220 pub(crate) encoded: &'a str,
221 pub(crate) decoded_len: usize,
222}
223
224impl<'a> EncodedText<'a> {
225 #[must_use]
227 pub const fn as_base64(self) -> &'a str {
228 self.encoded
229 }
230
231 #[must_use]
233 pub const fn decoded_len(self) -> usize {
234 self.decoded_len
235 }
236
237 pub fn decode_into(self, output: &mut [u8]) -> Result<&str, Error> {
242 let length = base64::decode(self.encoded.as_bytes(), output)?;
243 core::str::from_utf8(&output[..length]).map_err(|_| Error::InvalidUtf8)
244 }
245}
246
247#[derive(Clone, Copy, Debug, Eq, PartialEq)]
251pub struct Report<'a> {
252 pub(crate) action: Action,
253 pub(crate) id: Option<RecordId<'a>>,
254 pub(crate) kind: Option<Kind>,
255 pub(crate) progress: Option<Progress>,
256 pub(crate) app: Option<AppId<'a>>,
257 pub(crate) title: Option<EncodedText<'a>>,
258 pub(crate) message: Option<EncodedText<'a>>,
259}
260
261impl<'a> Report<'a> {
262 #[must_use]
264 pub const fn action(&self) -> Action {
265 self.action
266 }
267
268 #[must_use]
270 pub const fn id(&self) -> Option<RecordId<'a>> {
271 self.id
272 }
273
274 #[must_use]
276 pub const fn kind(&self) -> Option<Kind> {
277 self.kind
278 }
279
280 #[must_use]
282 pub const fn progress(&self) -> Option<Progress> {
283 self.progress
284 }
285
286 #[must_use]
288 pub const fn app(&self) -> Option<AppId<'a>> {
289 self.app
290 }
291
292 #[must_use]
294 pub const fn title(&self) -> Option<EncodedText<'a>> {
295 self.title
296 }
297
298 #[must_use]
300 pub const fn message(&self) -> Option<EncodedText<'a>> {
301 self.message
302 }
303}
304
305const DEFAULT_STATES: &[State] = &[
306 State::Idle,
307 State::Working,
308 State::Done,
309 State::Blocked,
310 State::Error,
311];
312const DEFAULT_KINDS: &[Kind] = &[Kind::Permission, Kind::Question, Kind::Auth];
313
314#[derive(Clone, Copy, Debug, Eq, PartialEq)]
320pub struct Capabilities<'a> {
321 pub(crate) states: Option<&'a str>,
322 pub(crate) kinds: Option<&'a str>,
323}
324
325impl Capabilities<'_> {
326 pub fn states(&self) -> impl Iterator<Item = State> {
328 let defaults = self.states.map_or(DEFAULT_STATES, |_| &[]);
329 self.states
330 .unwrap_or_default()
331 .split(',')
332 .filter_map(State::parse)
333 .chain(defaults.iter().copied())
334 }
335
336 pub fn kinds(&self) -> impl Iterator<Item = Kind> {
338 let defaults = self.kinds.map_or(DEFAULT_KINDS, |_| &[]);
339 self.kinds
340 .unwrap_or_default()
341 .split(',')
342 .filter_map(Kind::parse)
343 .chain(defaults.iter().copied())
344 }
345
346 #[must_use]
348 pub fn supports_state(&self, state: State) -> bool {
349 self.states().any(|value| value == state)
350 }
351
352 #[must_use]
354 pub fn supports_kind(&self, kind: Kind) -> bool {
355 self.kinds().any(|value| value == kind)
356 }
357}
358
359#[derive(Clone, Copy, Debug, Eq, PartialEq)]
361#[non_exhaustive]
362pub enum Event<'a> {
363 Report(Report<'a>),
365 Query,
368 Capabilities(Capabilities<'a>),
370}
371
372#[derive(Clone, Copy, Debug, Eq, PartialEq)]
374#[non_exhaustive]
375pub enum Limit {
376 Sequence,
378 Key,
380 Message,
382 Title,
384 App,
386 Id,
388}