rust-lang/mdBook · critical

invalid key `{name}`

Error message

invalid key `{name}`

What it means

Config::contains_key() is documented to work only with "output." and "preprocessor." prefixed names; any other name triggers a panic! (not a Result), because the method returns bool and has no error channel.

Source

Thrown at crates/mdbook-core/src/config.rs:222

                    .try_into()
                    .with_context(|| format!("Failed to deserialize `{name}`"))
            })
            .transpose()
    }

    /// Returns whether the config contains the given dotted key name.
    ///
    /// The key can have dotted indices to access nested items (e.g.
    /// `preprocessor.foo.bar` will check if that key is set in the config).
    ///
    /// This can only access the `output` and `preprocessor` tables.
    pub fn contains_key(&self, name: &str) -> bool {
        if let Some(key) = name.strip_prefix("output.") {
            self.output.read(key)
        } else if let Some(key) = name.strip_prefix("preprocessor.") {
            self.preprocessor.read(key)
        } else {
            panic!("invalid key `{name}`");
        }
        .is_some()
    }

    /// Returns the configuration for all preprocessors.
    pub fn preprocessors<'de, T: Deserialize<'de>>(&self) -> Result<BTreeMap<String, T>> {
        self.preprocessor
            .clone()
            .try_into()
            .with_context(|| "Failed to read preprocessors")
    }

    /// Returns the configuration for all renderers.
    pub fn outputs<'de, T: Deserialize<'de>>(&self) -> Result<BTreeMap<String, T>> {
        self.output
            .clone()
            .try_into()
            .with_context(|| "Failed to read renderers")

View on GitHub (pinned to dc21064fc2)

Solutions

  1. Only call contains_key() with output.* or preprocessor.* keys
  2. Use config.get::<serde_json::Value>(name) for other sections, which returns a Result
  3. Check the relevant typed field directly (config.build, config.book, etc.)

Example fix

// before
let has = config.contains_key("build.dir"); // panics
// after
let has = matches!(config.get::<serde_json::Value>("build.build-dir"), Ok(Some(_)));
Defensive patterns

Strategy: validation

Validate before calling

fn contains_key_safe(config: &Config, name: &str) -> bool {
    if name.starts_with("output.") || name.starts_with("preprocessor.") {
        config.contains_key(name)
    } else {
        matches!(config.get::<serde_json::Value>(name), Ok(Some(_)))
    }
}

Type guard

fn is_contains_key_safe(name: &str) -> bool {
    name.starts_with("output.") || name.starts_with("preprocessor.")
}

Prevention

When it happens

Trigger: Calling config.contains_key("build.dir") or any key without the output./preprocessor. prefix; typo like "output" (no dot).

Common situations: Plugin authors checking for optional plugin config; code refactored from get() calls to contains_key() assuming it accepts all keys.

Related errors


AI-assisted analysis of rust-lang/mdBook@dc21064fc2 (2026-09-01). Data as JSON: /api/errors/a505d215c3ce0b78. Report an issue: GitHub.