simplify config

This commit is contained in:
mat 2024-06-29 00:21:55 -05:00
parent 96258f8ac0
commit ea256f007f
7 changed files with 240 additions and 179 deletions

View File

@ -1,35 +0,0 @@
# This is the config that's used as a fallback when a field is missing from the user's config.toml.
bind = "0.0.0.0:28019"
[ui]
show_engine_list_separator = false
show_version_info = false
[image_search]
enabled = false
show_engines = true
proxy = { enabled = true, max_download_size = 10_000_000 }
[engines]
google = { weight = 1.05 }
bing = { weight = 1.0 }
brave = { weight = 1.25 }
google_scholar = { enabled = false, weight = 0.50 }
rightdao = { enabled = false, weight = 0.10 }
stract = { enabled = false, weight = 0.15 }
yep = { enabled = false, weight = 0.10 }
# calculators (give them a high weight so they're always the first thing in autocomplete)
numbat = { weight = 10 }
fend = { enabled = false, weight = 10 }
[engines.marginalia]
args = { profile = "corpo", js = "default", adtech = "default" }
weight = 0.15
[engines.mdn]
# the number of sections of text to display
# 1 is just the summary and 0 is no limit
max_sections = 1

View File

@ -6,35 +6,86 @@ use tracing::info;
use crate::engines::Engine; use crate::engines::Engine;
#[derive(Deserialize, Debug)] #[derive(Debug)]
pub struct Config { pub struct Config {
pub bind: SocketAddr, pub bind: SocketAddr,
#[serde(default)]
pub ui: UiConfig, pub ui: UiConfig,
#[serde(default)]
pub image_search: ImageSearchConfig, pub image_search: ImageSearchConfig,
#[serde(default)]
pub engines: EnginesConfig, pub engines: EnginesConfig,
} }
#[derive(Deserialize, Debug, Default)] #[derive(Deserialize, Debug)]
pub struct PartialConfig {
pub bind: Option<SocketAddr>,
pub ui: Option<PartialUiConfig>,
pub image_search: Option<PartialImageSearchConfig>,
pub engines: Option<PartialEnginesConfig>,
}
impl Config {
pub fn overlay(&mut self, partial: PartialConfig) {
self.bind = partial.bind.unwrap_or(self.bind);
self.ui.overlay(partial.ui.unwrap_or_default());
self.image_search
.overlay(partial.image_search.unwrap_or_default());
self.engines.overlay(partial.engines.unwrap_or_default());
}
}
#[derive(Debug)]
pub struct UiConfig { pub struct UiConfig {
pub show_engine_list_separator: bool,
pub show_version_info: bool,
}
#[derive(Deserialize, Debug, Default)]
pub struct PartialUiConfig {
#[serde(default)] #[serde(default)]
pub show_engine_list_separator: Option<bool>, pub show_engine_list_separator: Option<bool>,
#[serde(default)] #[serde(default)]
pub show_version_info: Option<bool>, pub show_version_info: Option<bool>,
} }
#[derive(Deserialize, Debug, Default)] impl UiConfig {
pub fn overlay(&mut self, partial: PartialUiConfig) {
self.show_engine_list_separator = partial
.show_engine_list_separator
.unwrap_or(self.show_engine_list_separator);
self.show_version_info = partial.show_version_info.unwrap_or(self.show_version_info);
}
}
#[derive(Debug)]
pub struct ImageSearchConfig { pub struct ImageSearchConfig {
pub enabled: Option<bool>, pub enabled: bool,
pub show_engines: Option<bool>, pub show_engines: bool,
#[serde(default)]
pub proxy: ImageProxyConfig, pub proxy: ImageProxyConfig,
} }
#[derive(Deserialize, Debug, Default)] #[derive(Deserialize, Debug, Default)]
pub struct PartialImageSearchConfig {
pub enabled: Option<bool>,
pub show_engines: Option<bool>,
#[serde(default)]
pub proxy: PartialImageProxyConfig,
}
impl ImageSearchConfig {
pub fn overlay(&mut self, partial: PartialImageSearchConfig) {
self.enabled = partial.enabled.unwrap_or(self.enabled);
self.show_engines = partial.show_engines.unwrap_or(self.show_engines);
self.proxy.overlay(partial.proxy);
}
}
#[derive(Debug)]
pub struct ImageProxyConfig { pub struct ImageProxyConfig {
pub enabled: bool,
pub max_download_size: u64,
}
#[derive(Deserialize, Debug, Default)]
pub struct PartialImageProxyConfig {
/// Whether we should proxy remote images through our server. This is mostly /// Whether we should proxy remote images through our server. This is mostly
/// a privacy feature. /// a privacy feature.
pub enabled: Option<bool>, pub enabled: Option<bool>,
@ -42,38 +93,87 @@ pub struct ImageProxyConfig {
pub max_download_size: Option<u64>, pub max_download_size: Option<u64>,
} }
#[derive(Deserialize, Debug, Default)] impl ImageProxyConfig {
pub fn overlay(&mut self, partial: PartialImageProxyConfig) {
self.enabled = partial.enabled.unwrap_or(self.enabled);
self.max_download_size = partial.max_download_size.unwrap_or(self.max_download_size);
}
}
#[derive(Debug)]
pub struct EnginesConfig { pub struct EnginesConfig {
pub map: HashMap<Engine, EngineConfig>,
}
#[derive(Deserialize, Debug, Default)]
pub struct PartialEnginesConfig {
#[serde(flatten)] #[serde(flatten)]
pub map: HashMap<Engine, DefaultableEngineConfig>, pub map: HashMap<Engine, PartialDefaultableEngineConfig>,
} }
#[derive(Deserialize, Clone, Debug)] #[derive(Deserialize, Clone, Debug)]
#[serde(untagged)] #[serde(untagged)]
pub enum DefaultableEngineConfig { pub enum PartialDefaultableEngineConfig {
Boolean(bool), Boolean(bool),
Full(FullEngineConfig), Full(PartialEngineConfig),
} }
#[derive(Deserialize, Clone, Debug)] impl EnginesConfig {
pub struct FullEngineConfig { pub fn overlay(&mut self, partial: PartialEnginesConfig) {
#[serde(default = "fn_true")] for (key, value) in partial.map {
pub enabled: bool, let full = match value {
PartialDefaultableEngineConfig::Boolean(enabled) => PartialEngineConfig {
enabled: Some(enabled),
..Default::default()
},
PartialDefaultableEngineConfig::Full(full) => full,
};
if let Some(existing) = self.map.get_mut(&key) {
existing.overlay(full);
} else {
let mut new = EngineConfig::default();
new.overlay(full);
self.map.insert(key, new);
}
}
}
/// The priority of this engine relative to the other engines. The default pub fn get(&self, engine: Engine) -> &EngineConfig {
/// is 1, and a value of 0 is treated as the default. self.map.get(&engine).unwrap_or(&DEFAULT_ENGINE_CONFIG_REF)
#[serde(default)] }
}
#[derive(Debug)]
pub struct EngineConfig {
pub enabled: bool,
pub weight: f64, pub weight: f64,
pub extra: toml::Table,
}
#[derive(Deserialize, Clone, Debug, Default)]
pub struct PartialEngineConfig {
#[serde(default)]
pub enabled: Option<bool>,
/// The priority of this engine relative to the other engines.
#[serde(default)]
pub weight: Option<f64>,
/// Per-engine configs. These are parsed at request time. /// Per-engine configs. These are parsed at request time.
#[serde(flatten)] #[serde(flatten)]
#[serde(default)]
pub extra: toml::Table, pub extra: toml::Table,
} }
impl EngineConfig {
pub fn overlay(&mut self, partial: PartialEngineConfig) {
self.enabled = partial.enabled.unwrap_or(self.enabled);
self.weight = partial.weight.unwrap_or(self.weight);
self.extra.extend(partial.extra);
}
}
impl Config { impl Config {
pub fn read_or_create(config_path: &Path) -> eyre::Result<Self> { pub fn read_or_create(config_path: &Path) -> eyre::Result<Self> {
let base_config_str = include_str!("../config-base.toml"); let mut config = Config::default();
let mut config: Config = toml::from_str(base_config_str)?;
if !config_path.exists() { if !config_path.exists() {
info!("No config found, creating one at {config_path:?}"); info!("No config found, creating one at {config_path:?}");
@ -81,122 +181,38 @@ impl Config {
fs::write(config_path, default_config_str)?; fs::write(config_path, default_config_str)?;
} }
let given_config = toml::from_str::<Config>(&fs::read_to_string(config_path)?)?; let given_config = toml::from_str::<PartialConfig>(&fs::read_to_string(config_path)?)?;
config.update(given_config); config.overlay(given_config);
Ok(config) Ok(config)
} }
// Update the current config with the given config. This is used to make it so
// the config-base.toml is always used as a fallback if the user decides to
// use the default for something.
pub fn update(&mut self, new: Config) {
self.bind = new.bind;
self.ui.update(new.ui);
self.image_search.update(new.image_search);
self.engines.update(new.engines);
}
} }
impl UiConfig { //
pub fn update(&mut self, new: UiConfig) { // DEFAULTS
self.show_engine_list_separator = new //
.show_engine_list_separator
.or(self.show_engine_list_separator);
assert_ne!(self.show_engine_list_separator, None);
self.show_version_info = new.show_version_info.or(self.show_version_info);
assert_ne!(self.show_version_info, None);
}
}
impl ImageSearchConfig { impl Default for Config {
pub fn update(&mut self, new: ImageSearchConfig) {
self.enabled = new.enabled.or(self.enabled);
assert_ne!(self.enabled, None);
self.show_engines = new.show_engines.or(self.show_engines);
assert_ne!(self.show_engines, None);
self.proxy.update(new.proxy);
}
}
impl ImageProxyConfig {
pub fn update(&mut self, new: ImageProxyConfig) {
self.enabled = new.enabled.or(self.enabled);
assert_ne!(self.enabled, None);
self.max_download_size = new.max_download_size.or(self.max_download_size);
assert_ne!(self.max_download_size, None);
}
}
static DEFAULT_ENABLED_FULL_ENGINE_CONFIG: Lazy<FullEngineConfig> =
Lazy::new(FullEngineConfig::default);
static DEFAULT_DISABLED_FULL_ENGINE_CONFIG: Lazy<FullEngineConfig> =
Lazy::new(|| FullEngineConfig {
enabled: false,
..Default::default()
});
impl EnginesConfig {
pub fn get(&self, engine: Engine) -> &FullEngineConfig {
match self.map.get(&engine) {
Some(engine_config) => match engine_config {
DefaultableEngineConfig::Boolean(enabled) => {
if *enabled {
&DEFAULT_ENABLED_FULL_ENGINE_CONFIG
} else {
&DEFAULT_DISABLED_FULL_ENGINE_CONFIG
}
}
DefaultableEngineConfig::Full(full) => full,
},
None => &DEFAULT_ENABLED_FULL_ENGINE_CONFIG,
}
}
pub fn update(&mut self, new: Self) {
for (key, new) in new.map {
if let Some(existing) = self.map.get_mut(&key) {
existing.update(new);
} else {
self.map.insert(key, new);
}
}
}
}
impl DefaultableEngineConfig {
pub fn update(&mut self, new: Self) {
let mut self_full = FullEngineConfig::from(self.clone());
let other_full = FullEngineConfig::from(new);
self_full.update(other_full);
*self = DefaultableEngineConfig::Full(self_full);
}
}
impl Default for DefaultableEngineConfig {
fn default() -> Self { fn default() -> Self {
Self::Boolean(true) Config {
} bind: "0.0.0.0:28019".parse().unwrap(),
} ui: UiConfig {
show_engine_list_separator: false,
// serde expects a function as the default, this just exists so "enabled" is show_version_info: false,
// always true by default
fn fn_true() -> bool {
true
}
impl From<DefaultableEngineConfig> for FullEngineConfig {
fn from(config: DefaultableEngineConfig) -> Self {
match config {
DefaultableEngineConfig::Boolean(enabled) => Self {
enabled,
..Default::default()
}, },
DefaultableEngineConfig::Full(full) => full, image_search: ImageSearchConfig {
enabled: false,
show_engines: true,
proxy: ImageProxyConfig {
enabled: true,
max_download_size: 10_000_000,
},
},
engines: EnginesConfig::default(),
} }
} }
} }
impl Default for FullEngineConfig { impl Default for EngineConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
enabled: true, enabled: true,
@ -205,13 +221,93 @@ impl Default for FullEngineConfig {
} }
} }
} }
static DEFAULT_ENGINE_CONFIG_REF: Lazy<EngineConfig> = Lazy::new(EngineConfig::default);
impl FullEngineConfig { impl EngineConfig {
pub fn update(&mut self, new: Self) { pub fn new() -> Self {
self.enabled = new.enabled; Self::default()
if new.weight != 0. {
self.weight = new.weight;
} }
self.extra = new.extra; pub fn with_weight(self, weight: f64) -> Self {
Self { weight, ..self }
}
pub fn disabled(self) -> Self {
Self {
enabled: false,
..self
}
}
pub fn with_extra(self, extra: toml::Table) -> Self {
Self { extra, ..self }
}
}
impl Default for EnginesConfig {
fn default() -> Self {
use toml::value::Value;
let mut map = HashMap::new();
// engines are enabled by default, so engines that aren't listed here are
// enabled
// main search engines
map.insert(Engine::Google, EngineConfig::new().with_weight(1.05));
map.insert(Engine::Bing, EngineConfig::new().with_weight(1.0));
map.insert(Engine::Brave, EngineConfig::new().with_weight(1.25));
map.insert(
Engine::Marginalia,
EngineConfig::new().with_weight(0.15).with_extra(
vec![(
"args".to_string(),
Value::Table(
vec![
("profile".to_string(), Value::String("corpo".to_string())),
("js".to_string(), Value::String("default".to_string())),
("adtech".to_string(), Value::String("default".to_string())),
]
.into_iter()
.collect(),
),
)]
.into_iter()
.collect(),
),
);
// additional search engines
map.insert(
Engine::GoogleScholar,
EngineConfig::new().with_weight(0.50).disabled(),
);
map.insert(
Engine::RightDao,
EngineConfig::new().with_weight(0.10).disabled(),
);
map.insert(
Engine::Stract,
EngineConfig::new().with_weight(0.15).disabled(),
);
map.insert(
Engine::Yep,
EngineConfig::new().with_weight(0.10).disabled(),
);
// calculators (give them a high weight so they're always the first thing in
// autocomplete)
map.insert(Engine::Numbat, EngineConfig::new().with_weight(10.0));
map.insert(
Engine::Fend,
EngineConfig::new().with_weight(10.0).disabled(),
);
// other engines
map.insert(
Engine::Mdn,
EngineConfig::new().with_extra(
vec![("max_sections".to_string(), Value::Integer(1))]
.into_iter()
.collect(),
),
);
Self { map }
} }
} }

