waddling-errors 0.7.3

Structured, secure-by-default diagnostic codes for distributed systems with no_std and role-based documentation
Documentation
//! Renderer trait and built-in renderer implementations

use super::registry::DocRegistry;
use super::types::ErrorDoc;
use crate::Role;
use std::path::Path;

/// Trait for rendering error documentation in various formats
///
/// Implement this trait to add support for custom documentation formats.
/// Built-in formats (JSON, HTML) are provided, but you can create your own.
///
/// The registry handles error filtering by role; renderers receive pre-filtered errors.
///
/// # Example
///
/// ```rust,no_run
/// use waddling_errors::doc_generator::{Renderer, DocRegistry, ErrorDoc};
/// use waddling_errors::Role;
/// use std::path::Path;
///
/// struct XmlRenderer;
///
/// impl Renderer for XmlRenderer {
///     fn format_name(&self) -> &str { "xml" }
///
///     fn render(
///         &self,
///         registry: &DocRegistry,
///         errors: &[&ErrorDoc],
///         output_path: &Path,
///         _filter_role: Option<Role>,
///     ) -> std::io::Result<()> {
///         let xml = format!("<?xml version='1.0'?><errors>{}</errors>",
///             errors.iter()
///                 .map(|e| format!("<error code='{}'/>", e.code))
///                 .collect::<Vec<_>>()
///                 .join("\n"));
///         std::fs::write(output_path, xml)
///     }
/// }
/// ```
pub trait Renderer: Send + Sync {
    /// Format identifier (e.g., "json", "html", "xml")
    ///
    /// This is used in generated filenames: `{project_name}.{format_name}`
    fn format_name(&self) -> &str;

    /// Render documentation for the given errors
    ///
    /// # Arguments
    ///
    /// * `registry` - The doc registry containing all metadata (components, primaries, etc.)
    /// * `errors` - Pre-filtered errors to render (filtered by role if applicable)
    /// * `output_path` - Full path where the output file should be written
    /// * `filter_role` - Optional role for field-level filtering (hints, tags, etc.)
    fn render(
        &self,
        registry: &DocRegistry,
        errors: &[&ErrorDoc],
        output_path: &Path,
        filter_role: Option<Role>,
    ) -> std::io::Result<()>;
}

/// Built-in JSON renderer
pub struct JsonRenderer;

impl Renderer for JsonRenderer {
    fn format_name(&self) -> &str {
        "json"
    }

    fn render(
        &self,
        registry: &DocRegistry,
        errors: &[&ErrorDoc],
        output_path: &Path,
        filter_role: Option<Role>,
    ) -> std::io::Result<()> {
        super::json::generate_json(
            output_path,
            registry.project_name(),
            registry.version(),
            errors,
            registry.components(),
            registry.primaries(),
            registry.sequences(),
            filter_role,
        )
    }
}

/// Built-in HTML renderer with optional customization
pub struct HtmlRenderer {
    /// Optional customization for HTML output
    pub customization: Option<super::html::HtmlCustomization>,
}

impl HtmlRenderer {
    /// Create a new HTML renderer with default settings
    pub fn new() -> Self {
        Self {
            customization: None,
        }
    }

    /// Create an HTML renderer with custom styling/branding
    ///
    /// # Example
    /// ```
    /// use waddling_errors::doc_generator::{HtmlRenderer, HtmlCustomization};
    ///
    /// let custom = HtmlCustomization::new()
    ///     .with_logo("https://example.com/logo.svg", "MyProject")
    ///     .with_accent_color("#FF5722", "#FF8A65")
    ///     .with_custom_css(".error-card { border-radius: 16px; }");
    ///
    /// let renderer = HtmlRenderer::with_customization(custom);
    /// ```
    pub fn with_customization(customization: super::html::HtmlCustomization) -> Self {
        Self {
            customization: Some(customization),
        }
    }
}

impl Default for HtmlRenderer {
    fn default() -> Self {
        Self::new()
    }
}

impl Renderer for HtmlRenderer {
    fn format_name(&self) -> &str {
        "html"
    }

    fn render(
        &self,
        registry: &DocRegistry,
        errors: &[&ErrorDoc],
        output_path: &Path,
        filter_role: Option<Role>,
    ) -> std::io::Result<()> {
        // Use provided filter_role, or try to infer from filename suffix (-pub, -dev, -int)
        let role_for_display = filter_role.or_else(|| {
            output_path
                .file_stem()
                .and_then(|s| s.to_str())
                .and_then(|name| {
                    if name.ends_with("-pub") {
                        Some(Role::Public)
                    } else if name.ends_with("-dev") {
                        Some(Role::Developer)
                    } else if name.ends_with("-int") {
                        Some(Role::Internal)
                    } else {
                        None
                    }
                })
        });

        super::html::generate_html(super::html::HtmlConfig {
            html_path: output_path,
            project_name: registry.project_name(),
            version: registry.version(),
            errors,
            components: registry.components(),
            primaries: registry.primaries(),
            sequences: registry.sequences(),
            filter_role: role_for_display, // For badge display
            customization: self.customization.as_ref(),
        })
    }
}