parx-cli 0.1.0

CLI tool for building and inspecting PARX sidecar files
/*
 * Copyright 2026 PARX Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
//! Verify command - validate checksums in a .parx file.

use anyhow::{Context, Result};
use parx_rs::ParxReader;

use super::storage;

/// Run the verify command.
pub async fn run(input: &str, with_source: bool) -> Result<()> {
    println!("Verifying: {}", input);

    // Read PARX file
    let parx_bytes = storage::read_all(input)
        .await
        .context("Failed to read PARX file")?;

    // Parse PARX file (this validates internal checksums)
    let reader = ParxReader::open(&parx_bytes).context("PARX file validation failed")?;

    println!(" Header magic: OK");
    println!(" Trailer magic: OK");
    println!(" Manifest CRC32C: OK");
    println!(" Footer CRC32C: OK");

    // Show compression status
    if reader.is_compressed() {
        if let Some(algo) = reader.compression_algorithm() {
            println!(" Decompression ({}): OK", algo);
        }
    }

    if with_source {
        let manifest = reader.manifest();

        if manifest.source_uri.is_empty() {
            println!();
            println!("Warning: No source URI in manifest, cannot verify against source");
        } else {
            println!();
            println!("Verifying against source: {}", manifest.source_uri);

            // Get source file size
            let source_meta = storage::head(&manifest.source_uri)
                .await
                .context("Failed to get source file metadata")?;

            // Validate size
            if reader.validate_source_size(source_meta.size as u64) {
                println!(" Source size: OK ({} bytes)", source_meta.size);
            } else {
                println!(
                    " Source size: MISMATCH (expected {}, got {})",
                    manifest.source_size, source_meta.size
                );
                anyhow::bail!("Source file size mismatch - PARX file is stale");
            }

            // Extract and verify source footer
            let (source_footer, _) = storage::extract_parquet_footer(&manifest.source_uri)
                .await
                .context("Failed to extract source Parquet footer")?;

            if reader.validate_source_footer(&source_footer) {
                println!(" Source footer hash: OK");
            } else {
                println!(" Source footer hash: MISMATCH");
                anyhow::bail!("Source footer hash mismatch - PARX file is stale");
            }
        }
    }

    println!();
    println!("Verification passed.");

    Ok(())
}