From 02172d9e25e3e1f3c92e5862e6359f194fe5952c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Fr=C3=B3es=20Franco?= Date: Thu, 21 May 2026 23:03:36 -0300 Subject: [PATCH] fix(config): accept integer 0/1 for bool fields in RawWiki deserialization VimL dicts serialise boolean-like settings such as `auto_export = 1` as the JSON number `1`, not `true`. serde's default `Option` deserializer rejects that, causing `serde_json::from_value::` 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 --- crates/nuwiki-lsp/src/config.rs | 41 +++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/crates/nuwiki-lsp/src/config.rs b/crates/nuwiki-lsp/src/config.rs index 8d36742..9871c15 100644 --- a/crates/nuwiki-lsp/src/config.rs +++ b/crates/nuwiki-lsp/src/config.rs @@ -389,6 +389,39 @@ impl Config { // ===== Wire schema ===== +/// Custom serde helper: accept JSON `true`/`false` **or** integers `0`/`1` +/// for `Option` 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, D::Error> + where + D: Deserializer<'de>, + { + let v = Option::::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, #[serde(default)] css_name: Option, - #[serde(default)] + #[serde(default, deserialize_with = "opt_bool_or_int::deserialize")] auto_export: Option, - #[serde(default)] + #[serde(default, deserialize_with = "opt_bool_or_int::deserialize")] html_filename_parameterization: Option, #[serde(default)] exclude_files: Option>, @@ -453,7 +486,7 @@ struct RawWiki { maxhi: Option, #[serde(default)] listsyms: Option, - #[serde(default)] + #[serde(default, deserialize_with = "opt_bool_or_int::deserialize")] listsyms_propagate: Option, #[serde(default)] list_margin: Option, @@ -461,7 +494,7 @@ struct RawWiki { links_space_char: Option, #[serde(default)] nested_syntaxes: Option>, - #[serde(default)] + #[serde(default, deserialize_with = "opt_bool_or_int::deserialize")] auto_toc: Option, }