weave-content 0.2.32

Content DSL parser, validator, and builder for OSINT case files
Documentation
use std::str::FromStr;

use nulid::Nulid;

use crate::parser::ParseError;

/// Parse a stored NULID string, or generate a new one if missing.
///
/// Returns the NULID and whether it was newly generated (needs write-back).
pub fn resolve_id(stored: Option<&str>, line: usize) -> Result<(Nulid, bool), ParseError> {
    match stored {
        Some(s) if !s.is_empty() => {
            let id = Nulid::from_str(s).map_err(|_| ParseError {
                line,
                message: format!("invalid NULID: {s:?}"),
            })?;
            Ok((id, false))
        }
        _ => {
            let id = Nulid::new().map_err(|e| ParseError {
                line,
                message: format!("failed to generate NULID: {e}"),
            })?;
            Ok((id, true))
        }
    }
}

/// Generate a new NULID. Returns error string on failure.
pub fn generate() -> Result<String, String> {
    let id = Nulid::new().map_err(|e| format!("failed to generate NULID: {e}"))?;
    Ok(id.to_string())
}

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

    #[test]
    fn resolve_stored_id() {
        let id = Nulid::new().unwrap();
        let s = id.to_string();
        let (resolved, generated) = resolve_id(Some(&s), 1).unwrap();
        assert_eq!(resolved, id);
        assert!(!generated);
    }

    #[test]
    fn resolve_missing_id_generates() {
        let (id, generated) = resolve_id(None, 1).unwrap();
        assert!(generated);
        assert_eq!(id.to_string().len(), 26);
    }

    #[test]
    fn resolve_empty_id_generates() {
        let (id, generated) = resolve_id(Some(""), 1).unwrap();
        assert!(generated);
        assert_eq!(id.to_string().len(), 26);
    }

    #[test]
    fn resolve_invalid_id_errors() {
        let result = resolve_id(Some("not-a-nulid"), 5);
        assert!(result.is_err());
        let err = result.unwrap_err();
        assert!(err.message.contains("invalid NULID"));
        assert_eq!(err.line, 5);
    }

    #[test]
    fn generated_ids_are_unique() {
        let (a, _) = resolve_id(None, 1).unwrap();
        let (b, _) = resolve_id(None, 1).unwrap();
        assert_ne!(a, b);
    }

    #[test]
    fn nulid_is_valid_string() {
        let (id, _) = resolve_id(None, 1).unwrap();
        let s = id.to_string();
        assert_eq!(s.len(), 26);
        assert!(s.chars().all(|c| c.is_ascii_alphanumeric()));
    }
}