use std::sync::Arc;
use std::time::Duration;
use tracing;
use crate::core::config::AppConfig;
use crate::fetch::FetchClient;
use crate::meta::MetadataSearchAdapter;
#[derive(Clone)]
pub struct ServerState {
pub config: Arc<AppConfig>,
pub adapter: Arc<MetadataSearchAdapter>,
pub fetch_client: Option<Arc<FetchClient>>,
}
impl std::fmt::Debug for ServerState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ServerState")
.field("mode", &self.config.search.mode)
.field("providers", &self.adapter.provider_ids())
.field("fetch_enabled", &self.config.fetch.enabled)
.finish()
}
}
impl ServerState {
pub fn build(config: AppConfig) -> anyhow::Result<Self> {
config.validate()?;
let config = Arc::new(config);
let enabled: Vec<String> = config
.search
.providers
.iter()
.filter_map(|(id, on)| if *on { Some(id.clone()) } else { None })
.collect();
let global_timeout = Duration::from_millis(config.search.timeout_ms);
let user_agent = Some(config.fetch.user_agent.clone());
let adapter = MetadataSearchAdapter::new(enabled, global_timeout, user_agent)?;
let misconfigured = config.misconfigured_default_providers();
for id in &misconfigured {
tracing::warn!(
provider_id = %id,
"provider listed in [search].default_providers is not enabled; \
it will be silently skipped. Enable it in [search].providers or \
remove it from default_providers."
);
}
if config.search.live.user_agent.is_some() {
tracing::warn!(
"[search].live.user_agent is reserved for future use and is not yet applied. \
The vendored HTML engines use a hard-coded browser-like user agent."
);
}
if config
.search
.live
.respect_robots_txt
.is_some_and(|v| v)
{
tracing::warn!(
"[search].live.respect_robots_txt is reserved for future use and is not yet applied. \
web_fetch does not consult robots.txt in the current build."
);
}
let fetch_client = if config.fetch.enabled {
let limits = config.fetch_limits();
let ua = config.fetch_user_agent();
match FetchClient::new(limits, ua) {
Ok(c) => Some(Arc::new(c)),
Err(e) => {
tracing::warn!(error = %e, "failed to build shared fetch client; web_fetch will fail at call time");
None
}
}
} else {
None
};
Ok(Self {
config,
adapter: Arc::new(adapter),
fetch_client,
})
}
pub fn with_adapter(config: AppConfig, adapter: std::sync::Arc<MetadataSearchAdapter>) -> Self {
let config = Arc::new(config);
let fetch_client = if config.fetch.enabled {
let limits = config.fetch_limits();
let ua = config.fetch_user_agent();
FetchClient::new(limits, ua).ok().map(Arc::new)
} else {
None
};
Self {
config,
adapter,
fetch_client,
}
}
pub fn fetch_client(&self) -> Option<Arc<FetchClient>> {
self.fetch_client.clone()
}
}