waddling-errors 0.7.3

Structured, secure-by-default diagnostic codes for distributed systems with no_std and role-based documentation
Documentation
//! Validation utilities for documentation

use super::types::{BrokenLink, ErrorDoc};
use std::collections::{HashMap, HashSet};
use std::string::{String, ToString};
use std::vec::Vec;

/// Validate see_also references and return any broken links
///
/// This function checks all `see_also` fields in registered errors to ensure they
/// reference valid error codes. Broken links don't prevent documentation generation,
/// but this helps identify typos and missing references.
///
/// # Returns
///
/// A vector of `BrokenLink` records, each containing:
/// - The source error code with the broken reference
/// - The target code that doesn't exist
/// - Suggestions for similar existing codes (based on prefix matching)
pub fn validate_see_also_links(errors: &HashMap<String, ErrorDoc>) -> Vec<BrokenLink> {
    // Build set of all valid error codes
    let valid_codes: HashSet<&str> = errors.values().map(|e| e.code.as_str()).collect();

    let mut broken_links = Vec::new();

    // Check each error's see_also references (from gated field)
    for error in errors.values() {
        for (referenced, _role) in &error.see_also_gated {
            if !valid_codes.contains(referenced.as_str()) {
                // Find suggestions based on prefix matching
                let prefix_len = referenced.len().min(10);
                let prefix = &referenced[..prefix_len];

                let suggestions: Vec<String> = valid_codes
                    .iter()
                    .filter(|code| code.starts_with(prefix) || is_similar(code, referenced))
                    .take(5)
                    .map(|s| s.to_string())
                    .collect();

                broken_links.push(
                    BrokenLink::new(error.code.clone(), referenced.clone())
                        .with_suggestions(suggestions),
                );
            }
        }
    }

    broken_links
}

/// Simple similarity check for suggesting alternatives
fn is_similar(code: &str, target: &str) -> bool {
    // Check if codes share the same component and primary
    let code_parts: Vec<&str> = code.split('.').collect();
    let target_parts: Vec<&str> = target.split('.').collect();

    if code_parts.len() >= 3 && target_parts.len() >= 3 {
        // Same severity, component, and primary
        code_parts[0] == target_parts[0]
            && code_parts[1] == target_parts[1]
            && code_parts[2] == target_parts[2]
    } else {
        false
    }
}