ic-query 0.10.1

Internet Computer query library for NNS, SNS, ICRC, and related public network metadata
Documentation
//! Module: sns::report::build::proposals
//!
//! Responsibility: build SNS proposal list and detail reports.
//! Does not own: command parsing, cache file primitives, proposal row conversion, or rendering.
//! Boundary: routes cache-backed requests through cache reports and live reads through sources.

use crate::{
    QueryProgress,
    progress::IgnoreQueryProgress,
    sns::report::{
        SnsHostError, SnsProposalReport, SnsProposalRequest, SnsProposalStatusFilter,
        SnsProposalsReport, SnsProposalsRequest,
        assemble::{
            SnsProposalReportParts, SnsProposalsReportParts, SnsReportProvenance,
            sns_proposal_report_from_parts, sns_proposals_report_from_parts,
        },
        live::LiveSnsSource,
        lookup::{lookup_request_from_parts, resolve_sns_lookup},
        proposals_cache::{
            build_sns_proposal_report_from_cache, build_sns_proposals_report_from_cache_or_refresh,
        },
        source::{SnsProposalSource, SnsProposalsSource},
        view::{
            proposal_matches_eligibility, proposal_matches_proposer, proposal_matches_query,
            sort_sns_proposal_rows,
        },
    },
};

pub fn build_sns_proposal_report(
    request: &SnsProposalRequest,
) -> Result<SnsProposalReport, SnsHostError> {
    build_sns_proposal_report_with_source(request, &LiveSnsSource)
}

pub fn build_sns_proposals_report(
    request: &SnsProposalsRequest,
) -> Result<SnsProposalsReport, SnsHostError> {
    build_sns_proposals_report_with_source(request, &LiveSnsSource)
}

/// Build an SNS proposal list report and emit cache-refresh progress when needed.
pub fn build_sns_proposals_report_with_progress(
    request: &SnsProposalsRequest,
    progress: &mut dyn QueryProgress,
) -> Result<SnsProposalsReport, SnsHostError> {
    build_sns_proposals_report_with_source_and_progress(request, &LiveSnsSource, progress)
}

pub fn build_sns_proposal_report_with_source(
    request: &SnsProposalRequest,
    source: &dyn SnsProposalSource,
) -> Result<SnsProposalReport, SnsHostError> {
    if let Some(icp_root) = request.icp_root.as_ref()
        && let Some(report) = build_sns_proposal_report_from_cache(request, icp_root)?
    {
        return Ok(report);
    }
    let lookup_request = lookup_request_from_parts(
        &request.network,
        &request.source_endpoint,
        request.now_unix_secs,
        &request.input,
    );
    let lookup = resolve_sns_lookup(&lookup_request, source)?;
    let proposal =
        source.fetch_sns_proposal(&lookup.fetch_request, &lookup.sns, request.proposal_id)?;
    Ok(sns_proposal_report_from_parts(SnsProposalReportParts {
        list: lookup.list,
        id: lookup.id,
        sns: lookup.sns,
        proposal_id: request.proposal_id,
        verbose: request.verbose,
        show_ballots: request.show_ballots,
        provenance: SnsReportProvenance::live(),
        proposal,
    }))
}

pub fn build_sns_proposals_report_with_source(
    request: &SnsProposalsRequest,
    source: &dyn SnsProposalsSource,
) -> Result<SnsProposalsReport, SnsHostError> {
    let mut progress = IgnoreQueryProgress;
    build_sns_proposals_report_with_source_and_progress(request, source, &mut progress)
}

fn build_sns_proposals_report_with_source_and_progress(
    request: &SnsProposalsRequest,
    source: &dyn SnsProposalsSource,
    progress: &mut dyn QueryProgress,
) -> Result<SnsProposalsReport, SnsHostError> {
    validate_sns_proposals_request(request)?;
    if let Some(icp_root) = request.icp_root.as_ref() {
        return build_sns_proposals_report_from_cache_or_refresh(
            request, icp_root, source, progress,
        );
    }
    build_sns_proposals_report_live(request, source)
}

fn validate_sns_proposals_request(request: &SnsProposalsRequest) -> Result<(), SnsHostError> {
    if request.status == SnsProposalStatusFilter::Decided && request.icp_root.is_none() {
        return Err(SnsHostError::UnsupportedProposalView {
            reason: "`--status decided` requires a complete proposal cache".to_string(),
        });
    }
    Ok(())
}

fn build_sns_proposals_report_live(
    request: &SnsProposalsRequest,
    source: &dyn SnsProposalsSource,
) -> Result<SnsProposalsReport, SnsHostError> {
    let lookup_request = lookup_request_from_parts(
        &request.network,
        &request.source_endpoint,
        request.now_unix_secs,
        &request.input,
    );
    let lookup = resolve_sns_lookup(&lookup_request, source)?;
    let include_status = request
        .status
        .governance_status_code()
        .into_iter()
        .collect::<Vec<_>>();
    let mut proposals = source.fetch_sns_proposals(
        &lookup.fetch_request,
        &lookup.sns,
        request.limit,
        request.before_proposal_id,
        &include_status,
        request.topic,
    )?;
    proposals
        .proposals
        .retain(|proposal| proposal_matches_eligibility(proposal, request.eligibility));
    proposals.proposals.retain(|proposal| {
        proposal_matches_proposer(proposal, request.proposer_neuron_id.as_deref())
    });
    proposals
        .proposals
        .retain(|proposal| proposal_matches_query(proposal, request.query.as_deref()));
    sort_sns_proposal_rows(
        &mut proposals.proposals,
        request.sort,
        request.sort_direction,
    );
    Ok(sns_proposals_report_from_parts(SnsProposalsReportParts {
        list: lookup.list,
        id: lookup.id,
        sns: lookup.sns,
        requested_limit: request.limit,
        before_proposal_id: request.before_proposal_id,
        status: request.status,
        topic: request.topic,
        eligibility: request.eligibility,
        proposer_neuron_id: request.proposer_neuron_id.clone(),
        query: request.query.clone(),
        sort: request.sort,
        sort_direction: request.sort_direction,
        verbose: request.verbose,
        provenance: SnsReportProvenance::live(),
        proposals,
    }))
}