#[cfg(feature = "std")]
use std::{collections::HashMap, fs, path::Path, string::String, vec::Vec};
use super::types::{ComponentMeta, ErrorDoc, PrimaryMeta, SequenceMeta};
use crate::traits::Role;
#[cfg(feature = "serde")]
#[allow(clippy::too_many_arguments)]
pub(super) fn generate_json(
path: impl AsRef<Path>,
project_name: &str,
version: &str,
errors: &[&ErrorDoc],
components: &HashMap<String, ComponentMeta>,
primaries: &HashMap<String, PrimaryMeta>,
sequences: &HashMap<u32, SequenceMeta>,
filter_role: Option<Role>,
) -> std::io::Result<()> {
use serde_json::json;
let role_filtered_errors: Vec<&ErrorDoc> = if let Some(role) = filter_role {
errors
.iter()
.filter(|e| super::html::is_visible_at_role(e.role.as_deref(), role))
.copied()
.collect()
} else {
errors.to_vec()
};
let visible_codes: std::collections::HashSet<&str> = role_filtered_errors
.iter()
.map(|e| e.code.as_str())
.collect();
let target_role = filter_role.unwrap_or(crate::traits::Role::Internal);
let filtered_errors: HashMap<String, serde_json::Value> = role_filtered_errors
.iter()
.map(|e| {
#[cfg(feature = "runtime-hash")]
let error_json = json!({
"code": e.code,
"severity": e.severity,
"component": e.component,
"primary": e.primary,
"sequence": e.sequence,
"description": e.description,
"message": e.message,
"fields": e.fields,
"hash": e.hash,
"namespace": e.namespace,
"namespace_hash": e.namespace_hash,
"hints": e.hints_for_role(target_role),
"tags": e.tags_for_role(target_role),
"introduced": e.introduced,
"deprecated": e.deprecated,
"docs_url": e.docs_url,
"related_codes": e.related_codes_for_role(target_role),
"see_also": e.see_also_for_role(target_role),
"role": e.role,
"code_snippets": e.code_snippets,
});
#[cfg(not(feature = "runtime-hash"))]
let error_json = json!({
"code": e.code,
"severity": e.severity,
"component": e.component,
"primary": e.primary,
"sequence": e.sequence,
"description": e.description,
"message": e.message,
"fields": e.fields,
"namespace": e.namespace,
"hints": e.hints_for_role(target_role),
"tags": e.tags_for_role(target_role),
"introduced": e.introduced,
"deprecated": e.deprecated,
"docs_url": e.docs_url,
"related_codes": e.related_codes_for_role(target_role),
"see_also": e.see_also_for_role(target_role),
"role": e.role,
"code_snippets": e.code_snippets,
});
(e.code.clone(), error_json)
})
.collect();
let mut by_component: HashMap<String, Vec<String>> = HashMap::new();
for error in &role_filtered_errors {
by_component
.entry(error.component.clone())
.or_default()
.push(error.code.clone());
}
let mut by_primary: HashMap<String, Vec<String>> = HashMap::new();
for error in &role_filtered_errors {
by_primary
.entry(error.primary.clone())
.or_default()
.push(error.code.clone());
}
let mut by_severity: HashMap<String, Vec<String>> = HashMap::new();
for error in &role_filtered_errors {
by_severity
.entry(error.severity.clone())
.or_default()
.push(error.code.clone());
}
#[cfg(feature = "runtime-hash")]
let hash_lookup: HashMap<String, String> = role_filtered_errors
.iter()
.filter_map(|e| e.hash.as_ref().map(|h| (h.clone(), e.code.clone())))
.collect();
let filtered_components: HashMap<String, serde_json::Value> = components
.iter()
.map(|(name, comp)| {
let mut filtered_errors: Vec<&str> = comp
.errors
.iter()
.filter(|code| visible_codes.contains(code.as_str()))
.map(|s| s.as_str())
.collect();
filtered_errors.sort_unstable();
filtered_errors.dedup();
let mut sorted_locations = comp.locations.clone();
sorted_locations
.sort_unstable_by(|a, b| a.path.cmp(&b.path).then_with(|| a.role.cmp(&b.role)));
(
name.clone(),
json!({
"name": comp.name,
"description": comp.description,
"examples": comp.examples,
"tags": comp.tags,
"errors": filtered_errors,
"error_count": filtered_errors.len(),
"locations": sorted_locations,
}),
)
})
.collect();
let filtered_primaries: HashMap<String, serde_json::Value> = primaries
.iter()
.map(|(name, prim)| {
let mut filtered_errors: Vec<&str> = prim
.errors
.iter()
.filter(|code| visible_codes.contains(code.as_str()))
.map(|s| s.as_str())
.collect();
filtered_errors.sort_unstable();
filtered_errors.dedup();
(
name.clone(),
json!({
"name": prim.name,
"description": prim.description,
"examples": prim.examples,
"related": prim.related,
"errors": filtered_errors,
"error_count": filtered_errors.len(),
}),
)
})
.collect();
#[cfg(feature = "runtime-hash")]
let output = json!({
"_format_version": "2.0-hashmap",
"project": project_name,
"version": version,
"total_errors": filtered_errors.len(),
"errors": filtered_errors,
"components": filtered_components,
"primaries": filtered_primaries,
"sequences": sequences,
"hash_lookup": hash_lookup,
"search_patterns": {
"by_component": by_component,
"by_primary": by_primary,
"by_severity": by_severity,
},
"role_filter": match filter_role {
Some(Role::Public) => "public",
Some(Role::Developer) => "developer",
Some(Role::Internal) => "internal",
None => "all",
}
});
#[cfg(not(feature = "runtime-hash"))]
let output = json!({
"_format_version": "2.0-hashmap",
"project": project_name,
"version": version,
"total_errors": filtered_errors.len(),
"errors": filtered_errors,
"components": filtered_components,
"primaries": filtered_primaries,
"sequences": sequences,
"search_patterns": {
"by_component": by_component,
"by_primary": by_primary,
"by_severity": by_severity,
},
"role_filter": match filter_role {
Some(Role::Public) => "public",
Some(Role::Developer) => "developer",
Some(Role::Internal) => "internal",
None => "all",
}
});
if let Some(parent) = path.as_ref().parent() {
fs::create_dir_all(parent)?;
}
let json_string = serde_json::to_string_pretty(&output)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
fs::write(path, json_string)
}