1use std::fmt;
13
14use snafu::ensure;
15
16use crate::Result;
17use crate::error;
18
19#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct Party {
28 pub id: String,
29 pub transport_endpoint: Option<String>,
30}
31
32impl Party {
33 pub fn new(id: impl Into<String>, transport_endpoint: Option<String>) -> Self {
34 Party {
35 id: id.into(),
36 transport_endpoint,
37 }
38 }
39
40 pub fn inproc(id: impl Into<String>) -> Self {
43 Party {
44 id: id.into(),
45 transport_endpoint: None,
46 }
47 }
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct PartyList {
57 parties: Vec<Party>,
58}
59
60impl PartyList {
61 pub fn new() -> Self {
62 PartyList {
63 parties: Vec::new(),
64 }
65 }
66
67 pub fn from_parties(parties: Vec<Party>) -> Self {
68 PartyList { parties }
69 }
70
71 pub fn parties(&self) -> &[Party] {
72 &self.parties
73 }
74
75 pub fn len(&self) -> usize {
76 self.parties.len()
77 }
78
79 pub fn is_empty(&self) -> bool {
80 self.parties.is_empty()
81 }
82
83 pub fn push(&mut self, party: Party) {
84 self.parties.push(party);
85 }
86
87 pub fn get(&self, idx: usize) -> Result<&Party> {
90 self.parties.get(idx).ok_or_else(|| {
91 error::PartyIndexOutOfRangeSnafu {
92 idx,
93 count: self.parties.len(),
94 }
95 .build()
96 })
97 }
98
99 pub fn find(&self, id: &str) -> Option<&Party> {
101 self.parties.iter().find(|p| p.id == id)
102 }
103
104 pub fn validate(&self, threshold: u32) -> Result<()> {
107 ensure!(!self.parties.is_empty(), error::EmptyPartyListSnafu {});
108 ensure!(threshold >= 1, error::ThresholdTooSmallSnafu { threshold });
109 ensure!(
110 threshold as usize <= self.parties.len(),
111 error::ThresholdTooLargeSnafu {
112 threshold,
113 party_count: self.parties.len(),
114 }
115 );
116 for (i, p) in self.parties.iter().enumerate() {
117 for q in &self.parties[i + 1..] {
118 ensure!(p.id != q.id, error::DuplicatePartyIdSnafu { id: &p.id });
119 }
120 }
121 Ok(())
122 }
123}
124
125impl Default for PartyList {
126 fn default() -> Self {
127 PartyList::new()
128 }
129}
130
131impl fmt::Display for Party {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 match &self.transport_endpoint {
134 Some(ep) => write!(f, "{}@{}", self.id, ep),
135 None => write!(f, "{}", self.id),
136 }
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn party_inproc_has_no_endpoint() {
146 let p = Party::inproc("node-1");
147 assert_eq!(p.id, "node-1");
148 assert!(p.transport_endpoint.is_none());
149 assert_eq!(format!("{p}"), "node-1");
150 }
151
152 #[test]
153 fn party_display_with_endpoint() {
154 let p = Party::new("node-1", Some("quic://h:443".to_string()));
155 assert_eq!(format!("{p}"), "node-1@quic://h:443");
156 }
157
158 #[test]
159 fn party_list_get_out_of_range_errors() {
160 let list = PartyList::from_parties(vec![Party::inproc("a")]);
161 let err = list.get(5).unwrap_err();
162 assert!(
163 matches!(
164 err,
165 error::Error::PartyIndexOutOfRange {
166 idx: 5,
167 count: 1,
168 ..
169 }
170 ),
171 "expected PartyIndexOutOfRange, got {err:?}"
172 );
173 }
174
175 #[test]
176 fn party_list_validate_rejects_empty() {
177 let list = PartyList::new();
178 assert!(list.validate(1).is_err());
179 }
180
181 #[test]
182 fn party_list_validate_rejects_zero_threshold() {
183 let list = PartyList::from_parties(vec![Party::inproc("a"), Party::inproc("b")]);
184 let err = list.validate(0).unwrap_err();
185 assert!(matches!(
186 err,
187 error::Error::ThresholdTooSmall { threshold: 0, .. }
188 ));
189 }
190
191 #[test]
192 fn party_list_validate_rejects_threshold_above_party_count() {
193 let list = PartyList::from_parties(vec![Party::inproc("a"), Party::inproc("b")]);
194 let err = list.validate(3).unwrap_err();
195 assert!(matches!(
196 err,
197 error::Error::ThresholdTooLarge {
198 threshold: 3,
199 party_count: 2,
200 ..
201 }
202 ));
203 }
204
205 #[test]
206 fn party_list_validate_rejects_duplicate_ids() {
207 let list = PartyList::from_parties(vec![Party::inproc("a"), Party::inproc("a")]);
208 let err = list.validate(1).unwrap_err();
209 assert!(matches!(err, error::Error::DuplicatePartyId { .. }));
210 }
211
212 #[test]
213 fn party_list_validate_accepts_valid_roster() {
214 let list = PartyList::from_parties(vec![
215 Party::inproc("a"),
216 Party::inproc("b"),
217 Party::inproc("c"),
218 ]);
219 list.validate(2).expect("valid roster should pass");
220 }
221
222 #[test]
223 fn party_list_find_by_id() {
224 let list = PartyList::from_parties(vec![Party::inproc("a"), Party::inproc("b")]);
225 assert_eq!(list.find("b").map(|p| p.id.as_str()), Some("b"));
226 assert!(list.find("z").is_none());
227 }
228}