ic_query/sns/report/model/requests/
proposals.rs1use super::lookup::SnsLookupRequest;
8use crate::report::SortDirection;
9use crate::sns::report::{
10 SnsProposalEligibilityFilter, SnsProposalStatusFilter, SnsProposalTopicFilter, SnsProposalsSort,
11};
12use std::path::{Path, PathBuf};
13use thiserror::Error as ThisError;
14
15#[derive(Clone, Debug, Eq, PartialEq)]
22pub enum SnsProposalCollection {
23 Live,
25 Cached {
27 cache_root: PathBuf,
29 },
30}
31
32#[derive(Clone, Copy, Debug, Eq, PartialEq, ThisError)]
39#[error("`--status decided` requires a complete proposal cache")]
40pub struct SnsProposalViewError;
41
42#[derive(Clone, Debug, Eq, PartialEq)]
49pub struct SnsProposalRequest {
50 pub lookup: SnsLookupRequest,
52 pub proposal_id: u64,
53 pub cache_root: Option<PathBuf>,
54}
55
56impl SnsProposalRequest {
57 #[must_use]
58 pub const fn new(lookup: SnsLookupRequest, proposal_id: u64) -> Self {
59 Self {
60 lookup,
61 proposal_id,
62 cache_root: None,
63 }
64 }
65
66 #[must_use]
67 pub fn with_cache_root(mut self, cache_root: impl Into<PathBuf>) -> Self {
68 self.cache_root = Some(cache_root.into());
69 self
70 }
71}
72
73#[derive(Clone, Debug, Eq, PartialEq)]
80pub struct SnsProposalsRequest {
81 pub lookup: SnsLookupRequest,
83 pub limit: u32,
84 pub before_proposal_id: Option<u64>,
85 status: SnsProposalStatusFilter,
86 pub topic: SnsProposalTopicFilter,
87 pub eligibility: SnsProposalEligibilityFilter,
88 pub proposer_neuron_id: Option<String>,
89 pub query: Option<String>,
90 pub sort: SnsProposalsSort,
91 pub sort_direction: SortDirection,
92 collection: SnsProposalCollection,
93}
94
95impl SnsProposalsRequest {
96 #[must_use]
97 pub fn new(lookup: SnsLookupRequest, limit: u32) -> Self {
98 Self {
99 lookup,
100 limit,
101 before_proposal_id: None,
102 status: SnsProposalStatusFilter::default(),
103 topic: SnsProposalTopicFilter::default(),
104 eligibility: SnsProposalEligibilityFilter::default(),
105 proposer_neuron_id: None,
106 query: None,
107 sort: SnsProposalsSort::default(),
108 sort_direction: SortDirection::default(),
109 collection: SnsProposalCollection::Live,
110 }
111 }
112
113 #[must_use]
114 pub const fn with_before_proposal_id(mut self, before_proposal_id: u64) -> Self {
115 self.before_proposal_id = Some(before_proposal_id);
116 self
117 }
118
119 pub fn with_status(
121 mut self,
122 status: SnsProposalStatusFilter,
123 ) -> Result<Self, SnsProposalViewError> {
124 if status == SnsProposalStatusFilter::Decided
125 && matches!(self.collection, SnsProposalCollection::Live)
126 {
127 return Err(SnsProposalViewError);
128 }
129 self.status = status;
130 Ok(self)
131 }
132
133 #[must_use]
134 pub const fn with_topic(mut self, topic: SnsProposalTopicFilter) -> Self {
135 self.topic = topic;
136 self
137 }
138
139 #[must_use]
140 pub const fn with_eligibility(mut self, eligibility: SnsProposalEligibilityFilter) -> Self {
141 self.eligibility = eligibility;
142 self
143 }
144
145 #[must_use]
146 pub fn with_proposer_neuron_id(mut self, proposer_neuron_id: impl Into<String>) -> Self {
147 self.proposer_neuron_id = Some(proposer_neuron_id.into());
148 self
149 }
150
151 #[must_use]
152 pub fn with_query(mut self, query: impl Into<String>) -> Self {
153 self.query = Some(query.into());
154 self
155 }
156
157 #[must_use]
158 pub const fn with_sort(mut self, sort: SnsProposalsSort) -> Self {
159 self.sort = sort;
160 self.sort_direction = sort.default_direction();
161 self
162 }
163
164 #[must_use]
165 pub const fn with_sort_direction(mut self, sort_direction: SortDirection) -> Self {
166 self.sort_direction = sort_direction;
167 self
168 }
169
170 #[must_use]
171 pub fn with_cache_root(mut self, cache_root: impl Into<PathBuf>) -> Self {
172 self.collection = SnsProposalCollection::Cached {
173 cache_root: cache_root.into(),
174 };
175 self
176 }
177
178 #[must_use]
180 pub const fn collection(&self) -> &SnsProposalCollection {
181 &self.collection
182 }
183
184 #[must_use]
186 pub const fn status(&self) -> SnsProposalStatusFilter {
187 self.status
188 }
189
190 #[must_use]
192 pub fn cache_root(&self) -> Option<&Path> {
193 match &self.collection {
194 SnsProposalCollection::Live => None,
195 SnsProposalCollection::Cached { cache_root } => Some(cache_root),
196 }
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn decided_filter_requires_an_explicit_complete_cache_route() {
206 let lookup = SnsLookupRequest::new("ic", "https://icp-api.io", 0, "aaaaa-aa").unwrap();
207 let live = SnsProposalsRequest::new(lookup.clone(), 10);
208 assert!(matches!(live.collection(), SnsProposalCollection::Live));
209 assert_eq!(
210 live.with_status(SnsProposalStatusFilter::Decided),
211 Err(SnsProposalViewError)
212 );
213 let cached = SnsProposalsRequest::new(lookup, 10)
214 .with_cache_root("cache")
215 .with_status(SnsProposalStatusFilter::Decided)
216 .unwrap();
217 assert!(
218 matches!(cached.collection(), SnsProposalCollection::Cached { cache_root } if cache_root == Path::new("cache"))
219 );
220 assert_eq!(cached.status(), SnsProposalStatusFilter::Decided);
221 }
222}