cryo_cli 0.3.2

cryo is the easiest way to extract blockchain data to parquet, csv, or json
Documentation
use super::{parse_schemas, partitions};
use crate::args::Args;
use cryo_freeze::{Dim, ParseError, Query, QueryLabels, Schemas, Source};
use std::sync::Arc;

/// parse Query struct from cli Args
pub async fn parse_query(args: &Args, source: Arc<Source>) -> Result<Query, ParseError> {
    let (datatypes, schemas) = parse_schemas(args)?;

    let arg_aliases = find_arg_aliases(args, &schemas);
    let new_args =
        if !arg_aliases.is_empty() { Some(apply_arg_aliases(args, arg_aliases)?) } else { None };
    let args = new_args.as_ref().unwrap_or(args);

    let (partitions, partitioned_by, time_dimension) =
        partitions::parse_partitions(args, source, &schemas).await?;
    let datatypes = cryo_freeze::cluster_datatypes(datatypes);
    let labels = QueryLabels { align: args.align, reorg_buffer: args.reorg_buffer };
    Ok(Query {
        datatypes,
        schemas,
        time_dimension,
        partitions,
        partitioned_by,
        exclude_failed: args.exclude_failed,
        js_tracer: args.js_tracer.clone(),
        labels,
    })
}

fn find_arg_aliases(args: &Args, schemas: &Schemas) -> Vec<(Dim, Dim)> {
    // does not currently handle optional args, just required args
    let mut swaps = Vec::new();
    for datatype in schemas.keys() {
        let aliases = datatype.arg_aliases();
        if aliases.is_empty() {
            continue
        }
        for dim in
            datatype.required_parameters().iter().chain(datatype.optional_parameters().iter())
        {
            if args.dim_is_none(dim) {
                for (k, v) in aliases.iter() {
                    if v == dim && args.dim_is_some(k) {
                        swaps.push((*k, *v));
                    }
                }
            }
        }
    }
    swaps
}

trait DimIsNone {
    fn dim_is_some(&self, dim: &Dim) -> bool;
    fn dim_is_none(&self, dim: &Dim) -> bool;
}

impl DimIsNone for Args {
    fn dim_is_some(&self, dim: &Dim) -> bool {
        match dim {
            Dim::BlockNumber => self.blocks.is_some(),
            Dim::TransactionHash => self.txs.is_some(),
            Dim::Address => self.address.is_some(),
            Dim::FromAddress => self.from_address.is_some(),
            Dim::ToAddress => self.to_address.is_some(),
            Dim::Contract => self.contract.is_some(),
            Dim::CallData => self.call_data.is_some(),
            Dim::Slot => self.slot.is_some(),
            Dim::Topic0 => self.topic0.is_some(),
            Dim::Topic1 => self.topic1.is_some(),
            Dim::Topic2 => self.topic2.is_some(),
            Dim::Topic3 => self.topic3.is_some(),
        }
    }

    fn dim_is_none(&self, dim: &Dim) -> bool {
        !self.dim_is_some(dim)
    }
}

fn apply_arg_aliases(args: &Args, arg_aliases: Vec<(Dim, Dim)>) -> Result<Args, ParseError> {
    let mut args = (*args).clone();
    for (k, v) in arg_aliases.iter() {
        args = match (k, v) {
            (Dim::Contract, Dim::Address) => {
                Args { address: args.contract.clone(), contract: None, ..args }
            }
            (Dim::ToAddress, Dim::Address) => {
                Args { address: args.to_address.clone(), to_address: None, ..args }
            }
            (Dim::Address, Dim::Contract) => {
                Args { contract: args.address.clone(), address: None, ..args }
            }
            (Dim::ToAddress, Dim::Contract) => {
                Args { contract: args.to_address.clone(), to_address: None, ..args }
            }
            (Dim::Address, Dim::ToAddress) => {
                Args { to_address: args.address.clone(), address: None, ..args }
            }
            (Dim::Contract, Dim::ToAddress) => {
                Args { to_address: args.contract.clone(), contract: None, ..args }
            }
            _ => return Err(ParseError::ParseError("invalid arg alias pairing".to_string())),
        };
    }
    Ok(args)
}