1use crate::as_yaml::{AsYaml, YamlNode};
20use crate::nodes::mapping::{Mapping, MappingEntry};
21use crate::nodes::sequence::Sequence;
22
23#[derive(Debug)]
28pub enum Entry<'a, K: AsYaml> {
29 Occupied(OccupiedEntry<'a>),
31 Vacant(VacantEntry<'a, K>),
33}
34
35#[derive(Debug)]
37pub struct OccupiedEntry<'a> {
38 pub(crate) mapping: &'a Mapping,
39 pub(crate) entry: MappingEntry,
40}
41
42#[derive(Debug)]
44pub struct VacantEntry<'a, K: AsYaml> {
45 pub(crate) mapping: &'a Mapping,
46 pub(crate) key: K,
47}
48
49impl<'a, K: AsYaml> Entry<'a, K> {
50 pub fn or_insert(self, default: impl AsYaml) -> YamlNode {
53 match self {
54 Entry::Occupied(o) => o.into_value(),
55 Entry::Vacant(v) => v.insert(default),
56 }
57 }
58
59 pub fn or_insert_with<V, F>(self, default: F) -> YamlNode
64 where
65 V: AsYaml,
66 F: FnOnce() -> V,
67 {
68 match self {
69 Entry::Occupied(o) => o.into_value(),
70 Entry::Vacant(v) => v.insert(default()),
71 }
72 }
73
74 pub fn or_insert_mapping(self) -> Mapping {
80 match self {
81 Entry::Occupied(o) => {
82 if let Some(existing) = o.get_mapping() {
83 return existing;
84 }
85 o.set_value(Mapping::new());
86 o.get_mapping()
87 .expect("value was just replaced with a mapping")
88 }
89 Entry::Vacant(v) => {
90 let VacantEntry { mapping, key } = v;
91 mapping.set(&key, Mapping::new());
92 mapping
93 .get_mapping(&key)
94 .expect("mapping was just inserted")
95 }
96 }
97 }
98
99 pub fn or_insert_sequence(self) -> Sequence {
105 match self {
106 Entry::Occupied(o) => {
107 if let Some(existing) = o.get_sequence() {
108 return existing;
109 }
110 o.set_value(Sequence::new());
111 o.get_sequence()
112 .expect("value was just replaced with a sequence")
113 }
114 Entry::Vacant(v) => {
115 let VacantEntry { mapping, key } = v;
116 mapping.set(&key, Sequence::new());
117 mapping
118 .get_sequence(&key)
119 .expect("sequence was just inserted")
120 }
121 }
122 }
123
124 pub fn and_modify<F>(self, f: F) -> Self
135 where
136 F: FnOnce(&OccupiedEntry<'a>),
137 {
138 if let Entry::Occupied(ref o) = self {
139 f(o);
140 }
141 self
142 }
143
144 pub fn key(&self) -> Option<YamlNode> {
150 match self {
151 Entry::Occupied(o) => o.key(),
152 Entry::Vacant(v) => key_as_yaml_node(&v.key),
153 }
154 }
155}
156
157impl<'a> OccupiedEntry<'a> {
158 pub fn key(&self) -> Option<YamlNode> {
162 self.entry.key_node()
163 }
164
165 pub fn get(&self) -> Option<YamlNode> {
169 self.entry.value_node()
170 }
171
172 pub fn get_mapping(&self) -> Option<Mapping> {
174 self.get().and_then(|n| n.as_mapping().cloned())
175 }
176
177 pub fn get_sequence(&self) -> Option<Sequence> {
179 self.get().and_then(|n| n.as_sequence().cloned())
180 }
181
182 pub fn set_value(&self, value: impl AsYaml) {
184 let flow_context = self.mapping.is_flow_style();
185 self.entry.set_value(value, flow_context);
186 }
187
188 pub fn remove_entry(self) -> MappingEntry {
191 self.entry.clone().remove();
192 self.entry
193 }
194
195 pub(crate) fn into_value(self) -> YamlNode {
200 self.entry
201 .value_node()
202 .expect("occupied entry has no value node")
203 }
204}
205
206impl<'a, K: AsYaml> VacantEntry<'a, K> {
207 pub fn key(&self) -> &K {
209 &self.key
210 }
211
212 pub fn into_key(self) -> K {
215 self.key
216 }
217
218 pub fn insert(self, value: impl AsYaml) -> YamlNode {
221 self.mapping.set(&self.key, value);
222 self.mapping
223 .get(&self.key)
224 .expect("value just inserted must be retrievable")
225 }
226}
227
228fn key_as_yaml_node<K: AsYaml>(key: &K) -> Option<YamlNode> {
229 use crate::lex::SyntaxKind;
230 use rowan::GreenNodeBuilder;
231
232 if let Some(node) = key.as_node() {
233 return YamlNode::from_syntax(node.clone());
234 }
235 let mut builder = GreenNodeBuilder::new();
236 builder.start_node(SyntaxKind::ROOT.into());
237 key.build_content(&mut builder, 0, false);
238 builder.finish_node();
239 let root = crate::nodes::SyntaxNode::new_root(builder.finish());
240 root.children().next().and_then(YamlNode::from_syntax)
241}
242
243#[cfg(test)]
244mod tests {
245 use crate::{Document, Entry};
246 use std::str::FromStr;
247
248 #[test]
249 fn or_insert_on_vacant_inserts_and_returns_value() {
250 let doc = Document::from_str("name: Alice\n").unwrap();
251 let mapping = doc.as_mapping().unwrap();
252
253 let v = mapping.entry("age").or_insert(30);
254 assert_eq!(v.to_string().trim(), "30");
255 assert_eq!(doc.to_string(), "name: Alice\nage: 30\n");
256 }
257
258 #[test]
259 fn or_insert_on_occupied_returns_existing_value_and_does_not_overwrite() {
260 let doc = Document::from_str("name: Alice\n").unwrap();
261 let mapping = doc.as_mapping().unwrap();
262
263 let v = mapping.entry("name").or_insert("Bob");
264 assert_eq!(v.to_string().trim(), "Alice");
265 assert_eq!(doc.to_string(), "name: Alice\n");
266 }
267
268 #[test]
269 fn or_insert_with_only_calls_closure_when_vacant() {
270 let doc = Document::from_str("count: 5\n").unwrap();
271 let mapping = doc.as_mapping().unwrap();
272
273 let mut called = false;
274 let _ = mapping.entry("count").or_insert_with(|| {
275 called = true;
276 99_i64
277 });
278 assert!(!called);
279
280 let mut called = false;
281 let _ = mapping.entry("other").or_insert_with(|| {
282 called = true;
283 123_i64
284 });
285 assert!(called);
286 assert_eq!(doc.to_string(), "count: 5\nother: 123\n");
287 }
288
289 #[test]
290 fn and_modify_on_occupied_runs_closure_and_updates_value() {
291 let doc = Document::from_str("count: 1\n").unwrap();
292 let mapping = doc.as_mapping().unwrap();
293
294 mapping.entry("count").and_modify(|e| e.set_value(42));
295 assert_eq!(doc.to_string(), "count: 42\n");
296 }
297
298 #[test]
299 fn and_modify_on_vacant_is_noop() {
300 let doc = Document::from_str("count: 1\n").unwrap();
301 let mapping = doc.as_mapping().unwrap();
302
303 let mut called = false;
304 mapping.entry("missing").and_modify(|_| {
305 called = true;
306 });
307 assert!(!called);
308 assert_eq!(doc.to_string(), "count: 1\n");
309 }
310
311 #[test]
312 fn and_modify_then_or_insert_pattern() {
313 let doc = Document::from_str("count: 1\n").unwrap();
314 let mapping = doc.as_mapping().unwrap();
315
316 mapping
317 .entry("count")
318 .and_modify(|e| e.set_value(2))
319 .or_insert(0);
320 assert_eq!(mapping.get("count").unwrap().to_string().trim(), "2");
321
322 mapping
323 .entry("new")
324 .and_modify(|e| e.set_value("never"))
325 .or_insert("first");
326 assert_eq!(mapping.get("new").unwrap().to_string().trim(), "first");
327 }
328
329 #[test]
330 fn entry_variants_expose_expected_state() {
331 let doc = Document::from_str("k: v\n").unwrap();
332 let mapping = doc.as_mapping().unwrap();
333
334 match mapping.entry("k") {
335 Entry::Occupied(o) => {
336 assert_eq!(o.key().unwrap().to_string().trim(), "k");
337 assert_eq!(o.get().unwrap().to_string().trim(), "v");
338 }
339 Entry::Vacant(_) => panic!("expected occupied"),
340 }
341
342 match mapping.entry("missing") {
343 Entry::Vacant(v) => {
344 assert_eq!(*v.key(), "missing");
345 }
346 Entry::Occupied(_) => panic!("expected vacant"),
347 }
348 }
349
350 #[test]
351 fn entry_key_agnostic_of_variant() {
352 let doc = Document::from_str("k: v\n").unwrap();
353 let mapping = doc.as_mapping().unwrap();
354
355 let occupied_key = mapping.entry("k").key().unwrap();
356 assert_eq!(occupied_key.to_string().trim(), "k");
357
358 let vacant_key = mapping.entry("missing").key().unwrap();
359 assert_eq!(vacant_key.to_string().trim(), "missing");
360 }
361
362 #[test]
363 fn vacant_entry_insert_returns_new_value_node() {
364 let doc = Document::from_str("a: 1\n").unwrap();
365 let mapping = doc.as_mapping().unwrap();
366
367 let Entry::Vacant(v) = mapping.entry("b") else {
368 panic!("expected vacant");
369 };
370 let node = v.insert(2_i64);
371 assert_eq!(node.to_string().trim(), "2");
372 assert_eq!(doc.to_string(), "a: 1\nb: 2\n");
373 }
374
375 #[test]
376 fn occupied_entry_remove_detaches_from_mapping() {
377 let doc = Document::from_str("a: 1\nb: 2\n").unwrap();
378 let mapping = doc.as_mapping().unwrap();
379
380 let Entry::Occupied(o) = mapping.entry("a") else {
381 panic!("expected occupied");
382 };
383 let removed = o.remove_entry();
384 assert_eq!(removed.key_node().unwrap().to_string().trim(), "a");
385 assert!(!mapping.contains_key("a"));
386 assert_eq!(doc.to_string(), "b: 2\n");
387 }
388
389 #[test]
390 fn or_insert_mapping_on_vacant_creates_and_returns_nested_mapping() {
391 let doc = Document::from_str("existing: value\n").unwrap();
392 let mapping = doc.as_mapping().unwrap();
393
394 let nested = mapping.entry("nested").or_insert_mapping();
395 nested.set("inner", "value");
396 assert_eq!(
397 doc.to_string(),
398 "existing: value\nnested:\n inner: value\n"
399 );
400 }
401
402 #[test]
403 fn or_insert_mapping_on_occupied_returns_existing_mapping() {
404 let doc = Document::from_str("root:\n a: 1\n").unwrap();
405 let mapping = doc.as_mapping().unwrap();
406
407 let nested = mapping.entry("root").or_insert_mapping();
408 nested.set("b", 2);
409 assert_eq!(doc.to_string(), "root:\n a: 1\n b: 2\n");
410 }
411
412 #[test]
413 fn or_insert_sequence_on_vacant_creates_and_returns_nested_sequence() {
414 let doc = Document::from_str("existing: value\n").unwrap();
415 let mapping = doc.as_mapping().unwrap();
416
417 let list = mapping.entry("items").or_insert_sequence();
418 list.push("apple");
419 list.push("banana");
420 assert_eq!(
421 doc.to_string(),
422 "existing: value\nitems:\n - apple\n - banana\n"
423 );
424 }
425
426 #[test]
427 fn or_insert_sequence_on_occupied_returns_existing_sequence() {
428 let doc = Document::from_str("items:\n - apple\n").unwrap();
429 let mapping = doc.as_mapping().unwrap();
430
431 let list = mapping.entry("items").or_insert_sequence();
432 list.push("banana");
433 assert_eq!(doc.to_string(), "items:\n - apple\n - banana\n");
434 }
435
436 #[test]
437 fn entry_preserves_comments_on_untouched_keys() {
438 let src = "keep: this # inline\nother: value\n";
439 let doc = Document::from_str(src).unwrap();
440 let mapping = doc.as_mapping().unwrap();
441
442 mapping.entry("added").or_insert(1_i64);
443 assert_eq!(
444 doc.to_string(),
445 "keep: this # inline\nother: value\nadded: 1\n"
446 );
447 }
448
449 #[test]
450 fn entry_key_semantic_equality() {
451 let doc = Document::from_str("\"name\": Alice\n").unwrap();
452 let mapping = doc.as_mapping().unwrap();
453
454 match mapping.entry("name") {
455 Entry::Occupied(_) => {}
456 Entry::Vacant(_) => panic!("expected occupied"),
457 }
458 }
459
460 #[test]
461 fn or_insert_with_can_return_different_types() {
462 let doc = Document::from_str("a: 1\n").unwrap();
463 let mapping = doc.as_mapping().unwrap();
464
465 let _ = mapping.entry("s").or_insert_with(|| "hello");
466 let _ = mapping.entry("n").or_insert_with(|| 42_i64);
467 let _ = mapping.entry("b").or_insert_with(|| true);
468 assert_eq!(doc.to_string(), "a: 1\ns: hello\nn: 42\nb: true\n");
469 }
470
471 #[test]
472 fn or_insert_mapping_replaces_non_mapping_value() {
473 let doc = Document::from_str("k: scalar\n").unwrap();
474 let mapping = doc.as_mapping().unwrap();
475
476 let nested = mapping.entry("k").or_insert_mapping();
477 nested.set("inner", "v");
478 assert_eq!(doc.to_string(), "k:\n inner: v\n");
479 }
480}