#![allow(non_upper_case_globals)]
#![allow(unused_imports)]
#[cfg(not(all(feature = "metadata", feature = "doc-gen", feature = "auto-register")))]
compile_error!(
"\n\n\
❌ This example requires 'metadata', 'doc-gen', and 'auto-register' features!\n\
\n\
Run with:\n\
cargo run --example custom_xml_renderer --features metadata,doc-gen,auto-register\n\
\n\
Or use --all-features to enable everything:\n\
cargo run --example custom_xml_renderer --all-features\n\
"
);
#[cfg(all(feature = "metadata", feature = "doc-gen", feature = "auto-register"))]
use std::fs::File;
#[cfg(all(feature = "metadata", feature = "doc-gen", feature = "auto-register"))]
use std::io::Write;
#[cfg(all(feature = "metadata", feature = "doc-gen", feature = "auto-register"))]
use std::path::Path;
#[cfg(all(feature = "metadata", feature = "doc-gen", feature = "auto-register"))]
use waddling_errors::doc_generator::{DocRegistry, ErrorDoc, Renderer};
#[cfg(all(feature = "metadata", feature = "doc-gen", feature = "auto-register"))]
use waddling_errors::registry;
#[cfg(all(feature = "metadata", feature = "doc-gen", feature = "auto-register"))]
use waddling_errors_macros::{component, diag, primary, setup};
#[cfg(all(feature = "metadata", feature = "doc-gen", feature = "auto-register"))]
setup! {
components = crate,
primaries = crate,
sequences = crate::sequences,
}
#[cfg(all(feature = "metadata", feature = "doc-gen", feature = "auto-register"))]
pub struct XmlRenderer;
#[cfg(all(feature = "metadata", feature = "doc-gen", feature = "auto-register"))]
impl Default for XmlRenderer {
fn default() -> Self {
Self::new()
}
}
#[cfg(all(feature = "metadata", feature = "doc-gen", feature = "auto-register"))]
impl XmlRenderer {
pub fn new() -> Self {
XmlRenderer
}
fn escape_xml(s: &str) -> String {
s.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
.replace('\'', "'")
}
}
#[cfg(all(feature = "metadata", feature = "doc-gen", feature = "auto-register"))]
impl Renderer for XmlRenderer {
fn format_name(&self) -> &str {
"xml"
}
fn render(
&self,
registry: &DocRegistry,
errors: &[&ErrorDoc],
output_path: &Path,
filter_role: Option<waddling_errors::Role>,
) -> std::io::Result<()> {
let mut xml = String::new();
xml.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
xml.push_str("<error-catalog>\n");
xml.push_str(&format!(
" <metadata>\n <project>{}</project>\n <version>{}</version>\n </metadata>\n",
Self::escape_xml(registry.project_name()),
Self::escape_xml(registry.version())
));
xml.push_str(" <components>\n");
for (code, meta) in registry.components() {
xml.push_str(&format!(
" <component code=\"{}\">\n",
Self::escape_xml(code)
));
if let Some(desc) = &meta.description {
xml.push_str(&format!(
" <description>{}</description>\n",
Self::escape_xml(desc)
));
}
if !meta.tags.is_empty() {
xml.push_str(" <tags>\n");
for tag in &meta.tags {
xml.push_str(&format!(" <tag>{}</tag>\n", Self::escape_xml(tag)));
}
xml.push_str(" </tags>\n");
}
if !meta.locations.is_empty() {
xml.push_str(" <locations>\n");
for location in &meta.locations {
let role_str = location.role.as_deref().unwrap_or("unspecified");
xml.push_str(&format!(
" <location path=\"{}\" role=\"{}\" />\n",
Self::escape_xml(&location.path),
Self::escape_xml(role_str)
));
}
xml.push_str(" </locations>\n");
}
xml.push_str(" </component>\n");
}
xml.push_str(" </components>\n");
xml.push_str(" <primaries>\n");
for (code, meta) in registry.primaries() {
xml.push_str(&format!(
" <primary code=\"{}\">\n",
Self::escape_xml(code)
));
if let Some(desc) = &meta.description {
xml.push_str(&format!(
" <description>{}</description>\n",
Self::escape_xml(desc)
));
}
if !meta.related.is_empty() {
xml.push_str(" <related>\n");
for related in &meta.related {
xml.push_str(&format!(
" <primary>{}</primary>\n",
Self::escape_xml(related)
));
}
xml.push_str(" </related>\n");
}
xml.push_str(" </primary>\n");
}
xml.push_str(" </primaries>\n");
xml.push_str(" <errors>\n");
for error in errors {
xml.push_str(&format!(
" <error code=\"{}\" severity=\"{}\">\n",
Self::escape_xml(&error.code),
Self::escape_xml(&error.severity)
));
xml.push_str(&format!(
" <description>{}</description>\n",
Self::escape_xml(&error.description)
));
let hints = if let Some(role) = filter_role {
error.hints_for_role(role)
} else {
error.hints_for_role(waddling_errors::Role::Internal)
};
if !hints.is_empty() {
xml.push_str(" <hints>\n");
for hint in &hints {
xml.push_str(&format!(
" <hint>{}</hint>\n",
Self::escape_xml(hint)
));
}
xml.push_str(" </hints>\n");
}
let tags = if let Some(role) = filter_role {
error.tags_for_role(role)
} else {
error.tags_for_role(waddling_errors::Role::Internal)
};
if !tags.is_empty() {
xml.push_str(" <tags>\n");
for tag in &tags {
xml.push_str(&format!(" <tag>{}</tag>\n", Self::escape_xml(tag)));
}
xml.push_str(" </tags>\n");
}
let related_codes = if let Some(role) = filter_role {
error.related_codes_for_role(role)
} else {
error.related_codes_for_role(waddling_errors::Role::Internal)
};
if !related_codes.is_empty() {
xml.push_str(" <related>\n");
for related in &related_codes {
xml.push_str(&format!(
" <code>{}</code>\n",
Self::escape_xml(related)
));
}
xml.push_str(" </related>\n");
}
if let Some(deprecated) = &error.deprecated {
xml.push_str(&format!(
" <deprecated since=\"{}\" />\n",
Self::escape_xml(deprecated)
));
}
if let Some(introduced) = &error.introduced {
xml.push_str(&format!(
" <introduced version=\"{}\" />\n",
Self::escape_xml(introduced)
));
}
xml.push_str(" </error>\n");
}
xml.push_str(" </errors>\n");
xml.push_str("</error-catalog>\n");
if let Some(parent) = output_path.parent() {
std::fs::create_dir_all(parent)?;
}
let mut file = File::create(output_path)?;
file.write_all(xml.as_bytes())?;
Ok(())
}
}
#[cfg(all(feature = "metadata", feature = "doc-gen", feature = "auto-register"))]
component! {
Api {
docs: "REST API endpoints and request handling",
tags: ["web", "rest", "http"],
},
Auth {
docs: "Authentication and authorization system",
tags: ["security", "authentication"],
},
}
#[cfg(all(feature = "metadata", feature = "doc-gen", feature = "auto-register"))]
pub mod sequences {
use waddling_errors_macros::sequence;
sequence! {
MISSING(1) {
description: "Required field is missing",
typical_severity: "Error",
},
INVALID(2) {
description: "Invalid input data",
typical_severity: "Error",
},
EXPIRED(3) {
description: "Token or resource has expired",
typical_severity: "Error",
},
DEPRECATED(4) {
description: "Deprecated API endpoint",
typical_severity: "Warning",
},
NOTFOUND(5) {
description: "Resource not found",
typical_severity: "Error",
},
}
}
#[cfg(not(all(feature = "metadata", feature = "doc-gen", feature = "auto-register")))]
pub mod sequences {}
#[cfg(all(feature = "metadata", feature = "doc-gen", feature = "auto-register"))]
primary! {
Validation {
docs: "Input validation and data constraints",
tags: ["validation", "input"],
},
Token {
docs: "Token handling and verification",
tags: ["jwt", "security"],
},
Request {
docs: "HTTP request processing",
tags: ["http", "web"],
},
}
#[cfg(all(feature = "metadata", feature = "doc-gen", feature = "auto-register"))]
diag! {
strict(component, primary, sequence, naming, duplicates, sequence_values, string_values),
<json, xml>,
E.Api.Validation.MISSING: {
message: "Required field '{{field}}' is missing from request (.001 = MISSING)",
'CR 'Pub description: "The API request is missing a required field",
'CR 'Pub hints: [
"Check the API documentation for required fields",
"Ensure all required parameters are included in the request",
],
'CR 'Dev hints: [
"Verify the request schema validation",
"Check the OpenAPI specification",
],
'CR 'Int hints: [
"Review the validation middleware configuration",
"Check if the field was recently added to the schema",
],
'R role: "Public",
'R tags: ["validation", "api", "request"],
'R related_codes: ["E.Api.Validation.INVALID"],
},
E.Api.Validation.INVALID: {
message: "Field '{{field}}' has invalid format (.003 = INVALID)",
'CR 'Pub description: "The provided value does not match the expected format",
'CR 'Pub hints: [
"Verify the field format matches the API specification",
"Check for typos or encoding issues",
],
'CR 'Dev hints: [
"Review the field validation rules",
"Check regex patterns or format validators",
],
'R role: "Developer",
'R tags: ["validation", "format"],
'R related_codes: ["E.Api.Validation.MISSING"],
},
E.Auth.Token.MISSING: {
message: "Authentication token not provided (.001 = MISSING)",
'CR 'Pub description: "The request is missing the required authentication token",
'CR 'Pub hints: [
"Include the Authorization header in your request",
"Use the format: Authorization: Bearer <token>",
],
'CR 'Int hints: [
"Check if authentication middleware is properly configured",
"Verify the token extraction logic",
],
'R role: "Public",
'R tags: ["authentication", "security", "token"],
},
E.Auth.Token.EXPIRED: {
message: "Authentication token has expired (.017 = TIMEOUT)",
'CR 'Pub description: "The provided authentication token is no longer valid",
'CR 'Pub hints: [
"Request a new token using your refresh token",
"Re-authenticate to obtain a new token",
],
'CR 'Dev hints: [
"Check the token TTL configuration",
"Verify the token refresh mechanism",
],
'R role: "Public",
'R tags: ["authentication", "token", "expiration"],
'R related_codes: ["E.Auth.Token.MISSING", "E.Auth.Token.INVALID"],
},
E.Auth.Token.INVALID: {
message: "Authentication token is invalid or malformed (.003 = INVALID)",
'CR 'Pub description: "The token could not be validated or parsed",
'CR 'Pub hints: [
"Ensure the token has not been tampered with",
"Verify you are using the correct token",
],
'CR 'Dev hints: [
"Check the JWT signature validation",
"Verify the token signing algorithm",
],
'CR 'Int hints: [
"Review the JWT_SECRET configuration",
"Check for key rotation issues",
],
'R role: "Developer",
'R tags: ["authentication", "jwt", "validation"],
'R related_codes: ["E.Auth.Token.MISSING", "E.Auth.Token.EXPIRED"],
},
W.Api.Request.DEPRECATED: {
message: "API endpoint '{{endpoint}}' is deprecated (.004 = deprecated marker)",
'CR 'Pub description: "This API endpoint will be removed in a future version",
'CR 'Pub hints: [
"Migrate to the new endpoint as specified in the documentation",
"Check the API changelog for migration guide",
],
'R role: "Public",
'R tags: ["api", "deprecation"],
'C deprecated: "2.0.0",
'C see_also: ["E.Api.Request.NOTFOUND"],
},
E.Api.Request.NOTFOUND: {
message: "API endpoint '{{endpoint}}' not found (.021 = NOTFOUND)",
'CR 'Pub description: "The requested API endpoint does not exist",
'CR 'Pub hints: [
"Check the API documentation for available endpoints",
"Verify the URL path is correct",
],
'R role: "Public",
'R tags: ["api", "routing"],
},
}
#[cfg(all(feature = "metadata", feature = "doc-gen", feature = "auto-register"))]
fn main() {
println!("🦆 Custom XML Renderer Example");
println!("================================\n");
println!("This example demonstrates:");
println!(" 1. Creating a custom XML renderer");
println!(" 2. Using auto-registration with custom formats");
println!(" 3. The <json, xml> format specifier in diag! macro");
println!();
#[cfg(not(all(feature = "metadata", feature = "doc-gen", feature = "auto-register")))]
{
println!("⚠️ This example requires multiple features:");
println!(
" cargo run --example custom_xml_renderer --features metadata,doc-gen,auto-register"
);
return;
}
#[cfg(all(feature = "metadata", feature = "doc-gen", feature = "auto-register"))]
{
use std::fs;
let mut registry = DocRegistry::new("Custom XML Example", "1.0.0");
println!("📝 Step 1: Auto-registering diagnostics...");
println!();
registry::register_all_with_doc_gen(&mut registry);
let (diag_count, comp_count, prim_count, _, _) = registry::statistics();
println!(" ✓ {} diagnostics auto-registered", diag_count);
println!(" ✓ {} components auto-registered", comp_count);
println!(" ✓ {} primaries auto-registered", prim_count);
println!();
println!("📋 Step 2: Listing auto-registered diagnostics:");
println!();
let diagnostics = registry::get_diagnostics();
for entry in diagnostics.iter().take(3) {
println!(
" • {} - registered for formats: {:?}",
entry.diagnostic.runtime.code, entry.formats
);
}
if diagnostics.len() > 3 {
println!(" ... and {} more", diagnostics.len() - 3);
}
println!();
println!("🎨 Step 3: Rendering with custom XML renderer...");
println!();
let xml_renderer = XmlRenderer::new();
let _output_path = "target/doc/custom-xml-example.xml";
match registry.render_all_roles(vec![Box::new(xml_renderer)], "target/doc") {
Ok(_) => {
println!(" ✓ XML rendered successfully!");
println!();
println!("📄 Generated XML files:");
if Path::new("target/doc/Custom XML Example-pub.xml").exists() {
println!(" • target/doc/Custom XML Example-pub.xml (Public)");
}
if Path::new("target/doc/Custom XML Example-dev.xml").exists() {
println!(" • target/doc/Custom XML Example-dev.xml (Developer)");
}
if Path::new("target/doc/Custom XML Example-int.xml").exists() {
println!(" • target/doc/Custom XML Example-int.xml (Internal)");
}
println!();
let preview_path = "target/doc/Custom XML Example-int.xml";
if let Ok(xml_content) = fs::read_to_string(preview_path) {
println!("📖 XML Preview (first 50 lines of internal docs):");
println!("{}", "=".repeat(70));
for (i, line) in xml_content.lines().enumerate() {
if i >= 50 {
println!("... ({} more lines)", xml_content.lines().count() - 50);
break;
}
println!("{}", line);
}
println!("{}", "=".repeat(70));
}
}
Err(e) => {
eprintln!(" ✗ Failed to render XML: {}", e);
}
}
println!();
println!("✨ Key Takeaways:");
println!(" 1. Custom renderers implement the Renderer trait");
println!(" 2. Use <json, xml> to auto-register for custom formats");
println!(" 3. The format name 'xml' matches your custom renderer");
println!(" 4. Auto-registration works with any custom renderer!");
println!(" 5. Role-based rendering generates separate files per role");
println!();
println!("💡 Try:");
println!(" - View the generated XML files in target/doc/");
println!(" - Compare pub.xml vs dev.xml vs int.xml for role filtering");
println!(" - Create your own custom renderer (YAML, Markdown, CSV, etc.)");
println!(" - Use <myformat> in diag! macro with your renderer");
}
}
#[cfg(not(all(feature = "metadata", feature = "doc-gen", feature = "auto-register")))]
fn main() {}