main-spex 0.25.0

AI-first development framework that turns specifications into deterministic, verifiable code
use std::fmt;
use std::sync::LazyLock;

use derive_more::Display;
use regex::Regex;
use serde::{Deserialize, Serialize};

#[cfg(test)]
#[path = "id_test.rs"]
mod id_test;

const ID_PATTERN: &str = "^[a-z0-9]+(-[a-z0-9]+)*$";
const ID_MAX_LENGTH: usize = 40;

static ID: LazyLock<Regex> = LazyLock::new(|| Regex::new(ID_PATTERN).expect("valid id regex"));

#[derive(Debug, PartialEq, Eq)]
pub struct InvalidRuleId(String);

impl fmt::Display for InvalidRuleId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "invalid rule id `{}`: use lowercase letters and digits separated by single hyphens, at most {ID_MAX_LENGTH} characters",
            self.0
        )
    }
}

impl std::error::Error for InvalidRuleId {}

#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Display, Deserialize, Serialize)]
#[serde(try_from = "String")]
pub struct RuleId(String);

impl RuleId {
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl TryFrom<String> for RuleId {
    type Error = InvalidRuleId;

    fn try_from(value: String) -> Result<Self, InvalidRuleId> {
        if value.len() <= ID_MAX_LENGTH && ID.is_match(&value) {
            Ok(Self(value))
        } else {
            Err(InvalidRuleId(value))
        }
    }
}