use super::{ExternalSignalSet, Result};
use async_trait::async_trait;
#[async_trait]
pub trait ExternalSignalProvider: Send + Sync {
fn name(&self) -> &str;
async fn get_signals(&self, episode: &crate::episode::Episode) -> Result<ExternalSignalSet>;
async fn health_check(&self) -> ProviderHealth;
fn validate_config(&self) -> Result<()>;
}
#[derive(Debug, Clone, PartialEq)]
pub enum ProviderHealth {
Healthy,
Degraded(String),
Unhealthy(String),
}
impl ProviderHealth {
pub fn is_healthy(&self) -> bool {
matches!(self, ProviderHealth::Healthy)
}
pub fn is_operational(&self) -> bool {
matches!(self, ProviderHealth::Healthy | ProviderHealth::Degraded(_))
}
}
#[cfg(test)]
pub mod mock {
use super::*;
use std::collections::HashMap;
pub struct MockExternalSignalProvider {
canned_signals: HashMap<String, super::super::ExternalSignalSet>,
health_status: ProviderHealth,
}
impl MockExternalSignalProvider {
pub fn with_signals(signals: Vec<(String, super::super::ExternalSignalSet)>) -> Self {
Self {
canned_signals: signals.into_iter().collect(),
health_status: ProviderHealth::Healthy,
}
}
#[must_use]
pub fn with_health(mut self, health: ProviderHealth) -> Self {
self.health_status = health;
self
}
}
#[async_trait]
impl ExternalSignalProvider for MockExternalSignalProvider {
fn name(&self) -> &'static str {
"mock"
}
async fn get_signals(
&self,
episode: &crate::episode::Episode,
) -> Result<ExternalSignalSet> {
let key = episode.episode_id.to_string();
Ok(self
.canned_signals
.get(&key)
.cloned()
.unwrap_or_else(|| ExternalSignalSet::empty("mock")))
}
async fn health_check(&self) -> ProviderHealth {
self.health_status.clone()
}
fn validate_config(&self) -> Result<()> {
Ok(())
}
}
}