Skip to main content

async_snmp/cli/
mib_cli.rs

1//! MIB loading support for CLI tools.
2//!
3//! Provides CLI arguments for loading MIBs and a helper to construct a
4//! [`Mib`] from those arguments. Gated on both `cli`
5//! and `mib` features.
6
7use crate::mib_support::{self, DiagnosticConfig, Loader, Mib, source};
8use clap::Parser;
9use std::path::PathBuf;
10
11/// MIB loading arguments for CLI tools.
12#[derive(Debug, Parser)]
13pub struct MibArgs {
14    /// Load MIBs from these directories.
15    #[arg(long = "mib-dir")]
16    pub mib_dir: Vec<PathBuf>,
17
18    /// Load specific MIB modules by name.
19    #[arg(long = "load-mibs")]
20    pub load_mibs: Vec<String>,
21
22    /// Use system MIB search paths (net-snmp, libsmi).
23    #[arg(long = "system-mibs")]
24    pub system_mibs: bool,
25}
26
27impl MibArgs {
28    /// Returns true if any MIB loading options were specified.
29    pub fn is_active(&self) -> bool {
30        !self.mib_dir.is_empty() || !self.load_mibs.is_empty() || self.system_mibs
31    }
32
33    /// Load MIBs based on the CLI arguments.
34    ///
35    /// Returns `None` if no MIB options were specified.
36    /// Uses `spawn_blocking` internally because mib-rs uses rayon for
37    /// parallel file loading.
38    pub async fn load(&self) -> Result<Option<Mib>, String> {
39        if !self.is_active() {
40            return Ok(None);
41        }
42
43        let mib_dirs = self.mib_dir.clone();
44        let load_mibs = self.load_mibs.clone();
45        let system_mibs = self.system_mibs;
46
47        let mib =
48            tokio::task::spawn_blocking(move || load_mib_sync(&mib_dirs, &load_mibs, system_mibs))
49                .await
50                .map_err(|e| format!("MIB loading task failed: {}", e))??;
51
52        Ok(Some(mib))
53    }
54}
55
56/// Synchronous MIB loading (runs on a blocking thread).
57fn load_mib_sync(
58    mib_dirs: &[PathBuf],
59    load_mibs: &[String],
60    system_mibs: bool,
61) -> Result<Mib, String> {
62    let mut loader = Loader::new();
63
64    // Add directory sources
65    for dir in mib_dirs {
66        let source = source::dir(dir)
67            .map_err(|e| format!("failed to open MIB directory {:?}: {}", dir, e))?;
68        loader = loader.source(source);
69    }
70
71    // Add system paths
72    if system_mibs {
73        loader = loader.system_paths();
74    }
75
76    // Restrict to named modules if specified
77    if !load_mibs.is_empty() {
78        loader = loader.modules(load_mibs.iter().map(String::as_str));
79    }
80
81    // Use quiet diagnostics for CLI
82    loader = loader.diagnostic_config(DiagnosticConfig::quiet());
83
84    loader
85        .load()
86        .map_err(|e| format!("MIB loading failed: {}", e))
87}
88
89/// Resolve an OID argument that may be a name (when MIBs are loaded) or
90/// dotted notation.
91///
92/// Arguments starting with a digit are parsed as dotted-decimal OIDs without
93/// attempting MIB resolution. Named arguments are resolved through the MIB
94/// if one is loaded, otherwise they fall back to the static hints table.
95pub fn resolve_oid_arg(mib: Option<&Mib>, s: &str) -> Result<crate::Oid, String> {
96    // If it starts with a digit, try dotted notation first
97    if s.chars().next().is_some_and(|c| c.is_ascii_digit()) {
98        return crate::Oid::parse(s).map_err(|e| format!("invalid OID '{}': {}", s, e));
99    }
100
101    // Try MIB resolution if available
102    if let Some(mib) = mib {
103        return mib_support::resolve_oid(mib, s)
104            .map_err(|e| format!("cannot resolve '{}': {}", s, e));
105    }
106
107    // Fall back to the static hints table
108    crate::cli::hints::parse_oid(s)
109}