use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum State {
#[cfg_attr(feature = "serde", serde(rename = "full"))]
Full,
#[cfg_attr(feature = "serde", serde(rename = "partial"))]
Partial,
#[cfg_attr(feature = "serde", serde(rename = "deleted"))]
Deleted,
}
impl fmt::Display for State {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Full => "full",
Self::Partial => "partial",
Self::Deleted => "deleted",
})
}
}
#[derive(Debug, Clone)]
pub struct ParseStateError(pub String);
impl fmt::Display for ParseStateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid conference-info state: {:?}", self.0)
}
}
impl std::error::Error for ParseStateError {}
impl FromStr for State {
type Err = ParseStateError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"full" => Ok(Self::Full),
"partial" => Ok(Self::Partial),
"deleted" => Ok(Self::Deleted),
other => Err(ParseStateError(other.to_owned())),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct ExecutionInfo {
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub when: Option<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub reason: Option<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub by: Option<String>,
}
impl ExecutionInfo {
pub fn new() -> Self {
Self::default()
}
pub fn with_when(mut self, when: impl Into<String>) -> Self {
self.when = Some(when.into());
self
}
pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
self.reason = Some(reason.into());
self
}
pub fn with_by(mut self, by: impl Into<String>) -> Self {
self.by = Some(by.into());
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn state_round_trip() {
for (s, expected) in [
("full", State::Full),
("partial", State::Partial),
("deleted", State::Deleted),
] {
let parsed: State = s
.parse()
.unwrap();
assert_eq!(parsed, expected);
assert_eq!(parsed.to_string(), s);
}
}
#[test]
fn state_invalid() {
assert!("Full"
.parse::<State>()
.is_err());
assert!("FULL"
.parse::<State>()
.is_err());
assert!(""
.parse::<State>()
.is_err());
}
#[test]
fn execution_info_builder() {
let info = ExecutionInfo::new()
.with_when("2026-02-24T14:26:16Z")
.with_by("sip:alice@example.com");
assert_eq!(
info.when
.as_deref(),
Some("2026-02-24T14:26:16Z")
);
assert_eq!(
info.by
.as_deref(),
Some("sip:alice@example.com")
);
assert!(info
.reason
.is_none());
}
}