{"record":{"id":"003c0759329c1890","repo":"vectordotdev/vector","slug":"metadata-extension-must-always-be-a-map","errorCode":null,"errorMessage":"metadata extension must always be a map","messagePattern":"metadata extension must always be a map","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/vector-config/src/schema/helpers.rs","lineNumber":102,"sourceCode":"        .default_value()\n        .map(ToValue::to_value)\n        .or(schema_metadata.default);\n    schema_metadata.deprecated = metadata.deprecated();\n    schema.metadata = Some(schema_metadata);\n}\n\nfn apply_custom_attributes(\n    schema: &mut SchemaObject,\n    metadata: &Metadata,\n    type_name: &'static str,\n) {\n    let map_entries_len = {\n        let custom_map = schema\n            .extensions\n            .entry(\"_metadata\".to_string())\n            .or_insert_with(|| Value::Object(Map::new()))\n            .as_object_mut()\n            .expect(\"metadata extension must always be a map\");\n\n        if let Some(message) = metadata.deprecated_message() {\n            custom_map.insert(\n                \"deprecated_message\".to_string(),\n                serde_json::Value::String(message.to_string()),\n            );\n        }\n\n        for attribute in metadata.custom_attributes() {\n            match attribute {\n                CustomAttribute::Flag(key) => {\n                    match custom_map.insert(key.to_string(), Value::Bool(true)) {\n                        // Overriding a flag is fine, because flags are only ever \"enabled\", so there's\n                        // no harm to enabling it... again. Likewise, if there was no existing value,\n                        // it's fine.\n                        Some(Value::Bool(_)) | None => {}\n                        // Any other value being present means we're clashing with a different metadata\n                        // attribute, which is not good, so we have to bail out.","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/vectordotdev/vector/blob/bdb87aeaa4c4ff27c0ba643c1c77b21bf2ef4013/lib/vector-config/src/schema/helpers.rs#L84-L120","documentation":"In vector-config's schema helpers, `apply_custom_attributes` reads the `_metadata` extension on a SchemaObject, inserting an empty JSON object if absent, and panics with \"metadata extension must always be a map\" if the value under `_metadata` is not a JSON object. The invariant holds only if nothing else writes non-object data under that key. A panic here means some code path (or a hand-built schema) stored a non-object value in the `_metadata` extension.","triggerScenarios":"Calling `apply_configurable_metadata`/`apply_custom_attributes` on a schema whose `.extensions[\"_metadata\"]` was pre-populated with a non-object Value (string, array, number) by another extension writer or by merging schemas from incompatible sources.","commonSituations":"Custom code or downstream tooling writing into schema extensions and colliding with vector-config's `_metadata` key; merging generated schemas where one side set `_metadata` to a non-map value.","solutions":["Search your codebase for writes to the `_metadata` extension key and ensure only objects (Map) are stored there","Before applying metadata, coerce or reset `_metadata` to an empty object if it is not already a map","Namespace custom extensions under a different key (e.g. `_custom_metadata`) to avoid colliding with vector-config's reserved `_metadata`"],"exampleFix":"// before\n.extensions.entry(\"_metadata\".to_string())\n    .or_insert_with(|| Value::Object(Map::new()))\n    .as_object_mut()\n    .expect(\"metadata extension must always be a map\");\n// after\n.extensions.entry(\"_metadata\".to_string())\n    .and_modify(|v| if !v.is_object() { *v = Value::Object(Map::new()); })\n    .or_insert_with(|| Value::Object(Map::new()))\n    .as_object_mut()\n    .expect(\"metadata extension must always be a map\");","handlingStrategy":"validation","validationCode":"// Validate the extension before applying metadata:\nif let Some(v) = schema.extensions.get(\"_metadata\") {\n    assert!(v.is_object(), \"_metadata must be a JSON object\");\n}","typeGuard":"fn metadata_is_map(schema: &SchemaObject) -> bool {\n    schema.extensions.get(\"_metadata\").map_or(true, |v| v.is_object())\n}","tryCatchPattern":"// guard around metadata application:\nif metadata_is_map(&schema) {\n    apply_configurable_metadata(&mut schema, metadata);\n}","preventionTips":["Never write non-object values under the reserved `_metadata` extension key","Use namespaced keys for custom extensions","When merging schemas, normalize `_metadata` to an object first"],"tags":["rust","schema","metadata","panic"],"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"}