ic-query 0.2.25

Internet Computer query CLI for NNS, SNS, and related public network metadata
Documentation
//! Module: nns::proposals::report::view
//!
//! Responsibility: apply NNS proposal list filters and ordering.
//! Does not own: proposal fetching, report assembly, command parsing, or rendering.
//! Boundary: transforms proposal rows without changing source identity.

use super::model::{
    NnsProposalRow, NnsProposalSortDirection, NnsProposalTopicFilter, NnsProposalsSort,
};
use std::cmp::Ordering;

pub(in crate::nns::proposals::report) fn proposal_matches_topic(
    proposal: &NnsProposalRow,
    topic: NnsProposalTopicFilter,
) -> bool {
    topic
        .topic_code()
        .is_none_or(|topic_code| proposal.topic == topic_code)
}

pub(in crate::nns::proposals::report) fn sort_nns_proposal_rows(
    proposals: &mut [NnsProposalRow],
    sort: NnsProposalsSort,
    direction: NnsProposalSortDirection,
) {
    match sort {
        NnsProposalsSort::Api => {}
        NnsProposalsSort::Id => {
            sort_by_optional_u64(proposals, direction, |proposal| proposal.proposal_id);
        }
        NnsProposalsSort::Status => {
            sort_by_text(proposals, direction, |proposal| {
                proposal.status_text.as_str()
            });
        }
        NnsProposalsSort::Topic => {
            sort_by_text(proposals, direction, |proposal| {
                proposal.topic_text.as_str()
            });
        }
        NnsProposalsSort::Proposer => {
            sort_by_optional_u64(proposals, direction, |proposal| proposal.proposer_neuron_id);
        }
        NnsProposalsSort::Title => {
            sort_by_optional_text(proposals, direction, |proposal| proposal.title.as_deref());
        }
        NnsProposalsSort::Action => {
            sort_by_optional_text(proposals, direction, |proposal| {
                proposal.action_text.as_deref()
            });
        }
        NnsProposalsSort::Yes => {
            sort_by_optional_u64(proposals, direction, |proposal| {
                proposal.latest_tally.as_ref().map(|tally| tally.yes)
            });
        }
        NnsProposalsSort::No => {
            sort_by_optional_u64(proposals, direction, |proposal| {
                proposal.latest_tally.as_ref().map(|tally| tally.no)
            });
        }
        NnsProposalsSort::TotalVotes => {
            sort_by_optional_u64(proposals, direction, |proposal| {
                proposal.latest_tally.as_ref().map(|tally| tally.total)
            });
        }
        NnsProposalsSort::Ballots => {
            sort_by_optional_u64(proposals, direction, |proposal| {
                Some(proposal.ballot_count as u64)
            });
        }
        NnsProposalsSort::RejectCost => {
            sort_by_optional_u64(proposals, direction, |proposal| {
                Some(proposal.reject_cost_e8s)
            });
        }
        NnsProposalsSort::RewardRound => {
            sort_by_optional_u64(proposals, direction, |proposal| {
                Some(proposal.reward_event_round)
            });
        }
        NnsProposalsSort::Proposed => {
            sort_by_optional_u64(proposals, direction, |proposal| {
                Some(proposal.proposal_timestamp_seconds)
            });
        }
        NnsProposalsSort::Decided => {
            sort_by_optional_u64(proposals, direction, |proposal| {
                nonzero_timestamp(proposal.decided_timestamp_seconds)
            });
        }
        NnsProposalsSort::Executed => {
            sort_by_optional_u64(proposals, direction, |proposal| {
                nonzero_timestamp(proposal.executed_timestamp_seconds)
            });
        }
        NnsProposalsSort::Failed => {
            sort_by_optional_u64(proposals, direction, |proposal| {
                nonzero_timestamp(proposal.failed_timestamp_seconds)
            });
        }
    }
}

fn sort_by_optional_u64(
    proposals: &mut [NnsProposalRow],
    direction: NnsProposalSortDirection,
    key: impl Fn(&NnsProposalRow) -> Option<u64>,
) {
    proposals.sort_by(|left, right| {
        compare_optional_u64(key(left), key(right), direction)
            .then_with(|| compare_optional_u64(left.proposal_id, right.proposal_id, direction))
    });
}

fn sort_by_text(
    proposals: &mut [NnsProposalRow],
    direction: NnsProposalSortDirection,
    key: impl Fn(&NnsProposalRow) -> &str,
) {
    proposals.sort_by(|left, right| {
        compare_text(key(left), key(right), direction)
            .then_with(|| compare_optional_u64(left.proposal_id, right.proposal_id, direction))
    });
}

fn sort_by_optional_text(
    proposals: &mut [NnsProposalRow],
    direction: NnsProposalSortDirection,
    key: impl for<'a> Fn(&'a NnsProposalRow) -> Option<&'a str>,
) {
    proposals.sort_by(|left, right| {
        compare_optional_text(key(left), key(right), direction)
            .then_with(|| compare_optional_u64(left.proposal_id, right.proposal_id, direction))
    });
}

fn compare_optional_text(
    left: Option<&str>,
    right: Option<&str>,
    direction: NnsProposalSortDirection,
) -> Ordering {
    match (left, right) {
        (Some(left), Some(right)) => compare_text(left, right, direction),
        (Some(_), None) => Ordering::Less,
        (None, Some(_)) => Ordering::Greater,
        (None, None) => Ordering::Equal,
    }
}

fn compare_text(left: &str, right: &str, direction: NnsProposalSortDirection) -> Ordering {
    let left_key = left.to_ascii_lowercase();
    let right_key = right.to_ascii_lowercase();
    match direction {
        NnsProposalSortDirection::Asc => left_key.cmp(&right_key),
        NnsProposalSortDirection::Desc => right_key.cmp(&left_key),
    }
}

fn compare_optional_u64(
    left: Option<u64>,
    right: Option<u64>,
    direction: NnsProposalSortDirection,
) -> Ordering {
    match (left, right) {
        (Some(left), Some(right)) => compare_ord(left, right, direction),
        (Some(_), None) => Ordering::Less,
        (None, Some(_)) => Ordering::Greater,
        (None, None) => Ordering::Equal,
    }
}

fn compare_ord<T>(left: T, right: T, direction: NnsProposalSortDirection) -> Ordering
where
    T: Ord,
{
    match direction {
        NnsProposalSortDirection::Asc => left.cmp(&right),
        NnsProposalSortDirection::Desc => right.cmp(&left),
    }
}

const fn nonzero_timestamp(timestamp_seconds: u64) -> Option<u64> {
    if timestamp_seconds > 0 {
        Some(timestamp_seconds)
    } else {
        None
    }
}