fix(config): accept integer 0/1 for bool fields in RawWiki deserialization
CI / cargo fmt --check (push) Successful in 31s
CI / cargo clippy (push) Successful in 39s
CI / cargo test (push) Successful in 36s
CI / editor keymaps (push) Successful in 1m25s

VimL dicts serialise boolean-like settings such as `auto_export = 1`
as the JSON number `1`, not `true`.  serde's default `Option<bool>`
deserializer rejects that, causing `serde_json::from_value::<InitOptions>`
to return `Err` and `.ok()` to silently discard the entire wikis list.
The server then fell back to `wiki_root` and could not resolve per-wiki
links, marking every cross-wiki link as broken.

Add an `opt_bool_or_int` serde helper that accepts JSON bool, `null`,
or integers (0 = false, non-zero = true), and apply it to `auto_export`,
`html_filename_parameterization`, `listsyms_propagate`, and `auto_toc`
in `RawWiki`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-21 23:03:36 -03:00
parent 8d11f7daef
commit 02172d9e25
+37 -4
View File
@@ -389,6 +389,39 @@ impl Config {
// ===== Wire schema =====
/// Custom serde helper: accept JSON `true`/`false` **or** integers `0`/`1`
/// for `Option<bool>` fields. VimL dicts serialise `auto_export = 1` as
/// the JSON number `1`, not `true`, which would otherwise silently break
/// deserialisation of the entire `RawWiki` and cause the server to fall
/// back to `wiki_root` (ignoring the wikis list and marking every link
/// broken).
mod opt_bool_or_int {
use serde::{Deserialize, Deserializer};
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
where
D: Deserializer<'de>,
{
let v = Option::<serde_json::Value>::deserialize(deserializer)?;
match v {
None | Some(serde_json::Value::Null) => Ok(None),
Some(serde_json::Value::Bool(b)) => Ok(Some(b)),
Some(serde_json::Value::Number(ref n)) => {
if let Some(i) = n.as_i64() {
Ok(Some(i != 0))
} else {
Err(serde::de::Error::custom(
"expected bool or 0/1 integer for bool field",
))
}
}
Some(other) => Err(serde::de::Error::custom(format!(
"expected bool or 0/1 integer, got {other}"
))),
}
}
}
#[derive(Debug, Default, Deserialize)]
#[serde(default, rename_all = "snake_case")]
struct InitOptions {
@@ -427,9 +460,9 @@ struct RawWiki {
template_date_format: Option<String>,
#[serde(default)]
css_name: Option<String>,
#[serde(default)]
#[serde(default, deserialize_with = "opt_bool_or_int::deserialize")]
auto_export: Option<bool>,
#[serde(default)]
#[serde(default, deserialize_with = "opt_bool_or_int::deserialize")]
html_filename_parameterization: Option<bool>,
#[serde(default)]
exclude_files: Option<Vec<String>>,
@@ -453,7 +486,7 @@ struct RawWiki {
maxhi: Option<u8>,
#[serde(default)]
listsyms: Option<String>,
#[serde(default)]
#[serde(default, deserialize_with = "opt_bool_or_int::deserialize")]
listsyms_propagate: Option<bool>,
#[serde(default)]
list_margin: Option<i32>,
@@ -461,7 +494,7 @@ struct RawWiki {
links_space_char: Option<String>,
#[serde(default)]
nested_syntaxes: Option<std::collections::HashMap<String, String>>,
#[serde(default)]
#[serde(default, deserialize_with = "opt_bool_or_int::deserialize")]
auto_toc: Option<bool>,
}