typestar

config_check.rs in Rust

A validation pipeline: parse key-value text, collect every problem, report.

use std::collections::HashMap;
use std::error::Error;
use std::fmt;

#[derive(Debug)]
enum Invalid {
    Missing(&'static str),
    NotANumber { key: &'static str, got: String },
    OutOfRange { key: &'static str, got: i64 },
}

impl fmt::Display for Invalid {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Invalid::Missing(key) => write!(f, "{key} is required"),
            Invalid::NotANumber { key, got } => {
                write!(f, "{key} should be a number, got {got:?}")
            }
            Invalid::OutOfRange { key, got } => {
                write!(f, "{key} is out of range: {got}")
            }
        }
    }
}

fn parse(raw: &str) -> HashMap<&str, &str> {
    raw.lines()
        .filter_map(|line| line.trim().split_once('='))
        .map(|(k, v)| (k.trim(), v.trim()))
        .collect()
}

fn number(
    fields: &HashMap<&str, &str>,
    key: &'static str,
    range: std::ops::RangeInclusive<i64>,
) -> Result<i64, Invalid> {
    let raw = fields.get(key).ok_or(Invalid::Missing(key))?;
    let value: i64 = raw.parse().map_err(|_| Invalid::NotANumber {
        key,
        got: raw.to_string(),
    })?;
    if !range.contains(&value) {
        return Err(Invalid::OutOfRange { key, got: value });
    }
    Ok(value)
}

fn check(raw: &str) -> Vec<Invalid> {
    let fields = parse(raw);
    let mut problems = Vec::new();

    if !fields.contains_key("host") {
        problems.push(Invalid::Missing("host"));
    }
    for (key, range) in [("port", 1..=65535), ("workers", 1..=64)] {
        if let Err(e) = number(&fields, key, range) {
            problems.push(e);
        }
    }
    problems
}

fn main() -> Result<(), Box<dyn Error>> {
    let raw = "host = typestar.io\nport = 99999\nworkers = many";
    let problems = check(raw);

    if problems.is_empty() {
        println!("config looks good");
        return Ok(());
    }

    println!("{} problem(s):", problems.len());
    for p in &problems {
        println!("  - {p}");
    }
    Ok(())
}

How it works

  1. One error enum covers the three ways a field can be wrong.
  2. Validation gathers all failures instead of stopping at the first.
  3. main returns Result, so a fatal parse error exits non-zero.

Keywords and builtins used here

The run, in numbers

Lines
78
Characters to type
1704
Tokens
554
Three-star pace
110 tpm

At the three-star pace of 110 tokens a minute, this run takes about 302 seconds.

Type this snippet

Step 1 of 1 in Encore, step 15 of 15 in Error handling.

← Previous