Skip to main content

wowlab_engine_domain/rotation/condition/
resolve.rs

1//! Resolution of `FieldRead` leaves against the descriptor table and spec resolver.
2
3#[cfg(test)]
4use wowlab_types::sim::Condition;
5use wowlab_types::{
6    combat::ResourceType,
7    sim::{AuraIdx, AuraOn, FieldRead, SpellIdx},
8};
9
10use super::{
11    super::{
12        buffer::{DescriptorId, DescriptorTable, SlotKind},
13        resolver::SpecResolver,
14    },
15    KeyCategory,
16};
17
18/// A [`FieldRead`] with its descriptor looked up and its key resolved to a typed ID.
19#[derive(Clone, Debug, PartialEq)]
20pub struct ResolvedFieldRead {
21    pub descriptor_id: DescriptorId,
22    pub key: Option<ResolvedKey>,
23    pub on: Option<AuraOn>,
24}
25
26/// Typed slot key for a resolved field read.
27#[derive(Clone, Debug, PartialEq)]
28#[non_exhaustive]
29pub enum ResolvedKey {
30    Spell(SpellIdx),
31    Aura(AuraIdx),
32    Resource(ResourceType),
33    Named(String),
34}
35
36pub(crate) fn resolve_field_read(
37    field: &FieldRead,
38    table: &DescriptorTable,
39    resolver: &SpecResolver,
40) -> super::super::error::Result<ResolvedFieldRead> {
41    let (descriptor_id, descriptor) = table
42        .lookup(&field.domain, &field.name)
43        .ok_or_else(|| super::super::error::Error::unknown_field(&field.domain, &field.name))?;
44
45    match descriptor.slot_kind {
46        SlotKind::Keyed if field.key.is_none() => {
47            return Err(super::super::error::Error::missing_key(
48                &field.domain,
49                &field.name,
50            ));
51        }
52        SlotKind::Singleton if field.key.is_some() => {
53            return Err(super::super::error::Error::unexpected_key(
54                &field.domain,
55                &field.name,
56            ));
57        }
58        _ => {}
59    }
60
61    let key = if let Some(key_str) = &field.key {
62        Some(resolve_key(&field.domain, key_str, resolver)?)
63    } else {
64        None
65    };
66
67    Ok(ResolvedFieldRead {
68        descriptor_id,
69        key,
70        on: field.on,
71    })
72}
73
74fn resolve_key(
75    domain: &str,
76    key: &str,
77    resolver: &SpecResolver,
78) -> super::super::error::Result<ResolvedKey> {
79    match KeyCategory::for_domain(domain) {
80        Some(KeyCategory::Spell) => Ok(ResolvedKey::Spell(resolver.resolve_spell(key)?)),
81        Some(KeyCategory::Aura) => Ok(ResolvedKey::Aura(resolver.resolve_aura(key)?)),
82        Some(KeyCategory::Resource) => {
83            let rt = key
84                .parse()
85                .ok()
86                .ok_or_else(|| super::super::error::Error::unknown_resource(key))?;
87
88            Ok(ResolvedKey::Resource(rt))
89        }
90        Some(KeyCategory::Named) => Ok(ResolvedKey::Named(key.to_string())),
91        None => Err(super::super::error::Error::unknown_domain(domain)),
92    }
93}
94
95#[cfg(test)]
96pub(crate) fn validate_condition(condition: &Condition, table: &DescriptorTable) -> Vec<String> {
97    let mut errors = Vec::new();
98
99    condition.walk(&mut |node| {
100        if let Condition::Read { field } = node {
101            if table.lookup(&field.domain, &field.name).is_none() {
102                // #t(rust_alloc_in_loop) only allocates on error (rare path)
103                errors.push(format!("unknown field: {}.{}", field.domain, field.name));
104            }
105        }
106    });
107
108    errors
109}