Skip to main content

cqlite_core/
config_json.rs

1//! JSON deserialization entry points for [`Config`] (issue #1696).
2//!
3//! Split out of `config.rs` under the campsite rule (epic #1116): that file is
4//! already over the size target, and these two constructors are one cohesive
5//! responsibility — turning a JSON document into a `Config` while REPORTING the
6//! keys CQLite has removed, which is the only signal a non-Rust authoring surface
7//! can get (see [`crate::config_removed_keys`]).
8
9use super::Config;
10
11impl Config {
12    /// Deserialize a JSON `Config` document, reporting every key #1696 REMOVED
13    /// that it still names.
14    ///
15    /// # Why this exists (#1696 roborev F1)
16    ///
17    /// Deleting a decorative field from this struct is a compile error for an
18    /// embedder writing Rust, which is the loudest signal available — but serde
19    /// DISCARDS unknown fields, so a JSON or dict authoring surface (the Python
20    /// bindings' `cqlite.open(path, config=...)` bridge) silently accepted a
21    /// pre-change document naming `performance`, `storage.block_size`,
22    /// `query.parallel` and the rest, and ignored it. The rule #1696 states —
23    /// *a removed knob must produce a LOUD signal at the layer where it is set* —
24    /// was therefore false at exactly the layer that cannot get a compile error.
25    ///
26    /// The posture matches the CLI's file surface, crate-wide and deliberately:
27    /// **parse-and-ignore PLUS a named warning**, never `deny_unknown_fields`,
28    /// which would hard-fail a caller whose config predates the removal with no
29    /// migration path.
30    ///
31    /// The warning is logged at WARN via `tracing`. A caller that must SURFACE it
32    /// (the bindings raise a Python `UserWarning`) or assert it wants
33    /// [`Self::from_json_str_reporting_removed`].
34    ///
35    /// # This constructor is OPTIONAL, so it does not enforce the rule
36    ///
37    /// `Config` derives `Deserialize`, so an embedder can call
38    /// `serde_json::from_str::<Config>` directly and bypass this entirely — serde
39    /// then DISCARDS the removed keys in silence. Enforcement at the serde
40    /// boundary itself is **issue #3520** (#1696 roborev r2 F3, scoped out
41    /// deliberately); do not read this constructor as universal coverage.
42    ///
43    /// # Errors
44    ///
45    /// The document is not valid JSON, or does not deserialize into a `Config`.
46    /// Note that `Config` is not `#[serde(default)]`, so the document must be
47    /// COMPLETE. This does NOT run [`Self::validate`] — the caller owns validating
48    /// the config it finally uses, possibly after folding in overrides.
49    pub fn from_json_str(json: &str) -> crate::Result<Self> {
50        let (config, warning) = Self::from_json_str_reporting_removed(json, "this configuration")?;
51        if let Some(warning) = warning {
52            tracing::warn!("{warning}");
53        }
54        Ok(config)
55    }
56
57    /// As [`Self::from_json_str`], but RETURNS the removed-key warning instead of
58    /// logging it, labelled with `source` (e.g. `"config dict"`).
59    ///
60    /// # ORDER
61    ///
62    /// The deserialize runs FIRST and the scan only on success, so this
63    /// constructor never returns a removed-key report for a document that did
64    /// not become a `Config`. Nothing is lost: serde drops the removed keys from
65    /// `Config`, but nothing drops them from the text they were read out of.
66    ///
67    /// This ordering is a property of the RETURN SHAPE, not a precondition of the
68    /// text: since #1696 roborev r5 F1 the warning asserts nothing about whether
69    /// the load succeeds, precisely so that no placement of it can be wrong (see
70    /// [`crate::config_removed_keys::deprecation_warning`]).
71    ///
72    /// # Errors
73    ///
74    /// See [`Self::from_json_str`].
75    pub fn from_json_str_reporting_removed(
76        json: &str,
77        source: &str,
78    ) -> crate::Result<(Self, Option<String>)> {
79        let config: Self = serde_json::from_str(json)
80            .map_err(|e| crate::Error::configuration(format!("invalid {source}: {e}")))?;
81        let warning = crate::config_removed_keys::warning_for_json(source, json);
82        Ok((config, warning))
83    }
84}