dynomite/seeds/mod.rs
1//! Pluggable seeds providers.
2//!
3//! A seeds provider hands the gossip task an up-to-date list of
4//! peers in the canonical
5//! `host:port:rack:dc:tokens|host:port:rack:dc:tokens` format.
6//! Three implementations ship with the engine:
7//!
8//! * [`simple::SimpleSeedsProvider`] - returns the seeds parsed
9//! from the YAML config.
10//! * [`dns::DnsSeedsProvider`] - resolves a configured DNS
11//! hostname to a list of IPs (mirroring the reference
12//! `dns_get_seeds`, with the resolver factored behind a trait so
13//! the unit test can substitute a deterministic implementation).
14//! * [`florida::FloridaSeedsProvider`] - HTTP GET to a Florida
15//! service, parses the body. Hand-rolled HTTP/1.0 client over
16//! `tokio::net::TcpStream` to stay within the locked dependency
17//! set.
18//!
19//! The trait shape is the seam Stage 13 will expose through the
20//! embedding API; this stage locks the surface so the embed
21//! wrapper only needs to forward.
22//!
23//! # Examples
24//!
25//! ```
26//! use dynomite::seeds::{SeedsProvider, simple::SimpleSeedsProvider};
27//! use dynomite::conf::ConfDynSeed;
28//! let seeds = vec![ConfDynSeed::parse("h1:8101:rA:dc1:1").unwrap()];
29//! let p = SimpleSeedsProvider::new(seeds);
30//! let got = p.get_seeds().unwrap();
31//! assert_eq!(got.len(), 1);
32//! ```
33
34pub mod dns;
35pub mod florida;
36pub mod simple;
37
38use std::io;
39
40use thiserror::Error;
41
42use crate::conf::ConfDynSeed;
43
44/// Error type for seeds providers.
45#[derive(Debug, Error)]
46pub enum SeedsError {
47 /// The provider has no fresh data: the gossip task should
48 /// retry on the next interval. Mirrors the reference engine's
49 /// `DN_NOOPS` return.
50 #[error("no fresh seeds")]
51 NoFreshSeeds,
52 /// I/O error.
53 #[error("io error: {0}")]
54 Io(#[from] io::Error),
55 /// Parse error.
56 #[error("parse error: {0}")]
57 Parse(String),
58 /// Endpoint returned an HTTP error.
59 #[error("http error: {0}")]
60 Http(String),
61}
62
63/// Pluggable seeds provider.
64///
65/// Implementations may block; the gossip task calls them from a
66/// dedicated tokio task with timeouts wrapped at the call site.
67/// `SeedsProvider` is an async trait emulated via an associated
68/// future type so it can be implemented for both blocking
69/// (`SimpleSeedsProvider`) and async (`FloridaSeedsProvider`)
70/// shapes.
71pub trait SeedsProvider: Send + Sync {
72 /// Return the current list of seeds, or an error explaining
73 /// why no fresh data is available.
74 ///
75 /// Blocking implementations do their work synchronously and
76 /// return immediately; async implementations should run on a
77 /// blocking task spawned by the caller. Stage 12 binary
78 /// wiring picks the right runtime path; the trait stays sync
79 /// to keep the surface small for embedders.
80 fn get_seeds(&self) -> Result<Vec<ConfDynSeed>, SeedsError>;
81}
82
83/// Marker trait used by Stage 13 to register custom seeds
84/// providers through the embedding API. Implementing
85/// [`SeedsProvider`] is sufficient.
86impl<T> SeedsProvider for std::sync::Arc<T>
87where
88 T: SeedsProvider + ?Sized,
89{
90 fn get_seeds(&self) -> Result<Vec<ConfDynSeed>, SeedsError> {
91 (**self).get_seeds()
92 }
93}