{"record":{"id":"a505d215c3ce0b78","repo":"rust-lang/mdBook","slug":"invalid-key-name","errorCode":null,"errorMessage":"invalid key `{name}`","messagePattern":"invalid key `(.+?)`","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/mdbook-core/src/config.rs","lineNumber":222,"sourceCode":"                    .try_into()\n                    .with_context(|| format!(\"Failed to deserialize `{name}`\"))\n            })\n            .transpose()\n    }\n\n    /// Returns whether the config contains the given dotted key name.\n    ///\n    /// The key can have dotted indices to access nested items (e.g.\n    /// `preprocessor.foo.bar` will check if that key is set in the config).\n    ///\n    /// This can only access the `output` and `preprocessor` tables.\n    pub fn contains_key(&self, name: &str) -> bool {\n        if let Some(key) = name.strip_prefix(\"output.\") {\n            self.output.read(key)\n        } else if let Some(key) = name.strip_prefix(\"preprocessor.\") {\n            self.preprocessor.read(key)\n        } else {\n            panic!(\"invalid key `{name}`\");\n        }\n        .is_some()\n    }\n\n    /// Returns the configuration for all preprocessors.\n    pub fn preprocessors<'de, T: Deserialize<'de>>(&self) -> Result<BTreeMap<String, T>> {\n        self.preprocessor\n            .clone()\n            .try_into()\n            .with_context(|| \"Failed to read preprocessors\")\n    }\n\n    /// Returns the configuration for all renderers.\n    pub fn outputs<'de, T: Deserialize<'de>>(&self) -> Result<BTreeMap<String, T>> {\n        self.output\n            .clone()\n            .try_into()\n            .with_context(|| \"Failed to read renderers\")","sourceCodeStart":204,"sourceCodeEnd":240,"githubUrl":"https://github.com/rust-lang/mdBook/blob/dc21064fc21d955a0e1f67e65e336883c3e5260b/crates/mdbook-core/src/config.rs#L204-L240","documentation":"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.","triggerScenarios":"Calling config.contains_key(\"build.dir\") or any key without the output./preprocessor. prefix; typo like \"output\" (no dot).","commonSituations":"Plugin authors checking for optional plugin config; code refactored from get() calls to contains_key() assuming it accepts all keys.","solutions":["Only call contains_key() with output.* or preprocessor.* keys","Use config.get::<serde_json::Value>(name) for other sections, which returns a Result","Check the relevant typed field directly (config.build, config.book, etc.)"],"exampleFix":"// before\nlet has = config.contains_key(\"build.dir\"); // panics\n// after\nlet has = matches!(config.get::<serde_json::Value>(\"build.build-dir\"), Ok(Some(_)));","handlingStrategy":"validation","validationCode":"fn contains_key_safe(config: &Config, name: &str) -> bool {\n    if name.starts_with(\"output.\") || name.starts_with(\"preprocessor.\") {\n        config.contains_key(name)\n    } else {\n        matches!(config.get::<serde_json::Value>(name), Ok(Some(_)))\n    }\n}","typeGuard":"fn is_contains_key_safe(name: &str) -> bool {\n    name.starts_with(\"output.\") || name.starts_with(\"preprocessor.\")\n}","tryCatchPattern":null,"preventionTips":["Never pass non-output/preprocessor keys to contains_key() — it panics rather than returning Err","Use config.get::<Value>(name) for existence checks on other sections","Prefer matches!(config.get(...), Ok(Some(_))) as a panic-free universal existence check"],"tags":["config","panic","api-misuse"],"backgroundTag":"invalid-config-key","analyzedSha":"dc21064fc21d955a0e1f67e65e336883c3e5260b","analyzedAt":"2026-09-01T10:15:12.134Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}