use std::sync::Mutex;
use std::vec::Vec;
#[cfg(feature = "metadata")]
#[derive(Debug, Clone)]
pub struct DiagnosticRegistration {
pub diagnostic: &'static crate::metadata::DiagnosticComplete,
pub formats: &'static [&'static str],
}
#[cfg(feature = "metadata")]
#[derive(Debug, Clone)]
pub struct ComponentRegistration {
pub name: &'static str,
pub description: Option<&'static str>,
pub examples: &'static [&'static str],
pub tags: &'static [&'static str],
pub related: &'static [&'static str],
pub formats: &'static [&'static str],
#[doc(hidden)]
#[allow(dead_code)]
pub(crate) module_path: &'static str,
#[doc(hidden)]
#[allow(dead_code)]
pub(crate) crate_name: &'static str,
}
#[cfg(feature = "metadata")]
#[derive(Debug, Clone)]
pub struct PrimaryRegistration {
pub name: &'static str,
pub description: Option<&'static str>,
pub examples: &'static [&'static str],
pub tags: &'static [&'static str],
pub related: &'static [&'static str],
#[doc(hidden)]
#[allow(dead_code)]
pub(crate) module_path: &'static str,
#[doc(hidden)]
#[allow(dead_code)]
pub(crate) crate_name: &'static str,
}
#[cfg(feature = "metadata")]
#[derive(Debug, Clone)]
pub struct SequenceRegistration {
pub name: &'static str,
pub number: u16,
pub description: Option<&'static str>,
pub typical_severity: Option<&'static str>,
pub hints: &'static [&'static str],
}
#[cfg(feature = "metadata")]
#[derive(Debug, Clone)]
pub struct ComponentLocationRegistration {
pub component: &'static str,
pub file: &'static str,
pub role: Option<crate::traits::Role>,
}
#[cfg(feature = "metadata")]
static DIAGNOSTIC_REGISTRY: Mutex<Option<Vec<DiagnosticRegistration>>> = Mutex::new(None);
#[cfg(feature = "metadata")]
static COMPONENT_REGISTRY: Mutex<Option<Vec<ComponentRegistration>>> = Mutex::new(None);
#[cfg(feature = "metadata")]
static PRIMARY_REGISTRY: Mutex<Option<Vec<PrimaryRegistration>>> = Mutex::new(None);
#[cfg(feature = "metadata")]
static SEQUENCE_REGISTRY: Mutex<Option<Vec<SequenceRegistration>>> = Mutex::new(None);
#[cfg(feature = "metadata")]
static COMPONENT_LOCATION_REGISTRY: Mutex<Option<Vec<ComponentLocationRegistration>>> =
Mutex::new(None);
#[cfg(feature = "metadata")]
pub fn register_diagnostic(
diagnostic: &'static crate::metadata::DiagnosticComplete,
formats: &'static [&'static str],
) {
let mut registry = DIAGNOSTIC_REGISTRY.lock().unwrap();
if registry.is_none() {
*registry = Some(Vec::new());
}
registry.as_mut().unwrap().push(DiagnosticRegistration {
diagnostic,
formats,
});
}
#[cfg(all(feature = "metadata", feature = "doc-gen"))]
pub fn collect_all_diagnostics() -> Vec<DiagnosticRegistration> {
let mut registry = DIAGNOSTIC_REGISTRY.lock().unwrap();
registry.take().unwrap_or_default()
}
#[cfg(feature = "metadata")]
pub fn get_diagnostics() -> Vec<DiagnosticRegistration> {
let registry = DIAGNOSTIC_REGISTRY.lock().unwrap();
registry.as_ref().map(|v| v.clone()).unwrap_or_default()
}
#[cfg(feature = "metadata")]
pub fn clear_diagnostics() {
let mut registry = DIAGNOSTIC_REGISTRY.lock().unwrap();
*registry = Some(Vec::new());
}
#[cfg(feature = "metadata")]
#[allow(clippy::too_many_arguments)]
pub fn register_component(
name: &'static str,
description: Option<&'static str>,
examples: &'static [&'static str],
tags: &'static [&'static str],
related: &'static [&'static str],
formats: &'static [&'static str],
module_path: &'static str,
crate_name: &'static str,
) {
let mut registry = COMPONENT_REGISTRY.lock().unwrap();
if registry.is_none() {
*registry = Some(Vec::new());
}
registry.as_mut().unwrap().push(ComponentRegistration {
name,
description,
examples,
tags,
related,
formats,
module_path,
crate_name,
});
}
#[cfg(feature = "metadata")]
pub fn collect_all_components() -> Vec<ComponentRegistration> {
let mut registry = COMPONENT_REGISTRY.lock().unwrap();
registry.take().unwrap_or_default()
}
#[cfg(feature = "metadata")]
pub fn get_components() -> Vec<ComponentRegistration> {
let registry = COMPONENT_REGISTRY.lock().unwrap();
registry.as_ref().map(|v| v.clone()).unwrap_or_default()
}
#[cfg(feature = "metadata")]
pub fn clear_components() {
let mut registry = COMPONENT_REGISTRY.lock().unwrap();
*registry = Some(Vec::new());
}
#[cfg(feature = "metadata")]
pub fn search_components(pattern: &str) -> Vec<ComponentRegistration> {
let components = get_components();
if pattern == "*" {
return components;
}
let pattern_upper = pattern.to_uppercase();
components
.into_iter()
.filter(|c| {
let name_upper = c.name.to_uppercase();
if pattern_upper.contains('*') {
let parts: Vec<&str> = pattern_upper.split('*').collect();
if parts.len() == 2 && parts[0].is_empty() {
name_upper.ends_with(parts[1])
} else if parts.len() == 2 && parts[1].is_empty() {
name_upper.starts_with(parts[0])
} else if parts.len() == 2 {
name_upper.starts_with(parts[0]) && name_upper.ends_with(parts[1])
} else {
name_upper.contains(&pattern_upper.replace('*', ""))
}
} else {
name_upper == pattern_upper
}
})
.collect()
}
#[cfg(feature = "metadata")]
pub fn register_primary(
name: &'static str,
description: Option<&'static str>,
examples: &'static [&'static str],
tags: &'static [&'static str],
related: &'static [&'static str],
module_path: &'static str,
crate_name: &'static str,
) {
let mut registry = PRIMARY_REGISTRY.lock().unwrap();
if registry.is_none() {
*registry = Some(Vec::new());
}
registry.as_mut().unwrap().push(PrimaryRegistration {
name,
description,
examples,
tags,
related,
module_path,
crate_name,
});
}
#[cfg(feature = "metadata")]
pub fn collect_all_primaries() -> Vec<PrimaryRegistration> {
let mut registry = PRIMARY_REGISTRY.lock().unwrap();
registry.take().unwrap_or_default()
}
#[cfg(feature = "metadata")]
pub fn get_primaries() -> Vec<PrimaryRegistration> {
let registry = PRIMARY_REGISTRY.lock().unwrap();
registry.as_ref().map(|v| v.clone()).unwrap_or_default()
}
#[cfg(feature = "metadata")]
pub fn clear_primaries() {
let mut registry = PRIMARY_REGISTRY.lock().unwrap();
*registry = Some(Vec::new());
}
#[cfg(feature = "metadata")]
pub fn search_primaries(pattern: &str) -> Vec<PrimaryRegistration> {
let primaries = get_primaries();
if pattern == "*" {
return primaries;
}
let pattern_upper = pattern.to_uppercase();
primaries
.into_iter()
.filter(|p| {
let name_upper = p.name.to_uppercase();
if pattern_upper.contains('*') {
let parts: Vec<&str> = pattern_upper.split('*').collect();
if parts.len() == 2 && parts[0].is_empty() {
name_upper.ends_with(parts[1])
} else if parts.len() == 2 && parts[1].is_empty() {
name_upper.starts_with(parts[0])
} else if parts.len() == 2 {
name_upper.starts_with(parts[0]) && name_upper.ends_with(parts[1])
} else {
name_upper.contains(&pattern_upper.replace('*', ""))
}
} else {
name_upper == pattern_upper
}
})
.collect()
}
#[cfg(feature = "metadata")]
pub fn register_sequence(
name: &'static str,
number: u16,
description: Option<&'static str>,
typical_severity: Option<&'static str>,
hints: &'static [&'static str],
) {
let mut registry = SEQUENCE_REGISTRY.lock().unwrap();
if registry.is_none() {
*registry = Some(Vec::new());
}
registry.as_mut().unwrap().push(SequenceRegistration {
name,
number,
description,
typical_severity,
hints,
});
}
#[cfg(feature = "metadata")]
pub fn collect_all_sequences() -> Vec<SequenceRegistration> {
let mut registry = SEQUENCE_REGISTRY.lock().unwrap();
registry.take().unwrap_or_default()
}
#[cfg(feature = "metadata")]
pub fn get_sequences() -> Vec<SequenceRegistration> {
let registry = SEQUENCE_REGISTRY.lock().unwrap();
registry.as_ref().map(|v| v.clone()).unwrap_or_default()
}
#[cfg(feature = "metadata")]
pub fn clear_sequences() {
let mut registry = SEQUENCE_REGISTRY.lock().unwrap();
*registry = Some(Vec::new());
}
#[cfg(feature = "metadata")]
pub fn register_component_location(
component: &'static str,
file: &'static str,
role: Option<crate::traits::Role>,
) {
let mut registry = COMPONENT_LOCATION_REGISTRY.lock().unwrap();
if registry.is_none() {
*registry = Some(Vec::new());
}
registry
.as_mut()
.unwrap()
.push(ComponentLocationRegistration {
component,
file,
role,
});
}
#[cfg(feature = "metadata")]
pub fn collect_all_component_locations() -> Vec<ComponentLocationRegistration> {
let mut registry = COMPONENT_LOCATION_REGISTRY.lock().unwrap();
registry.take().unwrap_or_default()
}
#[cfg(feature = "metadata")]
pub fn get_component_locations() -> Vec<ComponentLocationRegistration> {
let registry = COMPONENT_LOCATION_REGISTRY.lock().unwrap();
registry.as_ref().map(|v| v.clone()).unwrap_or_default()
}
#[cfg(feature = "metadata")]
pub fn clear_component_locations() {
let mut registry = COMPONENT_LOCATION_REGISTRY.lock().unwrap();
*registry = Some(Vec::new());
}
#[cfg(all(feature = "metadata", feature = "doc-gen", not(target_arch = "wasm32")))]
pub fn register_all_with_doc_gen(doc_gen: &mut crate::doc_generator::DocRegistry) {
let diagnostics = get_diagnostics();
for entry in &diagnostics {
doc_gen.register_diagnostic_complete_with_formats(entry.diagnostic, entry.formats);
}
let components = get_components();
for comp in &components {
doc_gen.register_component(comp.name, comp.description, comp.examples, comp.tags);
}
let primaries = get_primaries();
for prim in &primaries {
doc_gen.register_primary(prim.name, prim.description, prim.examples, prim.related);
}
let sequences = get_sequences();
for seq in &sequences {
doc_gen.register_sequence(
seq.name,
seq.number,
seq.description,
seq.typical_severity,
seq.hints,
);
}
let locations = get_component_locations();
for loc in &locations {
doc_gen.register_component_location_with_role(loc.component, loc.file, loc.role);
}
}
#[cfg(feature = "metadata")]
pub fn statistics() -> (usize, usize, usize, usize, usize) {
let diagnostics = get_diagnostics().len();
let components = get_components().len();
let primaries = get_primaries().len();
let sequences = get_sequences().len();
let locations = get_component_locations().len();
(diagnostics, components, primaries, sequences, locations)
}
#[cfg(feature = "metadata")]
#[doc(hidden)]
pub struct DiagnosticRegistrar {
_private: (),
}
#[cfg(feature = "metadata")]
impl DiagnosticRegistrar {
pub const fn new(
_diagnostic: &'static crate::metadata::DiagnosticComplete,
_formats: &'static [&'static str],
) -> Self {
Self { _private: () }
}
}
#[cfg(all(test, feature = "metadata"))]
mod tests {
use super::*;
#[test]
fn test_component_registration() {
clear_components();
register_component(
"TEST_COMP",
Some("Test component"),
&["Example 1"],
&["test"],
&[],
&["json"],
"test::module",
"test_crate",
);
let components = get_components();
assert_eq!(components.len(), 1);
assert_eq!(components[0].name, "TEST_COMP");
assert_eq!(components[0].description, Some("Test component"));
}
#[test]
fn test_primary_registration() {
clear_primaries();
register_primary(
"TEST_PRIM",
Some("Test primary"),
&["Example 1"],
&["test"],
&[],
"test::module",
"test_crate",
);
let primaries = get_primaries();
assert_eq!(primaries.len(), 1);
assert_eq!(primaries[0].name, "TEST_PRIM");
assert_eq!(primaries[0].description, Some("Test primary"));
}
#[test]
fn test_search_components() {
clear_components();
register_component("AUTH", Some("Auth"), &[], &[], &[], &[], "test", "test");
register_component("DATABASE", Some("DB"), &[], &[], &[], &[], "test", "test");
register_component("API", Some("API"), &[], &[], &[], &[], "test", "test");
let results = search_components("AUTH");
assert_eq!(results.len(), 1);
assert_eq!(results[0].name, "AUTH");
let results = search_components("A*");
assert_eq!(results.len(), 2);
let results = search_components("*BASE");
assert_eq!(results.len(), 1);
let results = search_components("*");
assert_eq!(results.len(), 3);
}
#[test]
fn test_search_primaries() {
clear_primaries();
register_primary("TOKEN", Some("Token"), &[], &[], &[], "test", "test");
register_primary("SESSION", Some("Session"), &[], &[], &[], "test", "test");
register_primary("TIMEOUT", Some("Timeout"), &[], &[], &[], "test", "test");
let results = search_primaries("TOKEN");
assert_eq!(results.len(), 1);
assert_eq!(results[0].name, "TOKEN");
let results = search_primaries("*OUT");
assert_eq!(results.len(), 1);
let results = search_primaries("*");
assert_eq!(results.len(), 3);
}
#[test]
fn test_statistics() {
clear_diagnostics();
clear_components();
clear_primaries();
clear_sequences();
register_component("C1", None, &[], &[], &[], &[], "test", "test");
register_component("C2", None, &[], &[], &[], &[], "test", "test");
register_primary("P1", None, &[], &[], &[], "test", "test");
register_sequence("S1", 1, None, None, &[]);
let (diags, comps, prims, seqs, _locs) = statistics();
assert_eq!(diags, 0);
assert_eq!(comps, 2);
assert_eq!(prims, 1);
assert_eq!(seqs, 1);
}
#[test]
fn test_sequence_registration() {
clear_sequences();
register_sequence(
"MISSING",
1,
Some("Required item"),
Some("Error"),
&["hint1"],
);
let sequences = get_sequences();
assert_eq!(sequences.len(), 1);
assert_eq!(sequences[0].name, "MISSING");
assert_eq!(sequences[0].number, 1);
assert_eq!(sequences[0].description, Some("Required item"));
}
}