Skip to main content

mib_rs/mib/
module.rs

1//! Loaded MIB module data and per-module symbol indices.
2//!
3//! [`ModuleData`] stores module-level metadata (organization, description,
4//! revisions, imports) along with per-entity name indices for fast lookup
5//! within a single module.
6//!
7//! For handle-oriented access, see [`Module`](super::handle::Module).
8
9use std::collections::{HashMap, HashSet};
10
11use crate::mib::Oid;
12use crate::types::Language;
13
14use super::symbol::Symbol;
15use super::types::*;
16
17/// A loaded and resolved MIB module.
18///
19/// Contains module-level metadata (organization, description, revisions),
20/// import declarations, and per-entity name indices. Access through the
21/// public accessor methods or the [`Module`](super::handle::Module) handle.
22pub struct ModuleData {
23    pub(crate) name: String,
24    pub(crate) language: Language,
25    pub(crate) source_path: String,
26    pub(crate) is_base: bool,
27    pub(crate) oid: Option<Oid>,
28    pub(crate) organization: String,
29    pub(crate) contact_info: String,
30    pub(crate) description: String,
31    pub(crate) last_updated: String,
32    pub(crate) revisions: Vec<Revision>,
33    pub(crate) imports: Vec<Import>,
34
35    pub(crate) objects: Vec<ObjectId>,
36    pub(crate) types: Vec<TypeId>,
37    pub(crate) notifications: Vec<NotificationId>,
38    pub(crate) groups: Vec<GroupId>,
39    pub(crate) compliances: Vec<ComplianceId>,
40    pub(crate) capabilities: Vec<CapabilityId>,
41    pub(crate) nodes: Vec<NodeId>,
42
43    pub(crate) line_table: Vec<usize>,
44
45    pub(crate) used_import_names: HashSet<String>,
46    pub(crate) resolved_imports: HashMap<String, ModuleId>,
47
48    pub(crate) objects_by_name: HashMap<String, ObjectId>,
49    pub(crate) types_by_name: HashMap<String, TypeId>,
50    pub(crate) notifications_by_name: HashMap<String, NotificationId>,
51    pub(crate) groups_by_name: HashMap<String, GroupId>,
52    pub(crate) compliances_by_name: HashMap<String, ComplianceId>,
53    pub(crate) capabilities_by_name: HashMap<String, CapabilityId>,
54    pub(crate) nodes_by_name: HashMap<String, NodeId>,
55}
56
57impl ModuleData {
58    pub(crate) fn new(name: String) -> Self {
59        Self {
60            name,
61            language: Language::Unknown,
62            source_path: String::new(),
63            is_base: false,
64            oid: None,
65            organization: String::new(),
66            contact_info: String::new(),
67            description: String::new(),
68            last_updated: String::new(),
69            revisions: Vec::new(),
70            imports: Vec::new(),
71            objects: Vec::new(),
72            types: Vec::new(),
73            notifications: Vec::new(),
74            groups: Vec::new(),
75            compliances: Vec::new(),
76            capabilities: Vec::new(),
77            nodes: Vec::new(),
78            line_table: Vec::new(),
79            used_import_names: HashSet::new(),
80            resolved_imports: HashMap::new(),
81            objects_by_name: HashMap::new(),
82            types_by_name: HashMap::new(),
83            notifications_by_name: HashMap::new(),
84            groups_by_name: HashMap::new(),
85            compliances_by_name: HashMap::new(),
86            capabilities_by_name: HashMap::new(),
87            nodes_by_name: HashMap::new(),
88        }
89    }
90
91    /// Return the module name.
92    pub fn name(&self) -> &str {
93        &self.name
94    }
95
96    /// Return the SMI language version.
97    pub fn language(&self) -> Language {
98        self.language
99    }
100
101    /// Return the file path this module was loaded from.
102    pub fn source_path(&self) -> &str {
103        &self.source_path
104    }
105
106    /// Return `true` if this is a synthetic base module.
107    ///
108    /// See [`Module::is_base`](super::Module::is_base) for details.
109    pub fn is_base(&self) -> bool {
110        self.is_base
111    }
112
113    /// Convert a byte offset to a line and column number.
114    pub fn line_col(&self, offset: crate::types::ByteOffset) -> (usize, usize) {
115        crate::types::line_col_from_table(&self.line_table, offset)
116    }
117
118    /// Return the module's MODULE-IDENTITY OID, if any.
119    pub fn oid(&self) -> Option<&Oid> {
120        self.oid.as_ref()
121    }
122
123    /// Return the ORGANIZATION clause text.
124    pub fn organization(&self) -> &str {
125        &self.organization
126    }
127
128    /// Return the CONTACT-INFO clause text.
129    pub fn contact_info(&self) -> &str {
130        &self.contact_info
131    }
132
133    /// Return the DESCRIPTION clause text.
134    pub fn description(&self) -> &str {
135        &self.description
136    }
137
138    /// Return the LAST-UPDATED timestamp string.
139    pub fn last_updated(&self) -> &str {
140        &self.last_updated
141    }
142
143    /// Return the REVISION entries.
144    pub fn revisions(&self) -> &[Revision] {
145        &self.revisions
146    }
147
148    /// Return the IMPORTS declarations.
149    pub fn imports(&self) -> &[Import] {
150        &self.imports
151    }
152
153    /// Return the object ids defined by this module.
154    pub fn objects(&self) -> &[ObjectId] {
155        &self.objects
156    }
157
158    /// Return the type ids defined by this module.
159    pub fn types(&self) -> &[TypeId] {
160        &self.types
161    }
162
163    /// Return the notification ids defined by this module.
164    pub fn notifications(&self) -> &[NotificationId] {
165        &self.notifications
166    }
167
168    /// Return the group ids defined by this module.
169    pub fn groups(&self) -> &[GroupId] {
170        &self.groups
171    }
172
173    /// Return the compliance ids defined by this module.
174    pub fn compliances(&self) -> &[ComplianceId] {
175        &self.compliances
176    }
177
178    /// Return the capability ids defined by this module.
179    pub fn capabilities(&self) -> &[CapabilityId] {
180        &self.capabilities
181    }
182
183    /// Return the node ids defined by this module.
184    pub fn nodes(&self) -> &[NodeId] {
185        &self.nodes
186    }
187
188    /// Look up an object by name within this module.
189    pub fn object_by_name(&self, name: &str) -> Option<ObjectId> {
190        self.objects_by_name.get(name).copied()
191    }
192
193    /// Look up a type by name within this module.
194    pub fn type_by_name(&self, name: &str) -> Option<TypeId> {
195        self.types_by_name.get(name).copied()
196    }
197
198    /// Look up a notification by name within this module.
199    pub fn notification_by_name(&self, name: &str) -> Option<NotificationId> {
200        self.notifications_by_name.get(name).copied()
201    }
202
203    /// Look up a group by name within this module.
204    pub fn group_by_name(&self, name: &str) -> Option<GroupId> {
205        self.groups_by_name.get(name).copied()
206    }
207
208    /// Look up a compliance statement by name within this module.
209    pub fn compliance_by_name(&self, name: &str) -> Option<ComplianceId> {
210        self.compliances_by_name.get(name).copied()
211    }
212
213    /// Look up a capability statement by name within this module.
214    pub fn capability_by_name(&self, name: &str) -> Option<CapabilityId> {
215        self.capabilities_by_name.get(name).copied()
216    }
217
218    /// Look up a node by name within this module.
219    pub fn node_by_name(&self, name: &str) -> Option<NodeId> {
220        self.nodes_by_name.get(name).copied()
221    }
222
223    /// Look up a symbol by name. Priority: objects, types, notifications,
224    /// groups, compliances, capabilities, then plain nodes.
225    pub fn symbol(&self, name: &str) -> Option<Symbol> {
226        if let Some(&id) = self.objects_by_name.get(name) {
227            return Some(Symbol::Object(id));
228        }
229        if let Some(&id) = self.types_by_name.get(name) {
230            return Some(Symbol::Type(id));
231        }
232        if let Some(&id) = self.notifications_by_name.get(name) {
233            return Some(Symbol::Notification(id));
234        }
235        if let Some(&id) = self.groups_by_name.get(name) {
236            return Some(Symbol::Group(id));
237        }
238        if let Some(&id) = self.compliances_by_name.get(name) {
239            return Some(Symbol::Compliance(id));
240        }
241        if let Some(&id) = self.capabilities_by_name.get(name) {
242            return Some(Symbol::Capability(id));
243        }
244        if let Some(&id) = self.nodes_by_name.get(name) {
245            return Some(Symbol::Node(id));
246        }
247        None
248    }
249
250    /// Return `true` if this module defines a symbol with the given name.
251    pub fn defines_symbol(&self, name: &str) -> bool {
252        self.symbol(name).is_some()
253    }
254
255    /// Return `true` if this module imports a symbol with the given name.
256    pub fn imports_symbol(&self, name: &str) -> bool {
257        self.imports
258            .iter()
259            .any(|imp| imp.symbols.iter().any(|s| s.name == name))
260    }
261
262    /// Return `true` if the named import was actually used during resolution.
263    pub fn is_import_used(&self, name: &str) -> bool {
264        self.used_import_names.contains(name)
265    }
266
267    /// Return the resolved source module for an imported name.
268    pub fn import_source(&self, name: &str) -> Option<ModuleId> {
269        self.resolved_imports.get(name).copied()
270    }
271
272    // Builder methods used during resolution.
273
274    pub(crate) fn add_object(&mut self, name: impl Into<String>, id: ObjectId) {
275        self.objects.push(id);
276        self.objects_by_name.entry(name.into()).or_insert(id);
277    }
278
279    pub(crate) fn add_type(&mut self, name: impl Into<String>, id: TypeId) {
280        self.types.push(id);
281        self.types_by_name.entry(name.into()).or_insert(id);
282    }
283
284    pub(crate) fn add_notification(&mut self, name: impl Into<String>, id: NotificationId) {
285        self.notifications.push(id);
286        self.notifications_by_name.entry(name.into()).or_insert(id);
287    }
288
289    pub(crate) fn add_group(&mut self, name: impl Into<String>, id: GroupId) {
290        self.groups.push(id);
291        self.groups_by_name.entry(name.into()).or_insert(id);
292    }
293
294    pub(crate) fn add_compliance(&mut self, name: impl Into<String>, id: ComplianceId) {
295        self.compliances.push(id);
296        self.compliances_by_name.entry(name.into()).or_insert(id);
297    }
298
299    pub(crate) fn add_capability(&mut self, name: impl Into<String>, id: CapabilityId) {
300        self.capabilities.push(id);
301        self.capabilities_by_name.entry(name.into()).or_insert(id);
302    }
303
304    pub(crate) fn add_node(&mut self, name: impl Into<String>, id: NodeId) {
305        self.nodes.push(id);
306        self.nodes_by_name.entry(name.into()).or_insert(id);
307    }
308
309    /// Yield all definitions in this module as [`Symbol`] values.
310    ///
311    /// Entity-backed definitions (objects, types, notifications, groups,
312    /// compliances, capabilities) come first. Plain nodes (not attached to
313    /// any entity) are yielded last.
314    pub fn definitions(&self) -> impl Iterator<Item = Symbol> + '_ {
315        // Covered node IDs: nodes whose names also appear in an entity map.
316        let covered_node_ids: HashSet<NodeId> = self
317            .nodes_by_name
318            .iter()
319            .filter_map(|(name, &id)| {
320                (self.objects_by_name.contains_key(name)
321                    || self.notifications_by_name.contains_key(name)
322                    || self.groups_by_name.contains_key(name)
323                    || self.compliances_by_name.contains_key(name)
324                    || self.capabilities_by_name.contains_key(name))
325                .then_some(id)
326            })
327            .collect();
328
329        self.objects
330            .iter()
331            .map(|&id| Symbol::Object(id))
332            .chain(self.types.iter().map(|&id| Symbol::Type(id)))
333            .chain(
334                self.notifications
335                    .iter()
336                    .map(|&id| Symbol::Notification(id)),
337            )
338            .chain(self.groups.iter().map(|&id| Symbol::Group(id)))
339            .chain(self.compliances.iter().map(|&id| Symbol::Compliance(id)))
340            .chain(self.capabilities.iter().map(|&id| Symbol::Capability(id)))
341            .chain(
342                self.nodes
343                    .iter()
344                    .filter(move |id| !covered_node_ids.contains(id))
345                    .map(|&id| Symbol::Node(id)),
346            )
347    }
348}
349
350impl std::fmt::Debug for ModuleData {
351    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
352        f.debug_struct("ModuleData")
353            .field("name", &self.name)
354            .field("language", &self.language)
355            .field("is_base", &self.is_base)
356            .finish()
357    }
358}
359
360#[cfg(test)]
361mod tests {
362    use super::*;
363
364    #[test]
365    fn definitions_include_only_plain_nodes() {
366        let mut module = ModuleData::new("TEST-MIB".to_string());
367
368        let object_id = ObjectId::new(0);
369        let object_node = NodeId::new(10);
370        let plain_node = NodeId::new(11);
371
372        module.add_object("ifIndex", object_id);
373        module.add_node("ifIndex", object_node);
374        module.add_node("internet", plain_node);
375
376        let defs: Vec<_> = module.definitions().collect();
377
378        assert_eq!(defs.len(), 2);
379        assert_eq!(defs[0], Symbol::Object(object_id));
380        assert_eq!(defs[1], Symbol::Node(plain_node));
381    }
382
383    #[test]
384    fn definitions_keep_plain_nodes_on_type_name_collision() {
385        let mut module = ModuleData::new("TEST-MIB".to_string());
386
387        let type_id = TypeId::new(0);
388        let plain_node = NodeId::new(11);
389
390        module.add_type("DisplayString", type_id);
391        module.add_node("DisplayString", plain_node);
392
393        let defs: Vec<_> = module.definitions().collect();
394
395        assert_eq!(defs.len(), 2);
396        assert_eq!(defs[0], Symbol::Type(type_id));
397        assert_eq!(defs[1], Symbol::Node(plain_node));
398    }
399}