eventcore_postgres/projections/
config.rs1use std::future::Future;
2use std::pin::Pin;
3use std::sync::Arc;
4use std::time::Duration;
5
6use eventcore_types::{BatchSize, ProjectionSelection};
7use thiserror::Error;
8use tokio_util::sync::CancellationToken;
9
10pub trait ProjectionRetrySleeper: std::fmt::Debug + Send + Sync {
16 fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>;
18}
19
20pub trait ProjectionPollSleeper: std::fmt::Debug + Send + Sync {
22 fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>;
24}
25
26#[derive(Debug, Clone, Copy, Default)]
28pub struct TokioProjectionRetrySleeper;
29
30impl ProjectionRetrySleeper for TokioProjectionRetrySleeper {
31 fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
32 Box::pin(tokio::time::sleep(duration))
33 }
34}
35
36#[derive(Debug, Clone, Copy, Default)]
38pub struct TokioProjectionPollSleeper;
39
40impl ProjectionPollSleeper for TokioProjectionPollSleeper {
41 fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
42 Box::pin(tokio::time::sleep(duration))
43 }
44}
45
46#[derive(Debug, Clone, PartialEq)]
48pub struct ProjectionRetryPolicy {
49 max_retries: u32,
50 initial_delay: Duration,
51 multiplier: f64,
52 maximum_delay: Duration,
53}
54
55impl ProjectionRetryPolicy {
56 pub fn new(
68 max_retries: u32,
69 initial_delay: Duration,
70 multiplier: f64,
71 maximum_delay: Duration,
72 ) -> Result<Self, ProjectionConfigurationError> {
73 if max_retries == u32::MAX {
74 return Err(ProjectionConfigurationError::TooManyRetries);
75 }
76 if !multiplier.is_finite() || multiplier < 1.0 {
77 return Err(ProjectionConfigurationError::InvalidRetryMultiplier);
78 }
79
80 Ok(Self {
81 max_retries,
82 initial_delay,
83 multiplier,
84 maximum_delay,
85 })
86 }
87
88 pub fn max_retries(&self) -> u32 {
90 self.max_retries
91 }
92
93 pub fn initial_delay(&self) -> Duration {
95 self.initial_delay
96 }
97
98 pub fn multiplier(&self) -> f64 {
100 self.multiplier
101 }
102
103 pub fn maximum_delay(&self) -> Duration {
105 self.maximum_delay
106 }
107}
108
109#[derive(Debug, Clone)]
111#[non_exhaustive]
112pub enum PostgresProjectionMode {
113 Batch,
115 Continuous(CancellationToken),
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Error)]
124#[non_exhaustive]
125pub enum ProjectionConfigurationError {
126 #[error("transactional projection batch size must be positive")]
128 ZeroBatchSize,
129 #[error("continuous poll interval must be positive")]
131 ZeroContinuousPollInterval,
132 #[error("retry multiplier must be finite and at least one")]
134 InvalidRetryMultiplier,
135 #[error("retry count is too large to represent the initial attempt plus retries")]
137 TooManyRetries,
138}
139
140#[derive(Debug, Clone)]
142pub struct PostgresProjectionConfig {
143 selection: ProjectionSelection,
144 batch_size: BatchSize,
145 mode: PostgresProjectionMode,
146 retry_policy: ProjectionRetryPolicy,
147 retry_sleeper: Arc<dyn ProjectionRetrySleeper>,
148 poll_sleeper: Arc<dyn ProjectionPollSleeper>,
149 continuous_poll_interval: Duration,
150}
151
152impl PostgresProjectionConfig {
153 pub fn new(selection: ProjectionSelection) -> Self {
160 Self {
161 selection,
162 batch_size: BatchSize::new(100),
163 mode: PostgresProjectionMode::Batch,
164 retry_policy: ProjectionRetryPolicy {
165 max_retries: 0,
166 initial_delay: Duration::from_millis(100),
167 multiplier: 2.0,
168 maximum_delay: Duration::from_secs(30),
169 },
170 retry_sleeper: Arc::new(TokioProjectionRetrySleeper),
171 poll_sleeper: Arc::new(TokioProjectionPollSleeper),
172 continuous_poll_interval: Duration::from_secs(1),
173 }
174 }
175
176 pub fn continuous(mut self, cancellation: CancellationToken) -> Self {
181 self.mode = PostgresProjectionMode::Continuous(cancellation);
182 self
183 }
184
185 pub fn with_batch_size(
191 mut self,
192 batch_size: BatchSize,
193 ) -> Result<Self, ProjectionConfigurationError> {
194 if usize::from(batch_size) == 0 {
195 return Err(ProjectionConfigurationError::ZeroBatchSize);
196 }
197
198 self.batch_size = batch_size;
199 Ok(self)
200 }
201
202 pub fn with_retry_policy(mut self, retry_policy: ProjectionRetryPolicy) -> Self {
204 self.retry_policy = retry_policy;
205 self
206 }
207
208 pub fn with_retry_sleeper(
210 mut self,
211 retry_sleeper: impl ProjectionRetrySleeper + 'static,
212 ) -> Self {
213 self.retry_sleeper = Arc::new(retry_sleeper);
214 self
215 }
216
217 pub fn with_poll_sleeper(mut self, poll_sleeper: impl ProjectionPollSleeper + 'static) -> Self {
219 self.poll_sleeper = Arc::new(poll_sleeper);
220 self
221 }
222
223 pub fn with_continuous_poll_interval(
230 mut self,
231 continuous_poll_interval: Duration,
232 ) -> Result<Self, ProjectionConfigurationError> {
233 if continuous_poll_interval.is_zero() {
234 return Err(ProjectionConfigurationError::ZeroContinuousPollInterval);
235 }
236
237 self.continuous_poll_interval = continuous_poll_interval;
238 Ok(self)
239 }
240
241 pub fn selection(&self) -> &ProjectionSelection {
243 &self.selection
244 }
245
246 pub fn batch_size(&self) -> BatchSize {
248 self.batch_size
249 }
250
251 pub fn mode(&self) -> &PostgresProjectionMode {
253 &self.mode
254 }
255
256 pub fn retry_policy(&self) -> &ProjectionRetryPolicy {
258 &self.retry_policy
259 }
260
261 pub fn retry_sleeper(&self) -> &(dyn ProjectionRetrySleeper + 'static) {
263 self.retry_sleeper.as_ref()
264 }
265
266 pub fn poll_sleeper(&self) -> &(dyn ProjectionPollSleeper + 'static) {
268 self.poll_sleeper.as_ref()
269 }
270
271 pub fn continuous_poll_interval(&self) -> Duration {
273 self.continuous_poll_interval
274 }
275}