The lens module is Monocle's use‑case layer. Each lens exposes a cohesive set of operations (search, parse, RPKI lookup, etc.) through a stable, programmatic API that can be reused by:
- the
monocleCLI, - the WebSocket server (
monocle server), - GUI frontends (planned: GPUI),
- other Rust applications embedding Monocle as a library.
A lens is responsible for domain logic, input normalization/validation, and returning typed results. Formatting is handled consistently via the unified OutputFormat.
lens/
├── mod.rs # Lens module exports (feature-gated)
├── README.md # This document
├── utils.rs # OutputFormat + formatting helpers
│
├── time/ # Time parsing / formatting
│ └── mod.rs
│
├── country/ # Country lookup (lib)
├── ip/ # IP information lookups
│ └── mod.rs
├── parse/ # MRT parsing + progress callbacks
│ └── mod.rs
├── search/ # Search across public MRT files
│ ├── mod.rs
│ └── query_builder.rs
├── rpki/ # RPKI operations
│ ├── mod.rs
│ └── commons.rs
├── pfx2as/ # Prefix→AS mapping types
│ └── mod.rs
├── as2rel/ # AS relationship lookups
│ ├── mod.rs
│ ├── args.rs
│ └── types.rs
│
└── inspect/ # Unified AS/prefix inspection
├── mod.rs # InspectLens implementation
└── types.rs # Result types, section selection
All lenses are available with the lib feature, which is the default for library usage:
# Library usage (all lenses + database)
monocle = { version = "1.1", default-features = false, features = ["lib"] }The lib feature includes:
TimeLens- Time parsing and formattingCountryLens- Country code/name lookupIpLens- IP information lookupParseLens- MRT file parsingSearchLens- BGP message search across MRT filesRpkiLens- RPKI validationPfx2asLens- Prefix-to-AS mappingAs2relLens- AS relationship lookupsInspectLens- Unified AS/prefix inspection
Lenses are designed to be called from multiple frontends:
- CLI: commands call into lenses and print results using
OutputFormat. - WebSocket: handlers call lenses and stream progress/results via
WsOpSink. - GUI: a UI can call lenses on a worker thread and stream progress/results back to the UI.
- Library: users can call lens APIs directly without going through CLI argument parsing.
A typical operation should look like:
- CLI / GUI: parse user input → construct Args → call lens method
- Lens: validate and execute → return typed results (and optionally progress events)
- Formatting: done via
OutputFormat(shared across the project)
The lens layer should not own long-term persistence primitives directly. When persistence is needed, lenses depend on database/ (for SQLite + caches) or external crates (Broker/Parser/etc.).
Prefer:
- typed argument structs (often
Serialize/Deserialize) - typed result structs/enums (often
Serialize/Deserialize) - explicit error returns (typically
anyhow::Result<_>)
This makes it straightforward to:
- serialize requests/results for GUI message passing,
- test units without a CLI,
- keep output formatting consistent.
Monocle uses a single output format enum across commands and lenses:
table(default)markdown/mdjsonjson-prettyjson-line/jsonl/ndjsonpsv
The OutputFormat type lives in:
lens/utils.rs
A common pattern is:
- lens methods return
Vec<T>(or similar) - the CLI chooses an
OutputFormat - formatting is done by calling
format.format(&results)(or equivalent helper)
This keeps formatting logic consistent and avoids per-command format flags drifting over time.
Some lenses can emit progress updates via callbacks (used by CLI progress bars today and intended for GUIs):
- Parse: emits periodic progress while processing messages.
- Search: emits progress for broker querying, file processing, and completion.
Progress types are designed to be:
Send + Syncfriendly (callbacks may be called from parallel workers),- serializable (
Serialize/Deserialize) for easy GUI integration.
These lenses require access to the persistent SQLite database (typically via MonocleDatabase):
As2relLens- AS-level relationshipsInspectLens- Unified AS/prefix inspection (uses ASInfo, AS2Rel, RPKI, Pfx2as repositories)
These lenses can use the database for caching but don't strictly require it:
RpkiLens- Uses database for current data cache; historical queries use bgpkit-commons directly
These lenses do not require a persistent database reference:
TimeLens- Time parsing and formattingCountryLens- Country code/name lookup (uses bgpkit-commons)IpLens- IP information lookup (uses external API)ParseLens- MRT file parsingSearchLens- BGP message search across MRT files
Note: code below is intentionally example-focused; check the module docs / rustdoc for exact function signatures where needed.
use monocle::lens::time::{TimeLens, TimeParseArgs};
use monocle::utils::OutputFormat;
let lens = TimeLens::new();
let args = TimeParseArgs::new(vec![
"1697043600".to_string(),
"2023-10-11T00:00:00Z".to_string(),
]);
let results = lens.parse(&args)?;
let out = OutputFormat::Table.format(&results);
println!("{}", out);use monocle::database::MonocleDatabase;
use monocle::lens::inspect::{InspectLens, InspectQueryOptions};
use monocle::utils::OutputFormat;
let db = MonocleDatabase::open_in_dir("~/.local/share/monocle")?;
let lens = InspectLens::new(&db);
// Query AS information
let options = InspectQueryOptions::default();
let result = lens.query_asn(13335, &options)?;
println!("AS{}: {}", result.asn, result.name.unwrap_or_default());
// Query prefix information
let result = lens.query_prefix("1.1.1.0/24".parse()?, &options)?;
println!("{:?}", result);
// Search by name
let results = lens.search_by_name("cloudflare", 20)?;
for r in results {
println!("AS{}: {}", r.asn, r.name.unwrap_or_default());
}use monocle::database::MonocleDatabase;
use monocle::lens::as2rel::{As2relLens, As2relSearchArgs};
use monocle::utils::OutputFormat;
let db = MonocleDatabase::open_in_dir("~/.local/share/monocle")?;
let lens = As2relLens::new(&db);
// Update data if needed
if lens.needs_update() {
lens.update()?;
}
// Query relationships for an ASN
let args = As2relSearchArgs::new(13335);
let results = lens.search(&args)?;
println!("{}", OutputFormat::Table.format(&results));use monocle::lens::search::{SearchLens, SearchFilters, SearchProgress};
use std::sync::Arc;
let lens = SearchLens::new();
let filters = SearchFilters {
// ...
..Default::default()
};
let on_progress = Arc::new(|p: SearchProgress| {
// send to UI / update progress bar
eprintln!("{:?}", p);
});
let on_elem = Arc::new(|elem, collector| {
// stream results somewhere
});
let summary = lens.search_with_progress(&filters, Some(on_progress), on_elem)?;
eprintln!("{:?}", summary);For a detailed contributor walkthrough, see DEVELOPMENT.md. In short:
- Create
src/lens/<newlens>/mod.rs(andargs.rs/types.rsif needed) - Define:
<NewLens>Args(input)<NewLens>Result(output)<NewLens>Lens(operations)
- Add feature gate in
src/lens/mod.rs:#[cfg(feature = "lib")] pub mod newlens;
- Wire into (optional):
- CLI command module under
src/bin/commands/ - WebSocket handler under
src/server/handlers/
- CLI command module under
Consistent naming makes lenses predictable:
- Lens struct:
<Name>Lens(e.g.,TimeLens,RpkiLens,InspectLens) - Arg structs:
<Name><Op>Args(e.g.,As2relSearchArgs,RpkiRoaLookupArgs) - Result structs:
<Name><Op>Result(e.g.,As2relSearchResult) - Module names: snake_case (
as2rel,pfx2as,inspect)
- Lens methods generally return
anyhow::Result<T>. - Favor descriptive messages that help CLI and GUI users.
- Avoid panics; the library should be robust when called from long-running frontends.
- Prefer unit tests close to lens code for pure logic.
- For filesystem/network interactions, use
#[ignore]tests or inject test data. - Search/parse workloads should be tested with small fixtures where possible.
ARCHITECTURE.md(project-level architecture)DEVELOPMENT.md(contributor guide)src/server/README.md(WebSocket API protocol)src/database/README.md(database module overview)examples/README.md(example code by feature tier)