Skip to main content

wowlab_engine_application/
conflicts.rs

1//! Cross-checks between declared catalog metadata and runtime introspection.
2
3use wowlab_engine_ports::{ContentCatalog, EngineError};
4use wowlab_types::sim::FastMap;
5
6use crate::{ApplicationError, ApplicationStage, error::EngineResultExt as _};
7
8/// Validate the content catalog and reject conflicting symbolic names within each spec.
9pub fn check_conflicts(catalog: &ContentCatalog) -> Result<(), ApplicationError> {
10    fn check_names<'a>(
11        kind: &str,
12        pairs: impl Iterator<Item = (&'a str, u32)>,
13        display_name: &str,
14    ) -> Result<(), EngineError> {
15        let mut seen: FastMap<&'a str, u32> = FastMap::default();
16
17        // #t(block: rust_alloc_in_loop) format! only executes on error path, immediately returns
18        for (name, id) in pairs {
19            if let Some(&first_id) = seen.get(&name) {
20                if first_id != id {
21                    return Err(EngineError::spec_construction(format!(
22                        "{kind} name conflict in '{display_name}': '{name}' bound to both ids {first_id} and {id}"
23                    )));
24                }
25            } else {
26                seen.insert(name, id);
27            }
28        }
29
30        Ok(())
31    }
32
33    for descriptor in catalog.descriptors() {
34        let intro = crate::introspect_spec(catalog, descriptor.spec_id)?;
35
36        check_names(
37            "spell",
38            intro.spells.iter().map(|s| (s.slug.as_str(), s.spell_id)),
39            descriptor.display_name,
40        )
41        .in_application_stage(ApplicationStage::ConflictCheck)?;
42        check_names(
43            "aura",
44            intro.auras.iter().map(|a| (a.slug.as_str(), a.aura_id)),
45            descriptor.display_name,
46        )
47        .in_application_stage(ApplicationStage::ConflictCheck)?;
48    }
49
50    Ok(())
51}