1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
//! Low-level raw data access for tooling: arena IDs, sub-clause spans,
//! import metadata, OID references, symbol tables, and OID tree traversal.
//!
//! The raw API (`mib.raw()`) is designed for tools that need capabilities
//! beyond the handle API: linters, language servers, exporters, and editor
//! integrations. This example demonstrates what it offers that the handle
//! API does not.
use mib_rs::Loader;
use mib_rs::types::Span;
fn main() {
// Use a MIB with a deliberately unused import (NoSuchThing) and
// a used-but-from-wrong-module pattern to show import analysis.
let source = mib_rs::source::memory(
"RAW-EXAMPLE-MIB",
br#"RAW-EXAMPLE-MIB DEFINITIONS ::= BEGIN
IMPORTS
MODULE-IDENTITY, OBJECT-TYPE, Integer32, enterprises
FROM SNMPv2-SMI
TEXTUAL-CONVENTION, DisplayString, TruthValue
FROM SNMPv2-TC;
rawMib MODULE-IDENTITY
LAST-UPDATED "202603120000Z"
ORGANIZATION "Example Corp"
CONTACT-INFO "support@example.com"
DESCRIPTION "Example module for raw API demo."
::= { enterprises 99990 }
RawName ::= TEXTUAL-CONVENTION
DISPLAY-HINT "255a"
STATUS current
DESCRIPTION "A name string."
SYNTAX DisplayString (SIZE (1..64))
rawScalars OBJECT IDENTIFIER ::= { rawMib 1 }
rawDeviceName OBJECT-TYPE
SYNTAX RawName
MAX-ACCESS read-only
STATUS current
DESCRIPTION "The device name."
::= { rawScalars 1 }
rawEnabled OBJECT-TYPE
SYNTAX TruthValue
MAX-ACCESS read-write
STATUS current
DESCRIPTION "Whether the device is enabled."
DEFVAL { true }
::= { rawScalars 2 }
rawCount OBJECT-TYPE
SYNTAX Integer32 (0..1000)
UNITS "items"
MAX-ACCESS read-only
STATUS current
DESCRIPTION "Item count."
::= { rawScalars 3 }
END
"#
.as_slice(),
);
let mib = Loader::new()
.source(source)
.modules(["RAW-EXAMPLE-MIB"])
.load()
.expect("should load");
let raw = mib.raw();
// ---------------------------------------------------------------
// 1. Sub-clause spans
//
// ObjectData exposes per-clause source locations: syntax_span(),
// access_span(), units_span(), augments_span(), default_value_span().
// These let a linter or language server point diagnostics at the
// specific clause that's wrong, not the whole definition.
// ---------------------------------------------------------------
println!("=== Sub-clause spans ===");
let mod_data = raw.module(mib.module_by_name("RAW-EXAMPLE-MIB").unwrap());
// Helper: absent clauses produce Span::ZERO or Span::SYNTHETIC.
let has_span = |s: Span| s != Span::ZERO && !s.is_synthetic();
for &obj_id in mod_data.objects() {
let obj = raw.object(obj_id);
println!(" {}:", obj.name());
// Definition span covers the whole OBJECT-TYPE.
let def = obj.span();
let (def_line, _) = mod_data.line_col(def.start);
println!(" definition: line {def_line}");
// SYNTAX clause span.
let syn = obj.syntax_span();
if has_span(syn) {
let (line, col) = mod_data.line_col(syn.start);
println!(" SYNTAX: line {line}, col {col}");
}
// MAX-ACCESS clause span.
let acc = obj.access_span();
if has_span(acc) {
let (line, col) = mod_data.line_col(acc.start);
println!(" MAX-ACCESS: line {line}, col {col}");
}
// UNITS clause span (only present on some objects).
let units = obj.units_span();
if has_span(units) {
let (line, col) = mod_data.line_col(units.start);
println!(" UNITS: line {line}, col {col}");
}
// DEFVAL clause span (only present on some objects).
let defval = obj.default_value_span();
if has_span(defval) {
let (line, col) = mod_data.line_col(defval.start);
println!(" DEFVAL: line {line}, col {col}");
}
}
// TypeData also has syntax_span() for the SYNTAX clause in TCs.
for &type_id in mod_data.types() {
let ty = raw.type_(type_id);
let syn = ty.syntax_span();
if has_span(syn) {
let (line, col) = mod_data.line_col(syn.start);
println!(" {} (type):", ty.name());
println!(" SYNTAX: line {line}, col {col}");
}
}
// ---------------------------------------------------------------
// 2. Import resolution metadata
//
// ModuleData tracks which imports were actually used during
// resolution, and where each imported symbol was resolved from.
// This is the data a linter needs for "unused import" warnings.
// ---------------------------------------------------------------
println!("\n=== Import analysis ===");
for imp in mod_data.imports() {
println!(" FROM {}:", imp.module);
for sym in &imp.symbols {
let used = mod_data.is_import_used(&sym.name);
let resolved_from = mod_data.import_source(&sym.name);
let status = if !used {
"UNUSED".to_string()
} else if let Some(source_id) = resolved_from {
let source_mod = raw.module(source_id);
if source_mod.name() != imp.module {
// Resolved from a different module than declared.
format!("resolved from {}", source_mod.name())
} else {
"ok".to_string()
}
} else {
"unresolved".to_string()
};
// ImportSymbol carries a span for "go to definition" on imports.
let (line, col) = mod_data.line_col(sym.span.start);
println!(" {:<24} line {}:{:<4} {}", sym.name, line, col, status);
}
}
// ---------------------------------------------------------------
// 3. OID references (oid_refs)
//
// Entity definitions record the symbolic names referenced in their
// OID value assignments. For example, { enterprises 99990 } produces
// an OidRef for "enterprises" with its span. A language server uses
// these for "go to definition" on OID components and for reference
// highlighting.
// ---------------------------------------------------------------
println!("\n=== OID references ===");
for &obj_id in mod_data.objects() {
let obj = raw.object(obj_id);
let refs = obj.oid_refs();
if !refs.is_empty() {
println!(" {}:", obj.name());
for r in refs {
let (line, col) = mod_data.line_col(r.span.start);
println!(" ref {:?} at line {}:{}", r.name, line, col);
}
}
}
// ---------------------------------------------------------------
// 4. Symbol tables and available_symbols
//
// Mib::available_symbols(mod_id) returns everything visible in a
// module's scope: own definitions first, then resolved imports.
// This is what a completion engine would use to suggest names.
// ---------------------------------------------------------------
println!("\n=== Available symbols in RAW-EXAMPLE-MIB ===");
let mod_id = mib.module_by_name("RAW-EXAMPLE-MIB").unwrap();
let symbols = mib.available_symbols(mod_id);
// Show own definitions vs imported symbols.
let own_count = mod_data.definitions().count();
println!(
" {} own definitions, {} total (including imports)",
own_count,
symbols.len()
);
println!("\n Own definitions:");
for sym in symbols.iter().take(own_count) {
let kind = match sym {
mib_rs::raw::Symbol::Object(_) => "object",
mib_rs::raw::Symbol::Type(_) => "type",
mib_rs::raw::Symbol::Node(_) => "node",
mib_rs::raw::Symbol::Notification(_) => "notification",
mib_rs::raw::Symbol::Group(_) => "group",
mib_rs::raw::Symbol::Compliance(_) => "compliance",
mib_rs::raw::Symbol::Capability(_) => "capability",
};
println!(" {:<24} {}", sym.name(&mib), kind);
}
println!("\n Imported symbols (first 10):");
for sym in symbols.iter().skip(own_count).take(10) {
let source_mod = sym
.module(&mib)
.map(|id| raw.module(id).name().to_string())
.unwrap_or_default();
println!(" {:<24} from {}", sym.name(&mib), source_mod);
}
// ---------------------------------------------------------------
// 5. ID-only workflows
//
// Handles and raw access share the same arena IDs (ObjectId,
// NodeId, etc.). Handles expose theirs via .id(), so you can
// always get an ID. The raw layer lets you work entirely in IDs:
// follow cross-refs like obj_data.type_id(), look up data with
// raw.object(id), and iterate arenas without constructing
// handles. IDs are Copy + Eq + Hash + Ord, so they work as map
// keys or can be sent across channels.
// ---------------------------------------------------------------
println!("\n=== ID-only workflows ===");
// Get an ID from a handle, or directly from Mib.
let handle = mib.object("rawDeviceName").unwrap();
let obj_id = handle.id(); // same as mib.object_by_name("rawDeviceName")
println!(" ObjectId index: {}", obj_id.index());
// Follow cross-references without handles.
let obj_data = raw.object(obj_id);
if let Some(type_id) = obj_data.type_id() {
let type_data = raw.type_(type_id);
println!(
" {} -> type {} (no handles needed)",
obj_data.name(),
type_data.name()
);
}
// IDs can be collected into sets for deduplication.
use std::collections::HashSet;
let mut seen = HashSet::new();
seen.insert(obj_id);
println!(" IDs are hashable: {}", seen.contains(&obj_id));
// ---------------------------------------------------------------
// 6. Bulk arena access
//
// raw.*_slice() gives direct &[Data] access to the arena backing
// stores. No iterator adapters, no handle wrapping. Useful for
// exporters, batch analysis, or building secondary indices.
// ---------------------------------------------------------------
println!("\n=== Arena slices ===");
println!(" Modules: {}", raw.modules_slice().len());
println!(" Objects: {}", raw.objects_slice().len());
println!(" Types: {}", raw.types_slice().len());
println!(" Notifications: {}", raw.notifications_slice().len());
// Build a quick index: type name -> list of objects using it.
use std::collections::HashMap;
let mut type_usage: HashMap<&str, Vec<&str>> = HashMap::new();
for obj_data in raw.objects_slice() {
if let Some(type_id) = obj_data.type_id() {
let type_name = raw.type_(type_id).name();
type_usage
.entry(type_name)
.or_default()
.push(obj_data.name());
}
}
println!("\n Type usage index:");
let mut entries: Vec<_> = type_usage.iter().collect();
entries.sort_by_key(|(name, _)| *name);
for (type_name, objects) in &entries {
if objects.iter().any(|o| o.starts_with("raw")) {
println!(" {:<20} used by {}", type_name, objects.join(", "));
}
}
// ---------------------------------------------------------------
// 7. OID tree direct access
//
// raw.tree() gives access to the OidTree with walk_oid(),
// subtree(), all_nodes(), and longest_prefix_from(). The node
// BTreeMap<u32, NodeId> children are in arc order, which matters
// for ordered tree walks in an OID browser.
// ---------------------------------------------------------------
println!("\n=== OID tree ===");
// Walk to a subtree and enumerate children with their arcs.
let scalars_id = raw.resolve("rawScalars").unwrap();
let scalars = raw.node(scalars_id);
println!(" Children of {} (arc order):", scalars.name());
for (arc, &child_id) in scalars.children() {
let child = raw.node(child_id);
let kind = child.kind();
println!(" arc {arc}: {:<20} [{kind:?}]", child.name());
}
// Longest prefix match (for instance OID resolution).
let instance_oid: mib_rs::Oid = "1.3.6.1.4.1.99990.1.1.42".parse().unwrap();
let prefix_id = raw.longest_prefix_by_oid(&instance_oid);
let prefix = raw.node(prefix_id);
println!("\n Longest prefix for {}: {}", instance_oid, prefix.name());
// Effective module ownership for a node.
if let Some(mod_id) = raw.effective_module(prefix_id) {
println!(" Effective owner: {}", raw.module(mod_id).name());
}
// Depth-first subtree iteration via the tree.
let tree = raw.tree();
let subtree_count = tree.subtree(scalars_id).count();
println!(
"\n Subtree size of {}: {} nodes",
scalars.name(),
subtree_count
);
// ---------------------------------------------------------------
// 8. Cross-reference queries
//
// Mib-level queries that return IDs rather than handles, useful
// for building reference indices.
// ---------------------------------------------------------------
println!("\n=== Cross-references ===");
// Which modules define a given symbol?
let definers = mib.modules_defining("rawDeviceName");
println!(" 'rawDeviceName' defined in:");
for mod_id in &definers {
println!(" {}", raw.module(*mod_id).name());
}
// Which modules import DisplayString?
let importers = mib.modules_importing("DisplayString");
println!(" 'DisplayString' imported by:");
for mod_id in &importers {
println!(" {}", raw.module(*mod_id).name());
}
// Find all objects of a given base type.
let counters = mib.objects_by_base_type(mib_rs::BaseType::Integer32);
println!(
"\n Objects with effective base Integer32: {}",
counters.len()
);
for id in counters.iter().take(5) {
let obj = raw.object(*id);
let mod_name = obj
.module()
.map(|mid| raw.module(mid).name())
.unwrap_or("?");
println!(" {}::{}", mod_name, obj.name());
}
// Find all objects using a specific named type.
let by_type = mib.objects_by_type_name("RawName");
println!("\n Objects with type 'RawName':");
for id in &by_type {
println!(" {}", raw.object(*id).name());
}
// ---------------------------------------------------------------
// 9. Combining handle and raw access
//
// The raw and handle APIs are views over the same data. You can
// freely cross between them: handle.id() drops to raw, and
// mib.*_by_id(id) lifts back to a handle. Use handles for
// navigation, raw for bulk work and span access.
// ---------------------------------------------------------------
println!("\n=== Crossing between handle and raw ===");
// Start with a handle, drop to raw for span info.
let handle = mib.object("rawCount").unwrap();
let id = handle.id(); // -> ObjectId
let data = raw.object(id); // -> &ObjectData
let (syn_line, _) = mod_data.line_col(data.syntax_span().start);
let (acc_line, _) = mod_data.line_col(data.access_span().start);
println!(
" {}: SYNTAX at line {}, MAX-ACCESS at line {}",
handle.name(),
syn_line,
acc_line
);
// Start with raw, lift to handle for navigation.
if let Some(type_id) = data.type_id() {
let type_handle = mib.type_by_id(type_id);
println!(
" Type chain: {} -> effective base {:?}",
type_handle.name(),
type_handle.effective_base()
);
}
}