#[cfg(feature = "nns-host")]
use super::NnsProposalHostError;
use super::{
NnsProposalError,
model::{NnsProposalListReport, NnsProposalListRequest},
source::{
NnsProposalSource, build_nns_proposal_list_report_with_source, validate_proposal_page_size,
},
};
#[cfg(feature = "nns-host")]
use crate::nns::LiveNnsSource;
use crate::nns::governance::{
GovernanceCollectionState, NnsGovernanceRequest, governance_collection_accessors,
validate_governance_continuation, validate_governance_request,
};
use serde::{Deserialize, Serialize};
pub const NNS_PROPOSAL_COLLECTION_STATE_SCHEMA_VERSION: u32 = 1;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct NnsProposalCollectionState {
#[serde(flatten)]
collection: GovernanceCollectionState,
proposals_fetched: u64,
next_before_proposal_id: Option<u64>,
}
impl NnsProposalCollectionState {
pub fn new(
request: &NnsGovernanceRequest,
page_size: u32,
max_pages: u32,
) -> Result<Self, NnsProposalError> {
validate_governance_request(request)?;
validate_proposal_page_size(page_size)?;
if max_pages == 0 {
return Err(NnsProposalError::InvalidCollectionMaxPages);
}
Ok(Self {
collection: GovernanceCollectionState::new(request, page_size, max_pages),
proposals_fetched: 0,
next_before_proposal_id: None,
})
}
#[cfg(feature = "nns-host")]
pub(in crate::nns) const fn from_complete_collection(
collection: GovernanceCollectionState,
rows: u64,
) -> Self {
Self {
collection,
proposals_fetched: rows,
next_before_proposal_id: None,
}
}
#[cfg(feature = "nns-host")]
pub(in crate::nns) fn collection_evidence(
&self,
) -> crate::nns::governance::collection::NnsGovernanceCollectionEvidence {
crate::nns::governance::collection::NnsGovernanceCollectionEvidence::from_state(
&self.collection,
)
}
governance_collection_accessors!(next_before_proposal_id);
#[must_use]
pub const fn proposals_fetched(&self) -> u64 {
self.proposals_fetched
}
#[must_use]
pub const fn next_before_proposal_id(&self) -> Option<u64> {
self.next_before_proposal_id
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct NnsProposalCollectionStep {
pub page: NnsProposalListReport,
pub state: NnsProposalCollectionState,
}
#[cfg(feature = "nns-host")]
pub async fn advance_nns_proposal_collection(
request: &NnsGovernanceRequest,
state: &NnsProposalCollectionState,
) -> Result<NnsProposalCollectionStep, NnsProposalHostError> {
Ok(
advance_nns_proposal_collection_with_source(request, state, &LiveNnsSource::default())
.await?,
)
}
pub async fn advance_nns_proposal_collection_with_source(
request: &NnsGovernanceRequest,
state: &NnsProposalCollectionState,
source: &dyn NnsProposalSource,
) -> Result<NnsProposalCollectionStep, NnsProposalError> {
validate_governance_request(request)?;
validate_collection_state(state)?;
validate_governance_continuation!(request, state, NnsProposalError)?;
let mut page_request = NnsProposalListRequest::new(request.clone(), state.collection.page_size);
page_request.before_proposal_id = state.next_before_proposal_id;
let page = build_nns_proposal_list_report_with_source(&page_request, source).await?;
let (collection, proposals_fetched) = state.collection.after_page(
request,
page.context.source.clone(),
state.proposals_fetched,
page.proposals.len(),
|expected, actual| NnsProposalError::CollectionSourceChanged { expected, actual },
NnsProposalError::CollectionAccountingOverflow,
)?;
let page_count = u32::try_from(page.proposals.len())
.map_err(|_| NnsProposalError::CollectionAccountingOverflow)?;
let next_before_proposal_id = (page_count == state.collection.page_size)
.then(|| {
page.proposals
.iter()
.filter_map(|proposal| proposal.proposal_id)
.min()
.filter(|proposal_id| *proposal_id > 1)
})
.flatten();
let next_state = NnsProposalCollectionState {
collection,
proposals_fetched,
next_before_proposal_id,
};
Ok(NnsProposalCollectionStep {
page,
state: next_state,
})
}
pub(super) fn validate_collection_state(
state: &NnsProposalCollectionState,
) -> Result<(), NnsProposalError> {
let invalid = |reason| NnsProposalError::InvalidCollectionState { reason };
state
.collection
.validate(NNS_PROPOSAL_COLLECTION_STATE_SCHEMA_VERSION, invalid)?;
validate_proposal_page_size(state.collection.page_size)?;
let proposals_fetched = state.proposals_fetched;
let maximum_rows =
state
.collection
.validate_row_bounds("proposals_fetched", proposals_fetched, invalid)?;
if state
.next_before_proposal_id
.is_some_and(|cursor| cursor <= 1)
{
return Err(invalid(
"next_before_proposal_id must be greater than one".to_string(),
));
}
state.collection.validate_lifecycle(
proposals_fetched,
maximum_rows,
state.next_before_proposal_id.is_some(),
true,
invalid,
)
}
#[cfg(test)]
mod tests;