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