use crate::{Error, MAX_APP, MAX_DEPTH, MAX_ID, MAX_SEGMENT, base64};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum State {
Idle,
Working,
Done,
Blocked,
Error,
}
impl State {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Idle => "idle",
Self::Working => "working",
Self::Done => "done",
Self::Blocked => "blocked",
Self::Error => "error",
}
}
#[must_use]
pub fn parse(value: &str) -> Option<Self> {
match value {
"idle" => Some(Self::Idle),
"working" => Some(Self::Working),
"done" => Some(Self::Done),
"blocked" => Some(Self::Blocked),
"error" => Some(Self::Error),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Kind {
Permission,
Question,
Auth,
}
impl Kind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Permission => "permission",
Self::Question => "question",
Self::Auth => "auth",
}
}
#[must_use]
pub fn parse(value: &str) -> Option<Self> {
match value {
"permission" => Some(Self::Permission),
"question" => Some(Self::Question),
"auth" => Some(Self::Auth),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Action {
Set(State),
Clear,
}
impl Action {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Set(state) => state.as_str(),
Self::Clear => "clear",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Progress(u8);
impl Progress {
pub const fn new(value: u8) -> Result<Self, Error> {
if value <= 100 {
Ok(Self(value))
} else {
Err(Error::InvalidProgress)
}
}
#[must_use]
pub const fn get(self) -> u8 {
self.0
}
}
impl TryFrom<u8> for Progress {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Self::new(value)
}
}
pub(crate) fn segment_byte(byte: u8) -> bool {
byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'.' | b'+' | b'-')
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RecordId<'a>(&'a str);
impl<'a> RecordId<'a> {
pub fn new(value: &'a str) -> Result<Self, Error> {
if value.is_empty() || value.len() > MAX_ID {
return Err(Error::InvalidRecordId);
}
let mut depth = 0;
for segment in value.split('/') {
depth += 1;
if depth > MAX_DEPTH
|| segment.is_empty()
|| segment.len() > MAX_SEGMENT
|| !segment.bytes().all(segment_byte)
{
return Err(Error::InvalidRecordId);
}
}
Ok(Self(value))
}
#[must_use]
pub const fn as_str(self) -> &'a str {
self.0
}
}
impl<'a> TryFrom<&'a str> for RecordId<'a> {
type Error = Error;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
Self::new(value)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AppId<'a>(&'a str);
impl<'a> AppId<'a> {
pub fn new(value: &'a str) -> Result<Self, Error> {
if value.is_empty() || value.len() > MAX_APP || !value.bytes().all(segment_byte) {
Err(Error::InvalidAppId)
} else {
Ok(Self(value))
}
}
#[must_use]
pub const fn as_str(self) -> &'a str {
self.0
}
}
impl<'a> TryFrom<&'a str> for AppId<'a> {
type Error = Error;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
Self::new(value)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct EncodedText<'a> {
pub(crate) encoded: &'a str,
pub(crate) decoded_len: usize,
}
impl<'a> EncodedText<'a> {
#[must_use]
pub const fn as_base64(self) -> &'a str {
self.encoded
}
#[must_use]
pub const fn decoded_len(self) -> usize {
self.decoded_len
}
pub fn decode_into(self, output: &mut [u8]) -> Result<&str, Error> {
let length = base64::decode(self.encoded.as_bytes(), output)?;
core::str::from_utf8(&output[..length]).map_err(|_| Error::InvalidUtf8)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Report<'a> {
pub(crate) action: Action,
pub(crate) id: Option<RecordId<'a>>,
pub(crate) kind: Option<Kind>,
pub(crate) progress: Option<Progress>,
pub(crate) app: Option<AppId<'a>>,
pub(crate) title: Option<EncodedText<'a>>,
pub(crate) message: Option<EncodedText<'a>>,
}
impl<'a> Report<'a> {
#[must_use]
pub const fn action(&self) -> Action {
self.action
}
#[must_use]
pub const fn id(&self) -> Option<RecordId<'a>> {
self.id
}
#[must_use]
pub const fn kind(&self) -> Option<Kind> {
self.kind
}
#[must_use]
pub const fn progress(&self) -> Option<Progress> {
self.progress
}
#[must_use]
pub const fn app(&self) -> Option<AppId<'a>> {
self.app
}
#[must_use]
pub const fn title(&self) -> Option<EncodedText<'a>> {
self.title
}
#[must_use]
pub const fn message(&self) -> Option<EncodedText<'a>> {
self.message
}
}
const DEFAULT_STATES: &[State] = &[
State::Idle,
State::Working,
State::Done,
State::Blocked,
State::Error,
];
const DEFAULT_KINDS: &[Kind] = &[Kind::Permission, Kind::Question, Kind::Auth];
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Capabilities<'a> {
pub(crate) states: Option<&'a str>,
pub(crate) kinds: Option<&'a str>,
}
impl Capabilities<'_> {
pub fn states(&self) -> impl Iterator<Item = State> {
let defaults = self.states.map_or(DEFAULT_STATES, |_| &[]);
self.states
.unwrap_or_default()
.split(',')
.filter_map(State::parse)
.chain(defaults.iter().copied())
}
pub fn kinds(&self) -> impl Iterator<Item = Kind> {
let defaults = self.kinds.map_or(DEFAULT_KINDS, |_| &[]);
self.kinds
.unwrap_or_default()
.split(',')
.filter_map(Kind::parse)
.chain(defaults.iter().copied())
}
#[must_use]
pub fn supports_state(&self, state: State) -> bool {
self.states().any(|value| value == state)
}
#[must_use]
pub fn supports_kind(&self, kind: Kind) -> bool {
self.kinds().any(|value| value == kind)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Event<'a> {
Report(Report<'a>),
Query,
Capabilities(Capabilities<'a>),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Limit {
Sequence,
Key,
Message,
Title,
App,
Id,
}