lace_sans_io/core/
listing.rs1use std::collections::BTreeMap;
4use std::sync::Arc;
5
6use super::{AdvertisementRecord, LogicError};
7use crate::records::RecordHash;
8
9pub type ListingEntries = BTreeMap<RecordHash, Arc<AdvertisementRecord>>;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct AdvertisementListing {
23 pub entries: Arc<ListingEntries>,
24 pub(super) canonical_bytes: u64,
25}
26
27impl Default for AdvertisementListing {
28 fn default() -> Self {
29 Self {
30 entries: Arc::default(),
31 canonical_bytes: crate::iltp::EMPTY_LISTING_BYTES,
32 }
33 }
34}
35
36impl AdvertisementListing {
37 #[cfg(test)]
40 pub(super) fn new(entries: ListingEntries) -> Result<Self, LogicError> {
41 let listing = Self {
42 canonical_bytes: crate::iltp::EMPTY_LISTING_BYTES
43 + entries.values().map(|e| e.canonical_bytes()).sum::<u64>(),
44 entries: Arc::new(entries),
45 };
46 listing.check_item_cap()?;
47 Ok(listing)
48 }
49
50 pub fn canonical_bytes(&self) -> u64 {
51 self.canonical_bytes
52 }
53
54 pub(super) fn insert(&mut self, entry: Arc<AdvertisementRecord>) -> Result<(), LogicError> {
56 let hash = entry.record_hash();
57 let bytes = entry.canonical_bytes();
58 if Arc::make_mut(&mut self.entries)
59 .insert(hash, entry)
60 .is_some()
61 {
62 return Err(LogicError::Integration(format!(
63 "listing change adds {hash}, which the listing holds"
64 )));
65 }
66 self.canonical_bytes += bytes;
67 Ok(())
68 }
69
70 pub(super) fn withdraw(
72 &mut self,
73 hash: &RecordHash,
74 ) -> Result<Arc<AdvertisementRecord>, LogicError> {
75 let entry = Arc::make_mut(&mut self.entries)
76 .remove(hash)
77 .ok_or_else(|| {
78 LogicError::Integration(format!(
79 "listing change withdraws {hash}, which the listing lacks"
80 ))
81 })?;
82 self.canonical_bytes -= entry.canonical_bytes();
83 Ok(entry)
84 }
85
86 pub(super) fn check_item_cap(&self) -> Result<(), LogicError> {
89 if self.canonical_bytes > crate::iltp::ITEM_MAX_BYTES as u64 {
90 return Err(LogicError::ResourceLimit(
91 "advertisement listing exceeds ILTP item limit".into(),
92 ));
93 }
94 Ok(())
95 }
96}
97
98#[cfg(test)]
99impl FromIterator<AdvertisementRecord> for AdvertisementListing {
100 fn from_iter<I: IntoIterator<Item = AdvertisementRecord>>(records: I) -> Self {
101 Self::new(
102 records
103 .into_iter()
104 .map(|record| (record.record_hash(), Arc::new(record)))
105 .collect(),
106 )
107 .expect("test listing fits the item cap")
108 }
109}
110
111#[derive(Debug, Clone, PartialEq, Eq)]
114pub enum ListingChange {
115 All,
118 Delta {
119 added: Vec<Arc<AdvertisementRecord>>,
120 withdrawn: Vec<Arc<AdvertisementRecord>>,
121 },
122}
123
124impl Default for ListingChange {
125 fn default() -> Self {
127 Self::Delta {
128 added: Vec::new(),
129 withdrawn: Vec::new(),
130 }
131 }
132}
133
134impl ListingChange {
135 pub(super) fn starting_from(previous: &ListingEntries) -> Self {
138 if previous.is_empty() {
139 Self::All
140 } else {
141 Self::default()
142 }
143 }
144
145 pub(super) fn note_added(&mut self, entry: &Arc<AdvertisementRecord>) {
146 if let Self::Delta { added, .. } = self {
147 added.push(entry.clone());
148 }
149 }
150
151 pub(super) fn note_withdrawn(&mut self, entry: Arc<AdvertisementRecord>) {
152 if let Self::Delta { withdrawn, .. } = self {
153 withdrawn.push(entry);
154 }
155 }
156
157 pub(super) fn added<'a>(
159 &'a self,
160 listing: &'a ListingEntries,
161 ) -> impl Iterator<Item = &'a Arc<AdvertisementRecord>> + 'a {
162 let (all, delta) = match self {
163 Self::All => (Some(listing.values()), None),
164 Self::Delta { added, .. } => (None, Some(added.iter())),
165 };
166 all.into_iter().flatten().chain(delta.into_iter().flatten())
167 }
168
169 pub(super) fn withdrawn(&self) -> &[Arc<AdvertisementRecord>] {
170 match self {
171 Self::All => &[],
172 Self::Delta { withdrawn, .. } => withdrawn,
173 }
174 }
175
176 #[cfg(test)]
178 pub(super) fn between(previous: &ListingEntries, current: &ListingEntries) -> Self {
179 Self::Delta {
180 added: current
181 .iter()
182 .filter(|(hash, _)| !previous.contains_key(hash))
183 .map(|(_, entry)| entry.clone())
184 .collect(),
185 withdrawn: previous
186 .iter()
187 .filter(|(hash, _)| !current.contains_key(hash))
188 .map(|(_, entry)| entry.clone())
189 .collect(),
190 }
191 }
192}