use std::fmt;
use std::str::FromStr;
use crate::domain::model::is_valid_kebab_lowercase;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct State(String);
impl State {
pub fn new(s: &str) -> anyhow::Result<Self> {
if is_valid_kebab_lowercase(s) {
Ok(State(s.to_string()))
} else {
anyhow::bail!("invalid state '{s}': must match [a-z0-9][a-z0-9-]*")
}
}
pub fn unknown() -> Self {
State::new("unknown").expect("'unknown' is a valid State")
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for State {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl FromStr for State {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
State::new(s)
}
}
impl serde::Serialize for State {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(&self.0)
}
}
#[cfg(test)]
pub mod strategy {
use super::State;
use proptest::prelude::*;
pub fn state() -> impl Strategy<Value = State> {
proptest::string::string_regex("[a-z][a-z0-9-]{0,11}")
.unwrap()
.prop_map(|s| State::new(&s).expect("regex guarantees valid State"))
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn accepts_simple_lowercase() {
assert!(State::new("proposed").is_ok());
assert!(State::new("in-progress").is_ok());
assert!(State::new("phase2").is_ok());
}
#[test]
fn rejects_empty() {
assert!(State::new("").is_err());
}
#[test]
fn rejects_uppercase() {
assert!(State::new("Accepted").is_err());
}
#[test]
fn rejects_leading_hyphen() {
assert!(State::new("-open").is_err());
}
#[test]
fn rejects_spaces() {
assert!(State::new("in progress").is_err());
}
#[test]
fn unknown_is_a_valid_state_named_unknown() {
assert_eq!(State::unknown().as_str(), "unknown");
}
proptest! {
#[test]
fn round_trips_through_as_str(state in strategy::state()) {
prop_assert_eq!(State::new(state.as_str()).unwrap(), state);
}
#[test]
fn from_str_round_trips(state in strategy::state()) {
let s = state.to_string();
prop_assert_eq!(s.parse::<State>().unwrap(), state);
}
}
}