Skip to main content

mib_rs/mib/
handle.rs

1//! Lightweight borrowed handles for navigating the resolved MIB model.
2//!
3//! Each handle type ([`Node`], [`Object`], [`Type`], [`Module`], etc.) wraps
4//! an arena id together with a `&Mib` reference. Methods on handles return
5//! further handles, so you can navigate the model without touching arena ids
6//! directly.
7//!
8//! Handles are `Copy` and inexpensive to pass around. Two handles are equal
9//! when they point to the same arena slot in the same [`Mib`].
10
11use std::fmt;
12use std::marker::PhantomData;
13use std::ptr;
14
15use crate::types::{Access, BaseType, ByteOffset, Kind, Language, Span, Status};
16
17use super::capability::CapabilityData;
18use super::compliance::ComplianceData;
19use super::group::GroupData;
20use super::mib::Mib;
21use super::module::ModuleData;
22use super::node::NodeData;
23use super::notification::NotificationData;
24use super::object::ObjectData;
25use super::typedef::TypeData;
26use super::types::*;
27
28macro_rules! define_handle {
29    ($name:ident, $id:ident, $data:ident, $getter:ident) => {
30        #[derive(Clone, Copy)]
31        #[doc = concat!("Borrowed handle to a resolved [`", stringify!($data), "`].")]
32        ///
33        /// Wraps a [`Mib`] reference and an arena id. Handles are `Copy` and
34        /// cheap to pass around. Two handles are equal when they point to the
35        /// same arena slot in the same [`Mib`].
36        pub struct $name<'a> {
37            pub(crate) mib: &'a Mib,
38            pub(crate) id: $id,
39        }
40
41        impl<'a> $name<'a> {
42            pub(crate) fn new(mib: &'a Mib, id: $id) -> Self {
43                Self { mib, id }
44            }
45
46            pub(crate) fn data(self) -> &'a $data {
47                self.mib.$getter(self.id)
48            }
49
50            /// Return the arena ID for this handle.
51            ///
52            /// Use IDs when you need deduplication, storage in collections,
53            /// or to call [`RawMib`](super::RawMib) methods.
54            pub fn id(self) -> $id {
55                self.id
56            }
57        }
58
59        impl PartialEq for $name<'_> {
60            fn eq(&self, other: &Self) -> bool {
61                self.id == other.id && ptr::eq(self.mib, other.mib)
62            }
63        }
64
65        impl Eq for $name<'_> {}
66
67        impl fmt::Debug for $name<'_> {
68            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69                f.debug_struct(stringify!($name))
70                    .field("id", &self.id)
71                    .field("name", &self.data().name())
72                    .finish()
73            }
74        }
75    };
76}
77
78define_handle!(Module, ModuleId, ModuleData, module_data);
79define_handle!(Object, ObjectId, ObjectData, object_data);
80define_handle!(Type, TypeId, TypeData, type_data);
81define_handle!(
82    Notification,
83    NotificationId,
84    NotificationData,
85    notification_data
86);
87define_handle!(Group, GroupId, GroupData, group_data);
88define_handle!(Compliance, ComplianceId, ComplianceData, compliance_data);
89define_handle!(Capability, CapabilityId, CapabilityData, capability_data);
90
91/// Borrowed handle to a resolved node in the OID tree.
92///
93/// A node represents a single position in the OID hierarchy. It may be a
94/// plain structural node (e.g. `iso`, `org`) or carry an attached entity
95/// such as an [`Object`], [`Notification`], [`Group`], [`Compliance`], or
96/// [`Capability`].
97///
98/// Use [`Node::object`], [`Node::notification`], etc. to access attached
99/// entities, and [`Node::children`] or [`Node::subtree`] for tree traversal.
100#[derive(Clone, Copy)]
101pub struct Node<'a> {
102    pub(crate) mib: &'a Mib,
103    pub(crate) id: NodeId,
104}
105
106impl<'a> Node<'a> {
107    pub(crate) fn new(mib: &'a Mib, id: NodeId) -> Self {
108        Self { mib, id }
109    }
110
111    pub(crate) fn data(self) -> &'a NodeData {
112        self.mib.node_data(self.id)
113    }
114
115    /// Return the arena ID for this node.
116    pub fn id(self) -> NodeId {
117        self.id
118    }
119
120    /// Return the node's numeric OID arc relative to its parent.
121    pub fn arc(self) -> u32 {
122        self.data().arc()
123    }
124
125    /// Return the node's local symbolic name.
126    pub fn name(self) -> &'a str {
127        self.data().name()
128    }
129
130    /// Return the DESCRIPTION text for this node.
131    pub fn description(self) -> &'a str {
132        self.data().description()
133    }
134
135    /// Return the REFERENCE text for this node, or empty if absent.
136    pub fn reference(self) -> &'a str {
137        self.data().reference()
138    }
139
140    /// Return the status if set on this node.
141    pub fn status(self) -> Option<Status> {
142        self.data().status()
143    }
144
145    /// Return the node kind (scalar, table, internal, etc.).
146    pub fn kind(self) -> Kind {
147        self.data().kind()
148    }
149
150    /// Return the source span of this node's definition.
151    pub fn span(self) -> Span {
152        self.data().span()
153    }
154
155    /// Return the node's full numeric OID.
156    pub fn oid(self) -> &'a super::oid::Oid {
157        self.mib.tree().oid_of(self.id)
158    }
159
160    /// Return the parent node, or `None` for the synthetic root.
161    pub fn parent(self) -> Option<Node<'a>> {
162        self.data().parent().map(|id| Node::new(self.mib, id))
163    }
164
165    /// Return the owning module for this node, determined during OID
166    /// resolution. For the defining module of a specific entity, use the
167    /// entity's own `module()` accessor instead (e.g.,
168    /// `node.object().module()`).
169    pub fn module(self) -> Option<Module<'a>> {
170        self.mib
171            .effective_module(self.id)
172            .map(|id| Module::new(self.mib, id))
173    }
174
175    /// Return the object attached to this node, if any.
176    pub fn object(self) -> Option<Object<'a>> {
177        self.data().object().map(|id| Object::new(self.mib, id))
178    }
179
180    /// Return the notification attached to this node, if any.
181    pub fn notification(self) -> Option<Notification<'a>> {
182        self.data()
183            .notification()
184            .map(|id| Notification::new(self.mib, id))
185    }
186
187    /// Return the group attached to this node, if any.
188    pub fn group(self) -> Option<Group<'a>> {
189        self.data().group().map(|id| Group::new(self.mib, id))
190    }
191
192    /// Return the compliance statement attached to this node, if any.
193    pub fn compliance(self) -> Option<Compliance<'a>> {
194        self.data()
195            .compliance()
196            .map(|id| Compliance::new(self.mib, id))
197    }
198
199    /// Return the capabilities statement attached to this node, if any.
200    pub fn capability(self) -> Option<Capability<'a>> {
201        self.data()
202            .capability()
203            .map(|id| Capability::new(self.mib, id))
204    }
205
206    /// Iterate the node's direct children in arc order.
207    pub fn children(self) -> impl Iterator<Item = Node<'a>> + 'a {
208        self.data()
209            .children()
210            .values()
211            .copied()
212            .map(|id| Node::new(self.mib, id))
213    }
214
215    /// Iterate the full subtree rooted at this node in depth-first order.
216    pub fn subtree(self) -> impl Iterator<Item = Node<'a>> + 'a {
217        self.mib.subtree(self.id).map(|id| Node::new(self.mib, id))
218    }
219}
220
221impl PartialEq for Node<'_> {
222    fn eq(&self, other: &Self) -> bool {
223        self.id == other.id && ptr::eq(self.mib, other.mib)
224    }
225}
226
227impl Eq for Node<'_> {}
228
229impl fmt::Debug for Node<'_> {
230    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231        f.debug_struct("Node")
232            .field("id", &self.id)
233            .field("name", &self.data().name())
234            .field("kind", &self.data().kind())
235            .finish()
236    }
237}
238
239/// A resolved index component for a table row.
240///
241/// Indexes may be object-backed (e.g. `INDEX { ifIndex }`) or bare-type
242/// indexes (e.g. `INDEX { INTEGER }`). Obtained from
243/// [`Object::effective_indexes`]. For the underlying data, see
244/// [`IndexEntry`].
245#[derive(Clone, Copy)]
246pub struct Index<'a> {
247    mib: &'a Mib,
248    row_id: ObjectId,
249    entry: &'a IndexEntry,
250}
251
252impl<'a> Index<'a> {
253    fn new(mib: &'a Mib, row_id: ObjectId, entry: &'a IndexEntry) -> Self {
254        Self { mib, row_id, entry }
255    }
256
257    /// Return the row whose effective index list this entry belongs to.
258    pub fn row(self) -> Object<'a> {
259        Object::new(self.mib, self.row_id)
260    }
261
262    /// Return the referenced index object when the index is object-backed.
263    pub fn object(self) -> Option<Object<'a>> {
264        self.entry.object.map(|id| Object::new(self.mib, id))
265    }
266
267    /// Return the source identifier written in the `INDEX` clause.
268    ///
269    /// For object-backed indexes this is the object name. For bare-type indexes
270    /// this is the type name as written in the clause.
271    pub fn name(self) -> &'a str {
272        &self.entry.name
273    }
274
275    /// Return the resolved type for this index component when available.
276    pub fn ty(self) -> Option<Type<'a>> {
277        self.entry.type_id.map(|id| Type::new(self.mib, id))
278    }
279
280    /// Return `true` if this index uses the IMPLIED keyword.
281    pub fn implied(self) -> bool {
282        self.entry.implied
283    }
284
285    /// Return the derived index encoding strategy.
286    pub fn encoding(self) -> crate::types::IndexEncoding {
287        self.entry.encoding
288    }
289
290    /// Return the fixed encoding width for this index entry, if determinable.
291    ///
292    /// Returns the width in sub-identifiers and `true` for fixed-width
293    /// encodings (integer = 1, IP address = 4, fixed-size string = SIZE value).
294    /// Returns `(0, false)` for variable-length or unknown encodings.
295    pub fn fixed_size(self) -> (usize, bool) {
296        match self.entry.encoding {
297            crate::types::IndexEncoding::Integer => (1, true),
298            crate::types::IndexEncoding::IpAddress => (4, true),
299            crate::types::IndexEncoding::FixedString => {
300                if let Some(obj) = self.object() {
301                    let sizes = obj.effective_sizes();
302                    if super::types::is_fixed_size(sizes) {
303                        return (sizes[0].min.as_u64().unwrap_or(0) as usize, true);
304                    }
305                }
306                (0, false)
307            }
308            _ => (0, false),
309        }
310    }
311
312    /// Return the source span of this index component.
313    pub fn span(self) -> Span {
314        self.entry.span
315    }
316
317    /// Return the underlying raw index entry.
318    ///
319    /// Most callers should prefer the typed accessors on [`Index`] directly.
320    pub fn entry(self) -> &'a IndexEntry {
321        self.entry
322    }
323}
324
325impl<'a> Module<'a> {
326    /// Return the module name.
327    pub fn name(self) -> &'a str {
328        self.data().name()
329    }
330
331    /// Return the detected SMI language version.
332    ///
333    /// Returns [`Language::Unknown`] when the available syntax and imports
334    /// provide insufficient or conflicting version evidence.
335    pub fn language(self) -> Language {
336        self.data().language()
337    }
338
339    /// Return the file path this module was loaded from.
340    pub fn source_path(self) -> &'a str {
341        self.data().source_path()
342    }
343
344    /// Return the ORGANIZATION clause text from MODULE-IDENTITY.
345    pub fn organization(self) -> &'a str {
346        self.data().organization()
347    }
348
349    /// Return the CONTACT-INFO clause text from MODULE-IDENTITY.
350    pub fn contact_info(self) -> &'a str {
351        self.data().contact_info()
352    }
353
354    /// Return the DESCRIPTION clause text from MODULE-IDENTITY.
355    pub fn description(self) -> &'a str {
356        self.data().description()
357    }
358
359    /// Return the LAST-UPDATED timestamp string from MODULE-IDENTITY.
360    pub fn last_updated(self) -> &'a str {
361        self.data().last_updated()
362    }
363
364    /// Return the REVISION entries from MODULE-IDENTITY.
365    pub fn revisions(self) -> &'a [super::types::Revision] {
366        self.data().revisions()
367    }
368
369    /// Return the IMPORTS declarations.
370    pub fn imports(self) -> &'a [super::types::Import] {
371        self.data().imports()
372    }
373
374    /// Return `true` if this is a synthetic base module (SNMPv2-SMI, etc.).
375    ///
376    /// Base modules define the SMI language itself and are constructed
377    /// programmatically rather than parsed from files. They have no real
378    /// source text, so spans are [`Span::SYNTHETIC`](crate::types::Span::SYNTHETIC)
379    /// and `source_path()` returns an empty string. See the crate-level
380    /// docs for the full list of base modules and their contents.
381    pub fn is_base(self) -> bool {
382        self.data().is_base()
383    }
384
385    /// Return the module's registered OID from its MODULE-IDENTITY, if any.
386    pub fn oid(self) -> Option<&'a super::oid::Oid> {
387        self.data().oid()
388    }
389
390    /// Convert a byte offset within this module's source to a line and column number.
391    pub fn line_col(self, offset: ByteOffset) -> (usize, usize) {
392        self.data().line_col(offset)
393    }
394
395    /// Return `true` if this module imports a symbol with the given name.
396    pub fn imports_symbol(self, name: &str) -> bool {
397        self.data().imports_symbol(name)
398    }
399
400    /// Return the resolved source module for an imported name.
401    pub fn import_source(self, name: &str) -> Option<Module<'a>> {
402        self.data()
403            .import_source(name)
404            .map(|id| Module::new(self.mib, id))
405    }
406
407    /// Look up an object defined by this module.
408    pub fn object(self, name: &str) -> Option<Object<'a>> {
409        self.data()
410            .object_by_name(name)
411            .map(|id| Object::new(self.mib, id))
412    }
413
414    /// Look up a type defined by this module.
415    pub fn r#type(self, name: &str) -> Option<Type<'a>> {
416        self.data()
417            .type_by_name(name)
418            .map(|id| Type::new(self.mib, id))
419    }
420
421    /// Look up any node defined by this module.
422    pub fn node(self, name: &str) -> Option<Node<'a>> {
423        self.data()
424            .node_by_name(name)
425            .map(|id| Node::new(self.mib, id))
426    }
427
428    /// Look up a notification defined by this module.
429    pub fn notification(self, name: &str) -> Option<Notification<'a>> {
430        self.data()
431            .notification_by_name(name)
432            .map(|id| Notification::new(self.mib, id))
433    }
434
435    /// Look up a group defined by this module.
436    pub fn group(self, name: &str) -> Option<Group<'a>> {
437        self.data()
438            .group_by_name(name)
439            .map(|id| Group::new(self.mib, id))
440    }
441
442    /// Look up a compliance statement defined by this module.
443    pub fn compliance(self, name: &str) -> Option<Compliance<'a>> {
444        self.data()
445            .compliance_by_name(name)
446            .map(|id| Compliance::new(self.mib, id))
447    }
448
449    /// Look up a capabilities statement defined by this module.
450    pub fn capability(self, name: &str) -> Option<Capability<'a>> {
451        self.data()
452            .capability_by_name(name)
453            .map(|id| Capability::new(self.mib, id))
454    }
455
456    /// Iterate objects defined by this module.
457    pub fn objects(self) -> impl Iterator<Item = Object<'a>> + 'a {
458        self.data()
459            .objects()
460            .iter()
461            .copied()
462            .map(|id| Object::new(self.mib, id))
463    }
464
465    /// Iterate types defined by this module.
466    pub fn types(self) -> impl Iterator<Item = Type<'a>> + 'a {
467        self.data()
468            .types()
469            .iter()
470            .copied()
471            .map(|id| Type::new(self.mib, id))
472    }
473
474    /// Iterate nodes defined by this module.
475    pub fn nodes(self) -> impl Iterator<Item = Node<'a>> + 'a {
476        self.data()
477            .nodes()
478            .iter()
479            .copied()
480            .map(|id| Node::new(self.mib, id))
481    }
482}
483
484impl<'a> Object<'a> {
485    /// Return the object name.
486    pub fn name(self) -> &'a str {
487        self.data().name()
488    }
489
490    /// Return the source span of this object definition.
491    pub fn span(self) -> Span {
492        self.data().span()
493    }
494
495    /// Return the module that defines this object.
496    pub fn module(self) -> Option<Module<'a>> {
497        self.data().module().map(|id| Module::new(self.mib, id))
498    }
499
500    /// Return the OID tree node for this object.
501    ///
502    /// # Panics
503    ///
504    /// Panics if the object's OID was not resolved during loading. This should
505    /// not happen for objects obtained from a fully resolved [`Mib`].
506    pub fn node(self) -> Node<'a> {
507        Node::new(
508            self.mib,
509            self.data().node().expect("resolved object missing node"),
510        )
511    }
512
513    /// Return the status (current, deprecated, obsolete).
514    pub fn status(self) -> Status {
515        self.data().status()
516    }
517
518    /// Return the DESCRIPTION clause text.
519    pub fn description(self) -> &'a str {
520        self.data().description()
521    }
522
523    /// Return the REFERENCE clause text, or empty if absent.
524    pub fn reference(self) -> &'a str {
525        self.data().reference()
526    }
527
528    /// Return the resolved type of this object, if it has one.
529    pub fn ty(self) -> Option<Type<'a>> {
530        self.data().type_id().map(|id| Type::new(self.mib, id))
531    }
532
533    /// Return the access level (read-only, read-write, etc.).
534    pub fn access(self) -> Access {
535        self.data().access()
536    }
537
538    /// Return the UNITS clause text, or empty if absent.
539    pub fn units(self) -> &'a str {
540        self.data().units()
541    }
542
543    /// Return the DEFVAL clause, if present.
544    pub fn default_value(self) -> Option<&'a DefVal> {
545        self.data().default_value()
546    }
547
548    /// Return the node kind (scalar, table, row, column).
549    pub fn kind(self) -> Kind {
550        self.data().kind(self.mib.tree())
551    }
552
553    /// Return the effective display hint from the type chain.
554    pub fn effective_display_hint(self) -> &'a str {
555        self.data().effective_display_hint()
556    }
557
558    /// Return SIZE constraints declared directly on this object.
559    pub fn sizes(self) -> &'a [Range] {
560        self.data().sizes()
561    }
562
563    /// Return SIZE constraints declared directly on this object.
564    pub fn declared_sizes(self) -> &'a [Range] {
565        self.data().declared_sizes()
566    }
567
568    /// Return the effective SIZE constraints from the type chain.
569    pub fn effective_sizes(self) -> &'a [Range] {
570        self.data().effective_sizes()
571    }
572
573    /// Return whether this object has an effective SIZE constraint.
574    pub fn effective_sizes_constrained(self) -> bool {
575        self.data().effective_sizes_constrained()
576    }
577
578    /// Return value range constraints declared directly on this object.
579    pub fn ranges(self) -> &'a [Range] {
580        self.data().ranges()
581    }
582
583    /// Return value range constraints declared directly on this object.
584    pub fn declared_ranges(self) -> &'a [Range] {
585        self.data().declared_ranges()
586    }
587
588    /// Return the effective range constraints from the type chain.
589    pub fn effective_ranges(self) -> &'a [Range] {
590        self.data().effective_ranges()
591    }
592
593    /// Return whether this object has an effective value range constraint.
594    pub fn effective_ranges_constrained(self) -> bool {
595        self.data().effective_ranges_constrained()
596    }
597
598    /// Return the effective enumeration values from the type chain.
599    pub fn effective_enums(self) -> &'a [NamedValue] {
600        self.data().effective_enums()
601    }
602
603    /// Return the effective BITS definitions from the type chain.
604    pub fn effective_bits(self) -> &'a [NamedValue] {
605        self.data().effective_bits()
606    }
607
608    /// Parse and validate this object's effective DISPLAY-HINT, returning a
609    /// structured [`DisplayHint`](super::display_hint::DisplayHint).
610    ///
611    /// Returns `None` if the object has no display hint or the hint is
612    /// malformed.
613    pub fn parsed_display_hint(self) -> Option<super::display_hint::DisplayHint> {
614        let hint = self.data().effective_display_hint();
615        if hint.is_empty() {
616            return None;
617        }
618        super::display_hint::DisplayHint::parse(hint)
619    }
620
621    /// Format an integer value using this object's effective DISPLAY-HINT.
622    ///
623    /// Returns `None` if the object has no display hint or the hint is
624    /// not a valid integer hint.
625    pub fn format_integer(
626        self,
627        value: i64,
628        hex_case: super::display_hint::HexCase,
629    ) -> Option<String> {
630        let hint = self.data().effective_display_hint();
631        if hint.is_empty() {
632            return None;
633        }
634        super::display_hint::format_integer(hint, value, hex_case)
635    }
636
637    /// Apply this object's DISPLAY-HINT as numeric scaling, returning `f64`.
638    ///
639    /// Only `d` and `d-N` hints produce a result (e.g. `d-2` on 1234
640    /// returns 12.34). Returns `None` if the hint is absent, non-decimal,
641    /// or malformed.
642    pub fn scale_integer(self, value: i64) -> Option<f64> {
643        let hint = self.data().effective_display_hint();
644        if hint.is_empty() {
645            return None;
646        }
647        super::display_hint::scale_integer(hint, value)
648    }
649
650    /// Format an octet string using this object's effective DISPLAY-HINT.
651    ///
652    /// Hexadecimal segments produce exactly two digits per byte, including
653    /// leading zeroes, and support segment lengths above eight bytes.
654    ///
655    /// Returns `None` if the object has no display hint, the hint is
656    /// malformed, or the data is empty.
657    pub fn format_octets(
658        self,
659        data: &[u8],
660        hex_case: super::display_hint::HexCase,
661    ) -> Option<String> {
662        let hint = self.data().effective_display_hint();
663        if hint.is_empty() {
664            return None;
665        }
666        super::display_hint::format_octets(hint, data, hex_case)
667    }
668
669    /// Return the containing table for a table, row, or column.
670    ///
671    /// Scalars return `None`.
672    pub fn table(self) -> Option<Object<'a>> {
673        self.mib
674            .object_table(self.id)
675            .map(|id| Object::new(self.mib, id))
676    }
677
678    /// Return the associated row for a table, row, or column.
679    ///
680    /// For tables this returns the child row entry. For rows it returns the row
681    /// itself. For columns it returns the parent row. Scalars return `None`.
682    pub fn row(self) -> Option<Object<'a>> {
683        self.mib
684            .object_row(self.id)
685            .map(|id| Object::new(self.mib, id))
686    }
687
688    /// Iterate the columns belonging to this table or row.
689    ///
690    /// Scalars and standalone objects yield an empty iterator.
691    pub fn columns(self) -> impl Iterator<Item = Object<'a>> + 'a {
692        self.mib
693            .object_columns(self.id)
694            .into_iter()
695            .map(|id| Object::new(self.mib, id))
696    }
697
698    /// Return the object this row augments, if any.
699    pub fn augments(self) -> Option<Object<'a>> {
700        self.data().augments().map(|id| Object::new(self.mib, id))
701    }
702
703    /// Iterate rows that augment this row.
704    pub fn augmented_by(self) -> impl Iterator<Item = Object<'a>> + 'a {
705        self.data()
706            .augmented_by()
707            .iter()
708            .copied()
709            .map(|id| Object::new(self.mib, id))
710    }
711
712    /// Return the raw INDEX entries from this object's definition.
713    ///
714    /// Only non-empty for row objects that define an INDEX clause directly.
715    /// For augmented or inherited indexes, use [`effective_indexes`](Self::effective_indexes).
716    pub fn index(self) -> impl Iterator<Item = Index<'a>> + 'a {
717        self.data()
718            .index()
719            .iter()
720            .map(move |entry| Index::new(self.mib, self.id, entry))
721    }
722
723    /// Iterate the effective indexes for this row, column, or augmented row.
724    ///
725    /// For columns, delegates to the parent row. For rows that use
726    /// `AUGMENTS`, follows the augment chain to the source row that owns
727    /// the effective `INDEX` clause.
728    pub fn effective_indexes(self) -> impl Iterator<Item = Index<'a>> + 'a {
729        self.mib
730            .effective_indexes_source(self.id)
731            .into_iter()
732            .flat_map(move |id| {
733                self.mib
734                    .object_data(id)
735                    .index()
736                    .iter()
737                    .map(move |entry| Index::new(self.mib, self.id, entry))
738            })
739    }
740
741    /// Return `true` if this object is a table.
742    pub fn is_table(self) -> bool {
743        self.mib.is_table(self.id)
744    }
745
746    /// Return `true` if this object is a table row.
747    pub fn is_row(self) -> bool {
748        self.mib.is_row(self.id)
749    }
750
751    /// Return `true` if this object is a table column.
752    pub fn is_column(self) -> bool {
753        self.mib.is_column(self.id)
754    }
755
756    /// Return `true` if this object is a scalar.
757    pub fn is_scalar(self) -> bool {
758        self.mib.is_scalar(self.id)
759    }
760
761    /// Return `true` if this object appears in its row's effective index list.
762    pub fn is_index(self) -> bool {
763        self.mib.is_index(self.id)
764    }
765}
766
767impl<'a> Type<'a> {
768    /// Return the type name.
769    pub fn name(self) -> &'a str {
770        self.data().name()
771    }
772
773    /// Return the source span of this type definition.
774    pub fn span(self) -> Span {
775        self.data().span()
776    }
777
778    /// Return the source span of the SYNTAX clause.
779    pub fn syntax_span(self) -> Span {
780        self.data().syntax_span()
781    }
782
783    /// Return the module that defines this type.
784    pub fn module(self) -> Option<Module<'a>> {
785        self.data().module().map(|id| Module::new(self.mib, id))
786    }
787
788    /// Return the directly assigned base type.
789    pub fn base(self) -> BaseType {
790        self.data().base()
791    }
792
793    /// Return the immediate parent type, if this is a derived type.
794    pub fn parent(self) -> Option<Type<'a>> {
795        self.data().parent().map(|id| Type::new(self.mib, id))
796    }
797
798    /// Return the status (current, deprecated, obsolete).
799    pub fn status(self) -> Status {
800        self.data().status()
801    }
802
803    /// Return this type's own DISPLAY-HINT, or empty if absent.
804    pub fn display_hint(self) -> &'a str {
805        self.data().display_hint()
806    }
807
808    /// Return the DESCRIPTION clause text.
809    pub fn description(self) -> &'a str {
810        self.data().description()
811    }
812
813    /// Return the REFERENCE clause text, or empty if absent.
814    pub fn reference(self) -> &'a str {
815        self.data().reference()
816    }
817
818    /// Return this type's own SIZE constraints (not inherited).
819    pub fn sizes(self) -> &'a [Range] {
820        self.data().sizes()
821    }
822
823    /// Return this type's own range constraints (not inherited).
824    pub fn ranges(self) -> &'a [Range] {
825        self.data().ranges()
826    }
827
828    /// Return this type's own enumeration values (not inherited).
829    pub fn enums(self) -> &'a [NamedValue] {
830        self.data().enums()
831    }
832
833    /// Return this type's own BITS definitions (not inherited).
834    pub fn bits(self) -> &'a [NamedValue] {
835        self.data().bits()
836    }
837
838    /// Return `true` if this type was defined as a TEXTUAL-CONVENTION.
839    pub fn is_textual_convention(self) -> bool {
840        self.data().is_textual_convention()
841    }
842
843    /// Walk the parent type chain and return the first type that is a
844    /// TEXTUAL-CONVENTION, or `None` if no type in the chain is a TC.
845    pub fn effective_tc(self) -> Option<Type<'a>> {
846        if self.data().is_textual_convention() {
847            return Some(self);
848        }
849        self.data()
850            .effective_tc_in_parents(self.mib.types_slice())
851            .map(|id| Type::new(self.mib, id))
852    }
853
854    /// Return the effective [`BaseType`] after following the parent type chain.
855    ///
856    /// Returns the first non-[`Unknown`](BaseType::Unknown) base type
857    /// encountered when walking from this type toward the root of the chain.
858    pub fn effective_base(self) -> BaseType {
859        self.data().effective_base(self.mib.types_slice())
860    }
861
862    /// Return the effective display hint after following parent type chains.
863    pub fn effective_display_hint(self) -> &'a str {
864        self.data().effective_display_hint(self.mib.types_slice())
865    }
866
867    /// Parse and validate the effective display hint, returning a structured
868    /// [`DisplayHint`](super::display_hint::DisplayHint).
869    ///
870    /// Returns `None` if there is no display hint in the type chain or the
871    /// hint is malformed.
872    pub fn parsed_display_hint(self) -> Option<super::display_hint::DisplayHint> {
873        let hint = self.data().effective_display_hint(self.mib.types_slice());
874        if hint.is_empty() {
875            return None;
876        }
877        super::display_hint::DisplayHint::parse(hint)
878    }
879
880    /// Return the effective SIZE constraints from the type chain.
881    pub fn effective_sizes(self) -> &'a [Range] {
882        self.data().effective_sizes(self.mib.types_slice())
883    }
884
885    /// Return whether this type has an effective SIZE constraint.
886    pub fn effective_sizes_constrained(self) -> bool {
887        self.data().effective_sizes_constrained()
888    }
889
890    /// Return the effective range constraints from the type chain.
891    pub fn effective_ranges(self) -> &'a [Range] {
892        self.data().effective_ranges(self.mib.types_slice())
893    }
894
895    /// Return whether this type has an effective value range constraint.
896    pub fn effective_ranges_constrained(self) -> bool {
897        self.data().effective_ranges_constrained()
898    }
899
900    /// Return the effective enumeration values from the type chain.
901    pub fn effective_enums(self) -> &'a [NamedValue] {
902        self.data().effective_enums(self.mib.types_slice())
903    }
904
905    /// Return the effective BITS definitions from the type chain.
906    pub fn effective_bits(self) -> &'a [NamedValue] {
907        self.data().effective_bits(self.mib.types_slice())
908    }
909
910    /// Return `true` if the effective base type is Counter32 or Counter64.
911    pub fn is_counter(self) -> bool {
912        self.data().is_counter(self.mib.types_slice())
913    }
914
915    /// Return `true` if the effective base type is Gauge32.
916    pub fn is_gauge(self) -> bool {
917        self.data().is_gauge(self.mib.types_slice())
918    }
919
920    /// Return `true` if the effective base type is OCTET STRING.
921    pub fn is_string(self) -> bool {
922        self.data().is_string(self.mib.types_slice())
923    }
924
925    /// Return `true` if this is an Integer32 type with enumeration values.
926    pub fn is_enumeration(self) -> bool {
927        self.data().is_enumeration(self.mib.types_slice())
928    }
929
930    /// Return `true` if this type has BITS definitions.
931    pub fn is_bits(self) -> bool {
932        self.data().is_bits(self.mib.types_slice())
933    }
934}
935
936macro_rules! entity_handle_impl {
937    ($name:ident) => {
938        impl<'a> $name<'a> {
939            /// Return the definition name.
940            pub fn name(self) -> &'a str {
941                self.data().name()
942            }
943
944            /// Return the source span.
945            pub fn span(self) -> Span {
946                self.data().span()
947            }
948
949            /// Return the defining module.
950            pub fn module(self) -> Option<Module<'a>> {
951                self.data().module().map(|id| Module::new(self.mib, id))
952            }
953
954            /// Return the OID tree node, if resolved.
955            pub fn node(self) -> Option<Node<'a>> {
956                self.data().node().map(|id| Node::new(self.mib, id))
957            }
958
959            /// Return the status.
960            pub fn status(self) -> Status {
961                self.data().status()
962            }
963
964            /// Return the DESCRIPTION clause text.
965            pub fn description(self) -> &'a str {
966                self.data().description()
967            }
968
969            /// Return the REFERENCE clause text.
970            pub fn reference(self) -> &'a str {
971                self.data().reference()
972            }
973
974            /// Return the symbolic OID references from the definition.
975            pub fn oid_refs(self) -> &'a [OidRef] {
976                self.data().oid_refs()
977            }
978        }
979    };
980}
981
982entity_handle_impl!(Notification);
983entity_handle_impl!(Group);
984entity_handle_impl!(Compliance);
985entity_handle_impl!(Capability);
986
987impl<'a> Notification<'a> {
988    /// Iterate the OBJECTS clause entries.
989    pub fn objects(self) -> impl Iterator<Item = Object<'a>> + 'a {
990        self.data()
991            .objects()
992            .iter()
993            .copied()
994            .map(|id| Object::new(self.mib, id))
995    }
996
997    /// Return SMIv1 TRAP-TYPE fields (enterprise, trap number), if this is a trap.
998    pub fn trap_info(self) -> Option<&'a TrapInfo> {
999        self.data().trap_info()
1000    }
1001}
1002
1003impl<'a> Group<'a> {
1004    /// Iterate the group's member nodes.
1005    pub fn members(self) -> impl Iterator<Item = Node<'a>> + 'a {
1006        self.data()
1007            .members()
1008            .iter()
1009            .copied()
1010            .map(|id| Node::new(self.mib, id))
1011    }
1012
1013    /// Return `true` if this is a NOTIFICATION-GROUP (vs OBJECT-GROUP).
1014    pub fn is_notification_group(self) -> bool {
1015        self.data().is_notification_group()
1016    }
1017}
1018
1019impl<'a> Compliance<'a> {
1020    /// Return the MODULE clauses in this compliance statement.
1021    pub fn modules(self) -> &'a [ComplianceModule] {
1022        self.data().modules()
1023    }
1024}
1025
1026impl<'a> Capability<'a> {
1027    /// Return the PRODUCT-RELEASE string.
1028    pub fn product_release(self) -> &'a str {
1029        self.data().product_release()
1030    }
1031
1032    /// Return the SUPPORTS clauses.
1033    pub fn supports(self) -> &'a [CapabilitiesModule] {
1034        self.data().supports()
1035    }
1036}
1037
1038/// Iterator adapter that converts arena id iteration into borrowed handles.
1039///
1040/// Returned by collection methods on [`Mib`] such as [`Mib::modules`],
1041/// [`Mib::objects`], [`Mib::types`], and [`Mib::nodes`]. Implements
1042/// [`Iterator`] for the corresponding handle type.
1043pub struct HandleIter<'a, H, I> {
1044    mib: &'a Mib,
1045    ids: I,
1046    _marker: PhantomData<H>,
1047}
1048
1049impl<'a, H, I> HandleIter<'a, H, I> {
1050    pub(crate) fn new(mib: &'a Mib, ids: I) -> Self {
1051        Self {
1052            mib,
1053            ids,
1054            _marker: PhantomData,
1055        }
1056    }
1057}
1058
1059impl<'a, I> Iterator for HandleIter<'a, Module<'a>, I>
1060where
1061    I: Iterator<Item = ModuleId>,
1062{
1063    type Item = Module<'a>;
1064
1065    fn next(&mut self) -> Option<Self::Item> {
1066        self.ids.next().map(|id| Module::new(self.mib, id))
1067    }
1068}
1069
1070impl<'a, I> Iterator for HandleIter<'a, Object<'a>, I>
1071where
1072    I: Iterator<Item = ObjectId>,
1073{
1074    type Item = Object<'a>;
1075
1076    fn next(&mut self) -> Option<Self::Item> {
1077        self.ids.next().map(|id| Object::new(self.mib, id))
1078    }
1079}
1080
1081impl<'a, I> Iterator for HandleIter<'a, Type<'a>, I>
1082where
1083    I: Iterator<Item = TypeId>,
1084{
1085    type Item = Type<'a>;
1086
1087    fn next(&mut self) -> Option<Self::Item> {
1088        self.ids.next().map(|id| Type::new(self.mib, id))
1089    }
1090}
1091
1092impl<'a, I> Iterator for HandleIter<'a, Node<'a>, I>
1093where
1094    I: Iterator<Item = NodeId>,
1095{
1096    type Item = Node<'a>;
1097
1098    fn next(&mut self) -> Option<Self::Item> {
1099        self.ids.next().map(|id| Node::new(self.mib, id))
1100    }
1101}
1102
1103impl<'a, I> Iterator for HandleIter<'a, Notification<'a>, I>
1104where
1105    I: Iterator<Item = NotificationId>,
1106{
1107    type Item = Notification<'a>;
1108
1109    fn next(&mut self) -> Option<Self::Item> {
1110        self.ids.next().map(|id| Notification::new(self.mib, id))
1111    }
1112}
1113
1114impl<'a, I> Iterator for HandleIter<'a, Group<'a>, I>
1115where
1116    I: Iterator<Item = GroupId>,
1117{
1118    type Item = Group<'a>;
1119
1120    fn next(&mut self) -> Option<Self::Item> {
1121        self.ids.next().map(|id| Group::new(self.mib, id))
1122    }
1123}
1124
1125impl<'a, I> Iterator for HandleIter<'a, Compliance<'a>, I>
1126where
1127    I: Iterator<Item = ComplianceId>,
1128{
1129    type Item = Compliance<'a>;
1130
1131    fn next(&mut self) -> Option<Self::Item> {
1132        self.ids.next().map(|id| Compliance::new(self.mib, id))
1133    }
1134}
1135
1136impl<'a, I> Iterator for HandleIter<'a, Capability<'a>, I>
1137where
1138    I: Iterator<Item = CapabilityId>,
1139{
1140    type Item = Capability<'a>;
1141
1142    fn next(&mut self) -> Option<Self::Item> {
1143        self.ids.next().map(|id| Capability::new(self.mib, id))
1144    }
1145}