vectordotdev/vector · error
Only leaf nodes should be allowed to be non-object values.
Error message
Only leaf nodes should be allowed to be non-object values.
What it means
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.
Source
Thrown at lib/docs-renderer/src/renderer.rs:87
self.with_mut_object(|map| {
// Split the path, and take the last element as the actual map key to write to.
let mut segments = path.split('/').collect::<VecDeque<_>>();
let key = segments.pop_back().expect("Path must end with a key.");
// Iterate over the remaining elements, traversing into the root object one level at a
// time, based on using `token` as the map key. If there's no map at the given key,
// we'll create one. If there's something other than a map, we'll panic.
let mut destination = map;
while let Some(segment) = segments.pop_front() {
if destination.contains_key(segment) {
match destination.get_mut(segment) {
Some(Value::Object(next)) => {
destination = next;
continue;
}
Some(_) => {
panic!("Only leaf nodes should be allowed to be non-object values.")
}
None => unreachable!("Already asserted that the given key exists."),
}
} else {
destination.insert(segment.to_string(), Value::Object(Map::new()));
match destination.get_mut(segment) {
Some(Value::Object(next)) => {
destination = next;
}
_ => panic!("New object was just inserted."),
}
}
}
destination.insert(key.to_string(), value.into());
});
}
View on GitHub (pinned to 3708c39b12)
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.
Example fix
// before
renderer.write("/function", Value::String(name.clone()));
renderer.write("/function/aliases", aliases); // panic: /function is a string
// after
renderer.write("/function/name", Value::String(name));
renderer.write("/function/aliases", aliases); Defensive patterns
Strategy: type-guard
Validate before calling
if let Some(existing) = renderer.get(parent_path) {
if !existing.is_object() {
renderer.delete(parent_path); // clear the scalar before nesting under it
}
}
renderer.write(full_path, value); Type guard
fn segment_is_object(v: Option<&Value>) -> bool {
v.map(|v| v.is_object()).unwrap_or(true)
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Paths must always start with a leading forward slash (`/`).
- path and query should never fail to parse
- Serializer does not support JSON
- Reader encountered unrecoverable error: {e:?}
- Key is not found: {:?}
AI-assisted analysis of vectordotdev/vector@3708c39b12 (2026-08-20).
Data as JSON: /api/errors/af3ad52a15089b68.
Report an issue: GitHub.