use super::alias::resolve_alias;
use super::parse::segment_display;
use super::read::{descend_one, resolve_for_read, spelled_key};
use super::{AliasPolicy, PathError, PathSegment, MAX_INDEX_GROWTH};
use crate::yaml::Mapping;
fn is_replaceable_scalar(node: &crate::as_yaml::YamlNode) -> bool {
matches!(node, crate::as_yaml::YamlNode::Scalar(_))
}
fn take_value(
mapping: &Mapping,
key: impl crate::AsYaml + Copy,
) -> Option<crate::as_yaml::YamlNode> {
let value = mapping.get(key)?;
mapping.remove(key);
Some(value)
}
fn segment_key(segment: &PathSegment) -> String {
match segment {
PathSegment::Key(key) => key.clone(),
PathSegment::Index(index) => index.to_string(),
}
}
pub(super) fn remove_path_impl(
root: crate::as_yaml::YamlNode,
holder: Option<&PathSegment>,
segments: &[PathSegment],
policy: AliasPolicy,
) -> Result<crate::as_yaml::YamlNode, PathError> {
debug_assert!(!segments.is_empty(), "caller must reject empty paths");
let seg = &segments[0];
if segments.len() > 1 {
let nested = descend_one(root, holder, seg, policy)?;
return remove_path_impl(nested, Some(seg), &segments[1..], policy);
}
let holder = holder.unwrap_or(seg);
let root = resolve_for_read(root, holder, policy)?;
let mapping = root.mapping_content().ok_or_else(|| {
let index_on_sequence =
matches!(seg, PathSegment::Index(_)) && root.sequence_content().is_some();
PathError::TypeMismatch {
at: segment_display(if index_on_sequence { seg } else { holder }),
}
})?;
let key = segment_key(seg);
take_value(&mapping, key.as_str())
.or_else(|| take_value(&mapping, &spelled_key(mapping.keys(), &key)?))
.ok_or_else(|| PathError::NotFound {
at: segment_display(seg),
})
}
fn undo_child_on_err(
container: &crate::yaml::SyntaxNode,
child: usize,
write: impl FnOnce() -> Result<(), PathError>,
) -> Result<(), PathError> {
let saved = container
.children_with_tokens()
.nth(child)
.and_then(|c| c.into_node())
.expect("child names a node")
.green()
.into_owned();
let result = write();
if result.is_err() {
let restored = crate::yaml::SyntaxNode::new_root_mut(saved);
crate::nodes::replace_children(container, child..child + 1, vec![restored.into()]);
}
result
}
fn set_path_on_node<V: crate::AsYaml>(
node: crate::as_yaml::YamlNode,
segments: &[PathSegment],
value: V,
policy: AliasPolicy,
via: &PathSegment,
) -> Result<(), PathError> {
if let Some(m) = node.mapping_content() {
set_path_on_mapping(&m, segments, value, policy)
} else if let Some(seq) = node.sequence_content() {
set_path_on_sequence(&seq, segments, value, policy)
} else {
Err(PathError::TypeMismatch {
at: segment_display(via),
})
}
}
pub(super) fn set_path_on_mapping<V: crate::AsYaml>(
mapping: &Mapping,
segments: &[PathSegment],
value: V,
policy: AliasPolicy,
) -> Result<(), PathError> {
debug_assert!(!segments.is_empty(), "caller must reject empty paths");
let key = segment_key(&segments[0]);
if mapping.contains_key(key.as_str()) {
return set_path_in_slot(Slot::Key(mapping, key.as_str()), segments, value, policy);
}
match spelled_key(mapping.keys(), &key) {
Some(existing) => set_path_in_slot(Slot::Key(mapping, &existing), segments, value, policy),
None => set_path_in_slot(Slot::Key(mapping, key.as_str()), segments, value, policy),
}
}
enum Slot<'a, K> {
Key(&'a Mapping, K),
Index(&'a crate::yaml::Sequence, usize),
}
impl<K: crate::AsYaml + Copy> Slot<'_, K> {
fn get(&self) -> Option<crate::as_yaml::YamlNode> {
match *self {
Slot::Key(mapping, key) => mapping.get(key),
Slot::Index(sequence, index) => sequence.get(index),
}
}
fn set(&self, value: impl crate::AsYaml) {
match *self {
Slot::Key(mapping, key) => mapping.set(key, value),
Slot::Index(sequence, index) => {
sequence.set(index, value);
}
}
}
fn entry(&self) -> (&crate::yaml::SyntaxNode, usize) {
match *self {
Slot::Key(mapping, key) => (
&mapping.0,
mapping
.find_entry_index_by_key(key)
.expect("get just found this key"),
),
Slot::Index(sequence, index) => (
&sequence.0,
crate::nodes::nth_entry_index(
&sequence.0,
crate::SyntaxKind::SEQUENCE_ENTRY,
index,
)
.expect("get just found this index"),
),
}
}
fn insert_container(&self, sequence: bool) {
match *self {
Slot::Key(mapping, key) => match (sequence, mapping.is_flow_style()) {
(true, true) => mapping.set(key, crate::yaml::Sequence::new_flow()),
(true, false) => mapping.set(key, crate::yaml::Sequence::new_pending_block()),
(false, true) => mapping.set(key, Mapping::new_flow()),
(false, false) => mapping.set(key, Mapping::new_pending_block()),
},
Slot::Index(..) if sequence => self.set(
crate::builder::SequenceBuilder::new()
.build_document()
.as_sequence()
.expect("SequenceBuilder always produces a sequence"),
),
Slot::Index(..) => self.set(
crate::builder::MappingBuilder::new()
.build_document()
.as_mapping()
.expect("MappingBuilder always produces a mapping"),
),
}
}
}
fn set_path_in_slot<K: crate::AsYaml + Copy, V: crate::AsYaml>(
slot: Slot<'_, K>,
segments: &[PathSegment],
value: V,
policy: AliasPolicy,
) -> Result<(), PathError> {
if segments.len() == 1 {
slot.set(value);
return Ok(());
}
let (at, rest) = (&segments[0], &segments[1..]);
let mismatch = || PathError::TypeMismatch {
at: segment_display(at),
};
let existing = slot.get();
if let Some(crate::as_yaml::YamlNode::Alias(alias)) = &existing {
let resolved = resolve_alias(alias, at, policy)?;
if policy != AliasPolicy::Expand {
return set_path_on_node(resolved, rest, value, policy, at);
}
let (container, child) = slot.entry();
return undo_child_on_err(container, child, || {
slot.set(resolved);
let target = slot.get().expect("set keeps the entry");
set_path_on_node(target, rest, value, policy, at)
});
}
if existing
.as_ref()
.and_then(|n| n.as_scalar())
.is_some_and(|sc| !sc.is_null())
{
return Err(mismatch());
}
let wants_sequence = matches!(rest[0], PathSegment::Index(_));
if wants_sequence {
if let Some(nested) = existing.as_ref().and_then(|n| n.sequence_content()) {
return set_path_on_sequence(&nested, rest, value, policy);
}
}
if let Some(nested) = existing.as_ref().and_then(|n| n.mapping_content()) {
return set_path_on_mapping(&nested, rest, value, policy);
}
if existing.as_ref().is_some_and(|n| !is_replaceable_scalar(n)) {
return Err(mismatch());
}
check_new_path(rest)?;
slot.insert_container(wants_sequence);
let created = slot.get();
if wants_sequence {
let nested = created
.and_then(|n| n.sequence_content())
.expect("we just inserted a sequence here");
set_path_on_sequence(&nested, rest, value, policy)
} else {
let nested = created
.and_then(|n| n.mapping_content())
.expect("we just inserted a mapping here");
set_path_on_mapping(&nested, rest, value, policy)
}
}
fn check_new_path(segments: &[PathSegment]) -> Result<(), PathError> {
segments.iter().try_for_each(|segment| match segment {
PathSegment::Index(index) if *index >= MAX_INDEX_GROWTH => Err(PathError::IndexTooFar {
at: segment_display(segment),
len: 0,
index: *index,
}),
_ => Ok(()),
})
}
fn set_path_on_sequence<V: crate::AsYaml>(
sequence: &crate::yaml::Sequence,
segments: &[PathSegment],
value: V,
policy: AliasPolicy,
) -> Result<(), PathError> {
debug_assert!(!segments.is_empty(), "caller must reject empty paths");
let index = match &segments[0] {
PathSegment::Index(i) => *i,
PathSegment::Key(_) => {
return Err(PathError::TypeMismatch {
at: segment_display(&segments[0]),
});
}
};
let len = sequence.len();
if index >= len && index - len >= MAX_INDEX_GROWTH {
return Err(PathError::IndexTooFar {
at: segment_display(&segments[0]),
len,
index,
});
}
if let Some(growth) = (index + 1).checked_sub(len) {
check_new_path(&segments[1..])?;
sequence.extend_with(crate::scalar::ScalarValue::null(), growth);
}
set_path_in_slot(
Slot::<&str>::Index(sequence, index),
segments,
value,
policy,
)
}