use crate::extract::{select_extractor, DynExtractor, ExtractorSelection};
pub(super) enum ExtractorResolveError {
DefaultNotConfigured,
InvalidRequest(String),
}
#[derive(Clone)]
struct NamedExtractor {
backend: Option<String>,
extractor: DynExtractor,
}
pub(super) struct ResolvedExtractor {
pub(super) backend: Option<String>,
pub(super) extractor: DynExtractor,
}
#[derive(Clone, Default)]
pub(super) struct ExtractorResolver {
default: Option<NamedExtractor>,
}
impl ExtractorResolver {
pub(super) fn unnamed(extractor: DynExtractor) -> Self {
Self {
default: Some(NamedExtractor {
backend: None,
extractor,
}),
}
}
pub(super) fn named(backend: String, extractor: DynExtractor) -> Result<Self, String> {
match select_extractor(&backend)? {
ExtractorSelection::Disabled => {
Err("a disabled extractor cannot be attached as the default".to_owned())
}
ExtractorSelection::Ready(_) | ExtractorSelection::NeedsRemoteConfig(_) => Ok(Self {
default: Some(NamedExtractor {
backend: Some(backend),
extractor,
}),
}),
}
}
pub(super) fn resolve_for_job(
&self,
requested: Option<&str>,
) -> Result<ResolvedExtractor, ExtractorResolveError> {
match requested {
Some(backend) => self.resolve_requested(backend),
None => self
.default
.as_ref()
.map(|configured| ResolvedExtractor {
backend: configured.backend.clone(),
extractor: configured.extractor.clone(),
})
.ok_or(ExtractorResolveError::DefaultNotConfigured),
}
}
pub(super) fn default_is_configured(&self) -> bool {
self.default.is_some()
}
fn resolve_requested(&self, backend: &str) -> Result<ResolvedExtractor, ExtractorResolveError> {
match select_extractor(backend).map_err(ExtractorResolveError::InvalidRequest)? {
ExtractorSelection::Ready(extractor) => Ok(ResolvedExtractor {
backend: Some(backend.to_owned()),
extractor,
}),
ExtractorSelection::NeedsRemoteConfig(name) => self.resolve_remote(name),
ExtractorSelection::Disabled => Err(ExtractorResolveError::InvalidRequest(
"extractor 'none' cannot extract a passage; omit the field to use the daemon default"
.to_owned(),
)),
}
}
fn resolve_remote(&self, backend: &str) -> Result<ResolvedExtractor, ExtractorResolveError> {
let Some(configured) = &self.default else {
return Err(ExtractorResolveError::InvalidRequest(format!(
"extractor '{backend}' is not configured for this server; start it with \
VELESDB_MEMORY_EXTRACTOR={backend} or request 'outline'"
)));
};
if configured.backend.as_deref() == Some(backend) {
return Ok(ResolvedExtractor {
backend: Some(backend.to_owned()),
extractor: configured.extractor.clone(),
});
}
let current = configured
.backend
.as_deref()
.unwrap_or("an unnamed backend");
Err(ExtractorResolveError::InvalidRequest(format!(
"extractor '{backend}' is unavailable; this server uses {current} as its default — \
restart it with VELESDB_MEMORY_EXTRACTOR={backend} or request 'outline'"
)))
}
}