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/// Trait for types that can be constructed from a TOML [`Item`].
679///
680/// Implement this to enable extraction via [`TableHelper::required`] and
681/// [`TableHelper::optional`]. Built-in implementations cover primitive types,
682/// `String`, `Vec<T>`, `Box<T>`, `Option<T>`, and more.
683///
684/// # Examples
685///
686/// ```
687/// use toml_spanner::{Item, Context, FromToml, Failed, TableHelper};
688///
689/// struct Point {
690///     x: f64,
691///     y: f64,
692/// }
693///
694/// impl<'de> FromToml<'de> for Point {
695///     fn from_toml(ctx: &mut Context<'de>, item: &Item<'de>) -> Result<Self, Failed> {
696///         let mut th = item.table_helper(ctx)?;
697///         let x = th.required("x")?;
698///         let y = th.required("y")?;
699///         th.require_empty()?;
700///         Ok(Point { x, y })
701///     }
702/// }
703/// ```
704pub trait FromToml<'de>: Sized {
705    /// Attempts to construct `Self` from a TOML [`Item`].
706    fn from_toml(ctx: &mut Context<'de>, item: &Item<'de>) -> Result<Self, Failed>;
707}
708
709/// Trait for types that can be constructed from flattened TOML table entries.
710///
711/// Used with `#[toml(flatten)]` on struct fields. Built-in implementations
712/// cover `HashMap` and `BTreeMap`.
713///
714/// If your type implements [`FromToml`], use
715/// `#[toml(flatten, with = flatten_any)]` in your derive instead of
716/// implementing this trait. See [`helper::flatten_any`](crate::helper::flatten_any).
717#[diagnostic::on_unimplemented(
718    message = "`{Self}` does not implement `FromFlattened`",
719    note = "if `{Self}` implements `FromToml`, you can use `#[toml(flatten, with = flatten_any)]` instead of a manual `FromFlattened` impl"
720)]
721pub trait FromFlattened<'de>: Sized {
722    /// Intermediate accumulator type used during conversion.
723    type Partial;
724    /// Creates an empty accumulator to collect flattened entries.
725    fn init() -> Self::Partial;
726    /// Inserts a single key-value pair into the accumulator.
727    fn insert(
728        ctx: &mut Context<'de>,
729        key: &Key<'de>,
730        item: &Item<'de>,
731        partial: &mut Self::Partial,
732    ) -> Result<(), Failed>;
733    /// Converts the accumulator into the final value after all entries
734    /// have been inserted. The `parent` parameter is the table that
735    /// contained the flattened entries.
736    fn finish(
737        ctx: &mut Context<'de>,
738        parent: &Table<'de>,
739        partial: Self::Partial,
740    ) -> Result<Self, Failed>;
741}
742
743/// Converts a TOML key into a map key, preserving span information.
744fn key_from_toml<'de, K: FromToml<'de>>(
745    ctx: &mut Context<'de>,
746    key: &Key<'de>,
747) -> Result<K, Failed> {
748    let item = Item::string_spanned(key.name, key.span);
749    K::from_toml(ctx, &item)
750}
751
752impl<'de, K, V, H> FromFlattened<'de> for std::collections::HashMap<K, V, H>
753where
754    K: Hash + Eq + FromToml<'de>,
755    V: FromToml<'de>,
756    H: Default + BuildHasher,
757{
758    type Partial = Self;
759    fn init() -> Self {
760        std::collections::HashMap::default()
761    }
762    fn insert(
763        ctx: &mut Context<'de>,
764        key: &Key<'de>,
765        item: &Item<'de>,
766        partial: &mut Self::Partial,
767    ) -> Result<(), Failed> {
768        let k = key_from_toml(ctx, key)?;
769        let v = match V::from_toml(ctx, item) {
770            Ok(v) => v,
771            Err(_) => return Err(Failed),
772        };
773        partial.insert(k, v);
774        Ok(())
775    }
776    fn finish(
777        _ctx: &mut Context<'de>,
778        _parent: &Table<'de>,
779        partial: Self::Partial,
780    ) -> Result<Self, Failed> {
781        Ok(partial)
782    }
783}
784
785impl<'de, K, V, H> FromToml<'de> for std::collections::HashMap<K, V, H>
786where
787    K: Hash + Eq + FromToml<'de>,
788    V: FromToml<'de>,
789    H: Default + BuildHasher,
790{
791    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
792        let table = value.require_table(ctx)?;
793        let mut map = std::collections::HashMap::default();
794        let mut had_error = false;
795        for (key, item) in table {
796            let k = match key_from_toml(ctx, key) {
797                Ok(k) => k,
798                Err(_) => {
799                    had_error = true;
800                    continue;
801                }
802            };
803            match V::from_toml(ctx, item) {
804                Ok(v) => {
805                    map.insert(k, v);
806                }
807                Err(_) => had_error = true,
808            }
809        }
810        if had_error { Err(Failed) } else { Ok(map) }
811    }
812}
813
814impl<'de, K, V> FromFlattened<'de> for BTreeMap<K, V>
815where
816    K: Ord + FromToml<'de>,
817    V: FromToml<'de>,
818{
819    type Partial = Self;
820    fn init() -> Self {
821        BTreeMap::new()
822    }
823    fn insert(
824        ctx: &mut Context<'de>,
825        key: &Key<'de>,
826        item: &Item<'de>,
827        partial: &mut Self::Partial,
828    ) -> Result<(), Failed> {
829        let k = key_from_toml(ctx, key)?;
830        let v = match V::from_toml(ctx, item) {
831            Ok(v) => v,
832            Err(_) => return Err(Failed),
833        };
834        partial.insert(k, v);
835        Ok(())
836    }
837    fn finish(
838        _ctx: &mut Context<'de>,
839        _parent: &Table<'de>,
840        partial: Self::Partial,
841    ) -> Result<Self, Failed> {
842        Ok(partial)
843    }
844}
845
846impl<'de, T: FromToml<'de>, const N: usize> FromToml<'de> for [T; N] {
847    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
848        let boxed_slice = Box::<[T]>::from_toml(ctx, value)?;
849        match <Box<[T; N]>>::try_from(boxed_slice) {
850            Ok(array) => Ok(*array),
851            Err(res) => Err(ctx.push_error(Error::custom(
852                format!(
853                    "expected an array with a size of {}, found one with a size of {}",
854                    N,
855                    res.len()
856                ),
857                value.span_unchecked(),
858            ))),
859        }
860    }
861}
862
863macro_rules! impl_from_toml_tuple {
864    ($len:expr, $($idx:tt => $T:ident, $var:ident),+) => {
865        impl<'de, $($T: FromToml<'de>),+> FromToml<'de> for ($($T,)+) {
866            fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
867                let arr = value.require_array(ctx)?;
868                if arr.len() != $len {
869                    return Err(ctx.push_error(Error::custom(
870                        format!(
871                            "expected an array with a size of {}, found one with a size of {}",
872                            $len,
873                            arr.len()
874                        ),
875                        value.span_unchecked(),
876                    )));
877                }
878                let slice = arr.as_slice();
879                let mut had_error = false;
880                $(
881                    let $var = match $T::from_toml(ctx, &slice[$idx]) {
882                        Ok(v) => Some(v),
883                        Err(_) => { had_error = true; None }
884                    };
885                )+
886                if had_error {
887                    return Err(Failed);
888                }
889                Ok(($($var.unwrap(),)+))
890            }
891        }
892    };
893}
894
895impl_from_toml_tuple!(1, 0 => A, a);
896impl_from_toml_tuple!(2, 0 => A, a, 1 => B, b);
897impl_from_toml_tuple!(3, 0 => A, a, 1 => B, b, 2 => C, c);
898
899impl<'de> FromToml<'de> for String {
900    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
901        match value.as_str() {
902            Some(s) => Ok(s.to_string()),
903            None => Err(ctx.report_expected_but_found(&"a string", value)),
904        }
905    }
906}
907
908impl<'de> FromToml<'de> for PathBuf {
909    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
910        match value.as_str() {
911            Some(s) => Ok(PathBuf::from(s)),
912            None => Err(ctx.report_expected_but_found(&"a path", value)),
913        }
914    }
915}
916
917impl<'de, T: FromToml<'de>> FromToml<'de> for Option<T> {
918    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
919        T::from_toml(ctx, value).map(Some)
920    }
921}
922
923impl<'de, T: FromToml<'de>> FromToml<'de> for Box<T> {
924    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
925        match T::from_toml(ctx, value) {
926            Ok(v) => Ok(Box::new(v)),
927            Err(e) => Err(e),
928        }
929    }
930}
931impl<'de, T: FromToml<'de>> FromToml<'de> for Box<[T]> {
932    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
933        match Vec::<T>::from_toml(ctx, value) {
934            Ok(vec) => Ok(vec.into_boxed_slice()),
935            Err(e) => Err(e),
936        }
937    }
938}
939impl<'de> FromToml<'de> for Box<str> {
940    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
941        match value.value() {
942            item::Value::String(&s) => Ok(s.into()),
943            _ => Err(ctx.report_expected_but_found(&"a string", value)),
944        }
945    }
946}
947impl<'de> FromToml<'de> for &'de str {
948    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
949        match value.value() {
950            item::Value::String(s) => Ok(*s),
951            _ => Err(ctx.report_expected_but_found(&"a string", value)),
952        }
953    }
954}
955
956impl<'de> FromToml<'de> for std::borrow::Cow<'de, str> {
957    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
958        match value.value() {
959            item::Value::String(s) => Ok(std::borrow::Cow::Borrowed(*s)),
960            _ => Err(ctx.report_expected_but_found(&"a string", value)),
961        }
962    }
963}
964
965impl<'de> FromToml<'de> for bool {
966    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
967        match value.as_bool() {
968            Some(b) => Ok(b),
969            None => Err(ctx.report_expected_but_found(&"a bool", value)),
970        }
971    }
972}
973
974fn deser_integer_ctx<'de>(
975    ctx: &mut Context<'de>,
976    value: &Item<'de>,
977    min: i128,
978    max: i128,
979    ty: &'static &'static str,
980    range: &'static &'static str,
981) -> Result<i128, Failed> {
982    match value.as_i128() {
983        Some(i) if i >= min && i <= max => Ok(i),
984        Some(_) => Err(ctx.report_out_of_range(ty, range, value)),
985        None => Err(ctx.report_expected_but_found(&"an integer", value)),
986    }
987}
988
989macro_rules! integer_new {
990    ($($num:ty => $range:literal),+) => {$(
991        impl<'de> FromToml<'de> for $num {
992            fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
993                match deser_integer_ctx(ctx, value, <$num>::MIN as i128, <$num>::MAX as i128, &stringify!($num), &$range) {
994                    Ok(i) => Ok(i as $num),
995                    Err(e) => Err(e),
996                }
997            }
998        }
999    )+};
1000}
1001
1002integer_new!(
1003    i8 => "-128..=127",
1004    i16 => "-32768..=32767",
1005    i32 => "-2147483648..=2147483647",
1006    u8 => "0..=255",
1007    u16 => "0..=65535",
1008    u32 => "0..=4294967295"
1009);
1010
1011impl<'de> FromToml<'de> for isize {
1012    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1013        #[cfg(target_pointer_width = "32")]
1014        const RANGE: &str = "-2147483648..=2147483647";
1015        #[cfg(target_pointer_width = "64")]
1016        const RANGE: &str = "-9223372036854775808..=9223372036854775807";
1017        match deser_integer_ctx(
1018            ctx,
1019            value,
1020            isize::MIN as i128,
1021            isize::MAX as i128,
1022            &"isize",
1023            &RANGE,
1024        ) {
1025            Ok(i) => Ok(i as isize),
1026            Err(e) => Err(e),
1027        }
1028    }
1029}
1030
1031impl<'de> FromToml<'de> for i64 {
1032    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1033        match deser_integer_ctx(
1034            ctx,
1035            value,
1036            i64::MIN as i128,
1037            i64::MAX as i128,
1038            &"i64",
1039            &"-9223372036854775808..=9223372036854775807",
1040        ) {
1041            Ok(i) => Ok(i as i64),
1042            Err(e) => Err(e),
1043        }
1044    }
1045}
1046
1047impl<'de> FromToml<'de> for u64 {
1048    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1049        match deser_integer_ctx(
1050            ctx,
1051            value,
1052            0,
1053            u64::MAX as i128,
1054            &"u64",
1055            &"0..=18446744073709551615",
1056        ) {
1057            Ok(i) => Ok(i as u64),
1058            Err(e) => Err(e),
1059        }
1060    }
1061}
1062
1063impl<'de> FromToml<'de> for i128 {
1064    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1065        match deser_integer_ctx(
1066            ctx,
1067            value,
1068            i128::MIN,
1069            i128::MAX,
1070            &"i128",
1071            &"-170141183460469231731687303715884105728..=170141183460469231731687303715884105727",
1072        ) {
1073            Ok(i) => Ok(i),
1074            Err(e) => Err(e),
1075        }
1076    }
1077}
1078
1079impl<'de> FromToml<'de> for u128 {
1080    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1081        match deser_integer_ctx(
1082            ctx,
1083            value,
1084            0,
1085            i128::MAX,
1086            &"u128",
1087            &"0..=340282366920938463463374607431768211455",
1088        ) {
1089            Ok(i) => Ok(i as u128),
1090            Err(e) => Err(e),
1091        }
1092    }
1093}
1094
1095impl<'de> FromToml<'de> for usize {
1096    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1097        #[cfg(target_pointer_width = "32")]
1098        const RANGE: &str = "0..=4294967295";
1099        #[cfg(target_pointer_width = "64")]
1100        const RANGE: &str = "0..=18446744073709551615";
1101        match deser_integer_ctx(ctx, value, 0, usize::MAX as i128, &"usize", &RANGE) {
1102            Ok(i) => Ok(i as usize),
1103            Err(e) => Err(e),
1104        }
1105    }
1106}
1107
1108impl<'de> FromToml<'de> for f32 {
1109    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1110        match value.as_f64() {
1111            Some(f) => Ok(f as f32),
1112            None => Err(ctx.report_expected_but_found(&"a float", value)),
1113        }
1114    }
1115}
1116
1117impl<'de> FromToml<'de> for f64 {
1118    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1119        match value.as_f64() {
1120            Some(f) => Ok(f),
1121            None => Err(ctx.report_expected_but_found(&"a float", value)),
1122        }
1123    }
1124}
1125
1126impl<'de, T> FromToml<'de> for Vec<T>
1127where
1128    T: FromToml<'de>,
1129{
1130    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1131        let arr = value.require_array(ctx)?;
1132        let mut result = Vec::with_capacity(arr.len());
1133        let mut had_error = false;
1134        for item in arr {
1135            match T::from_toml(ctx, item) {
1136                Ok(v) => result.push(v),
1137                Err(_) => had_error = true,
1138            }
1139        }
1140        if had_error { Err(Failed) } else { Ok(result) }
1141    }
1142}
1143
1144impl<'de, T> FromToml<'de> for BTreeSet<T>
1145where
1146    T: Ord + FromToml<'de>,
1147{
1148    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1149        let arr = value.require_array(ctx)?;
1150        let mut result = BTreeSet::new();
1151        let mut had_error = false;
1152        for item in arr {
1153            match T::from_toml(ctx, item) {
1154                Ok(v) => {
1155                    result.insert(v);
1156                }
1157                Err(_) => had_error = true,
1158            }
1159        }
1160        if had_error { Err(Failed) } else { Ok(result) }
1161    }
1162}
1163
1164impl<'de, K, V> FromToml<'de> for BTreeMap<K, V>
1165where
1166    K: Ord + FromToml<'de>,
1167    V: FromToml<'de>,
1168{
1169    fn from_toml(ctx: &mut Context<'de>, value: &Item<'de>) -> Result<Self, Failed> {
1170        let table = value.require_table(ctx)?;
1171        let mut map = BTreeMap::new();
1172        let mut had_error = false;
1173        for (key, item) in table {
1174            let k = match key_from_toml(ctx, key) {
1175                Ok(k) => k,
1176                Err(_) => {
1177                    had_error = true;
1178                    continue;
1179                }
1180            };
1181            match V::from_toml(ctx, item) {
1182                Ok(v) => {
1183                    map.insert(k, v);
1184                }
1185                Err(_) => had_error = true,
1186            }
1187        }
1188        if had_error { Err(Failed) } else { Ok(map) }
1189    }
1190}
1191
1192impl<'de> Item<'de> {
1193    /// Returns a string, or records an error with a custom `expected` message.
1194    ///
1195    /// Use instead of [`require_string`](Self::require_string) when the
1196    /// expected value is more specific than "a string", for example
1197    /// `"an IPv4 address"` or `"a hex color"`.
1198    #[doc(alias = "expect_custom_string")]
1199    pub fn require_custom_string(
1200        &self,
1201        ctx: &mut Context<'de>,
1202        expected: &'static &'static str,
1203    ) -> Result<&'de str, Failed> {
1204        match self.value() {
1205            item::Value::String(s) => Ok(*s),
1206            _ => Err(ctx.report_expected_but_found(expected, self)),
1207        }
1208    }
1209    /// Returns a string, or records an error if this is not a string.
1210    #[doc(alias = "expect_string")]
1211    pub fn require_string(&self, ctx: &mut Context<'de>) -> Result<&'de str, Failed> {
1212        match self.value() {
1213            item::Value::String(s) => Ok(*s),
1214            _ => Err(ctx.report_expected_but_found(&"a string", self)),
1215        }
1216    }
1217
1218    /// Returns an array reference, or records an error if this is not an array.
1219    #[doc(alias = "expect_array")]
1220    pub fn require_array(&self, ctx: &mut Context<'de>) -> Result<&crate::Array<'de>, Failed> {
1221        match self.as_array() {
1222            Some(arr) => Ok(arr),
1223            None => Err(ctx.report_expected_but_found(&"an array", self)),
1224        }
1225    }
1226
1227    /// Returns a table reference, or records an error if this is not a table.
1228    #[doc(alias = "expect_table")]
1229    pub fn require_table(&self, ctx: &mut Context<'de>) -> Result<&crate::Table<'de>, Failed> {
1230        match self.as_table() {
1231            Some(table) => Ok(table),
1232            None => Err(ctx.report_expected_but_found(&"a table", self)),
1233        }
1234    }
1235
1236    /// Creates a [`TableHelper`] for this item, returning an error if it is
1237    /// not a table.
1238    ///
1239    /// Typical entry point for implementing [`FromToml`].
1240    pub fn table_helper<'ctx, 'item>(
1241        &'item self,
1242        ctx: &'ctx mut Context<'de>,
1243    ) -> Result<TableHelper<'ctx, 'item, 'de>, Failed> {
1244        let Some(table) = self.as_table() else {
1245            return Err(ctx.report_expected_but_found(&"a table", self));
1246        };
1247        Ok(TableHelper::new(ctx, table))
1248    }
1249}
1250
1251/// Collects errors encountered during parsing and conversion.
1252///
1253/// Returned by [`from_str`](crate::from_str) and [`Document::to`](crate::Document::to).
1254/// Contains one or more [`Error`] values, each with its own source span.
1255///
1256/// # Examples
1257///
1258/// ```
1259/// let result = toml_spanner::from_str::<std::collections::HashMap<String, String>>(
1260///     "bad toml {"
1261/// );
1262/// assert!(result.is_err());
1263/// let err = result.unwrap_err();
1264/// assert!(!err.errors.is_empty());
1265/// ```
1266#[derive(Debug)]
1267pub struct FromTomlError {
1268    /// The accumulated errors.
1269    pub errors: Vec<Error>,
1270}
1271
1272impl Display for FromTomlError {
1273    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1274        let Some(first) = self.errors.first() else {
1275            return f.write_str("deserialization failed");
1276        };
1277        Display::fmt(first, f)?;
1278        let remaining = self.errors.len() - 1;
1279        if remaining > 0 {
1280            write!(
1281                f,
1282                " (+{remaining} more error{})",
1283                if remaining == 1 { "" } else { "s" }
1284            )?;
1285        }
1286        Ok(())
1287    }
1288}
1289
1290impl std::error::Error for FromTomlError {}
1291
1292impl From<Error> for FromTomlError {
1293    fn from(error: Error) -> Self {
1294        Self {
1295            errors: vec![error],
1296        }
1297    }
1298}
1299
1300impl From<Vec<Error>> for FromTomlError {
1301    fn from(errors: Vec<Error>) -> Self {
1302        Self { errors }
1303    }
1304}
1305
1306impl IntoIterator for FromTomlError {
1307    type Item = Error;
1308    type IntoIter = std::vec::IntoIter<Error>;
1309    fn into_iter(self) -> Self::IntoIter {
1310        self.errors.into_iter()
1311    }
1312}
1313
1314impl<'a> IntoIterator for &'a FromTomlError {
1315    type Item = &'a Error;
1316    type IntoIter = std::slice::Iter<'a, Error>;
1317    fn into_iter(self) -> Self::IntoIter {
1318        self.errors.iter()
1319    }
1320}