use std::time::Duration;
use crate::error::Error;
use crate::source::Format;
use super::watch::{Pace, Watching};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum WatchCapability {
Native,
Conditional,
Interval,
}
impl std::fmt::Display for WatchCapability {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Native => "native",
Self::Conditional => "conditional",
Self::Interval => "interval",
})
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct Fetched {
pub text: String,
pub format: Format,
}
impl Fetched {
#[must_use]
pub fn new(text: impl Into<String>, format: Format) -> Self {
Self {
text: text.into(),
format,
}
}
}
impl std::fmt::Debug for Fetched {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Fetched")
.field("format", &self.format)
.field("bytes", &self.text.len())
.finish()
}
}
pub trait RemoteSource: Send + Sync + 'static {
fn fetch(&self) -> Result<Fetched, Error>;
fn describe(&self) -> String;
fn watch_capability(&self) -> WatchCapability {
WatchCapability::Interval
}
fn watch(
&self,
watching: &Watching,
interval: Duration,
on_change: &mut dyn FnMut(Fetched) -> Result<(), Error>,
) -> Result<(), Error> {
let mut pace = Pace::new(interval);
let mut last: Option<Fetched> = None;
while watching.keep_going() {
match self.fetch() {
Ok(fetched) => {
pace.succeeded();
if last.as_ref() != Some(&fetched) {
last = Some(fetched.clone());
on_change(fetched)?;
}
}
Err(_) => pace.failed(),
}
pace.wait(watching);
}
Ok(())
}
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub trait AsyncRemoteSource: Send + Sync + 'static {
fn fetch(
&self,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Fetched, Error>> + Send + '_>>;
fn describe(&self) -> String;
fn watch_capability(&self) -> WatchCapability {
WatchCapability::Interval
}
fn watch<'a>(
&'a self,
watching: &'a Watching,
interval: Duration,
on_change: &'a mut (dyn FnMut(Fetched) -> Result<(), Error> + Send),
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), Error>> + Send + 'a>> {
Box::pin(async move {
#[cfg(not(feature = "tokio"))]
{
let _ = (watching, interval, on_change);
Err(Error::new(
crate::ErrorKind::Remote,
format!(
"`{}` has no watch of its own, and this build has no timer to poll it \
with; add features = [\"tokio\"] to your dynamic-config dependency, \
or call `refresh_remote_async` on a timer of your own",
self.describe()
),
))
}
#[cfg(feature = "tokio")]
{
let mut pace = Pace::new(interval);
let mut last: Option<Fetched> = None;
while watching.keep_going() {
match self.fetch().await {
Ok(fetched) => {
pace.succeeded();
if last.as_ref() != Some(&fetched) {
last = Some(fetched.clone());
on_change(fetched)?;
}
}
Err(_) => pace.failed(),
}
tokio::time::sleep(pace.next_wait()).await;
}
Ok(())
}
})
}
}