Skip to main content

toml_spanner/
de.rs

1#[cfg(all(test, feature = "to-toml"))]
2#[path = "./de_tests.rs"]
3mod tests;
4
5use std::collections::{BTreeMap, BTreeSet};
6use std::hash::BuildHasher;
7use std::hash::Hash;
8use std::num::NonZeroU64;
9use std::path::PathBuf;
10
11use foldhash::HashMap;
12
13use std::fmt::{self, Debug, Display};
14
15use crate::Value;
16use crate::error::ErrorInner;
17use crate::{
18    Arena, Key, Span, Table,
19    error::{Error, ErrorKind, MaybeTomlPath, PathComponent},
20    item::{self, Item},
21    parser::{INDEXED_TABLE_THRESHOLD, KeyRef},
22};
23
24/// Walks the parsed item tree and resolves uncomputed TOML paths in errors.
25///
26/// After `FromToml` runs, errors contain raw item pointers (via `TomlPath::uncomputed`).
27/// This function walks the tree to find which table entry or array element each
28/// pointer belongs to, then replaces the uncomputed path with a real one.
29pub(crate) fn compute_paths(root: &Table<'_>, errors: &mut [Error]) {
30    let mut pending: Vec<(*const u8, Option<&mut MaybeTomlPath>)> = Vec::new();
31    for error in errors.iter_mut() {
32        if error.path.is_uncomputed() {
33            pending.push((
34                error.path.uncomputed_ptr() as *const u8,
35                Some(&mut error.path),
36            ));
37        }
38    }
39    if pending.is_empty() {
40        return;
41    }
42    pending.sort_unstable_by_key(|(addr, _)| *addr as usize);
43
44    let mut path_stack: [PathComponent<'_>; 32] = [PathComponent::Index(0); 32];
45    compute_paths_walk(root.as_item(), &mut pending, &mut path_stack, 0);
46}
47
48fn pending_region_start(
49    pending: &[(*const u8, Option<&mut MaybeTomlPath>)],
50    base_addr: usize,
51) -> usize {
52    pending.partition_point(|p| (p.0 as usize) < base_addr)
53}
54
55fn compute_paths_walk<'de>(
56    item: &Item<'de>,
57    pending: &mut [(*const u8, Option<&mut MaybeTomlPath>)],
58    path_stack: &mut [PathComponent<'de>; 32],
59    path_depth: usize,
60) {
61    if path_depth >= path_stack.len() {
62        return;
63    }
64    match item.value() {
65        Value::Table(table) => {
66            let entries = table.entries();
67            if entries.is_empty() {
68                return;
69            }
70            let entry_size = std::mem::size_of::<(Key<'_>, Item<'_>)>();
71            let base = entries.as_ptr() as *const u8;
72            // SAFETY: entries is a valid slice; pointer arithmetic stays in bounds.
73            let end = unsafe { base.add(entries.len() * entry_size) };
74            let base_addr = base as usize;
75            let end_addr = end as usize;
76
77            let start_idx = pending_region_start(pending, base_addr);
78            for slot in &mut pending[start_idx..] {
79                if (slot.0 as usize) >= end_addr {
80                    break;
81                }
82                let Some(path) = slot.1.take() else { continue };
83                // SAFETY: slot.0 is in [base, end) by partition_point + break bounds.
84                let byte_offset = unsafe { slot.0.byte_offset_from(base) } as usize;
85                let entry_index = byte_offset / entry_size;
86                path_stack[path_depth] = PathComponent::Key(entries[entry_index].0);
87                *path = MaybeTomlPath::from_components(&path_stack[..path_depth + 1]);
88            }
89
90            for (key, child) in table {
91                path_stack[path_depth] = PathComponent::Key(*key);
92                compute_paths_walk(child, pending, path_stack, path_depth + 1);
93            }
94        }
95        Value::Array(array) => {
96            let slice = array.as_slice();
97            if slice.is_empty() {
98                return;
99            }
100            let item_size = std::mem::size_of::<Item<'_>>();
101            let base = slice.as_ptr() as *const u8;
102            // SAFETY: slice is a valid slice; pointer arithmetic stays in bounds.
103            let end = unsafe { base.add(slice.len() * item_size) };
104            let base_addr = base as usize;
105            let end_addr = end as usize;
106
107            let start_idx = pending_region_start(pending, base_addr);
108            for slot in &mut pending[start_idx..] {
109                if (slot.0 as usize) >= end_addr {
110                    break;
111                }
112                let Some(path) = slot.1.take() else { continue };
113                // SAFETY: slot.0 is in [base, end) by partition_point + break bounds.
114                let byte_offset = unsafe { slot.0.byte_offset_from(base) } as usize;
115                let elem_index = byte_offset / item_size;
116                path_stack[path_depth] = PathComponent::Index(elem_index);
117                *path = MaybeTomlPath::from_components(&path_stack[..path_depth + 1]);
118            }
119
120            let mut idx = 0; // For some reason more efficient then iter() enumerate()
121            for child in array {
122                path_stack[path_depth] = PathComponent::Index(idx);
123                compute_paths_walk(child, pending, path_stack, path_depth + 1);
124                idx += 1;
125            }
126        }
127        _ => (),
128    }
129}
130
131/// Guides extraction from a [`Table`] by tracking which fields have been
132/// consumed.
133///
134/// Create via [`Document::table_helper`](crate::Document::table_helper) for the root
135/// table, or [`Item::table_helper`] / [`TableHelper::new`] for nested
136/// tables. Extract fields with [`required`](Self::required) and
137/// [`optional`](Self::optional), then call
138/// [`require_empty`](Self::require_empty) to reject unknown keys.
139///
140/// Errors accumulate in the shared [`Context`] rather than failing on the
141/// first problem, so a single pass can report multiple issues.
142///
143/// # Examples
144///
145/// ```
146/// use toml_spanner::{Arena, FromToml, Item, Context, Failed, TableHelper};
147///
148/// struct Config {
149///     name: String,
150///     port: u16,
151///     debug: bool,
152/// }
153///
154/// impl<'de> FromToml<'de> for Config {
155///     fn from_toml(ctx: &mut Context<'de>, item: &Item<'de>) -> Result<Self, Failed> {
156///         let mut th = item.table_helper(ctx)?;
157///         let name = th.required("name")?;
158///         let port = th.required("port")?;
159///         let debug = th.optional("debug").unwrap_or(false);
160///         th.require_empty()?;
161///         Ok(Config { name, port, debug })
162///     }
163/// }
164/// ```
165pub struct TableHelper<'ctx, 'table, 'de> {
166    pub ctx: &'ctx mut Context<'de>,
167    pub table: &'table Table<'de>,
168    // -1 means don't use table index.
169    table_id: i32,
170    // Used for detecting unused fields or iterating over remaining for flatten into collection.
171    used_count: u32,
172    used: &'de mut FixedBitset,
173}
174
175#[repr(transparent)]
176struct FixedBitset([u64]);
177
178impl FixedBitset {
179    #[allow(clippy::mut_from_ref)]
180    pub fn new(capacity: usize, arena: &Arena) -> &mut FixedBitset {
181        let bitset_bucket_count = capacity.div_ceil(64);
182        let bitset = arena
183            .alloc(bitset_bucket_count * std::mem::size_of::<u64>())
184            .cast::<u64>();
185        for offset in 0..bitset_bucket_count {
186            // SAFETY: `bitset_len * size_of::<u64>()` bytes were allocated above,
187            // so `bitset.add(offset)` for offset in 0..bitset_len is within bounds.
188            unsafe {
189                bitset.add(offset).write(0);
190            }
191        }
192        // SAFETY: bitset points to `bitset_len` initialized u64 values in the arena.
193        let slice = unsafe { std::slice::from_raw_parts_mut(bitset.as_ptr(), bitset_bucket_count) };
194        // SAFETY: FixedBitset is #[repr(transparent)] over [u64].
195        unsafe { &mut *(slice as *mut [u64] as *mut FixedBitset) }
196    }
197
198    pub fn insert(&mut self, index: usize) -> bool {
199        let offset = index >> 6;
200        let bit = 1 << (index & 63);
201        let old = self.0[offset];
202        self.0[offset] |= bit;
203        old & bit == 0
204    }
205
206    pub fn get(&self, index: usize) -> bool {
207        let offset = index >> 6;
208        let bit = 1 << (index & 63);
209        self.0[offset] & bit != 0
210    }
211}
212
213/// An iterator over table entries that were **not** consumed by
214/// [`TableHelper::required`], [`TableHelper::optional`] or similar methods.
215///
216/// Obtained via [`TableHelper::into_remaining`].
217pub struct RemainingEntriesIter<'t, 'de> {
218    entries: &'t [(Key<'de>, Item<'de>)],
219    remaining_cells: std::slice::Iter<'de, u64>,
220    bits: u64,
221}
222impl RemainingEntriesIter<'_, '_> {
223    fn next_bucket(&mut self) -> bool {
224        let Some(bucket) = self.remaining_cells.next() else {
225            return false;
226        };
227        debug_assert!(self.entries.len() > 64);
228        let Some(remaining) = self.entries.get(64..) else {
229            return false;
230        };
231        self.entries = remaining;
232        self.bits = !*bucket;
233        true
234    }
235}
236
237impl<'t, 'de> Iterator for RemainingEntriesIter<'t, 'de> {
238    type Item = &'t (Key<'de>, Item<'de>);
239
240    fn next(&mut self) -> Option<Self::Item> {
241        loop {
242            if let Some(bits) = NonZeroU64::new(self.bits) {
243                let bit_index = bits.trailing_zeros() as usize;
244                self.bits &= self.bits - 1;
245                return self.entries.get(bit_index);
246            }
247            if !self.next_bucket() {
248                return None;
249            }
250        }
251    }
252}
253
254impl<'ctx, 't, 'de> TableHelper<'ctx, 't, 'de> {
255    /// Creates a new helper for the given table.
256    ///
257    /// Prefer [`Item::table_helper`] inside [`FromToml`] implementations, or
258    /// [`Document::table_helper`](crate::Document::table_helper) for the root table.
259    pub fn new(ctx: &'ctx mut Context<'de>, table: &'t Table<'de>) -> Self {
260        let table_id = if table.len() > INDEXED_TABLE_THRESHOLD && table.meta.is_span_mode() {
261            table.entries()[0].0.span.start as i32
262        } else {
263            -1
264        };
265        Self {
266            used: FixedBitset::new(table.len(), ctx.arena),
267            ctx,
268            table,
269            table_id,
270            used_count: 0,
271        }
272    }
273
274    /// Looks up a key-value entry without marking it as consumed.
275    ///
276    /// Useful for peeking at a field before deciding how to convert it.
277    /// The entry will still be flagged as unexpected by
278    /// [`require_empty`](Self::require_empty) unless later consumed by
279    /// [`required`](Self::required) or [`optional`](Self::optional).
280    pub fn get_entry(&self, key: &str) -> Option<&'t (Key<'de>, Item<'de>)> {
281        if self.table_id < 0 {
282            for entry in self.table.entries() {
283                if entry.0.name == key {
284                    return Some(entry);
285                }
286            }
287            None
288        } else {
289            match self.ctx.index.get(&KeyRef::new(key, self.table_id as u32)) {
290                Some(index) => Some(&self.table.entries()[*index]),
291                None => None,
292            }
293        }
294    }
295
296    /// Extracts a required field and transforms it with `func`.
297    ///
298    /// Looks up `name`, marks it as consumed, and passes the [`Item`] to
299    /// `func`. Useful for parsing string values via [`Item::parse`] or
300    /// applying custom validation without implementing [`FromToml`].
301    ///
302    /// # Errors
303    ///
304    /// Returns [`Failed`] if the key is absent or if `func` returns an error.
305    /// In both cases the error is pushed onto the shared [`Context`].
306    pub fn required_mapped<T>(
307        &mut self,
308        name: &'static str,
309        func: fn(&Item<'de>) -> Result<T, Error>,
310    ) -> Result<T, Failed> {
311        let Some((_, item)) = self.optional_entry(name) else {
312            return Err(self.report_missing_field(name));
313        };
314
315        match func(item) {
316            Ok(t) => Ok(t),
317            Err(e) => Err(self.ctx.push_error(Error::custom(e, item.span_unchecked()))),
318        }
319    }
320
321    /// Extracts an optional field and transforms it with `func`.
322    ///
323    /// Returns [`None`] if the key is missing (no error recorded) or if
324    /// `func` returns an error (the error is pushed onto the [`Context`]).
325    /// The field is marked as consumed so
326    /// [`require_empty`](Self::require_empty) will not flag it as unexpected.
327    pub fn optional_mapped<T>(
328        &mut self,
329        name: &'static str,
330        func: fn(&Item<'de>) -> Result<T, Error>,
331    ) -> Option<T> {
332        let Some((_, item)) = self.optional_entry(name) else {
333            return None;
334        };
335
336        match func(item) {
337            Ok(t) => Some(t),
338            Err(e) => {
339                self.ctx.push_error(Error::custom(e, item.span_unchecked()));
340                None
341            }
342        }
343    }
344
345    /// Returns the raw [`Item`] for a required field.
346    ///
347    /// Like [`required`](Self::required) but skips conversion, giving direct
348    /// access to the parsed value. The field is marked as consumed.
349    ///
350    /// # Errors
351    ///
352    /// Returns [`Failed`] and records a
353    /// [`MissingField`](crate::ErrorKind::MissingField) error if the key is
354    /// absent.
355    pub fn required_item(&mut self, name: &'static str) -> Result<&'t Item<'de>, Failed> {
356        if let Ok((_, item)) = self.required_entry(name) {
357            Ok(item)
358        } else {
359            Err(self.report_missing_field(name))
360        }
361    }
362
363    /// Returns the raw [`Item`] for an optional field.
364    ///
365    /// Like [`optional`](Self::optional) but skips conversion, giving direct
366    /// access to the parsed value. Returns [`None`] when the key is missing
367    /// (no error recorded). The field is marked as consumed.
368    pub fn optional_item(&mut self, name: &'static str) -> Option<&'t Item<'de>> {
369        if let Some((_, item)) = self.optional_entry(name) {
370            Some(item)
371        } else {
372            None
373        }
374    }
375
376    /// Returns the `(`[`Key`]`, `[`Item`]`)` pair for a required field.
377    ///
378    /// Useful when the key's [`Span`](crate::Span) is needed in addition to
379    /// the value. The field is marked as consumed.
380    ///
381    /// # Errors
382    ///
383    /// Returns [`Failed`] and records a
384    /// [`MissingField`](crate::ErrorKind::MissingField) error if the key is
385    /// absent.
386    pub fn required_entry(
387        &mut self,
388        name: &'static str,
389    ) -> Result<&'t (Key<'de>, Item<'de>), Failed> {
390        match self.optional_entry(name) {
391            Some(entry) => Ok(entry),
392            None => Err(self.report_missing_field(name)),
393        }
394    }
395
396    /// Returns the `(`[`Key`]`, `[`Item`]`)` pair for an optional field.
397    ///
398    /// Returns [`None`] when the key is missing (no error recorded). Useful
399    /// when the key's [`Span`](crate::Span) is needed in addition to the
400    /// value. The field is marked as consumed.
401    pub fn optional_entry(&mut self, key: &str) -> Option<&'t (Key<'de>, Item<'de>)> {
402        let Some(entry) = self.get_entry(key) else {
403            return None;
404        };
405        // SAFETY: `entry` was returned by get_entry(), which either performs a
406        // linear scan of self.table.entries() or indexes into that same slice
407        // via the hash index. In both cases `entry` points to an element within
408        // the slice whose base pointer is `base`. offset_from is valid because
409        // both pointers derive from the same allocation and the result is a
410        // non-negative element index (< table.len()).
411        let index = unsafe {
412            let ptr = entry as *const (Key<'de>, Item<'de>);
413            let base = self.table.entries().as_ptr();
414            ptr.offset_from(base) as usize
415        };
416        if self.used.insert(index) {
417            self.used_count += 1;
418        }
419        Some(entry)
420    }
421
422    #[cold]
423    fn report_missing_field(&mut self, name: &'static str) -> Failed {
424        self.ctx.errors.push(Error::new_with_path(
425            ErrorKind::MissingField(name),
426            self.table.span(),
427            MaybeTomlPath::uncomputed(self.table.as_item()),
428        ));
429        Failed
430    }
431
432    /// Extracts and converts a required field via [`FromToml`].
433    ///
434    /// The field is marked as consumed so [`require_empty`](Self::require_empty)
435    /// will not flag it as unexpected.
436    ///
437    /// # Errors
438    ///
439    /// Returns [`Failed`] if the key is absent or if conversion fails.
440    /// In both cases the error is pushed onto the shared [`Context`].
441    pub fn required<T: FromToml<'de>>(&mut self, name: &'static str) -> Result<T, Failed> {
442        let Some((_, val)) = self.optional_entry(name) else {
443            return Err(self.report_missing_field(name));
444        };
445
446        T::from_toml(self.ctx, val)
447    }
448
449    /// Extracts and converts an optional field via [`FromToml`], returning
450    /// [`None`] if the key is missing or conversion fails (recording the
451    /// error in the [`Context`]).
452    ///
453    /// The field is marked as consumed so [`require_empty`](Self::require_empty)
454    /// will not flag it as unexpected.
455    pub fn optional<T: FromToml<'de>>(&mut self, name: &str) -> Option<T> {
456        let Some((_, val)) = self.optional_entry(name) else {
457            return None;
458        };
459
460        #[allow(clippy::manual_ok_err)]
461        match T::from_toml(self.ctx, val) {
462            Ok(value) => Some(value),
463            Err(_) => None,
464        }
465    }
466
467    /// Returns the number of unused entries remaining in the table.
468    pub fn remaining_count(&self) -> usize {
469        self.table.len() - self.used_count as usize
470    }
471
472    /// Iterate over unused `&(Key<'de>, Item<'de>)` entries in the table.
473    pub fn into_remaining(self) -> RemainingEntriesIter<'t, 'de> {
474        let entries = self.table.entries();
475        let mut remaining_cells = self.used.0.iter();
476        RemainingEntriesIter {
477            bits: if let Some(value) = remaining_cells.next() {
478                !*value
479            } else {
480                0
481            },
482            entries,
483            remaining_cells,
484        }
485    }
486
487    /// Finishes field extraction, recording an error for any fields not
488    /// consumed by [`required`](Self::required) or
489    /// [`optional`](Self::optional).
490    ///
491    /// Call as the last step in a [`FromToml`] implementation to reject
492    /// unknown keys.
493    ///
494    /// # Errors
495    ///
496    /// Returns [`Failed`] and pushes an [`ErrorKind::UnexpectedKey`](crate::ErrorKind::UnexpectedKey)
497    /// error if unconsumed fields remain.
498    #[doc(alias = "expect_empty")]
499    #[inline(never)]
500    pub fn require_empty(self) -> Result<(), Failed> {
501        if self.used_count as usize == self.table.len() {
502            return Ok(());
503        }
504
505        let mut had_unexpected = false;
506        for (i, (key, item)) in self.table.entries().iter().enumerate() {
507            if !self.used.get(i) {
508                self.ctx.errors.push(Error {
509                    kind: ErrorInner::Static(ErrorKind::UnexpectedKey { tag: 0 }),
510                    span: key.span,
511                    path: MaybeTomlPath::uncomputed(item),
512                });
513
514                had_unexpected = true;
515            }
516        }
517
518        if had_unexpected { Err(Failed) } else { Ok(()) }
519    }
520}
521
522/// Shared state that accumulates errors and holds the arena.
523///
524/// Created by [`parse`](crate::parse) and stored inside
525/// [`Document`](crate::Document). Pass it into [`TableHelper::new`] or
526/// [`Item::table_helper`] when implementing [`FromToml`].
527///
528/// Multiple errors can be recorded during a single conversion pass.
529/// Inspect them via [`Document::errors`](crate::Document::errors).
530pub struct Context<'de> {
531    pub arena: &'de Arena,
532    pub(crate) index: HashMap<KeyRef<'de>, usize>,
533    pub errors: Vec<Error>,
534    pub(crate) source: &'de str,
535}
536
537impl<'de> Context<'de> {
538    /// Returns the original TOML source string passed to [`parse`](crate::parse).
539    pub fn source(&self) -> &'de str {
540        self.source
541    }
542
543    /// Records a "expected X, found Y" type-mismatch error and returns [`Failed`].
544    #[cold]
545    pub fn report_expected_but_found(
546        &mut self,
547        message: &'static &'static str,
548        found: &Item<'de>,
549    ) -> Failed {
550        let path = MaybeTomlPath::uncomputed(found);
551        self.errors.push(Error::new_with_path(
552            ErrorKind::Wanted {
553                expected: message,
554                found: found.type_str(),
555            },
556            found.span(),
557            path,
558        ));
559        Failed
560    }
561
562    /// Records an "unknown variant" error listing the accepted variants and returns [`Failed`].
563    #[cold]
564    pub fn report_unexpected_variant(
565        &mut self,
566        expected: &'static [&'static str],
567        found: &Item<'de>,
568    ) -> Failed {
569        let path = MaybeTomlPath::uncomputed(found);
570        self.errors.push(Error::new_with_path(
571            ErrorKind::UnexpectedVariant { expected },
572            found.span(),
573            path,
574        ));
575        Failed
576    }
577
578    /// Records a custom error message at the given span and returns [`Failed`].
579    #[cold]
580    pub fn report_error_at(&mut self, message: &'static str, at: Span) -> Failed {
581        self.errors.push(Error::custom_static(message, at));
582        Failed
583    }
584    /// Pushes a pre-built [`Error`] and returns [`Failed`].
585    #[cold]
586    pub fn push_error(&mut self, error: Error) -> Failed {
587        self.errors.push(error);
588        Failed
589    }
590
591    /// Records a custom error anchored to `item` and returns [`Failed`].
592    ///
593    /// Equivalent to pushing [`Error::custom_at(error, item)`](Error::custom_at).
594    /// The recorded [`Error::path`] resolves to the dotted TOML path of
595    /// `item` once
596    /// [`Document::compute_error_paths`](crate::Document::compute_error_paths)
597    /// has run, which [`Document::to`](crate::Document::to) and
598    /// [`Document::to_allowing_errors`](crate::Document::to_allowing_errors)
599    /// do automatically.
600    ///
601    /// Use [`Context::report_error_at`](Self::report_error_at) when only a
602    /// [`Span`] is available.
603    #[cold]
604    pub fn report_custom_error(&mut self, error: impl ToString, item: &Item<'de>) -> Failed {
605        self.push_error(Error::custom_at(error, item))
606    }
607
608    /// Records an out-of-range error for the type `name` and returns [`Failed`].
609    #[cold]
610    pub fn report_out_of_range(
611        &mut self,
612        ty: &'static &'static str,
613        range: &'static &'static str,
614        found: &Item<'de>,
615    ) -> Failed {
616        let path = MaybeTomlPath::uncomputed(found);
617        self.errors.push(Error::new_with_path(
618            ErrorKind::OutOfRange { ty, range },
619            found.span(),
620            path,
621        ));
622        Failed
623    }
624
625    /// Records a missing-field error and returns [`Failed`].
626    ///
627    /// Used by generated `FromToml` implementations that iterate over table
628    /// entries instead of using [`TableHelper`].
629    #[cold]
630    pub fn report_missing_field(&mut self, name: &'static str, item: &Item<'de>) -> Failed {
631        let path = MaybeTomlPath::uncomputed(item);
632        self.errors.push(Error::new_with_path(
633            ErrorKind::MissingField(name),
634            item.span(),
635            path,
636        ));
637        Failed
638    }
639
640    /// Records a duplicate-field error and returns [`Failed`].
641    ///
642    /// Used by generated `FromToml` implementations when a field with aliases
643    /// is set more than once (e.g. both the primary key and an alias appear).
644    #[cold]
645    pub fn report_duplicate_field(
646        &mut self,
647        name: &'static str,
648        key_span: Span,
649        first_key_span: Span,
650        item: &Item<'de>,
651    ) -> Failed {
652        self.push_error(Error::new_with_path(
653            ErrorKind::DuplicateField {
654                field: name,
655                first: first_key_span,
656            },
657            key_span,
658            MaybeTomlPath::uncomputed(item),
659        ))
660    }
661
662    /// Records a deprecated-field warning with TOML path information.
663    ///
664    /// Unlike other `report_*` methods this is **non-fatal**: it pushes
665    /// an error but does not return [`Failed`], so deserialization continues.
666    #[cold]
667    pub fn report_deprecated_field(
668        &mut self,
669        tag: u32,
670        old: &'static &'static str,
671        new: &'static &'static str,
672        key_span: Span,
673        item: &Item<'de>,
674    ) {
675        self.errors.push(Error::new_with_path(
676            ErrorKind::Deprecated { tag, old, new },
677            key_span,
678            MaybeTomlPath::uncomputed(item),
679        ));
680    }
681
682    /// Records an unexpected-key error with TOML path information.
683    #[cold]
684    pub fn report_unexpected_key(&mut self, tag: u32, item: &Item<'de>, key_span: Span) -> Failed {
685        let path = MaybeTomlPath::uncomputed(item);
686        self.errors.push(Error::new_with_path(
687            ErrorKind::UnexpectedKey { tag },
688            key_span,
689            path,
690        ));
691        Failed
692    }
693}
694
695pub use crate::Failed;
696
697/// Converts a TOML [`Item`] into a Rust type.
698///
699/// `#[derive(Toml)]` generates `FromToml` by default, or add
700/// `#[toml(FromToml)]` to be explicit (required when also deriving
701/// `ToToml`). See the [`Toml`](macro@crate::Toml) derive macro for the full
702/// set of attributes, enum representations, and field options.
703///
704/// # Examples
705///
706/// [`from_str`](crate::from_str) parses and deserializes when all fields
707/// are owned:
708///
709#[cfg_attr(feature = "derive", doc = "```")]
710#[cfg_attr(not(feature = "derive"), doc = "```ignore")]
711/// use toml_spanner::Toml;
712///
713/// #[derive(Toml)]
714/// struct Config {
715///     name: String,
716///     port: u16,
717/// }
718///
719/// let config: Config = toml_spanner::from_str("name = 'app'\nport = 8080").unwrap();
720/// ```
721///
722/// Parse into a [`Document`](crate::Document) when fields borrow from the
723/// input. The `'de` lifetime spans both the source text and the [`Arena`],
724/// so `&'de str` fields work even when the value contains escape sequences
725/// (the arena stores the decoded string).
726///
727#[cfg_attr(feature = "derive", doc = "```")]
728#[cfg_attr(not(feature = "derive"), doc = "```ignore")]
729/// use toml_spanner::{Arena, parse, Toml};
730///
731/// #[derive(Toml)]
732/// struct Package<'a> {
733///     name: &'a str,
734///     version: &'a str,
735/// }
736///
737/// let arena = Arena::new();
738/// let mut doc = parse("name = 'my-pkg'\nversion = '1.0'", &arena).unwrap();
739/// let pkg: Package<'_> = doc.to().unwrap();
740/// assert_eq!(pkg.name, "my-pkg");
741/// ```
742///
743/// Manual implementation with [`TableHelper`]:
744///
745/// ```
746/// use toml_spanner::{Item, Context, FromToml, Failed};
747///
748/// struct Point {
749///     x: f64,
750///     y: f64,
751/// }
752///
753/// impl<'de> FromToml<'de> for Point {
754///     fn from_toml(ctx: &mut Context<'de>, item: &Item<'de>) -> Result<Self, Failed> {
755///         let mut th = item.table_helper(ctx)?;
756///         let x = th.required("x")?;
757///         let y = th.required("y")?;
758///         th.require_empty()?;
759///         Ok(Point { x, y })
760///     }
761/// }
762/// ```
763///
764/// [`require_empty`](TableHelper::require_empty) rejects unknown keys by
765/// returning [`Failed`]. The derive defaults to recording them as warnings
766/// instead, a distinction covered below.
767///
768/// # Error handling
769///
770/// [`Failed`] is a zero size sentinel, not an error type. All actual errors
771/// are recorded in the shared [`Context`], which collects every problem
772/// across the entire pass so they can be reported together. Use the
773/// `report_*` methods on [`Context`] (such as
774/// [`report_custom_error`](Context::report_custom_error)) to record an
775/// error and receive a [`Failed`] to return. Always push at least one error
776/// before returning `Err(Failed)`.
777///
778/// An implementation can also push errors while still returning `Ok`,
779/// recording problems without preventing construction. This is how the
780/// derive handles unknown keys by default (`warn_unknown_fields`).
781/// [`Document::to`](crate::Document::to) treats any recorded error as
782/// fatal, returning `Err` even when `from_toml` succeeded.
783/// [`Document::to_allowing_errors`](crate::Document::to_allowing_errors)
784/// returns both the value and the accumulated errors, letting the caller
785/// decide which are acceptable.
786///
787/// For untagged enums, the derive snapshots the error count before
788/// attempting each variant and truncates on failure, so only errors from
789/// the matching (or final) variant are reported.
790pub trait FromToml<'de>: Sized {
791    /// Attempts to construct `Self` from a TOML [`Item`].
792    fn from_toml(ctx: &mut Context<'de>, item: &Item<'de>) -> Result<Self, Failed>;
793}
794
795/// Trait for types that can be constructed from flattened TOML table entries.
796///
797/// Used with `#[toml(flatten)]` on struct fields. Built-in implementations
798/// cover `HashMap` and `BTreeMap`.
799///
800/// If your type implements [`FromToml`], use
801/// `#[toml(flatten, with = flatten_any)]` in your derive instead of
802/// implementing this trait. See [`helper::flatten_any`](crate::helper::flatten_any).
803#[diagnostic::on_unimplemented(
804    message = "`{Self}` does not implement `FromFlattened`",
805    note = "if `{Self}` implements `FromToml`, you can use `#[toml(flatten, with = flatten_any)]` instead of a manual `FromFlattened` impl"
806)]
807pub trait FromFlattened<'de>: Sized {
808    /// Intermediate accumulator type used during conversion.
809    type Partial;
810    /// Creates an empty accumulator to collect flattened entries.
811    fn init() -> Self::Partial;
812    /// Inserts a single key-value pair into the accumulator.
813    fn insert(
814        ctx: &mut Context<'de>,
815        key: &Key<'de>,
816        item: &Item<'de>,
817        partial: &mut Self::Partial,
818    ) -> Result<(), Failed>;
819    /// Converts the accumulator into the final value after all entries
820    /// have been inserted. The `parent` parameter is the table that
821    /// contained the flattened entries.
822    fn finish(
823        ctx: &mut Context<'de>,
824        parent: &Table<'de>,
825        partial: Self::Partial,
826    ) -> Result<Self, Failed>;
827}
828
829/// Converts a TOML key into a map key, preserving span information.
830fn key_from_toml<'de, K: FromToml<'de>>(
831    ctx: &mut Context<'de>,
832    key: &Key<'de>,
833) -> Result<K, Failed> {
834    let item = Item::string_spanned(key.name, key.span);
835    K::from_toml(ctx, &item)
836}
837
838impl<'de, K, V, H> FromFlattened<'de> for std::collections::HashMap<K, V, H>
839where
840    K: Hash + Eq + FromToml<'de>,
841    V: FromToml<'de>,
842    H: Default + BuildHasher,
843{
844    type Partial = Self;
845    fn init() -> Self {
846        std::collections::HashMap::default()
847    }
848    fn insert(
849        ctx: &mut Context<'de>,
850        key: &Key<'de>,
851        item: &Item<'de>,
852        partial: &mut Self::Partial,
853    ) -> Result<(), Failed> {
854        let k = key_from_toml(ctx, key)?;
855        let v = match V::from_toml(ctx, item) {
856            Ok(v) => v,
857            Err(_) => return Err(Failed),
858        };
859        partial.insert(k, v);
860        Ok(())
861    }
862    fn finish(
863        _ctx: &mut Context<'de>,
864        _parent: &Table<'de>,
865        partial: Self::Partial,
866    ) -> Result<Self, Failed> {
867        Ok(partial)
868    }
869}
870
871impl<'de, K, V, H> FromToml<'de> for std::collections::HashMap<K, V, H>
872where
873    K: Hash + Eq + FromToml<'de>,
874    V: FromToml<'de>,
875    H: Default + BuildHasher,
876{
877    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
878        let table = value.require_table(ctx)?;
879        let mut map = std::collections::HashMap::default();
880        let mut had_error = false;
881        for (key, item) in table {
882            let k = match key_from_toml(ctx, key) {
883                Ok(k) => k,
884                Err(_) => {
885                    had_error = true;
886                    continue;
887                }
888            };
889            match V::from_toml(ctx, item) {
890                Ok(v) => {
891                    map.insert(k, v);
892                }
893                Err(_) => had_error = true,
894            }
895        }
896        if had_error { Err(Failed) } else { Ok(map) }
897    }
898}
899
900impl<'de, K, V> FromFlattened<'de> for BTreeMap<K, V>
901where
902    K: Ord + FromToml<'de>,
903    V: FromToml<'de>,
904{
905    type Partial = Self;
906    fn init() -> Self {
907        BTreeMap::new()
908    }
909    fn insert(
910        ctx: &mut Context<'de>,
911        key: &Key<'de>,
912        item: &Item<'de>,
913        partial: &mut Self::Partial,
914    ) -> Result<(), Failed> {
915        let k = key_from_toml(ctx, key)?;
916        let v = match V::from_toml(ctx, item) {
917            Ok(v) => v,
918            Err(_) => return Err(Failed),
919        };
920        partial.insert(k, v);
921        Ok(())
922    }
923    fn finish(
924        _ctx: &mut Context<'de>,
925        _parent: &Table<'de>,
926        partial: Self::Partial,
927    ) -> Result<Self, Failed> {
928        Ok(partial)
929    }
930}
931
932impl<'de> FromFlattened<'de> for Table<'de> {
933    type Partial = Table<'de>;
934    fn init() -> Self::Partial {
935        Table::new()
936    }
937    fn insert(
938        ctx: &mut Context<'de>,
939        key: &Key<'de>,
940        item: &Item<'de>,
941        partial: &mut Self::Partial,
942    ) -> Result<(), Failed> {
943        partial.insert_unique(*key, item.clone_in(ctx.arena), ctx.arena);
944        Ok(())
945    }
946    fn finish(
947        _ctx: &mut Context<'de>,
948        parent: &Table<'de>,
949        mut partial: Self::Partial,
950    ) -> Result<Self, Failed> {
951        partial.meta = parent.meta;
952        Ok(partial)
953    }
954}
955
956impl<'de> FromFlattened<'de> for Item<'de> {
957    type Partial = Table<'de>;
958    fn init() -> Self::Partial {
959        Table::new()
960    }
961    fn insert(
962        ctx: &mut Context<'de>,
963        key: &Key<'de>,
964        item: &Item<'de>,
965        partial: &mut Self::Partial,
966    ) -> Result<(), Failed> {
967        <Table<'de> as FromFlattened<'de>>::insert(ctx, key, item, partial)
968    }
969    fn finish(
970        _ctx: &mut Context<'de>,
971        parent: &Table<'de>,
972        mut partial: Self::Partial,
973    ) -> Result<Self, Failed> {
974        partial.meta = parent.meta;
975        Ok(partial.into_item())
976    }
977}
978
979impl<'de, T: FromToml<'de>, const N: usize> FromToml<'de> for [T; N] {
980    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
981        let boxed_slice = Box::<[T]>::from_toml(ctx, value)?;
982        match <Box<[T; N]>>::try_from(boxed_slice) {
983            Ok(array) => Ok(*array),
984            Err(res) => Err(ctx.push_error(Error::custom(
985                format!(
986                    "expected an array with a size of {}, found one with a size of {}",
987                    N,
988                    res.len()
989                ),
990                value.span_unchecked(),
991            ))),
992        }
993    }
994}
995
996macro_rules! impl_from_toml_tuple {
997    ($len:expr, $($idx:tt => $T:ident, $var:ident),+) => {
998        impl<'de, $($T: FromToml<'de>),+> FromToml<'de> for ($($T,)+) {
999            fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1000                let arr = value.require_array(ctx)?;
1001                if arr.len() != $len {
1002                    return Err(ctx.push_error(Error::custom(
1003                        format!(
1004                            "expected an array with a size of {}, found one with a size of {}",
1005                            $len,
1006                            arr.len()
1007                        ),
1008                        value.span_unchecked(),
1009                    )));
1010                }
1011                let slice = arr.as_slice();
1012                let mut had_error = false;
1013                $(
1014                    let $var = match $T::from_toml(ctx, &slice[$idx]) {
1015                        Ok(v) => Some(v),
1016                        Err(_) => { had_error = true; None }
1017                    };
1018                )+
1019                if had_error {
1020                    return Err(Failed);
1021                }
1022                Ok(($($var.unwrap(),)+))
1023            }
1024        }
1025    };
1026}
1027
1028impl_from_toml_tuple!(1, 0 => A, a);
1029impl_from_toml_tuple!(2, 0 => A, a, 1 => B, b);
1030impl_from_toml_tuple!(3, 0 => A, a, 1 => B, b, 2 => C, c);
1031
1032impl<'de> FromToml<'de> for String {
1033    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1034        match value.as_str() {
1035            Some(s) => Ok(s.to_string()),
1036            None => Err(ctx.report_expected_but_found(&"a string", value)),
1037        }
1038    }
1039}
1040
1041impl<'de> FromToml<'de> for PathBuf {
1042    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1043        match value.as_str() {
1044            Some(s) => Ok(PathBuf::from(s)),
1045            None => Err(ctx.report_expected_but_found(&"a path", value)),
1046        }
1047    }
1048}
1049
1050impl<'de, T: FromToml<'de>> FromToml<'de> for Option<T> {
1051    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1052        T::from_toml(ctx, value).map(Some)
1053    }
1054}
1055
1056impl<'de, T: FromToml<'de>> FromToml<'de> for Box<T> {
1057    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1058        match T::from_toml(ctx, value) {
1059            Ok(v) => Ok(Box::new(v)),
1060            Err(e) => Err(e),
1061        }
1062    }
1063}
1064impl<'de, T: FromToml<'de>> FromToml<'de> for Box<[T]> {
1065    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1066        match Vec::<T>::from_toml(ctx, value) {
1067            Ok(vec) => Ok(vec.into_boxed_slice()),
1068            Err(e) => Err(e),
1069        }
1070    }
1071}
1072impl<'de> FromToml<'de> for Box<str> {
1073    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1074        match value.value() {
1075            item::Value::String(&s) => Ok(s.into()),
1076            _ => Err(ctx.report_expected_but_found(&"a string", value)),
1077        }
1078    }
1079}
1080impl<'de> FromToml<'de> for &'de str {
1081    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1082        match value.value() {
1083            item::Value::String(s) => Ok(*s),
1084            _ => Err(ctx.report_expected_but_found(&"a string", value)),
1085        }
1086    }
1087}
1088
1089impl<'de> FromToml<'de> for std::borrow::Cow<'de, str> {
1090    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1091        match value.value() {
1092            item::Value::String(s) => Ok(std::borrow::Cow::Borrowed(*s)),
1093            _ => Err(ctx.report_expected_but_found(&"a string", value)),
1094        }
1095    }
1096}
1097
1098impl<'de> FromToml<'de> for bool {
1099    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1100        match value.as_bool() {
1101            Some(b) => Ok(b),
1102            None => Err(ctx.report_expected_but_found(&"a bool", value)),
1103        }
1104    }
1105}
1106
1107fn deser_integer_ctx<'de>(
1108    ctx: &mut Context<'de>,
1109    value: &Item<'de>,
1110    min: i128,
1111    max: i128,
1112    ty: &'static &'static str,
1113    range: &'static &'static str,
1114) -> Result<i128, Failed> {
1115    match value.as_i128() {
1116        Some(i) if i >= min && i <= max => Ok(i),
1117        Some(_) => Err(ctx.report_out_of_range(ty, range, value)),
1118        None => Err(ctx.report_expected_but_found(&"an integer", value)),
1119    }
1120}
1121
1122macro_rules! integer_new {
1123    ($($num:ty => $range:literal),+) => {$(
1124        impl<'de> FromToml<'de> for $num {
1125            fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1126                match deser_integer_ctx(ctx, value, <$num>::MIN as i128, <$num>::MAX as i128, &stringify!($num), &$range) {
1127                    Ok(i) => Ok(i as $num),
1128                    Err(e) => Err(e),
1129                }
1130            }
1131        }
1132    )+};
1133}
1134
1135integer_new!(
1136    i8 => "-128..=127",
1137    i16 => "-32768..=32767",
1138    i32 => "-2147483648..=2147483647",
1139    u8 => "0..=255",
1140    u16 => "0..=65535",
1141    u32 => "0..=4294967295"
1142);
1143
1144impl<'de> FromToml<'de> for isize {
1145    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1146        #[cfg(target_pointer_width = "32")]
1147        const RANGE: &str = "-2147483648..=2147483647";
1148        #[cfg(target_pointer_width = "64")]
1149        const RANGE: &str = "-9223372036854775808..=9223372036854775807";
1150        match deser_integer_ctx(
1151            ctx,
1152            value,
1153            isize::MIN as i128,
1154            isize::MAX as i128,
1155            &"isize",
1156            &RANGE,
1157        ) {
1158            Ok(i) => Ok(i as isize),
1159            Err(e) => Err(e),
1160        }
1161    }
1162}
1163
1164impl<'de> FromToml<'de> for i64 {
1165    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1166        match deser_integer_ctx(
1167            ctx,
1168            value,
1169            i64::MIN as i128,
1170            i64::MAX as i128,
1171            &"i64",
1172            &"-9223372036854775808..=9223372036854775807",
1173        ) {
1174            Ok(i) => Ok(i as i64),
1175            Err(e) => Err(e),
1176        }
1177    }
1178}
1179
1180impl<'de> FromToml<'de> for u64 {
1181    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1182        match deser_integer_ctx(
1183            ctx,
1184            value,
1185            0,
1186            u64::MAX as i128,
1187            &"u64",
1188            &"0..=18446744073709551615",
1189        ) {
1190            Ok(i) => Ok(i as u64),
1191            Err(e) => Err(e),
1192        }
1193    }
1194}
1195
1196impl<'de> FromToml<'de> for i128 {
1197    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1198        match deser_integer_ctx(
1199            ctx,
1200            value,
1201            i128::MIN,
1202            i128::MAX,
1203            &"i128",
1204            &"-170141183460469231731687303715884105728..=170141183460469231731687303715884105727",
1205        ) {
1206            Ok(i) => Ok(i),
1207            Err(e) => Err(e),
1208        }
1209    }
1210}
1211
1212impl<'de> FromToml<'de> for u128 {
1213    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1214        match deser_integer_ctx(
1215            ctx,
1216            value,
1217            0,
1218            i128::MAX,
1219            &"u128",
1220            &"0..=340282366920938463463374607431768211455",
1221        ) {
1222            Ok(i) => Ok(i as u128),
1223            Err(e) => Err(e),
1224        }
1225    }
1226}
1227
1228impl<'de> FromToml<'de> for usize {
1229    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1230        #[cfg(target_pointer_width = "32")]
1231        const RANGE: &str = "0..=4294967295";
1232        #[cfg(target_pointer_width = "64")]
1233        const RANGE: &str = "0..=18446744073709551615";
1234        match deser_integer_ctx(ctx, value, 0, usize::MAX as i128, &"usize", &RANGE) {
1235            Ok(i) => Ok(i as usize),
1236            Err(e) => Err(e),
1237        }
1238    }
1239}
1240
1241impl<'de> FromToml<'de> for f32 {
1242    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1243        match value.as_f64() {
1244            Some(f) => Ok(f as f32),
1245            None => Err(ctx.report_expected_but_found(&"a float", value)),
1246        }
1247    }
1248}
1249
1250impl<'de> FromToml<'de> for f64 {
1251    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1252        match value.as_f64() {
1253            Some(f) => Ok(f),
1254            None => Err(ctx.report_expected_but_found(&"a float", value)),
1255        }
1256    }
1257}
1258
1259impl<'de, T> FromToml<'de> for Vec<T>
1260where
1261    T: FromToml<'de>,
1262{
1263    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1264        let arr = value.require_array(ctx)?;
1265        let mut result = Vec::with_capacity(arr.len());
1266        let mut had_error = false;
1267        for item in arr {
1268            match T::from_toml(ctx, item) {
1269                Ok(v) => result.push(v),
1270                Err(_) => had_error = true,
1271            }
1272        }
1273        if had_error { Err(Failed) } else { Ok(result) }
1274    }
1275}
1276
1277impl<'de, T> FromToml<'de> for BTreeSet<T>
1278where
1279    T: Ord + FromToml<'de>,
1280{
1281    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1282        let arr = value.require_array(ctx)?;
1283        let mut result = BTreeSet::new();
1284        let mut had_error = false;
1285        for item in arr {
1286            match T::from_toml(ctx, item) {
1287                Ok(v) => {
1288                    result.insert(v);
1289                }
1290                Err(_) => had_error = true,
1291            }
1292        }
1293        if had_error { Err(Failed) } else { Ok(result) }
1294    }
1295}
1296
1297impl<'de, K, V> FromToml<'de> for BTreeMap<K, V>
1298where
1299    K: Ord + FromToml<'de>,
1300    V: FromToml<'de>,
1301{
1302    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1303        let table = value.require_table(ctx)?;
1304        let mut map = BTreeMap::new();
1305        let mut had_error = false;
1306        for (key, item) in table {
1307            let k = match key_from_toml(ctx, key) {
1308                Ok(k) => k,
1309                Err(_) => {
1310                    had_error = true;
1311                    continue;
1312                }
1313            };
1314            match V::from_toml(ctx, item) {
1315                Ok(v) => {
1316                    map.insert(k, v);
1317                }
1318                Err(_) => had_error = true,
1319            }
1320        }
1321        if had_error { Err(Failed) } else { Ok(map) }
1322    }
1323}
1324
1325impl<'de> Item<'de> {
1326    /// Returns a string, or records an error with a custom `expected` message.
1327    ///
1328    /// Use instead of [`require_string`](Self::require_string) when the
1329    /// expected value is more specific than "a string", for example
1330    /// `"an IPv4 address"` or `"a hex color"`.
1331    #[doc(alias = "expect_custom_string")]
1332    pub fn require_custom_string(
1333        &self,
1334        ctx: &mut Context<'de>,
1335        expected: &'static &'static str,
1336    ) -> Result<&'de str, Failed> {
1337        match self.value() {
1338            item::Value::String(s) => Ok(*s),
1339            _ => Err(ctx.report_expected_but_found(expected, self)),
1340        }
1341    }
1342    /// Returns a string, or records an error if this is not a string.
1343    #[doc(alias = "expect_string")]
1344    pub fn require_string(&self, ctx: &mut Context<'de>) -> Result<&'de str, Failed> {
1345        match self.value() {
1346            item::Value::String(s) => Ok(*s),
1347            _ => Err(ctx.report_expected_but_found(&"a string", self)),
1348        }
1349    }
1350
1351    /// Returns an array reference, or records an error if this is not an array.
1352    #[doc(alias = "expect_array")]
1353    pub fn require_array(&self, ctx: &mut Context<'de>) -> Result<&crate::Array<'de>, Failed> {
1354        match self.as_array() {
1355            Some(arr) => Ok(arr),
1356            None => Err(ctx.report_expected_but_found(&"an array", self)),
1357        }
1358    }
1359
1360    /// Returns a table reference, or records an error if this is not a table.
1361    #[doc(alias = "expect_table")]
1362    pub fn require_table(&self, ctx: &mut Context<'de>) -> Result<&crate::Table<'de>, Failed> {
1363        match self.as_table() {
1364            Some(table) => Ok(table),
1365            None => Err(ctx.report_expected_but_found(&"a table", self)),
1366        }
1367    }
1368
1369    /// Creates a [`TableHelper`] for this item, returning an error if it is
1370    /// not a table.
1371    ///
1372    /// Typical entry point for implementing [`FromToml`].
1373    pub fn table_helper<'ctx, 'item>(
1374        &'item self,
1375        ctx: &'ctx mut Context<'de>,
1376    ) -> Result<TableHelper<'ctx, 'item, 'de>, Failed> {
1377        let Some(table) = self.as_table() else {
1378            return Err(ctx.report_expected_but_found(&"a table", self));
1379        };
1380        Ok(TableHelper::new(ctx, table))
1381    }
1382}
1383
1384/// Collects errors encountered during parsing and conversion.
1385///
1386/// Returned by [`from_str`](crate::from_str) and [`Document::to`](crate::Document::to).
1387/// Contains one or more [`Error`] values, each with its own source span.
1388///
1389/// # Examples
1390///
1391/// ```
1392/// let result = toml_spanner::from_str::<std::collections::HashMap<String, String>>(
1393///     "bad toml {"
1394/// );
1395/// assert!(result.is_err());
1396/// let err = result.unwrap_err();
1397/// assert!(!err.errors.is_empty());
1398/// ```
1399#[derive(Debug)]
1400pub struct FromTomlError {
1401    /// The accumulated errors.
1402    pub errors: Vec<Error>,
1403}
1404
1405impl Display for FromTomlError {
1406    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1407        let Some(first) = self.errors.first() else {
1408            return f.write_str("deserialization failed");
1409        };
1410        Display::fmt(first, f)?;
1411        let remaining = self.errors.len() - 1;
1412        if remaining > 0 {
1413            write!(
1414                f,
1415                " (+{remaining} more error{})",
1416                if remaining == 1 { "" } else { "s" }
1417            )?;
1418        }
1419        Ok(())
1420    }
1421}
1422
1423impl std::error::Error for FromTomlError {}
1424
1425impl From<Error> for FromTomlError {
1426    fn from(error: Error) -> Self {
1427        Self {
1428            errors: vec![error],
1429        }
1430    }
1431}
1432
1433impl From<Vec<Error>> for FromTomlError {
1434    fn from(errors: Vec<Error>) -> Self {
1435        Self { errors }
1436    }
1437}
1438
1439impl IntoIterator for FromTomlError {
1440    type Item = Error;
1441    type IntoIter = std::vec::IntoIter<Error>;
1442    fn into_iter(self) -> Self::IntoIter {
1443        self.errors.into_iter()
1444    }
1445}
1446
1447impl<'a> IntoIterator for &'a FromTomlError {
1448    type Item = &'a Error;
1449    type IntoIter = std::slice::Iter<'a, Error>;
1450    fn into_iter(self) -> Self::IntoIter {
1451        self.errors.iter()
1452    }
1453}