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