monocle 1.2.0

A commandline application to search, parse, and process BGP information in public sources.
Documentation
# Lens Module

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 `monocle` CLI,
- 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`.

---

## Directory Layout

```
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
```

---

## Feature Requirements

All lenses are available with the `lib` feature, which is the default for library usage:

```toml
# Library usage (all lenses + database)
monocle = { version = "1.1", default-features = false, features = ["lib"] }
```

The `lib` feature includes:
- `TimeLens` - Time parsing and formatting
- `CountryLens` - Country code/name lookup
- `IpLens` - IP information lookup
- `ParseLens` - MRT file parsing
- `SearchLens` - BGP message search across MRT files
- `RpkiLens` - RPKI validation
- `Pfx2asLens` - Prefix-to-AS mapping
- `As2relLens` - AS relationship lookups
- `InspectLens` - Unified AS/prefix inspection

---

## Design Philosophy

### 1) Interface-neutral, frontend-friendly

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.

### 2) Clear responsibilities

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.).

### 3) Stable, typed APIs

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.

---

## Output Formatting: Unified `OutputFormat`

Monocle uses a single output format enum across commands and lenses:

- `table` (default)
- `markdown` / `md`
- `json`
- `json-pretty`
- `json-line` / `jsonl` / `ndjson`
- `psv`

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.

---

## Progress Reporting (GUI-friendly)

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 + Sync` friendly (callbacks may be called from parallel workers),
- serializable (`Serialize`/`Deserialize`) for easy GUI integration.

---

## Lens Categories

### Database-backed lenses

These lenses require access to the persistent SQLite database (typically via `MonocleDatabase`):

- `As2relLens` - AS-level relationships
- `InspectLens` - Unified AS/prefix inspection (uses ASInfo, AS2Rel, RPKI, Pfx2as repositories)

### Database-optional lenses

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

### Standalone lenses

These lenses do not require a persistent database reference:

- `TimeLens` - Time parsing and formatting
- `CountryLens` - Country code/name lookup (uses bgpkit-commons)
- `IpLens` - IP information lookup (uses external API)
- `ParseLens` - MRT file parsing
- `SearchLens` - BGP message search across MRT files

---

## Usage Examples

> Note: code below is intentionally example-focused; check the module docs / rustdoc for exact function signatures where needed.

### TimeLens

```rust,ignore
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);
```

### InspectLens (database-backed)

```rust,ignore
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());
}
```

### As2relLens (database-backed)

```rust,ignore
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));
```

### SearchLens with progress

```rust,ignore
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);
```

---

## Adding a New Lens (high level)

For a detailed contributor walkthrough, see `DEVELOPMENT.md`. In short:

1. Create `src/lens/<newlens>/mod.rs` (and `args.rs` / `types.rs` if needed)
2. Define:
   - `<NewLens>Args` (input)
   - `<NewLens>Result` (output)
   - `<NewLens>Lens` (operations)
3. Add feature gate in `src/lens/mod.rs`:
    ```rust
    #[cfg(feature = "lib")]
    pub mod newlens;
    ```
4. Wire into (optional):
   - CLI command module under `src/bin/commands/`
   - WebSocket handler under `src/server/handlers/`

---

## Naming Conventions

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`)

---

## Error Handling

- 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.

---

## Testing Notes

- 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.

---

## Related Documentation

- `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)