use clap::Args;
use monocle::database::MonocleDatabase;
use monocle::lens::inspect::{
InspectDataSection, InspectDisplayConfig, InspectLens, InspectQueryOptions, InspectQueryType,
InspectResult,
};
use monocle::utils::OutputFormat;
use monocle::MonocleConfig;
use std::collections::HashSet;
struct ShowParseResult {
sections: HashSet<InspectDataSection>,
invalid_sections: Vec<String>,
}
#[derive(Args)]
pub struct InspectArgs {
#[clap(required_unless_present_any = ["country", "update"])]
pub query: Vec<String>,
#[clap(short = 'a', long, conflicts_with_all = ["prefix", "name"])]
pub asn: bool,
#[clap(short = 'p', long, conflicts_with_all = ["asn", "name"])]
pub prefix: bool,
#[clap(short = 'n', long, conflicts_with_all = ["asn", "prefix"])]
pub name: bool,
#[clap(short = 'c', long, conflicts_with_all = ["asn", "prefix", "name"])]
pub country: Option<String>,
#[clap(long = "show", value_name = "SECTION")]
pub show: Vec<String>,
#[clap(long)]
pub full: bool,
#[clap(long)]
pub full_roas: bool,
#[clap(long)]
pub full_prefixes: bool,
#[clap(long)]
pub full_connectivity: bool,
#[clap(long, value_name = "N")]
pub limit: Option<usize>,
#[clap(short = 'u', long)]
pub update: bool,
}
pub fn run(
config: &MonocleConfig,
args: InspectArgs,
output_format: OutputFormat,
no_update: bool,
) {
let sqlite_path = config.sqlite_path();
let db = match MonocleDatabase::open(&sqlite_path) {
Ok(db) => db,
Err(e) => {
eprintln!("Failed to open database: {}", e);
std::process::exit(1);
}
};
let lens = InspectLens::new(&db, config);
if args.update {
if no_update {
eprintln!("[monocle] Warning: --update ignored because --no-update is set");
} else {
eprintln!("[monocle] Updating all data sources...");
match lens.ensure_data_available() {
Ok(summary) => {
for msg in summary.format_messages() {
eprintln!("[monocle] {}", msg);
}
if !summary.any_refreshed {
eprintln!("[monocle] All data sources are up to date.");
}
}
Err(e) => {
eprintln!("[monocle] Failed to update data: {}", e);
std::process::exit(1);
}
}
if args.query.is_empty() && args.country.is_none() {
return;
}
}
}
let (options, select_result) = build_query_options(&args);
let required_sections = determine_required_sections(&args, &options, &lens);
if !no_update {
match lens.ensure_data_for_sections(&required_sections) {
Ok(summary) => {
for msg in summary.format_messages() {
eprintln!("[monocle] {}", msg);
}
}
Err(e) => {
eprintln!(
"[monocle] Warning: Could not verify data availability: {}",
e
);
}
}
}
let result = if let Some(ref country) = args.country {
match lens.query_by_country(country, &options) {
Ok(r) => r,
Err(e) => {
eprintln!("Query failed: {}", e);
std::process::exit(1);
}
}
} else if args.asn {
match lens.query_as_asn(&args.query, &options) {
Ok(r) => r,
Err(e) => {
eprintln!("Query failed: {}", e);
std::process::exit(1);
}
}
} else if args.prefix {
match lens.query_as_prefix(&args.query, &options) {
Ok(r) => r,
Err(e) => {
eprintln!("Query failed: {}", e);
std::process::exit(1);
}
}
} else if args.name {
match lens.query_as_name(&args.query, &options) {
Ok(r) => r,
Err(e) => {
eprintln!("Query failed: {}", e);
std::process::exit(1);
}
}
} else {
match lens.query(&args.query, &options) {
Ok(r) => r,
Err(e) => {
eprintln!("Query failed: {}", e);
std::process::exit(1);
}
}
};
output_results(&lens, &result, output_format, &select_result);
}
fn parse_show_options(args: &InspectArgs) -> ShowParseResult {
let mut sections = HashSet::new();
let mut invalid_sections = Vec::new();
for s in &args.show {
let s_lower = s.to_lowercase();
match s_lower.as_str() {
"all" => {
sections.extend(InspectDataSection::all());
}
_ => {
if let Some(section) = InspectDataSection::from_str(&s_lower) {
sections.insert(section);
} else {
invalid_sections.push(s.clone());
}
}
}
}
ShowParseResult {
sections,
invalid_sections,
}
}
fn build_query_options(args: &InspectArgs) -> (InspectQueryOptions, ShowParseResult) {
let mut options = if args.full {
InspectQueryOptions::full()
} else {
InspectQueryOptions::default()
};
let show_result = parse_show_options(args);
if !show_result.invalid_sections.is_empty() {
eprintln!(
"Error: Unknown section(s): {}",
show_result.invalid_sections.join(", ")
);
eprintln!(
"Available sections: {}, all",
InspectDataSection::all_names().join(", ")
);
std::process::exit(1);
}
if !show_result.sections.is_empty() {
options.select = Some(show_result.sections.clone());
}
if args.full_roas {
options.max_roas = 0;
}
if args.full_prefixes {
options.max_prefixes = 0;
}
if args.full_connectivity {
options.max_neighbors = 0;
}
if let Some(limit) = args.limit {
options.max_search_results = limit;
}
(options, show_result)
}
fn determine_required_sections(
args: &InspectArgs,
options: &InspectQueryOptions,
lens: &InspectLens,
) -> HashSet<InspectDataSection> {
let mut sections = HashSet::new();
if let Some(ref selected) = options.select {
return selected.clone();
}
if args.country.is_some() {
sections.insert(InspectDataSection::Basic);
return sections;
}
if args.name {
sections.insert(InspectDataSection::Basic);
return sections;
}
if args.asn {
for s in InspectDataSection::default_for_asn() {
sections.insert(s);
}
return sections;
}
if args.prefix {
for s in InspectDataSection::default_for_prefix() {
sections.insert(s);
}
sections.insert(InspectDataSection::Prefixes);
sections.insert(InspectDataSection::Rpki);
return sections;
}
for query in &args.query {
let query_type = lens.detect_query_type(query);
let defaults = match query_type {
InspectQueryType::Asn => InspectDataSection::default_for_asn(),
InspectQueryType::Prefix => InspectDataSection::default_for_prefix(),
InspectQueryType::Name => InspectDataSection::default_for_name(),
};
for s in defaults {
sections.insert(s);
}
if query_type == InspectQueryType::Prefix {
sections.insert(InspectDataSection::Prefixes);
sections.insert(InspectDataSection::Rpki);
}
}
if sections.is_empty() {
sections.insert(InspectDataSection::Basic);
}
sections
}
fn output_results(
lens: &InspectLens,
result: &InspectResult,
format: OutputFormat,
_show_result: &ShowParseResult,
) {
let config = InspectDisplayConfig::auto();
match format {
OutputFormat::Json => {
println!("{}", lens.format_json(result, false));
}
OutputFormat::JsonPretty => {
println!("{}", lens.format_json(result, true));
}
OutputFormat::JsonLine => {
for query_result in &result.queries {
if let Ok(json) = serde_json::to_string(query_result) {
println!("{}", json);
}
}
}
OutputFormat::Markdown => {
let config = config.with_markdown(true);
println!("{}", lens.format_table(result, &config));
}
OutputFormat::Psv => {
eprintln!("PSV format is not supported for inspect command. Use --format json or --format table.");
std::process::exit(1);
}
OutputFormat::Table => {
println!("{}", lens.format_table(result, &config));
}
}
}