{"record":{"id":"af3ad52a15089b68","repo":"vectordotdev/vector","slug":"only-leaf-nodes-should-be-allowed-to-be-non-object","errorCode":null,"errorMessage":"Only leaf nodes should be allowed to be non-object values.","messagePattern":"Only leaf nodes should be allowed to be non-object values\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/docs-renderer/src/renderer.rs","lineNumber":87,"sourceCode":"\n        self.with_mut_object(|map| {\n            // Split the path, and take the last element as the actual map key to write to.\n            let mut segments = path.split('/').collect::<VecDeque<_>>();\n            let key = segments.pop_back().expect(\"Path must end with a key.\");\n\n            // Iterate over the remaining elements, traversing into the root object one level at a\n            // time, based on using `token` as the map key. If there's no map at the given key,\n            // we'll create one. If there's something other than a map, we'll panic.\n            let mut destination = map;\n            while let Some(segment) = segments.pop_front() {\n                if destination.contains_key(segment) {\n                    match destination.get_mut(segment) {\n                        Some(Value::Object(next)) => {\n                            destination = next;\n                            continue;\n                        }\n                        Some(_) => {\n                            panic!(\"Only leaf nodes should be allowed to be non-object values.\")\n                        }\n                        None => unreachable!(\"Already asserted that the given key exists.\"),\n                    }\n                } else {\n                    destination.insert(segment.to_string(), Value::Object(Map::new()));\n                    match destination.get_mut(segment) {\n                        Some(Value::Object(next)) => {\n                            destination = next;\n                        }\n                        _ => panic!(\"New object was just inserted.\"),\n                    }\n                }\n            }\n\n            destination.insert(key.to_string(), value.into());\n        });\n    }\n","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/vectordotdev/vector/blob/3708c39b12a93212ed8b8d7510b4cc7769cb5864/lib/docs-renderer/src/renderer.rs#L69-L105","documentation":"While walking the intermediate segments of a path, `Renderer::write` in lib/docs-renderer requires every non-leaf node to be a JSON object so it can descend one level at a time. If an intermediate segment already holds a scalar/array value, there is nowhere to descend and the method panics with this assertion. Leaf segments are the only place non-object values may live.","triggerScenarios":"Sequence like `renderer.write(\"/a\", \"scalar\")` followed by `renderer.write(\"/a/b\", 1)` — segment `a` is a string, so descending to `b` panics. Also triggered by writing `/root/nested/key` after `root` was previously set to an array or scalar.","commonSituations":"Generated-docs code that first writes a summary string under a key and later tries to hang structured sub-keys under the same key; merging two data sources that disagree on whether a path is a leaf; refactoring a flat key into a nested object without clearing the old value first.","solutions":["Keep paths consistent: choose either `/a` as a leaf or `/a/...` as a branch, never both over time.","Call `renderer.delete(\"/a\")` (or `clear`) before restructuring a scalar leaf into an object parent.","Write parent objects before their leaves and assert `renderer.get(\"/a\")` is absent or an object before deeper writes."],"exampleFix":"// before\nrenderer.write(\"/function\", Value::String(name.clone()));\nrenderer.write(\"/function/aliases\", aliases); // panic: /function is a string\n\n// after\nrenderer.write(\"/function/name\", Value::String(name));\nrenderer.write(\"/function/aliases\", aliases);","handlingStrategy":"type-guard","validationCode":"if let Some(existing) = renderer.get(parent_path) {\n    if !existing.is_object() {\n        renderer.delete(parent_path); // clear the scalar before nesting under it\n    }\n}\nrenderer.write(full_path, value);","typeGuard":"fn segment_is_object(v: Option<&Value>) -> bool {\n    v.map(|v| v.is_object()).unwrap_or(true)\n}","tryCatchPattern":null,"preventionTips":["Pick one shape per path — leaf scalar or parent object — and enforce it in the data model.","Write parent branches before leaves in collection code so objects exist first.","When restructuring docs output, delete the affected subtree before rewriting it."],"tags":["rust","vector","docs-renderer","json-pointer","panic"],"backgroundTag":"json-pointer-type-conflict","analyzedSha":"3708c39b12a93212ed8b8d7510b4cc7769cb5864","analyzedAt":"2026-08-20T07:02:18.786Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}