solverforge-cli 2.2.3

CLI for scaffolding and managing SolverForge constraint solver projects
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct CountableRange {
    pub from: usize,
    pub to: usize,
}

impl CountableRange {
    pub(crate) fn parse(raw: &str) -> Result<Self, String> {
        let (from, to) = raw.split_once("..").ok_or_else(|| {
            "countable range must use `from..to` syntax with non-negative integers".to_string()
        })?;
        if to.contains("..") {
            return Err(
                "countable range must use `from..to` syntax with non-negative integers".to_string(),
            );
        }
        let from = from
            .trim()
            .parse::<usize>()
            .map_err(|_| "countable range start must be a non-negative integer".to_string())?;
        let to = to
            .trim()
            .parse::<usize>()
            .map_err(|_| "countable range end must be a non-negative integer".to_string())?;
        if from >= to {
            return Err("countable range must satisfy from < to".to_string());
        }
        if i64::try_from(from).is_err() || i64::try_from(to).is_err() {
            return Err("countable range bounds must fit in a signed 64-bit integer".to_string());
        }
        Ok(Self { from, to })
    }
}

#[cfg(test)]
mod tests {
    use super::CountableRange;

    #[test]
    fn parses_half_open_non_negative_range() {
        assert_eq!(
            CountableRange::parse("2..7"),
            Ok(CountableRange { from: 2, to: 7 })
        );
    }

    #[test]
    fn rejects_invalid_and_empty_ranges() {
        assert!(CountableRange::parse("-1..3").is_err());
        assert!(CountableRange::parse("3..3").is_err());
        assert!(CountableRange::parse("4..2").is_err());
        assert!(CountableRange::parse("1...3").is_err());
    }
}