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::source::{SourceId, SourceRange};
13use crate::types::{Language, Status};
14
15use super::navigation::SemanticSpanIndex;
16use super::symbol::Symbol;
17use super::types::*;
18
19/// The declared kind of a module-scoped OID identity.
20///
21/// Unlike the global OID tree's winning [`Kind`](crate::Kind), this value is
22/// retained independently for every declaration, including aliases and OID
23/// collisions.
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25pub enum ModuleIdentityKind {
26    /// A `MODULE-IDENTITY` declaration.
27    ModuleIdentity,
28    /// An `OBJECT-IDENTITY` declaration.
29    ObjectIdentity,
30    /// A plain `OBJECT IDENTIFIER` value assignment.
31    ObjectIdentifier,
32}
33
34/// Exact module-scoped data for one resolved OID identity declaration.
35///
36/// Multiple records may have the same numeric OID. This preserves aliases and
37/// declarations that lost global OID-tree ownership to another module or to
38/// an object, group, notification, or conformance definition.
39#[derive(Debug, Clone)]
40pub struct ModuleIdentityData {
41    pub(crate) name: String,
42    pub(crate) kind: ModuleIdentityKind,
43    pub(crate) oid: Oid,
44    pub(crate) status: Option<Status>,
45    pub(crate) description: String,
46    pub(crate) reference: String,
47    pub(crate) last_updated: String,
48    pub(crate) organization: String,
49    pub(crate) contact_info: String,
50    pub(crate) revisions: Vec<Revision>,
51    pub(crate) oid_refs: Vec<OidRef>,
52    pub(crate) range: SourceRange,
53}
54
55impl ModuleIdentityData {
56    /// Return the name exactly as declared in this module.
57    pub fn name(&self) -> &str {
58        &self.name
59    }
60
61    /// Return the declaration kind independently of global OID ownership.
62    pub fn kind(&self) -> ModuleIdentityKind {
63        self.kind
64    }
65
66    /// Return the resolved numeric OID.
67    pub fn oid(&self) -> &Oid {
68        &self.oid
69    }
70
71    /// Return the declared status for an `OBJECT-IDENTITY`.
72    pub fn status(&self) -> Option<Status> {
73        self.status
74    }
75
76    /// Return the declared description text.
77    pub fn description(&self) -> &str {
78        &self.description
79    }
80
81    /// Return the declared reference text.
82    pub fn reference(&self) -> &str {
83        &self.reference
84    }
85
86    /// Return the `LAST-UPDATED` value for a `MODULE-IDENTITY`.
87    pub fn last_updated(&self) -> &str {
88        &self.last_updated
89    }
90
91    /// Return the `ORGANIZATION` text for a `MODULE-IDENTITY`.
92    pub fn organization(&self) -> &str {
93        &self.organization
94    }
95
96    /// Return the `CONTACT-INFO` text for a `MODULE-IDENTITY`.
97    pub fn contact_info(&self) -> &str {
98        &self.contact_info
99    }
100
101    /// Return the revisions for a `MODULE-IDENTITY` in declaration order.
102    pub fn revisions(&self) -> &[Revision] {
103        &self.revisions
104    }
105
106    /// Return symbolic references with exact resolved module/version provenance.
107    pub fn oid_refs(&self) -> &[OidRef] {
108        &self.oid_refs
109    }
110
111    /// Return the exact symbolic parent reference used by this declaration.
112    pub fn declared_oid_parent(&self) -> Option<&OidRef> {
113        let parent = self.oid.parent()?;
114        self.oid_refs
115            .iter()
116            .rev()
117            .find(|reference| reference.oid() == Some(&parent))
118    }
119
120    /// Return the exact symbolic parent name, or an empty string when the
121    /// assignment used no exact symbolic parent.
122    pub fn declared_oid_parent_name(&self) -> &str {
123        self.declared_oid_parent()
124            .map_or("", |reference| reference.name.as_str())
125    }
126
127    /// Return the complete source range of the declaration.
128    pub fn range(&self) -> SourceRange {
129        self.range
130    }
131}
132
133/// A loaded and resolved MIB module.
134///
135/// Contains module-level metadata (organization, description, revisions),
136/// import declarations, and per-entity name indices. Access through the
137/// public accessor methods or the [`Module`](super::handle::Module) handle.
138pub struct ModuleData {
139    pub(crate) name: String,
140    pub(crate) language: Language,
141    pub(crate) source_id: Option<SourceId>,
142    pub(crate) is_base: bool,
143    pub(crate) oid: Option<Oid>,
144    pub(crate) organization: String,
145    pub(crate) contact_info: String,
146    pub(crate) description: String,
147    pub(crate) last_updated: String,
148    pub(crate) revisions: Vec<Revision>,
149    pub(crate) imports: Vec<Import>,
150    pub(crate) identities: Vec<ModuleIdentityData>,
151
152    pub(crate) objects: Vec<ObjectId>,
153    pub(crate) types: Vec<TypeId>,
154    pub(crate) notifications: Vec<NotificationId>,
155    pub(crate) groups: Vec<GroupId>,
156    pub(crate) compliances: Vec<ComplianceId>,
157    pub(crate) capabilities: Vec<CapabilityId>,
158    pub(crate) nodes: Vec<NodeId>,
159
160    pub(crate) used_import_names: HashSet<String>,
161    pub(crate) resolved_imports: HashMap<String, ModuleId>,
162    pub(crate) import_resolutions: HashMap<String, ImportResolution>,
163    pub(crate) semantic_spans: SemanticSpanIndex,
164
165    pub(crate) objects_by_name: HashMap<String, ObjectId>,
166    pub(crate) types_by_name: HashMap<String, TypeId>,
167    pub(crate) notifications_by_name: HashMap<String, NotificationId>,
168    pub(crate) groups_by_name: HashMap<String, GroupId>,
169    pub(crate) compliances_by_name: HashMap<String, ComplianceId>,
170    pub(crate) capabilities_by_name: HashMap<String, CapabilityId>,
171    pub(crate) nodes_by_name: HashMap<String, NodeId>,
172}
173
174impl ModuleData {
175    pub(crate) fn new(name: String) -> Self {
176        Self {
177            name,
178            language: Language::Unknown,
179            source_id: None,
180            is_base: false,
181            oid: None,
182            organization: String::new(),
183            contact_info: String::new(),
184            description: String::new(),
185            last_updated: String::new(),
186            revisions: Vec::new(),
187            imports: Vec::new(),
188            identities: Vec::new(),
189            objects: Vec::new(),
190            types: Vec::new(),
191            notifications: Vec::new(),
192            groups: Vec::new(),
193            compliances: Vec::new(),
194            capabilities: Vec::new(),
195            nodes: Vec::new(),
196            used_import_names: HashSet::new(),
197            resolved_imports: HashMap::new(),
198            import_resolutions: HashMap::new(),
199            semantic_spans: SemanticSpanIndex::default(),
200            objects_by_name: HashMap::new(),
201            types_by_name: HashMap::new(),
202            notifications_by_name: HashMap::new(),
203            groups_by_name: HashMap::new(),
204            compliances_by_name: HashMap::new(),
205            capabilities_by_name: HashMap::new(),
206            nodes_by_name: HashMap::new(),
207        }
208    }
209
210    /// Return the module name.
211    pub fn name(&self) -> &str {
212        &self.name
213    }
214
215    /// Return the SMI language version.
216    pub fn language(&self) -> Language {
217        self.language
218    }
219
220    /// Return the compilation-local source identity, if this module came from source text.
221    pub fn source_id(&self) -> Option<SourceId> {
222        self.source_id
223    }
224
225    /// Return `true` if this is an SMI foundation module.
226    ///
227    /// See [`Module::is_base`](super::Module::is_base) for details.
228    pub fn is_base(&self) -> bool {
229        self.is_base
230    }
231
232    /// Return the module's MODULE-IDENTITY OID, if any.
233    pub fn oid(&self) -> Option<&Oid> {
234        self.oid.as_ref()
235    }
236
237    /// Return the ORGANIZATION clause text.
238    pub fn organization(&self) -> &str {
239        &self.organization
240    }
241
242    /// Return the CONTACT-INFO clause text.
243    pub fn contact_info(&self) -> &str {
244        &self.contact_info
245    }
246
247    /// Return the DESCRIPTION clause text.
248    pub fn description(&self) -> &str {
249        &self.description
250    }
251
252    /// Return the LAST-UPDATED timestamp string.
253    pub fn last_updated(&self) -> &str {
254        &self.last_updated
255    }
256
257    /// Return the REVISION entries.
258    pub fn revisions(&self) -> &[Revision] {
259        &self.revisions
260    }
261
262    /// Return the IMPORTS declarations.
263    pub fn imports(&self) -> &[Import] {
264        &self.imports
265    }
266
267    /// Return exact module-scoped OID identity declarations.
268    ///
269    /// Records retain aliases and collisions independently of the global OID
270    /// tree's selected name, kind, metadata, and owning module.
271    pub fn identities(&self) -> &[ModuleIdentityData] {
272        &self.identities
273    }
274
275    /// Return the object ids defined by this module.
276    pub fn objects(&self) -> &[ObjectId] {
277        &self.objects
278    }
279
280    /// Return the type ids defined by this module.
281    pub fn types(&self) -> &[TypeId] {
282        &self.types
283    }
284
285    /// Return the notification ids defined by this module.
286    pub fn notifications(&self) -> &[NotificationId] {
287        &self.notifications
288    }
289
290    /// Return the group ids defined by this module.
291    pub fn groups(&self) -> &[GroupId] {
292        &self.groups
293    }
294
295    /// Return the compliance ids defined by this module.
296    pub fn compliances(&self) -> &[ComplianceId] {
297        &self.compliances
298    }
299
300    /// Return the capability ids defined by this module.
301    pub fn capabilities(&self) -> &[CapabilityId] {
302        &self.capabilities
303    }
304
305    /// Return the node ids defined by this module.
306    pub fn nodes(&self) -> &[NodeId] {
307        &self.nodes
308    }
309
310    /// Look up an object by name within this module.
311    pub fn object_by_name(&self, name: &str) -> Option<ObjectId> {
312        self.objects_by_name.get(name).copied()
313    }
314
315    /// Look up a type by name within this module.
316    pub fn type_by_name(&self, name: &str) -> Option<TypeId> {
317        self.types_by_name.get(name).copied()
318    }
319
320    /// Look up a notification by name within this module.
321    pub fn notification_by_name(&self, name: &str) -> Option<NotificationId> {
322        self.notifications_by_name.get(name).copied()
323    }
324
325    /// Look up a group by name within this module.
326    pub fn group_by_name(&self, name: &str) -> Option<GroupId> {
327        self.groups_by_name.get(name).copied()
328    }
329
330    /// Look up a compliance statement by name within this module.
331    pub fn compliance_by_name(&self, name: &str) -> Option<ComplianceId> {
332        self.compliances_by_name.get(name).copied()
333    }
334
335    /// Look up a capability statement by name within this module.
336    pub fn capability_by_name(&self, name: &str) -> Option<CapabilityId> {
337        self.capabilities_by_name.get(name).copied()
338    }
339
340    /// Look up a node by name within this module.
341    pub fn node_by_name(&self, name: &str) -> Option<NodeId> {
342        self.nodes_by_name.get(name).copied()
343    }
344
345    /// Look up a symbol by name. Priority: objects, types, notifications,
346    /// groups, compliances, capabilities, then plain nodes.
347    pub fn symbol(&self, name: &str) -> Option<Symbol> {
348        if let Some(&id) = self.objects_by_name.get(name) {
349            return Some(Symbol::Object(id));
350        }
351        if let Some(&id) = self.types_by_name.get(name) {
352            return Some(Symbol::Type(id));
353        }
354        if let Some(&id) = self.notifications_by_name.get(name) {
355            return Some(Symbol::Notification(id));
356        }
357        if let Some(&id) = self.groups_by_name.get(name) {
358            return Some(Symbol::Group(id));
359        }
360        if let Some(&id) = self.compliances_by_name.get(name) {
361            return Some(Symbol::Compliance(id));
362        }
363        if let Some(&id) = self.capabilities_by_name.get(name) {
364            return Some(Symbol::Capability(id));
365        }
366        if let Some(&id) = self.nodes_by_name.get(name) {
367            return Some(Symbol::Node(id));
368        }
369        None
370    }
371
372    /// Look up every distinct definition kind with this name in the module.
373    ///
374    /// An OID entity is returned instead of its attached plain node. A type
375    /// and an OID entity with the same name are both retained.
376    pub fn symbols(&self, name: &str) -> Vec<Symbol> {
377        let mut symbols = Vec::new();
378        if let Some(&id) = self.objects_by_name.get(name) {
379            symbols.push(Symbol::Object(id));
380        }
381        if let Some(&id) = self.notifications_by_name.get(name) {
382            symbols.push(Symbol::Notification(id));
383        }
384        if let Some(&id) = self.groups_by_name.get(name) {
385            symbols.push(Symbol::Group(id));
386        }
387        if let Some(&id) = self.compliances_by_name.get(name) {
388            symbols.push(Symbol::Compliance(id));
389        }
390        if let Some(&id) = self.capabilities_by_name.get(name) {
391            symbols.push(Symbol::Capability(id));
392        }
393        if symbols.is_empty()
394            && let Some(&id) = self.nodes_by_name.get(name)
395        {
396            symbols.push(Symbol::Node(id));
397        }
398        if let Some(&id) = self.types_by_name.get(name) {
399            symbols.push(Symbol::Type(id));
400        }
401        symbols
402    }
403
404    /// Return `true` if this module defines a symbol with the given name.
405    pub fn defines_symbol(&self, name: &str) -> bool {
406        self.symbol(name).is_some()
407    }
408
409    /// Return `true` if this module imports a symbol with the given name.
410    pub fn imports_symbol(&self, name: &str) -> bool {
411        self.imports
412            .iter()
413            .any(|imp| imp.symbols.iter().any(|s| s.name == name))
414    }
415
416    /// Return `true` if the named import was actually used during resolution.
417    pub fn is_import_used(&self, name: &str) -> bool {
418        self.used_import_names.contains(name)
419    }
420
421    /// Return the resolved source module for an imported name.
422    pub fn import_source(&self, name: &str) -> Option<ModuleId> {
423        self.resolved_imports.get(name).copied()
424    }
425
426    /// Return retained pre-collapse resolution provenance for an imported symbol.
427    pub fn import_resolution(&self, name: &str) -> Option<&ImportResolution> {
428        self.import_resolutions.get(name)
429    }
430
431    // Builder methods used during resolution.
432
433    pub(crate) fn add_object(&mut self, name: impl Into<String>, id: ObjectId) {
434        self.objects.push(id);
435        self.objects_by_name.entry(name.into()).or_insert(id);
436    }
437
438    pub(crate) fn add_type(&mut self, name: impl Into<String>, id: TypeId) {
439        self.types.push(id);
440        self.types_by_name.entry(name.into()).or_insert(id);
441    }
442
443    pub(crate) fn add_notification(&mut self, name: impl Into<String>, id: NotificationId) {
444        self.notifications.push(id);
445        self.notifications_by_name.entry(name.into()).or_insert(id);
446    }
447
448    pub(crate) fn add_group(&mut self, name: impl Into<String>, id: GroupId) {
449        self.groups.push(id);
450        self.groups_by_name.entry(name.into()).or_insert(id);
451    }
452
453    pub(crate) fn add_compliance(&mut self, name: impl Into<String>, id: ComplianceId) {
454        self.compliances.push(id);
455        self.compliances_by_name.entry(name.into()).or_insert(id);
456    }
457
458    pub(crate) fn add_capability(&mut self, name: impl Into<String>, id: CapabilityId) {
459        self.capabilities.push(id);
460        self.capabilities_by_name.entry(name.into()).or_insert(id);
461    }
462
463    pub(crate) fn add_node(&mut self, name: impl Into<String>, id: NodeId) {
464        self.nodes.push(id);
465        self.nodes_by_name.entry(name.into()).or_insert(id);
466    }
467
468    pub(crate) fn add_identity(&mut self, identity: ModuleIdentityData) {
469        self.identities.push(identity);
470    }
471
472    /// Yield all definitions in this module as [`Symbol`] values.
473    ///
474    /// Entity-backed definitions (objects, types, notifications, groups,
475    /// compliances, capabilities) come first. Plain nodes (not attached to
476    /// any entity) are yielded last.
477    pub fn definitions(&self) -> impl Iterator<Item = Symbol> + '_ {
478        // Covered node IDs: nodes whose names also appear in an entity map.
479        let covered_node_ids: HashSet<NodeId> = self
480            .nodes_by_name
481            .iter()
482            .filter_map(|(name, &id)| {
483                (self.objects_by_name.contains_key(name)
484                    || self.notifications_by_name.contains_key(name)
485                    || self.groups_by_name.contains_key(name)
486                    || self.compliances_by_name.contains_key(name)
487                    || self.capabilities_by_name.contains_key(name))
488                .then_some(id)
489            })
490            .collect();
491
492        self.objects
493            .iter()
494            .map(|&id| Symbol::Object(id))
495            .chain(self.types.iter().map(|&id| Symbol::Type(id)))
496            .chain(
497                self.notifications
498                    .iter()
499                    .map(|&id| Symbol::Notification(id)),
500            )
501            .chain(self.groups.iter().map(|&id| Symbol::Group(id)))
502            .chain(self.compliances.iter().map(|&id| Symbol::Compliance(id)))
503            .chain(self.capabilities.iter().map(|&id| Symbol::Capability(id)))
504            .chain(
505                self.nodes
506                    .iter()
507                    .filter(move |id| !covered_node_ids.contains(id))
508                    .map(|&id| Symbol::Node(id)),
509            )
510    }
511}
512
513impl std::fmt::Debug for ModuleData {
514    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
515        f.debug_struct("ModuleData")
516            .field("name", &self.name)
517            .field("language", &self.language)
518            .field("is_base", &self.is_base)
519            .finish()
520    }
521}
522
523#[cfg(test)]
524mod tests {
525    use super::*;
526
527    #[test]
528    fn definitions_include_only_plain_nodes() {
529        let mut module = ModuleData::new("TEST-MIB".to_string());
530
531        let object_id = ObjectId::new(0);
532        let object_node = NodeId::new(10);
533        let plain_node = NodeId::new(11);
534
535        module.add_object("ifIndex", object_id);
536        module.add_node("ifIndex", object_node);
537        module.add_node("internet", plain_node);
538
539        let defs: Vec<_> = module.definitions().collect();
540
541        assert_eq!(defs.len(), 2);
542        assert_eq!(defs[0], Symbol::Object(object_id));
543        assert_eq!(defs[1], Symbol::Node(plain_node));
544    }
545
546    #[test]
547    fn definitions_keep_plain_nodes_on_type_name_collision() {
548        let mut module = ModuleData::new("TEST-MIB".to_string());
549
550        let type_id = TypeId::new(0);
551        let plain_node = NodeId::new(11);
552
553        module.add_type("DisplayString", type_id);
554        module.add_node("DisplayString", plain_node);
555
556        let defs: Vec<_> = module.definitions().collect();
557
558        assert_eq!(defs.len(), 2);
559        assert_eq!(defs[0], Symbol::Type(type_id));
560        assert_eq!(defs[1], Symbol::Node(plain_node));
561    }
562}