bee-check 0.1.0

Retrievability checker for Ethereum Swarm references. Multi-vantage stewardship probes, per-chunk drill-down, and one-shot re-seed.
Documentation
//! `bee-check` — retrievability checker for Swarm references.
//!
//! Multi-vantage probe of `GET /stewardship/{ref}` across one or more
//! Bee nodes, with optional per-chunk drill-down and re-seed.

use anyhow::{Context, Result};
use clap::{Parser, ValueEnum};

use bee_check::{
    OutputFormat, ReseedRequest, check_multi_vantage, drill_down, render_report, reseed,
};

#[derive(Parser, Debug)]
#[command(
    name = "bee-check",
    version,
    about = "Retrievability checker for Swarm references",
    long_about = "Probe one or more Bee nodes to determine whether a Swarm \
                  reference is retrievable from the network. Supports \
                  per-chunk drill-down and one-shot re-seed via stewardship."
)]
struct Cli {
    /// Swarm reference (64-hex) to check.
    #[arg(value_name = "REF")]
    reference: String,

    /// Bee API URL(s) to probe. Repeat for multi-vantage. Defaults to
    /// $BEE_API_URL or http://localhost:1633.
    #[arg(short = 'b', long = "bee", value_name = "URL")]
    bee: Vec<String>,

    /// Walk the manifest and probe each chunk per vantage.
    #[arg(long)]
    per_chunk: bool,

    /// After probing, re-upload the reference via PUT /stewardship/{ref}.
    /// Requires --stamp.
    #[arg(long)]
    reseed: bool,

    /// Postage batch ID for re-seed.
    #[arg(long, value_name = "ID", requires = "reseed")]
    stamp: Option<String>,

    /// Per-call timeout in seconds.
    #[arg(long, default_value_t = 60)]
    timeout: u64,

    /// Max concurrent chunk probes during drill-down.
    #[arg(long, default_value_t = 8)]
    concurrency: usize,

    /// Output format.
    #[arg(long, value_enum, default_value_t = OutputKind::Text)]
    output: OutputKind,
}

#[derive(Copy, Clone, Debug, ValueEnum)]
enum OutputKind {
    Text,
    Json,
}

impl From<OutputKind> for OutputFormat {
    fn from(k: OutputKind) -> Self {
        match k {
            OutputKind::Text => OutputFormat::Text,
            OutputKind::Json => OutputFormat::Json,
        }
    }
}

fn default_bees() -> Vec<String> {
    std::env::var("BEE_API_URL")
        .ok()
        .into_iter()
        .chain(std::iter::once("http://localhost:1633".to_string()))
        .take(1)
        .collect()
}

#[tokio::main]
async fn main() -> Result<()> {
    let cli = Cli::parse();

    let bees: Vec<String> = if cli.bee.is_empty() {
        default_bees()
    } else {
        cli.bee.clone()
    };

    let timeout = std::time::Duration::from_secs(cli.timeout);

    let report = check_multi_vantage(&cli.reference, &bees, timeout)
        .await
        .context("multi-vantage check failed")?;

    let report = if cli.per_chunk {
        drill_down(report, &bees, timeout, cli.concurrency)
            .await
            .context("per-chunk drill-down failed")?
    } else {
        report
    };

    print!("{}", render_report(&report, cli.output.into()));

    if cli.reseed {
        let stamp = cli
            .stamp
            .as_ref()
            .context("--reseed requires --stamp <batch-id>")?;
        let target_bee = bees.first().context("no bee URL for --reseed")?;
        let req = ReseedRequest {
            reference: cli.reference.clone(),
            bee_url: target_bee.clone(),
            batch_id: stamp.clone(),
            timeout,
        };
        reseed(req).await.context("re-seed failed")?;
        eprintln!("re-seeded {} via {}", cli.reference, target_bee);
    }

    let any_retrievable = report.vantages.iter().any(|v| v.retrievable == Some(true));
    if !any_retrievable {
        std::process::exit(2);
    }
    Ok(())
}