{"record":{"id":"8ecfd4fc1fc8e024","repo":"vectordotdev/vector","slug":"schema-metadata-must-always-be-an-object","errorCode":null,"errorMessage":"schema metadata must always be an object","messagePattern":"schema metadata must always be an object","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/vector-config/src/schema/visitors/human_name.rs","lineNumber":100,"sourceCode":"}\n\nfn get_schema_metadata_attr_str<'a>(schema: &'a SchemaObject, key: &str) -> Option<&'a str> {\n    schema\n        .extensions\n        .get(constants::METADATA)\n        .and_then(|metadata| metadata.get(key))\n        .and_then(|value| value.as_str())\n}\n\nfn set_schema_metadata_attr_str(schema: &mut SchemaObject, key: &str, value: String) {\n    let metadata = schema\n        .extensions\n        .entry(constants::METADATA.to_string())\n        .or_insert_with(|| Value::Object(serde_json::Map::new()));\n\n    let metadata_map = metadata\n        .as_object_mut()\n        .expect(\"schema metadata must always be an object\");\n    metadata_map.insert(key.to_string(), Value::String(value));\n}\n\n#[cfg(test)]\nmod tests {\n    use serde_json::json;\n    use vector_config_common::schema::visit::Visitor;\n\n    use super::GenerateHumanFriendlyNameVisitor;\n    use crate::schema::visitors::test::{as_schema, assert_schemas_eq};\n\n    #[test]\n    fn logical_name() {\n        let mut actual_schema = as_schema(json!({\n            \"type\": \"string\",\n            \"_metadata\": {\n                \"logical_name\": \"LogToMetric\"\n            }","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/vectordotdev/vector/blob/bdb87aeaa4c4ff27c0ba643c1c77b21bf2ef4013/lib/vector-config/src/schema/visitors/human_name.rs#L82-L118","documentation":"`set_schema_metadata_attr_str` inserts a metadata entry into a schema object's extensions and then asserts it is a JSON object before inserting the key. The panic fires when the existing metadata extension value is not a JSON object (e.g. it is a string, number, or array), so `as_object_mut()` returns `None`.","triggerScenarios":"A schema's extensions map already contains a `metadata` key whose value is not a JSON object, then a visitor such as `visit_schema_object` calls `set_schema_metadata_attr_str` to add a human-readable name; or a custom `Configurable` type writes `constants::METADATA` as a non-object value.","commonSituations":"Hand-rolled `Configurable` implementations writing malformed custom attributes into schema metadata; merging schemas generated by different schemars/vector-config versions where the metadata convention differs.","solutions":["Check any custom `Configurable` implementations for code writing the `metadata` extension key directly and make them write a JSON object","Ensure all crates in the workspace use the same vector-config version so the metadata convention matches","Reproduce with a minimal config schema and inspect the generated extensions map before the visitor runs","Reset to stock vector-config types to confirm the bug is in custom code"],"exampleFix":"// before\nmetadata.extensions.insert(constants::METADATA.to_string(), Value::String(\"my field\".into()));\n// after\nmetadata.extensions.insert(constants::METADATA.to_string(), json!({}));","handlingStrategy":"type-guard","validationCode":"fn ensure_metadata_object(extensions: &Map<String, Value>) -> Result<(), String> {\n    match extensions.get(constants::METADATA) {\n        Some(v) if !v.is_object() => Err(format!(\"metadata extension must be an object, got {v}\")),\n        _ => Ok(()),\n    }\n}","typeGuard":"fn as_metadata_object(v: &Value) -> Option<&serde_json::Map<String, Value>> { v.as_object() }","tryCatchPattern":"let metadata_map = metadata.as_object_mut().ok_or_else(||\n    anyhow::anyhow!(\"schema metadata extension is not a JSON object\"))?;","preventionTips":["Always write the `metadata` extension key as a JSON object in custom Configurable impls","Use vector-config's helpers (set_schema_metadata_attr_str) instead of touching extensions directly","Run schema-generation unit tests for every custom Configurable type"],"tags":["rust","schema-generation","json"],"backgroundTag":"internal-invariant-violation","analyzedSha":"bdb87aeaa4c4ff27c0ba643c1c77b21bf2ef4013","analyzedAt":"2026-09-16T02:53:35.741Z","contentChangedAt":"2026-09-16T02:53:35.741Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}