1#![deny(missing_docs)]
2
3use std::{
54 error::Error,
55 sync::{
56 atomic::{AtomicU64, Ordering},
57 Arc,
58 },
59};
60
61use async_nats::{
62 jetstream::{
63 self,
64 consumer::PullConsumer,
65 context::{
66 CreateKeyValueErrorKind, CreateStreamError, GetStreamByNameErrorKind,
67 GetStreamErrorKind, KeyValueErrorKind,
68 },
69 kv::{self, Operation, Store},
70 stream::ConsumerError,
71 Context,
72 },
73 subject::ToSubject,
74 Client, ConnectError, ConnectErrorKind,
75};
76use futures::StreamExt;
77use tokio::task::JoinHandle;
78
79pub use async_nats;
80
81pub mod subject;
83
84#[derive(Clone, serde::Deserialize)]
89pub struct Config {
90 app_name: Option<String>,
91 host: Option<String>,
92 port: Option<u16>,
93 #[serde(skip)]
94 address: Option<String>,
95 sys_locks: Option<String>,
96 events_stream_name: Option<String>,
97 events_stream_subject: Option<String>,
98}
99
100impl Config {
101 pub fn new() -> envy::Result<Self> {
103 ConfigBuilder::default().build()
104 }
105
106 pub fn builder<'a>() -> ConfigBuilder<'a> {
108 ConfigBuilder::default()
109 }
110
111 pub fn address(&self) -> &str {
113 self.address.as_deref().unwrap()
114 }
115
116 pub fn port(&self) -> u16 {
118 self.port.unwrap_or(3000)
119 }
120
121 pub fn sys_locks(&self) -> &str {
123 self.sys_locks.as_deref().unwrap_or("SYS_LOCKS")
124 }
125
126 pub fn events_stream_name(&self) -> &str {
128 self.events_stream_name.as_deref().unwrap_or("EVENTS")
129 }
130
131 pub fn events_stream_subject(&self) -> &str {
133 self.events_stream_subject.as_deref().unwrap_or("ev.>")
134 }
135}
136
137#[derive(Default)]
139pub struct ConfigBuilder<'a> {
140 prefix: Option<&'a str>,
141}
142
143impl<'a> ConfigBuilder<'a> {
144 pub fn with_prefix(mut self, prefix: &'a str) -> Self {
146 self.prefix = Some(prefix);
147 self
148 }
149
150 pub fn build(self) -> envy::Result<Config> {
152 let prefix = self.prefix.unwrap_or("NATS_");
153 let mut cfg: Config = envy::prefixed(prefix).from_env()?;
154 if cfg.app_name.is_none() {
155 cfg.app_name = Some("edd-service-rs".into());
156 }
157 let host = cfg.host.as_deref().unwrap_or("127.0.0.1");
158 let port = cfg.port.unwrap_or(4222);
159 cfg.address = Some(format!("nats://{}:{}", host, port));
160 Ok(cfg)
161 }
162}
163
164pub struct Inner {
166 client: Client,
167 config: Config,
168}
169
170#[derive(Clone)]
175pub struct Nats {
176 inner: Arc<Inner>,
177}
178
179impl Nats {
180 pub async fn new(config: Config) -> Result<Self, ConnectError> {
182 let client = async_nats::ConnectOptions::new()
183 .max_reconnects(Some(1))
184 .connect(config.address())
185 .await?;
186 Ok(Self {
187 inner: Arc::new(Inner { client, config }),
188 })
189 }
190
191 pub fn client(&self) -> &Client {
193 &self.inner.client
194 }
195
196 pub fn config(&self) -> &Config {
198 &self.inner.config
199 }
200
201 pub async fn publisher(&self) -> Result<Publisher, CreateStreamError> {
203 let ctx = jetstream::new(self.inner.client.clone());
204 let p = Publisher { ctx };
205 p.init(&self.inner.config).await?;
206 Ok(p)
207 }
208
209 pub async fn sys_consumer(&self, name: String) -> Result<PullConsumer, ConsumerError> {
211 let ctx = jetstream::new(self.inner.client.clone());
212 ctx.create_consumer_on_stream(
213 jetstream::consumer::pull::Config {
214 durable_name: Some(name),
215 ..Default::default()
216 },
217 self.inner.config.events_stream_name(),
218 )
219 .await
220 }
221
222 pub async fn sys_consumer_with_filter(
224 &self,
225 name: String,
226 filter_subject: String,
227 ) -> Result<PullConsumer, ConsumerError> {
228 let ctx = jetstream::new(self.inner.client.clone());
229 ctx.create_consumer_on_stream(
230 jetstream::consumer::pull::Config {
231 durable_name: Some(name),
232 filter_subject,
233 ..Default::default()
234 },
235 self.inner.config.events_stream_name(),
236 )
237 .await
238 }
239
240 pub async fn sys_consumer_with_filters(
242 &self,
243 name: String,
244 filter_subjects: Vec<String>,
245 ) -> Result<PullConsumer, ConsumerError> {
246 let ctx = jetstream::new(self.inner.client.clone());
247 ctx.create_consumer_on_stream(
248 jetstream::consumer::pull::Config {
249 durable_name: Some(name),
250 filter_subjects,
251 ..Default::default()
252 },
253 self.inner.config.events_stream_name(),
254 )
255 .await
256 }
257
258 pub async fn tmp_sys_consumer_with_filter(
260 &self,
261 filter_subject: String,
262 ) -> Result<PullConsumer, ConsumerError> {
263 let ctx = jetstream::new(self.inner.client.clone());
264 ctx.create_consumer_on_stream(
265 jetstream::consumer::pull::Config {
266 filter_subject,
267 deliver_policy: jetstream::consumer::DeliverPolicy::Last,
268 ..Default::default()
269 },
270 self.inner.config.events_stream_name(),
271 )
272 .await
273 }
274
275 pub async fn distributed_locks(&self) -> Result<DistributedLocks, DistributedLocksError> {
277 let ctx = jetstream::new(self.inner.client.clone());
278 DistributedLocks::new(ctx, &self.inner.config).await
279 }
280
281 pub fn sequence_manager(&self) -> SequenceManager {
283 let ctx = jetstream::new(self.inner.client.clone());
284 SequenceManager { ctx }
285 }
286}
287
288pub trait EventToSubject<M> {
293 fn event_to_subject(&self) -> async_nats::Subject;
295}
296
297pub struct Publisher {
302 ctx: Context,
303}
304
305impl Publisher {
306 async fn init(&self, config: &Config) -> Result<(), CreateStreamError> {
307 let stream_name = self
308 .ctx
309 .stream_by_subject(config.events_stream_subject().to_string())
310 .await;
311 if let Err(err) = stream_name {
312 if matches!(err.kind(), GetStreamByNameErrorKind::NotFound) {
313 self.ctx
314 .create_stream(jetstream::stream::Config {
315 name: config.events_stream_name().to_string(),
316 subjects: vec![config.events_stream_subject().into()],
317 allow_direct: true,
318 deny_delete: true,
319 deny_purge: true,
320 ..Default::default()
321 })
322 .await?;
323 }
324 }
325 Ok(())
326 }
327
328 pub async fn publish<S: ToSubject, P: ?Sized + serde::Serialize>(
330 &self,
331 subject: S,
332 payload: &P,
333 ) -> anyhow::Result<()> {
334 self.ctx
335 .publish(subject, serde_json::to_vec(payload)?.into())
336 .await?;
337 Ok(())
338 }
339
340 pub async fn publish_event<S, M, P>(&self, subject: &S, payload: &P) -> anyhow::Result<()>
342 where
343 S: ?Sized + EventToSubject<M>,
344 P: ?Sized + serde::Serialize,
345 {
346 self.ctx
347 .publish(
348 subject.event_to_subject(),
349 serde_json::to_vec(payload)?.into(),
350 )
351 .await?;
352 Ok(())
353 }
354
355 pub async fn publish_event_with_headers<S, M, P>(
357 &self,
358 subject: &S,
359 payload: &P,
360 headers: async_nats::HeaderMap,
361 ) -> anyhow::Result<()>
362 where
363 S: ?Sized + EventToSubject<M>,
364 P: ?Sized + serde::Serialize,
365 {
366 self.ctx
367 .publish_with_headers(
368 subject.event_to_subject(),
369 headers,
370 serde_json::to_vec(payload)?.into(),
371 )
372 .await?;
373 Ok(())
374 }
375}
376
377impl AsRef<Context> for Publisher {
378 fn as_ref(&self) -> &Context {
379 &self.ctx
380 }
381}
382
383#[derive(thiserror::Error, Debug)]
385pub enum DistributedLocksError {
386 #[error(transparent)]
388 Connect(#[from] async_nats::error::Error<ConnectErrorKind>),
389 #[error(transparent)]
391 CreateKeyValue(#[from] async_nats::error::Error<CreateKeyValueErrorKind>),
392 #[error(transparent)]
394 KeyValue(#[from] async_nats::error::Error<KeyValueErrorKind>),
395}
396
397#[derive(Clone)]
401pub struct DistributedLocks {
402 ctx: Context,
403 sys_locks: String,
404}
405
406impl DistributedLocks {
407 async fn new(ctx: Context, config: &Config) -> Result<Self, DistributedLocksError> {
408 let lm = DistributedLocks {
409 ctx,
410 sys_locks: config.sys_locks().to_string(),
411 };
412 if !lm.exists(config.sys_locks()).await? {
413 lm.create(config.sys_locks(), 5).await?;
414 }
415 Ok(lm)
416 }
417
418 async fn create<T: Into<String>>(
419 &self,
420 name: T,
421 max_age: u64,
422 ) -> Result<Store, DistributedLocksError> {
423 Ok(self
424 .ctx
425 .create_key_value(kv::Config {
426 bucket: name.into(),
427 max_age: std::time::Duration::from_secs(max_age),
428 history: 1,
429 ..Default::default()
430 })
431 .await?)
432 }
433
434 async fn exists<T: Into<String>>(&self, bucket: T) -> Result<bool, DistributedLocksError> {
435 if let Err(err) = self.ctx.get_key_value(bucket).await {
436 if err.kind() == KeyValueErrorKind::GetBucket {
437 if let Some(src) = err.source() {
438 let err = src.downcast_ref::<async_nats::error::Error<GetStreamErrorKind>>();
439 if let Some(err) = err {
440 if let GetStreamErrorKind::JetStream(err) = err.kind() {
441 if err.code() == 404 {
442 return Ok(false);
443 }
444 }
445 }
446 }
447 }
448 Err(err)?;
449 }
450 Ok(true)
451 }
452
453 pub async fn sys_locks(&self) -> anyhow::Result<LockManager> {
455 let kv = self.ctx.get_key_value(&self.sys_locks).await?;
456 Ok(LockManager { kv: Arc::new(kv) })
457 }
458}
459
460#[derive(thiserror::Error, Debug)]
462pub enum LockManagerError {
463 #[error(transparent)]
465 CreateKeyValue(#[from] async_nats::error::Error<CreateKeyValueErrorKind>),
466 #[error(transparent)]
468 KeyValue(#[from] async_nats::error::Error<KeyValueErrorKind>),
469 #[error(transparent)]
471 Watch(#[from] async_nats::error::Error<kv::WatchErrorKind>),
472 #[error("unable to lock resource after {0:?}")]
474 OutOfRetries(std::time::Duration),
475}
476
477#[derive(thiserror::Error, Debug)]
479pub enum SequenceManagerError {
480 #[error(transparent)]
482 Connect(#[from] async_nats::error::Error<ConnectErrorKind>),
483 #[error(transparent)]
485 CreateKeyValue(#[from] async_nats::error::Error<CreateKeyValueErrorKind>),
486 #[error(transparent)]
488 KeyValue(#[from] async_nats::error::Error<KeyValueErrorKind>),
489 #[error(transparent)]
491 Put(#[from] async_nats::error::Error<async_nats::jetstream::kv::PutErrorKind>),
492 #[error(transparent)]
494 Entry(#[from] async_nats::error::Error<async_nats::jetstream::kv::EntryErrorKind>),
495}
496
497pub struct SequenceManager {
502 ctx: Context,
503}
504
505impl SequenceManager {
506 async fn create<T: Into<String>>(&self, name: T) -> Result<Store, SequenceManagerError> {
507 Ok(self
508 .ctx
509 .create_key_value(kv::Config {
510 bucket: name.into(),
511 ..Default::default()
512 })
513 .await?)
514 }
515
516 async fn exists<T: Into<String>>(&self, bucket: T) -> Result<bool, SequenceManagerError> {
517 if let Err(err) = self.ctx.get_key_value(bucket).await {
518 if err.kind() == KeyValueErrorKind::GetBucket {
519 if let Some(src) = err.source() {
520 let err = src.downcast_ref::<async_nats::error::Error<GetStreamErrorKind>>();
521 if let Some(err) = err {
522 if let GetStreamErrorKind::JetStream(err) = err.kind() {
523 if err.code() == 404 {
524 return Ok(false);
525 }
526 }
527 }
528 }
529 }
530 Err(err)?;
531 }
532 Ok(true)
533 }
534
535 async fn get<T: Into<String>>(&self, bucket: T) -> Result<Store, SequenceManagerError> {
536 Ok(self.ctx.get_key_value(bucket).await?)
537 }
538
539 pub async fn next(&self, prefix: &str, id: i64) -> Result<i64, SequenceManagerError> {
541 let bucket = format!("sm-{prefix}");
542 if !self.exists(&bucket).await? {
543 let store = self.create(&bucket).await?;
544 let result = store.put("id", id.to_be_bytes().to_vec().into()).await?;
545 Ok(result as i64)
546 } else {
547 let store = self.get(&bucket).await?;
548 let e = store.entry("id").await?;
549 if let Some(e) = e {
550 Ok(e.revision as i64)
551 } else {
552 let result = store.put("id", id.to_be_bytes().to_vec().into()).await?;
553 Ok(result as i64)
554 }
555 }
556 }
557
558 pub async fn increment(&self, prefix: &str, id: i64) -> Result<i64, SequenceManagerError> {
560 let bucket = format!("sm-{prefix}");
561 let store = self.get(&bucket).await?;
562 let e = store.put("id", id.to_be_bytes().to_vec().into()).await?;
563 Ok(e as i64)
564 }
565}
566
567pub struct LockManager {
573 kv: Arc<Store>,
574}
575
576impl LockManager {
577 pub async fn run_locked<N, O, F, E>(&self, name: N, f: F) -> Result<O, E>
582 where
583 N: Into<String>,
584 F: std::future::Future<Output = Result<O, E>>,
585 E: From<LockManagerError>,
586 {
587 let lock = self.try_lock(name.into(), 3, 5).await?;
588 let result = f.await;
589 let w_kv = self.kv.clone();
590 tokio::spawn(async move {
591 if !lock.jh.is_finished() {
592 lock.jh.abort();
593 let result = lock.jh.await;
594 if let Err(err) = result {
595 if !err.is_cancelled() {
596 tracing::error!("{err:#?}");
597 }
598 }
599 }
600 w_kv.delete_expect_revision(lock.name, Some(lock.revision.load(Ordering::SeqCst)))
601 .await
602 .ok();
603 });
604 result
605 }
606
607 async fn try_lock(
608 &self,
609 name: String,
610 timeout: u64,
611 retries: usize,
612 ) -> Result<Lock, LockManagerError> {
613 let now = std::time::Instant::now();
614 let max_retries = retries;
615 let mut tries = 0;
616 let revision = Arc::new(AtomicU64::new(0));
617 let kv = &self.kv;
618 loop {
619 if tries >= max_retries {
620 return Err(LockManagerError::OutOfRetries(now.elapsed()));
621 }
622 let v = kv.create(&name, "r".into()).await;
623 if let Err(err) = v {
624 if err.kind() == async_nats::jetstream::kv::CreateErrorKind::AlreadyExists {
625 tracing::debug!("seems to be locked already, {tries} try to watch for changes");
626 let mut w = kv.watch(&name).await?;
627 let f = async {
628 'inner: while let Some(m) = w.next().await {
629 if let Ok(e) = m {
630 if e.operation == Operation::Delete {
631 tracing::debug!("retry because prev lock was deleted");
632 break 'inner;
633 }
634 }
635 }
636 };
637 let t = async {
638 tokio::time::sleep(std::time::Duration::from_secs(timeout)).await;
639 };
640 let change = tokio::select! {
641 _ = f => true,
642 _ = t => false,
643 };
644 if !change {
645 tries += 1;
646 }
647 }
648 } else {
649 let r = v.unwrap();
650 revision.store(r, Ordering::SeqCst);
651 tracing::debug!("got lock: '{name}'");
652 break;
653 }
654 }
655 let w_kv = self.kv.clone();
656 let w_name = name.clone();
657 let w_revision = revision.clone();
658
659 let jh = tokio::spawn(async move {
660 let mut run = 0;
661 loop {
662 run += 1;
663 tokio::time::sleep(std::time::Duration::from_secs(2)).await;
664 tracing::debug!("refresh lock {w_name}");
665 let result = w_kv
666 .update(&w_name, "u".into(), w_revision.load(Ordering::SeqCst))
667 .await;
668 if let Err(err) = result {
669 tracing::error!("{err:#?}");
670 break;
671 } else {
672 w_revision.store(result.unwrap(), Ordering::SeqCst);
673 }
674 if run >= 5 {
675 tracing::debug!("release lock after timeout");
676 break;
677 }
678 }
679 anyhow::Ok(())
680 });
681
682 Ok(Lock { name, revision, jh })
683 }
684}
685
686#[derive(Debug, PartialEq, Eq)]
688pub enum LockState {
689 Registering,
691 Registered,
693}
694
695#[derive(Debug)]
700pub struct Lock {
701 name: String,
702 revision: Arc<AtomicU64>,
703 jh: JoinHandle<anyhow::Result<()>>,
704}