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 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 SMI language version (SMIv1 or SMIv2).
332    pub fn language(self) -> Language {
333        self.data().language()
334    }
335
336    /// Return the file path this module was loaded from.
337    pub fn source_path(self) -> &'a str {
338        self.data().source_path()
339    }
340
341    /// Return the ORGANIZATION clause text from MODULE-IDENTITY.
342    pub fn organization(self) -> &'a str {
343        self.data().organization()
344    }
345
346    /// Return the CONTACT-INFO clause text from MODULE-IDENTITY.
347    pub fn contact_info(self) -> &'a str {
348        self.data().contact_info()
349    }
350
351    /// Return the DESCRIPTION clause text from MODULE-IDENTITY.
352    pub fn description(self) -> &'a str {
353        self.data().description()
354    }
355
356    /// Return the LAST-UPDATED timestamp string from MODULE-IDENTITY.
357    pub fn last_updated(self) -> &'a str {
358        self.data().last_updated()
359    }
360
361    /// Return the REVISION entries from MODULE-IDENTITY.
362    pub fn revisions(self) -> &'a [super::types::Revision] {
363        self.data().revisions()
364    }
365
366    /// Return the IMPORTS declarations.
367    pub fn imports(self) -> &'a [super::types::Import] {
368        self.data().imports()
369    }
370
371    /// Return `true` if this is a synthetic base module (SNMPv2-SMI, etc.).
372    ///
373    /// Base modules define the SMI language itself and are constructed
374    /// programmatically rather than parsed from files. They have no real
375    /// source text, so spans are [`Span::SYNTHETIC`](crate::types::Span::SYNTHETIC)
376    /// and `source_path()` returns an empty string. See the crate-level
377    /// docs for the full list of base modules and their contents.
378    pub fn is_base(self) -> bool {
379        self.data().is_base()
380    }
381
382    /// Return the module's registered OID from its MODULE-IDENTITY, if any.
383    pub fn oid(self) -> Option<&'a super::oid::Oid> {
384        self.data().oid()
385    }
386
387    /// Convert a byte offset within this module's source to a line and column number.
388    pub fn line_col(self, offset: ByteOffset) -> (usize, usize) {
389        self.data().line_col(offset)
390    }
391
392    /// Return `true` if this module imports a symbol with the given name.
393    pub fn imports_symbol(self, name: &str) -> bool {
394        self.data().imports_symbol(name)
395    }
396
397    /// Return the resolved source module for an imported name.
398    pub fn import_source(self, name: &str) -> Option<Module<'a>> {
399        self.data()
400            .import_source(name)
401            .map(|id| Module::new(self.mib, id))
402    }
403
404    /// Look up an object defined by this module.
405    pub fn object(self, name: &str) -> Option<Object<'a>> {
406        self.data()
407            .object_by_name(name)
408            .map(|id| Object::new(self.mib, id))
409    }
410
411    /// Look up a type defined by this module.
412    pub fn r#type(self, name: &str) -> Option<Type<'a>> {
413        self.data()
414            .type_by_name(name)
415            .map(|id| Type::new(self.mib, id))
416    }
417
418    /// Look up any node defined by this module.
419    pub fn node(self, name: &str) -> Option<Node<'a>> {
420        self.data()
421            .node_by_name(name)
422            .map(|id| Node::new(self.mib, id))
423    }
424
425    /// Look up a notification defined by this module.
426    pub fn notification(self, name: &str) -> Option<Notification<'a>> {
427        self.data()
428            .notification_by_name(name)
429            .map(|id| Notification::new(self.mib, id))
430    }
431
432    /// Look up a group defined by this module.
433    pub fn group(self, name: &str) -> Option<Group<'a>> {
434        self.data()
435            .group_by_name(name)
436            .map(|id| Group::new(self.mib, id))
437    }
438
439    /// Look up a compliance statement defined by this module.
440    pub fn compliance(self, name: &str) -> Option<Compliance<'a>> {
441        self.data()
442            .compliance_by_name(name)
443            .map(|id| Compliance::new(self.mib, id))
444    }
445
446    /// Look up a capabilities statement defined by this module.
447    pub fn capability(self, name: &str) -> Option<Capability<'a>> {
448        self.data()
449            .capability_by_name(name)
450            .map(|id| Capability::new(self.mib, id))
451    }
452
453    /// Iterate objects defined by this module.
454    pub fn objects(self) -> impl Iterator<Item = Object<'a>> + 'a {
455        self.data()
456            .objects()
457            .iter()
458            .copied()
459            .map(|id| Object::new(self.mib, id))
460    }
461
462    /// Iterate types defined by this module.
463    pub fn types(self) -> impl Iterator<Item = Type<'a>> + 'a {
464        self.data()
465            .types()
466            .iter()
467            .copied()
468            .map(|id| Type::new(self.mib, id))
469    }
470
471    /// Iterate nodes defined by this module.
472    pub fn nodes(self) -> impl Iterator<Item = Node<'a>> + 'a {
473        self.data()
474            .nodes()
475            .iter()
476            .copied()
477            .map(|id| Node::new(self.mib, id))
478    }
479}
480
481impl<'a> Object<'a> {
482    /// Return the object name.
483    pub fn name(self) -> &'a str {
484        self.data().name()
485    }
486
487    /// Return the source span of this object definition.
488    pub fn span(self) -> Span {
489        self.data().span()
490    }
491
492    /// Return the module that defines this object.
493    pub fn module(self) -> Option<Module<'a>> {
494        self.data().module().map(|id| Module::new(self.mib, id))
495    }
496
497    /// Return the OID tree node for this object.
498    ///
499    /// # Panics
500    ///
501    /// Panics if the object's OID was not resolved during loading. This should
502    /// not happen for objects obtained from a fully resolved [`Mib`].
503    pub fn node(self) -> Node<'a> {
504        Node::new(
505            self.mib,
506            self.data().node().expect("resolved object missing node"),
507        )
508    }
509
510    /// Return the status (current, deprecated, obsolete).
511    pub fn status(self) -> Status {
512        self.data().status()
513    }
514
515    /// Return the DESCRIPTION clause text.
516    pub fn description(self) -> &'a str {
517        self.data().description()
518    }
519
520    /// Return the REFERENCE clause text, or empty if absent.
521    pub fn reference(self) -> &'a str {
522        self.data().reference()
523    }
524
525    /// Return the resolved type of this object, if it has one.
526    pub fn ty(self) -> Option<Type<'a>> {
527        self.data().type_id().map(|id| Type::new(self.mib, id))
528    }
529
530    /// Return the access level (read-only, read-write, etc.).
531    pub fn access(self) -> Access {
532        self.data().access()
533    }
534
535    /// Return the UNITS clause text, or empty if absent.
536    pub fn units(self) -> &'a str {
537        self.data().units()
538    }
539
540    /// Return the DEFVAL clause, if present.
541    pub fn default_value(self) -> Option<&'a DefVal> {
542        self.data().default_value()
543    }
544
545    /// Return the node kind (scalar, table, row, column).
546    pub fn kind(self) -> Kind {
547        self.data().kind(self.mib.tree())
548    }
549
550    /// Return the effective display hint from the type chain.
551    pub fn effective_display_hint(self) -> &'a str {
552        self.data().effective_display_hint()
553    }
554
555    /// Return the effective SIZE constraints from the type chain.
556    pub fn effective_sizes(self) -> &'a [Range] {
557        self.data().effective_sizes()
558    }
559
560    /// Return the effective range constraints from the type chain.
561    pub fn effective_ranges(self) -> &'a [Range] {
562        self.data().effective_ranges()
563    }
564
565    /// Return the effective enumeration values from the type chain.
566    pub fn effective_enums(self) -> &'a [NamedValue] {
567        self.data().effective_enums()
568    }
569
570    /// Return the effective BITS definitions from the type chain.
571    pub fn effective_bits(self) -> &'a [NamedValue] {
572        self.data().effective_bits()
573    }
574
575    /// Parse and validate this object's effective DISPLAY-HINT, returning a
576    /// structured [`DisplayHint`](super::display_hint::DisplayHint).
577    ///
578    /// Returns `None` if the object has no display hint or the hint is
579    /// malformed.
580    pub fn parsed_display_hint(self) -> Option<super::display_hint::DisplayHint> {
581        let hint = self.data().effective_display_hint();
582        if hint.is_empty() {
583            return None;
584        }
585        super::display_hint::DisplayHint::parse(hint)
586    }
587
588    /// Format an integer value using this object's effective DISPLAY-HINT.
589    ///
590    /// Returns `None` if the object has no display hint or the hint is
591    /// not a valid integer hint.
592    pub fn format_integer(
593        self,
594        value: i64,
595        hex_case: super::display_hint::HexCase,
596    ) -> Option<String> {
597        let hint = self.data().effective_display_hint();
598        if hint.is_empty() {
599            return None;
600        }
601        super::display_hint::format_integer(hint, value, hex_case)
602    }
603
604    /// Apply this object's DISPLAY-HINT as numeric scaling, returning `f64`.
605    ///
606    /// Only `d` and `d-N` hints produce a result (e.g. `d-2` on 1234
607    /// returns 12.34). Returns `None` if the hint is absent, non-decimal,
608    /// or malformed.
609    pub fn scale_integer(self, value: i64) -> Option<f64> {
610        let hint = self.data().effective_display_hint();
611        if hint.is_empty() {
612            return None;
613        }
614        super::display_hint::scale_integer(hint, value)
615    }
616
617    /// Format an octet string using this object's effective DISPLAY-HINT.
618    ///
619    /// Returns `None` if the object has no display hint, the hint is
620    /// malformed, or the data is empty.
621    pub fn format_octets(
622        self,
623        data: &[u8],
624        hex_case: super::display_hint::HexCase,
625    ) -> Option<String> {
626        let hint = self.data().effective_display_hint();
627        if hint.is_empty() {
628            return None;
629        }
630        super::display_hint::format_octets(hint, data, hex_case)
631    }
632
633    /// Return the containing table for a table, row, or column.
634    ///
635    /// Scalars return `None`.
636    pub fn table(self) -> Option<Object<'a>> {
637        self.mib
638            .object_table(self.id)
639            .map(|id| Object::new(self.mib, id))
640    }
641
642    /// Return the associated row for a table, row, or column.
643    ///
644    /// For tables this returns the child row entry. For rows it returns the row
645    /// itself. For columns it returns the parent row. Scalars return `None`.
646    pub fn row(self) -> Option<Object<'a>> {
647        self.mib
648            .object_row(self.id)
649            .map(|id| Object::new(self.mib, id))
650    }
651
652    /// Iterate the columns belonging to this table or row.
653    ///
654    /// Scalars and standalone objects yield an empty iterator.
655    pub fn columns(self) -> impl Iterator<Item = Object<'a>> + 'a {
656        self.mib
657            .object_columns(self.id)
658            .into_iter()
659            .map(|id| Object::new(self.mib, id))
660    }
661
662    /// Return the object this row augments, if any.
663    pub fn augments(self) -> Option<Object<'a>> {
664        self.data().augments().map(|id| Object::new(self.mib, id))
665    }
666
667    /// Iterate rows that augment this row.
668    pub fn augmented_by(self) -> impl Iterator<Item = Object<'a>> + 'a {
669        self.data()
670            .augmented_by()
671            .iter()
672            .copied()
673            .map(|id| Object::new(self.mib, id))
674    }
675
676    /// Return the raw INDEX entries from this object's definition.
677    ///
678    /// Only non-empty for row objects that define an INDEX clause directly.
679    /// For augmented or inherited indexes, use [`effective_indexes`](Self::effective_indexes).
680    pub fn index(self) -> impl Iterator<Item = Index<'a>> + 'a {
681        self.data()
682            .index()
683            .iter()
684            .map(move |entry| Index::new(self.mib, self.id, entry))
685    }
686
687    /// Iterate the effective indexes for this row, column, or augmented row.
688    ///
689    /// For columns, delegates to the parent row. For rows that use
690    /// `AUGMENTS`, follows the augment chain to the source row that owns
691    /// the effective `INDEX` clause.
692    pub fn effective_indexes(self) -> impl Iterator<Item = Index<'a>> + 'a {
693        self.mib
694            .effective_indexes_source(self.id)
695            .into_iter()
696            .flat_map(move |id| {
697                self.mib
698                    .object_data(id)
699                    .index()
700                    .iter()
701                    .map(move |entry| Index::new(self.mib, self.id, entry))
702            })
703    }
704
705    /// Return `true` if this object is a table.
706    pub fn is_table(self) -> bool {
707        self.mib.is_table(self.id)
708    }
709
710    /// Return `true` if this object is a table row.
711    pub fn is_row(self) -> bool {
712        self.mib.is_row(self.id)
713    }
714
715    /// Return `true` if this object is a table column.
716    pub fn is_column(self) -> bool {
717        self.mib.is_column(self.id)
718    }
719
720    /// Return `true` if this object is a scalar.
721    pub fn is_scalar(self) -> bool {
722        self.mib.is_scalar(self.id)
723    }
724
725    /// Return `true` if this object appears in its row's effective index list.
726    pub fn is_index(self) -> bool {
727        self.mib.is_index(self.id)
728    }
729}
730
731impl<'a> Type<'a> {
732    /// Return the type name.
733    pub fn name(self) -> &'a str {
734        self.data().name()
735    }
736
737    /// Return the source span of this type definition.
738    pub fn span(self) -> Span {
739        self.data().span()
740    }
741
742    /// Return the source span of the SYNTAX clause.
743    pub fn syntax_span(self) -> Span {
744        self.data().syntax_span()
745    }
746
747    /// Return the module that defines this type.
748    pub fn module(self) -> Option<Module<'a>> {
749        self.data().module().map(|id| Module::new(self.mib, id))
750    }
751
752    /// Return the directly assigned base type.
753    pub fn base(self) -> BaseType {
754        self.data().base()
755    }
756
757    /// Return the immediate parent type, if this is a derived type.
758    pub fn parent(self) -> Option<Type<'a>> {
759        self.data().parent().map(|id| Type::new(self.mib, id))
760    }
761
762    /// Return the status (current, deprecated, obsolete).
763    pub fn status(self) -> Status {
764        self.data().status()
765    }
766
767    /// Return this type's own DISPLAY-HINT, or empty if absent.
768    pub fn display_hint(self) -> &'a str {
769        self.data().display_hint()
770    }
771
772    /// Return the DESCRIPTION clause text.
773    pub fn description(self) -> &'a str {
774        self.data().description()
775    }
776
777    /// Return the REFERENCE clause text, or empty if absent.
778    pub fn reference(self) -> &'a str {
779        self.data().reference()
780    }
781
782    /// Return this type's own SIZE constraints (not inherited).
783    pub fn sizes(self) -> &'a [Range] {
784        self.data().sizes()
785    }
786
787    /// Return this type's own range constraints (not inherited).
788    pub fn ranges(self) -> &'a [Range] {
789        self.data().ranges()
790    }
791
792    /// Return this type's own enumeration values (not inherited).
793    pub fn enums(self) -> &'a [NamedValue] {
794        self.data().enums()
795    }
796
797    /// Return this type's own BITS definitions (not inherited).
798    pub fn bits(self) -> &'a [NamedValue] {
799        self.data().bits()
800    }
801
802    /// Return `true` if this type was defined as a TEXTUAL-CONVENTION.
803    pub fn is_textual_convention(self) -> bool {
804        self.data().is_textual_convention()
805    }
806
807    /// Walk the parent type chain and return the first type that is a
808    /// TEXTUAL-CONVENTION, or `None` if no type in the chain is a TC.
809    pub fn effective_tc(self) -> Option<Type<'a>> {
810        if self.data().is_textual_convention() {
811            return Some(self);
812        }
813        self.data()
814            .effective_tc_in_parents(self.mib.types_slice())
815            .map(|id| Type::new(self.mib, id))
816    }
817
818    /// Return the effective [`BaseType`] after following the parent type chain.
819    ///
820    /// Returns the first non-[`Unknown`](BaseType::Unknown) base type
821    /// encountered when walking from this type toward the root of the chain.
822    pub fn effective_base(self) -> BaseType {
823        self.data().effective_base(self.mib.types_slice())
824    }
825
826    /// Return the effective display hint after following parent type chains.
827    pub fn effective_display_hint(self) -> &'a str {
828        self.data().effective_display_hint(self.mib.types_slice())
829    }
830
831    /// Parse and validate the effective display hint, returning a structured
832    /// [`DisplayHint`](super::display_hint::DisplayHint).
833    ///
834    /// Returns `None` if there is no display hint in the type chain or the
835    /// hint is malformed.
836    pub fn parsed_display_hint(self) -> Option<super::display_hint::DisplayHint> {
837        let hint = self.data().effective_display_hint(self.mib.types_slice());
838        if hint.is_empty() {
839            return None;
840        }
841        super::display_hint::DisplayHint::parse(hint)
842    }
843
844    /// Return the effective SIZE constraints from the type chain.
845    pub fn effective_sizes(self) -> &'a [Range] {
846        self.data().effective_sizes(self.mib.types_slice())
847    }
848
849    /// Return the effective range constraints from the type chain.
850    pub fn effective_ranges(self) -> &'a [Range] {
851        self.data().effective_ranges(self.mib.types_slice())
852    }
853
854    /// Return the effective enumeration values from the type chain.
855    pub fn effective_enums(self) -> &'a [NamedValue] {
856        self.data().effective_enums(self.mib.types_slice())
857    }
858
859    /// Return the effective BITS definitions from the type chain.
860    pub fn effective_bits(self) -> &'a [NamedValue] {
861        self.data().effective_bits(self.mib.types_slice())
862    }
863
864    /// Return `true` if the effective base type is Counter32 or Counter64.
865    pub fn is_counter(self) -> bool {
866        self.data().is_counter(self.mib.types_slice())
867    }
868
869    /// Return `true` if the effective base type is Gauge32.
870    pub fn is_gauge(self) -> bool {
871        self.data().is_gauge(self.mib.types_slice())
872    }
873
874    /// Return `true` if the effective base type is OCTET STRING.
875    pub fn is_string(self) -> bool {
876        self.data().is_string(self.mib.types_slice())
877    }
878
879    /// Return `true` if this is an Integer32 type with enumeration values.
880    pub fn is_enumeration(self) -> bool {
881        self.data().is_enumeration(self.mib.types_slice())
882    }
883
884    /// Return `true` if this type has BITS definitions.
885    pub fn is_bits(self) -> bool {
886        self.data().is_bits(self.mib.types_slice())
887    }
888}
889
890macro_rules! entity_handle_impl {
891    ($name:ident) => {
892        impl<'a> $name<'a> {
893            /// Return the definition name.
894            pub fn name(self) -> &'a str {
895                self.data().name()
896            }
897
898            /// Return the source span.
899            pub fn span(self) -> Span {
900                self.data().span()
901            }
902
903            /// Return the defining module.
904            pub fn module(self) -> Option<Module<'a>> {
905                self.data().module().map(|id| Module::new(self.mib, id))
906            }
907
908            /// Return the OID tree node, if resolved.
909            pub fn node(self) -> Option<Node<'a>> {
910                self.data().node().map(|id| Node::new(self.mib, id))
911            }
912
913            /// Return the status.
914            pub fn status(self) -> Status {
915                self.data().status()
916            }
917
918            /// Return the DESCRIPTION clause text.
919            pub fn description(self) -> &'a str {
920                self.data().description()
921            }
922
923            /// Return the REFERENCE clause text.
924            pub fn reference(self) -> &'a str {
925                self.data().reference()
926            }
927
928            /// Return the symbolic OID references from the definition.
929            pub fn oid_refs(self) -> &'a [OidRef] {
930                self.data().oid_refs()
931            }
932        }
933    };
934}
935
936entity_handle_impl!(Notification);
937entity_handle_impl!(Group);
938entity_handle_impl!(Compliance);
939entity_handle_impl!(Capability);
940
941impl<'a> Notification<'a> {
942    /// Iterate the OBJECTS clause entries.
943    pub fn objects(self) -> impl Iterator<Item = Object<'a>> + 'a {
944        self.data()
945            .objects()
946            .iter()
947            .copied()
948            .map(|id| Object::new(self.mib, id))
949    }
950
951    /// Return SMIv1 TRAP-TYPE fields (enterprise, trap number), if this is a trap.
952    pub fn trap_info(self) -> Option<&'a TrapInfo> {
953        self.data().trap_info()
954    }
955}
956
957impl<'a> Group<'a> {
958    /// Iterate the group's member nodes.
959    pub fn members(self) -> impl Iterator<Item = Node<'a>> + 'a {
960        self.data()
961            .members()
962            .iter()
963            .copied()
964            .map(|id| Node::new(self.mib, id))
965    }
966
967    /// Return `true` if this is a NOTIFICATION-GROUP (vs OBJECT-GROUP).
968    pub fn is_notification_group(self) -> bool {
969        self.data().is_notification_group()
970    }
971}
972
973impl<'a> Compliance<'a> {
974    /// Return the MODULE clauses in this compliance statement.
975    pub fn modules(self) -> &'a [ComplianceModule] {
976        self.data().modules()
977    }
978}
979
980impl<'a> Capability<'a> {
981    /// Return the PRODUCT-RELEASE string.
982    pub fn product_release(self) -> &'a str {
983        self.data().product_release()
984    }
985
986    /// Return the SUPPORTS clauses.
987    pub fn supports(self) -> &'a [CapabilitiesModule] {
988        self.data().supports()
989    }
990}
991
992/// Iterator adapter that converts arena id iteration into borrowed handles.
993///
994/// Returned by collection methods on [`Mib`] such as [`Mib::modules`],
995/// [`Mib::objects`], [`Mib::types`], and [`Mib::nodes`]. Implements
996/// [`Iterator`] for the corresponding handle type.
997pub struct HandleIter<'a, H, I> {
998    mib: &'a Mib,
999    ids: I,
1000    _marker: PhantomData<H>,
1001}
1002
1003impl<'a, H, I> HandleIter<'a, H, I> {
1004    pub(crate) fn new(mib: &'a Mib, ids: I) -> Self {
1005        Self {
1006            mib,
1007            ids,
1008            _marker: PhantomData,
1009        }
1010    }
1011}
1012
1013impl<'a, I> Iterator for HandleIter<'a, Module<'a>, I>
1014where
1015    I: Iterator<Item = ModuleId>,
1016{
1017    type Item = Module<'a>;
1018
1019    fn next(&mut self) -> Option<Self::Item> {
1020        self.ids.next().map(|id| Module::new(self.mib, id))
1021    }
1022}
1023
1024impl<'a, I> Iterator for HandleIter<'a, Object<'a>, I>
1025where
1026    I: Iterator<Item = ObjectId>,
1027{
1028    type Item = Object<'a>;
1029
1030    fn next(&mut self) -> Option<Self::Item> {
1031        self.ids.next().map(|id| Object::new(self.mib, id))
1032    }
1033}
1034
1035impl<'a, I> Iterator for HandleIter<'a, Type<'a>, I>
1036where
1037    I: Iterator<Item = TypeId>,
1038{
1039    type Item = Type<'a>;
1040
1041    fn next(&mut self) -> Option<Self::Item> {
1042        self.ids.next().map(|id| Type::new(self.mib, id))
1043    }
1044}
1045
1046impl<'a, I> Iterator for HandleIter<'a, Node<'a>, I>
1047where
1048    I: Iterator<Item = NodeId>,
1049{
1050    type Item = Node<'a>;
1051
1052    fn next(&mut self) -> Option<Self::Item> {
1053        self.ids.next().map(|id| Node::new(self.mib, id))
1054    }
1055}
1056
1057impl<'a, I> Iterator for HandleIter<'a, Notification<'a>, I>
1058where
1059    I: Iterator<Item = NotificationId>,
1060{
1061    type Item = Notification<'a>;
1062
1063    fn next(&mut self) -> Option<Self::Item> {
1064        self.ids.next().map(|id| Notification::new(self.mib, id))
1065    }
1066}
1067
1068impl<'a, I> Iterator for HandleIter<'a, Group<'a>, I>
1069where
1070    I: Iterator<Item = GroupId>,
1071{
1072    type Item = Group<'a>;
1073
1074    fn next(&mut self) -> Option<Self::Item> {
1075        self.ids.next().map(|id| Group::new(self.mib, id))
1076    }
1077}
1078
1079impl<'a, I> Iterator for HandleIter<'a, Compliance<'a>, I>
1080where
1081    I: Iterator<Item = ComplianceId>,
1082{
1083    type Item = Compliance<'a>;
1084
1085    fn next(&mut self) -> Option<Self::Item> {
1086        self.ids.next().map(|id| Compliance::new(self.mib, id))
1087    }
1088}
1089
1090impl<'a, I> Iterator for HandleIter<'a, Capability<'a>, I>
1091where
1092    I: Iterator<Item = CapabilityId>,
1093{
1094    type Item = Capability<'a>;
1095
1096    fn next(&mut self) -> Option<Self::Item> {
1097        self.ids.next().map(|id| Capability::new(self.mib, id))
1098    }
1099}