ic-query 0.1.13

Internet Computer query CLI for NNS, SNS, and related public network metadata
Documentation
use super::super::{SnsProposalReport, SnsProposalRow, SnsProposalsReport};
use super::common::{optional_text, optional_u64_text, truncate_text_value};
use crate::{
    nns::render::yes_no,
    table::{ColumnAlign, render_table},
    token_amount::e8s_decimal_text,
};

const SNS_PROPOSAL_TITLE_TEXT_LIMIT: usize = 96;
const SNS_PROPOSAL_DETAIL_TEXT_LIMIT: usize = 240;

#[must_use]
pub fn sns_proposal_report_text(report: &SnsProposalReport) -> String {
    let mut lines = vec![
        format!("network: {}", report.network),
        format!("sns_id: {}", report.id),
        format!("name: {}", report.name),
        format!("root_canister_id: {}", report.root_canister_id),
        format!("governance_canister_id: {}", report.governance_canister_id),
        format!("proposal_id: {}", report.proposal_id),
        format!("verbose: {}", yes_no(report.verbose)),
        format!("sns_wasm_canister_id: {}", report.sns_wasm_canister_id),
        format!("fetched_at: {}", report.fetched_at),
        format!("source_endpoint: {}", report.source_endpoint),
        String::new(),
        "proposal:".to_string(),
    ];
    let detail_limit = (!report.verbose).then_some(SNS_PROPOSAL_DETAIL_TEXT_LIMIT);
    lines.extend(sns_proposal_detail_lines(&report.proposal, detail_limit));
    lines.join("\n")
}

#[must_use]
pub fn sns_proposals_report_text(report: &SnsProposalsReport) -> String {
    let mut lines = vec![
        format!("network: {}", report.network),
        format!("sns_id: {}", report.id),
        format!("name: {}", report.name),
        format!("root_canister_id: {}", report.root_canister_id),
        format!("governance_canister_id: {}", report.governance_canister_id),
        format!("requested_limit: {}", report.requested_limit),
        format!(
            "before_proposal_id: {}",
            optional_u64_text(report.before_proposal_id)
        ),
        format!("status_filter: {}", report.status_filter),
        format!("verbose: {}", yes_no(report.verbose)),
        format!("proposal_count: {}", report.proposal_count),
        format!("sns_wasm_canister_id: {}", report.sns_wasm_canister_id),
        format!("fetched_at: {}", report.fetched_at),
        format!("source_endpoint: {}", report.source_endpoint),
    ];
    if !report.proposals.is_empty() {
        lines.push(String::new());
        lines.push(render_table(
            &["ID", "ACTION", "DECISION", "CREATED_AT", "TITLE"],
            &report
                .proposals
                .iter()
                .map(|proposal| {
                    [
                        optional_u64_text(proposal.proposal_id),
                        proposal.action.clone(),
                        proposal.decision_state.clone(),
                        proposal.created_at.clone(),
                        proposal_title_for_list(proposal, report.verbose),
                    ]
                })
                .collect::<Vec<_>>(),
            &[
                ColumnAlign::Right,
                ColumnAlign::Left,
                ColumnAlign::Left,
                ColumnAlign::Left,
                ColumnAlign::Left,
            ],
        ));
    }
    if report.verbose && !report.proposals.is_empty() {
        lines.push(String::new());
        lines.push("proposal_details:".to_string());
        for proposal in &report.proposals {
            lines.extend(sns_proposal_detail_lines(
                proposal,
                Some(SNS_PROPOSAL_DETAIL_TEXT_LIMIT),
            ));
        }
    }
    lines.join("\n")
}

fn proposal_title_for_list(proposal: &SnsProposalRow, verbose: bool) -> String {
    if verbose {
        proposal.title.clone()
    } else {
        truncate_text_value(&proposal.title, SNS_PROPOSAL_TITLE_TEXT_LIMIT)
    }
}

fn sns_proposal_detail_lines(
    proposal: &SnsProposalRow,
    detail_limit: Option<usize>,
) -> Vec<String> {
    let mut lines = vec![
        format!("- proposal_id: {}", optional_u64_text(proposal.proposal_id)),
        format!("  action_id: {}", proposal.action_id),
        format!("  action: {}", proposal.action),
        format!("  decision_state: {}", proposal.decision_state),
        format!("  title: {}", proposal.title),
        format!("  url: {}", optional_text(proposal.url.as_ref())),
        format!(
            "  proposer_neuron_id: {}",
            optional_text(proposal.proposer_neuron_id.as_ref())
        ),
        format!(
            "  reject_cost: {}",
            e8s_decimal_text(proposal.reject_cost_e8s)
        ),
        format!("  created_at: {}", proposal.created_at),
        format!(
            "  decided_at: {}",
            optional_text(proposal.decided_at.as_ref())
        ),
        format!(
            "  executed_at: {}",
            optional_text(proposal.executed_at.as_ref())
        ),
        format!(
            "  failed_at: {}",
            optional_text(proposal.failed_at.as_ref())
        ),
        format!("  reward_event_round: {}", proposal.reward_event_round),
        format!(
            "  is_eligible_for_rewards: {}",
            yes_no(proposal.is_eligible_for_rewards)
        ),
        format!("  ballot_count: {}", proposal.ballot_count),
    ];
    if let Some(tally) = proposal.latest_tally.as_ref() {
        lines.extend([
            format!("  tally_yes: {}", tally.yes),
            format!("  tally_no: {}", tally.no),
            format!("  tally_total: {}", tally.total),
        ]);
    }
    if let Some(reason) = proposal.failure_reason.as_ref() {
        lines.extend([
            format!("  failure_error_type: {}", reason.error_type),
            format!("  failure_error_message: {}", reason.error_message),
        ]);
    }
    if !proposal.summary.is_empty() {
        lines.push(format!(
            "  summary: {}",
            proposal_detail_text(&proposal.summary, detail_limit)
        ));
    }
    if let Some(rendering) = proposal.payload_text_rendering.as_ref() {
        lines.push(format!(
            "  payload_text_rendering: {}",
            proposal_detail_text(rendering, detail_limit)
        ));
    }
    lines
}

fn proposal_detail_text(value: &str, detail_limit: Option<usize>) -> String {
    detail_limit.map_or_else(
        || value.to_string(),
        |limit| truncate_text_value(value, limit),
    )
}