use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DrStatus {
Proposed,
Accepted,
Rejected,
Deprecated,
Superseded,
}
impl DrStatus {
pub const INITIAL: DrStatus = DrStatus::Proposed;
pub fn as_str(&self) -> &'static str {
match self {
DrStatus::Proposed => "proposed",
DrStatus::Accepted => "accepted",
DrStatus::Rejected => "rejected",
DrStatus::Deprecated => "deprecated",
DrStatus::Superseded => "superseded",
}
}
pub fn allowed_next(&self) -> &'static [DrStatus] {
match self {
DrStatus::Proposed => &[DrStatus::Accepted, DrStatus::Rejected],
DrStatus::Accepted => &[DrStatus::Deprecated, DrStatus::Superseded],
DrStatus::Deprecated => &[DrStatus::Superseded],
DrStatus::Rejected | DrStatus::Superseded => &[],
}
}
pub fn allows(&self, next: DrStatus) -> bool {
self.allowed_next().contains(&next)
}
pub fn is_terminal(&self) -> bool {
self.allowed_next().is_empty()
}
pub fn all() -> &'static [DrStatus] {
&[
DrStatus::Proposed,
DrStatus::Accepted,
DrStatus::Rejected,
DrStatus::Deprecated,
DrStatus::Superseded,
]
}
}
impl fmt::Display for DrStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for DrStatus {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"proposed" => Ok(DrStatus::Proposed),
"accepted" => Ok(DrStatus::Accepted),
"rejected" => Ok(DrStatus::Rejected),
"deprecated" => Ok(DrStatus::Deprecated),
"superseded" => Ok(DrStatus::Superseded),
other => anyhow::bail!("unknown decision-record status '{other}'"),
}
}
}
impl serde::Serialize for DrStatus {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trips_through_from_str_and_display() {
for st in DrStatus::all() {
let s = st.to_string();
assert_eq!(DrStatus::from_str(&s).unwrap(), *st);
}
}
#[test]
fn unknown_name_is_rejected() {
assert!(DrStatus::from_str("draft").is_err());
assert!(DrStatus::from_str("").is_err());
}
#[test]
fn proposed_allows_accepted_and_rejected_only() {
let p = DrStatus::Proposed;
assert!(p.allows(DrStatus::Accepted));
assert!(p.allows(DrStatus::Rejected));
assert!(!p.allows(DrStatus::Deprecated));
assert!(!p.allows(DrStatus::Superseded));
assert!(!p.allows(DrStatus::Proposed));
}
#[test]
fn accepted_allows_deprecated_and_superseded_only() {
let a = DrStatus::Accepted;
assert!(a.allows(DrStatus::Deprecated));
assert!(a.allows(DrStatus::Superseded));
assert!(!a.allows(DrStatus::Rejected));
}
#[test]
fn deprecated_allows_only_superseded() {
let d = DrStatus::Deprecated;
assert!(d.allows(DrStatus::Superseded));
assert!(!d.allows(DrStatus::Accepted));
assert!(!d.allows(DrStatus::Rejected));
}
#[test]
fn rejected_and_superseded_are_terminal() {
assert!(DrStatus::Rejected.is_terminal());
assert!(DrStatus::Superseded.is_terminal());
assert!(!DrStatus::Proposed.is_terminal());
assert!(!DrStatus::Accepted.is_terminal());
assert!(!DrStatus::Deprecated.is_terminal());
}
}
#[cfg(test)]
pub mod strategy {
use super::DrStatus;
use proptest::prelude::*;
pub fn dr_status() -> impl Strategy<Value = DrStatus> {
prop_oneof![
Just(DrStatus::Proposed),
Just(DrStatus::Accepted),
Just(DrStatus::Rejected),
Just(DrStatus::Deprecated),
Just(DrStatus::Superseded),
]
}
}