use std::num::NonZeroU32;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct BuildJobs(NonZeroU32);
impl BuildJobs {
pub fn new(value: u32) -> Result<Self, BuildJobsParseError> {
NonZeroU32::new(value)
.map(Self)
.ok_or(BuildJobsParseError::Zero)
}
pub fn get(self) -> u32 {
self.0.get()
}
}
impl FromStr for BuildJobs {
type Err = BuildJobsParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let trimmed = s.trim();
if trimmed.is_empty() {
return Err(BuildJobsParseError::Empty);
}
let parsed: u32 = trimmed.parse().map_err(|_| BuildJobsParseError::Invalid {
value: s.to_owned(),
})?;
Self::new(parsed)
}
}
impl std::fmt::Display for BuildJobs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Error, PartialEq, Eq)]
pub enum BuildJobsParseError {
#[error("expected a positive integer, got an empty value")]
Empty,
#[error("expected a positive integer, got 0")]
Zero,
#[error("invalid jobs value {value:?}; expected a positive integer")]
Invalid {
value: String,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accepts_positive_integers() {
let one = BuildJobs::from_str("1").unwrap();
assert_eq!(one.get(), 1);
let many = BuildJobs::from_str("64").unwrap();
assert_eq!(many.get(), 64);
}
#[test]
fn rejects_zero() {
assert_eq!(BuildJobs::from_str("0"), Err(BuildJobsParseError::Zero));
assert_eq!(BuildJobs::new(0), Err(BuildJobsParseError::Zero));
}
#[test]
fn rejects_negative_number() {
match BuildJobs::from_str("-1") {
Err(BuildJobsParseError::Invalid { value }) => assert_eq!(value, "-1"),
other => panic!("expected Invalid, got {other:?}"),
}
}
#[test]
fn rejects_non_numeric() {
match BuildJobs::from_str("many") {
Err(BuildJobsParseError::Invalid { value }) => assert_eq!(value, "many"),
other => panic!("expected Invalid, got {other:?}"),
}
}
#[test]
fn rejects_empty() {
assert_eq!(BuildJobs::from_str(""), Err(BuildJobsParseError::Empty));
assert_eq!(BuildJobs::from_str(" "), Err(BuildJobsParseError::Empty));
}
#[test]
fn trims_surrounding_whitespace() {
let parsed = BuildJobs::from_str(" 4 ").unwrap();
assert_eq!(parsed.get(), 4);
}
#[test]
fn display_matches_underlying_integer() {
let jobs = BuildJobs::new(8).unwrap();
assert_eq!(jobs.to_string(), "8");
}
}