View File

@ -537,7 +537,7 @@ pub async fn search(
SearchTab::All => { SearchTab::All => {
make_requests(query, progress_tx, start_time, &send_engine_progress_update).await? make_requests(query, progress_tx, start_time, &send_engine_progress_update).await?
} }
SearchTab::Images if query.config.image_search.enabled.unwrap() => { SearchTab::Images if query.config.image_search.enabled => {
make_image_requests(query, progress_tx, start_time, &send_engine_progress_update) make_image_requests(query, progress_tx, start_time, &send_engine_progress_update)
.await? .await?
} }

View File

@ -15,7 +15,7 @@ pub async fn route(
) -> Response { ) -> Response {
let image_search_config = &config.image_search; let image_search_config = &config.image_search;
let proxy_config = &image_search_config.proxy; let proxy_config = &image_search_config.proxy;
if !image_search_config.enabled.unwrap() || !proxy_config.enabled.unwrap() { if !image_search_config.enabled || !proxy_config.enabled {
return (StatusCode::FORBIDDEN, "Image proxy is disabled").into_response(); return (StatusCode::FORBIDDEN, "Image proxy is disabled").into_response();
}; };
let url = params.get("url").cloned().unwrap_or_default(); let url = params.get("url").cloned().unwrap_or_default();
@ -36,7 +36,7 @@ pub async fn route(
} }
}; };
let max_size = proxy_config.max_download_size.unwrap(); let max_size = proxy_config.max_download_size;
if res.content_length().unwrap_or_default() > max_size { if res.content_length().unwrap_or_default() > max_size {
return (StatusCode::PAYLOAD_TOO_LARGE, "Image too large").into_response(); return (StatusCode::PAYLOAD_TOO_LARGE, "Image too large").into_response();

View File

@ -33,7 +33,7 @@ pub async fn index(State(config): State<Arc<Config>>) -> impl IntoResponse {
input type="submit" value="Search"; input type="submit" value="Search";
} }
} }
@if config.ui.show_version_info.unwrap() { @if config.ui.show_version_info {
span."version-info" { span."version-info" {
@if COMMIT_HASH == "unknown" || COMMIT_HASH_SHORT == "unknown" { @if COMMIT_HASH == "unknown" || COMMIT_HASH_SHORT == "unknown" {
"Version " "Version "

View File

@ -40,7 +40,7 @@ fn render_beginning_of_html(search: &SearchQuery) -> String {
input #"search-input" type="text" name="q" placeholder="Search" value=(search.query) autofocus onfocus="this.select()" autocomplete="off"; input #"search-input" type="text" name="q" placeholder="Search" value=(search.query) autofocus onfocus="this.select()" autocomplete="off";
input type="submit" value="Search"; input type="submit" value="Search";
} }
@if search.config.image_search.enabled.unwrap() { @if search.config.image_search.enabled {
div.search-tabs { div.search-tabs {
@if search.tab == SearchTab::All { span.search-tab.selected { "All" } } @if search.tab == SearchTab::All { span.search-tab.selected { "All" } }
@else { a.search-tab href={ "?q=" (search.query) } { "All" } } @else { a.search-tab href={ "?q=" (search.query) } { "All" } }
@ -105,11 +105,11 @@ fn render_engine_progress_update(
pub fn render_engine_list(engines: &[engines::Engine], config: &Config) -> PreEscaped<String> { pub fn render_engine_list(engines: &[engines::Engine], config: &Config) -> PreEscaped<String> {
let mut html = String::new(); let mut html = String::new();
for (i, engine) in engines.iter().enumerate() { for (i, engine) in engines.iter().enumerate() {
if config.ui.show_engine_list_separator.unwrap() && i > 0 { if config.ui.show_engine_list_separator && i > 0 {
html.push_str(" &middot; "); html.push_str(" &middot; ");
} }
let raw_engine_id = &engine.id(); let raw_engine_id = &engine.id();
let engine_id = if config.ui.show_engine_list_separator.unwrap() { let engine_id = if config.ui.show_engine_list_separator {
raw_engine_id.replace('_', " ") raw_engine_id.replace('_', " ")
} else { } else {
raw_engine_id.to_string() raw_engine_id.to_string()

View File

@ -21,7 +21,7 @@ fn render_image_result(
config: &Config, config: &Config,
) -> PreEscaped<String> { ) -> PreEscaped<String> {
let original_image_src = &result.result.image_url; let original_image_src = &result.result.image_url;
let image_src = if config.image_search.proxy.enabled.unwrap() { let image_src = if config.image_search.proxy.enabled {
// serialize url params // serialize url params
let escaped_param = let escaped_param =
url::form_urlencoded::byte_serialize(original_image_src.as_bytes()).collect::<String>(); url::form_urlencoded::byte_serialize(original_image_src.as_bytes()).collect::<String>();
@ -40,7 +40,7 @@ fn render_image_result(
span.image-result-page-url.search-result-url { (result.result.page_url) } span.image-result-page-url.search-result-url { (result.result.page_url) }
span.image-result-title { (result.result.title) } span.image-result-title { (result.result.title) }
} }
@if config.image_search.show_engines.unwrap() { @if config.image_search.show_engines {
{(render_engine_list(&result.engines.iter().copied().collect::<Vec<_>>(), &config))} {(render_engine_list(&result.engines.iter().copied().collect::<Vec<_>>(), &config))}
} }
} }