Skip to main content

uv_resolver/lock/
deserialize.rs

1//! Deserializes the canonical lockfile layout without building a TOML tree.
2//!
3//! This parser is deliberately limited to the syntax emitted by the lockfile
4//! serializer. The public entry point falls back to the general TOML parser
5//! when a lock uses another valid representation.
6
7use std::borrow::Cow;
8use std::fmt;
9
10use memchr::memchr3;
11use rustc_hash::{FxBuildHasher, FxHashSet};
12use serde::Deserialize;
13use serde::de::{self, DeserializeSeed, EnumAccess, MapAccess, SeqAccess, VariantAccess, Visitor};
14use serde::forward_to_deserialize_any;
15use smallvec::SmallVec;
16use toml_parser::decoder::{Encoding, IntegerRadix, ScalarKind};
17use toml_parser::{Raw, Span};
18
19use super::Lock;
20
21/// Parses a canonical lock directly into its existing, validated wire format.
22pub(super) fn from_str(input: &str) -> Result<Lock, Error> {
23    let mut cursor = Cursor::new(input);
24    let lock = Lock::deserialize(DocumentDeserializer {
25        cursor: &mut cursor,
26    })?;
27    cursor.skip_whitespace();
28    if cursor.peek().is_some() {
29        return Err(cursor.unsupported("unexpected trailing input"));
30    }
31    Ok(lock)
32}
33
34/// An error returned when parsing a lockfile without the general TOML fallback.
35#[derive(Debug, thiserror::Error)]
36pub enum Error {
37    #[error("unsupported canonical lock syntax at byte {offset}: {message}")]
38    Unsupported {
39        offset: usize,
40        message: &'static str,
41    },
42    #[error("invalid canonical lock syntax at byte {offset}: {message}")]
43    Invalid { offset: usize, message: String },
44    #[error("failed to deserialize canonical lock: {0}")]
45    Deserialize(String),
46}
47
48impl de::Error for Error {
49    fn custom<T: fmt::Display>(message: T) -> Self {
50        Self::Deserialize(message.to_string())
51    }
52}
53
54struct Cursor<'de> {
55    input: &'de str,
56    offset: usize,
57    container_depth: u8,
58}
59
60impl<'de> Cursor<'de> {
61    fn new(input: &'de str) -> Self {
62        Self {
63            input,
64            offset: 0,
65            container_depth: 0,
66        }
67    }
68
69    fn peek(&self) -> Option<u8> {
70        self.input.as_bytes().get(self.offset).copied()
71    }
72
73    fn unsupported(&self, message: &'static str) -> Error {
74        Error::Unsupported {
75            offset: self.offset,
76            message,
77        }
78    }
79
80    fn invalid(&self, error: &toml_parser::ParseError) -> Error {
81        Error::Invalid {
82            offset: error
83                .unexpected()
84                .or_else(|| error.context())
85                .map_or(self.offset, |span| span.start()),
86            message: error.description().to_owned(),
87        }
88    }
89
90    fn skip_whitespace(&mut self) {
91        loop {
92            match self.peek() {
93                Some(b' ' | b'\t' | b'\n') => self.offset += 1,
94                Some(b'\r') if self.input.as_bytes().get(self.offset + 1) == Some(&b'\n') => {
95                    self.offset += 2;
96                }
97                _ => break,
98            }
99        }
100    }
101
102    fn skip_horizontal_whitespace(&mut self) {
103        while matches!(self.peek(), Some(b' ' | b'\t')) {
104            self.offset += 1;
105        }
106    }
107
108    fn consume(&mut self, expected: u8) -> Result<(), Error> {
109        if self.peek() == Some(expected) {
110            self.offset += 1;
111            Ok(())
112        } else {
113            Err(self.unsupported("unexpected delimiter"))
114        }
115    }
116
117    fn header(&self) -> Result<&'de str, Error> {
118        if self.peek() != Some(b'[') {
119            return Err(self.unsupported("expected a table header"));
120        }
121        let remaining = &self.input[self.offset..];
122        let length = remaining.find('\n').unwrap_or(remaining.len());
123        Ok(remaining[..length].trim_end_matches('\r'))
124    }
125
126    fn consume_header(&mut self, expected: &'static str) -> Result<(), Error> {
127        if self.header()? != expected {
128            return Err(self.unsupported("unknown or noncanonical table header"));
129        }
130        self.offset += expected.len();
131
132        match self.peek() {
133            Some(b'\r') => {
134                self.offset += 1;
135                self.consume(b'\n')
136            }
137            Some(b'\n') => {
138                self.offset += 1;
139                Ok(())
140            }
141            None => Ok(()),
142            _ => Err(self.unsupported("expected the end of a table header")),
143        }
144    }
145
146    fn assignment_key(&mut self) -> Result<&'de str, Error> {
147        let start = self.offset;
148        while matches!(
149            self.peek(),
150            Some(b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | b'-')
151        ) {
152            self.offset += 1;
153        }
154        if self.offset == start {
155            return Err(self.unsupported("expected a canonical bare key"));
156        }
157        let key = &self.input[start..self.offset];
158        self.skip_horizontal_whitespace();
159        self.consume(b'=')?;
160        self.skip_horizontal_whitespace();
161        Ok(key)
162    }
163
164    fn finish_assignment(&mut self) -> Result<(), Error> {
165        self.skip_horizontal_whitespace();
166
167        if self.peek() == Some(b'#') {
168            self.offset += 1;
169
170            while let Some(byte) = self.peek() {
171                match byte {
172                    b'\n' | b'\r' => break,
173                    b'\t' | b' '..=b'~' | 0x80..=0xff => self.offset += 1,
174                    _ => return Err(self.unsupported("invalid character in a TOML comment")),
175                }
176            }
177        }
178
179        match self.peek() {
180            Some(b'\r') => {
181                self.offset += 1;
182                self.consume(b'\n')
183            }
184            Some(b'\n') => {
185                self.offset += 1;
186                Ok(())
187            }
188            None => Ok(()),
189            _ => Err(self.unsupported("expected the end of an assignment")),
190        }
191    }
192
193    fn string(&mut self) -> Result<Cow<'de, str>, Error> {
194        let start = self.offset;
195        self.consume(b'"')?;
196        if self.input[self.offset..].starts_with("\"\"") {
197            return Err(self.unsupported("multiline strings require the TOML fallback"));
198        }
199
200        let mut escaped = false;
201        loop {
202            let remaining = &self.input.as_bytes()[self.offset..];
203            let Some(delimiter) = memchr3(b'"', b'\\', 0x7f, remaining) else {
204                return Err(self.unsupported("unterminated basic string"));
205            };
206
207            let content = &remaining[..delimiter];
208            // The minimum is SIMD-vectorizable; locate the exact offset only for invalid input.
209            if content
210                .iter()
211                .copied()
212                .min()
213                .is_some_and(|byte| byte < 0x20)
214                && let Some(control) = content.iter().position(|byte| *byte < 0x20)
215            {
216                self.offset += control;
217                return Err(self.unsupported("control character in a basic string"));
218            }
219
220            self.offset += delimiter;
221            match self.peek() {
222                Some(b'"') => {
223                    let end = self.offset;
224                    self.offset += 1;
225                    if escaped {
226                        let encoded = &self.input[start..self.offset];
227                        let raw = Raw::new_unchecked(
228                            encoded,
229                            Some(Encoding::BasicString),
230                            Span::new_unchecked(start, self.offset),
231                        );
232                        let mut decoded = Cow::Borrowed("");
233                        let mut error = None;
234                        let kind = raw.decode_scalar(&mut decoded, &mut error);
235                        debug_assert_eq!(kind, ScalarKind::String);
236
237                        if let Some(error) = error {
238                            return Err(self.invalid(&error));
239                        }
240
241                        return Ok(decoded);
242                    }
243                    return Ok(Cow::Borrowed(&self.input[start + 1..end]));
244                }
245                Some(b'\\') => {
246                    escaped = true;
247                    self.offset += 1;
248                    if self.peek().is_none() {
249                        return Err(self.unsupported("unterminated string escape"));
250                    }
251                    self.offset += 1;
252                }
253                _ => return Err(self.unsupported("control character in a basic string")),
254            }
255        }
256    }
257
258    fn number(&mut self) -> Result<Cow<'de, str>, Error> {
259        let start = self.offset;
260        while matches!(
261            self.peek(),
262            Some(b'0'..=b'9' | b'-' | b'+' | b'.' | b'e' | b'E' | b'_')
263        ) {
264            self.offset += 1;
265        }
266        if self.offset == start {
267            return Err(self.unsupported("expected a canonical number"));
268        }
269
270        let encoded = &self.input[start..self.offset];
271        let raw = Raw::new_unchecked(encoded, None, Span::new_unchecked(start, self.offset));
272        let mut decoded = Cow::Borrowed("");
273        let mut error = None;
274        let kind = raw.decode_scalar(&mut decoded, &mut error);
275
276        if let Some(error) = error {
277            return Err(self.invalid(&error));
278        }
279
280        if !matches!(
281            kind,
282            ScalarKind::Float | ScalarKind::Integer(IntegerRadix::Dec)
283        ) {
284            return Err(self.unsupported("expected a canonical number"));
285        }
286
287        Ok(decoded)
288    }
289
290    fn literal(&mut self, literal: &'static str) -> Result<(), Error> {
291        if self.input[self.offset..].starts_with(literal) {
292            self.offset += literal.len();
293            Ok(())
294        } else {
295            Err(self.unsupported("expected a canonical boolean"))
296        }
297    }
298
299    fn with_container<T>(
300        &mut self,
301        deserialize: impl FnOnce(&mut Self) -> Result<T, Error>,
302    ) -> Result<T, Error> {
303        if self.container_depth == 80 {
304            return Err(self.unsupported("maximum TOML nesting depth exceeded"));
305        }
306
307        self.container_depth += 1;
308        let result = deserialize(self);
309        self.container_depth -= 1;
310        result
311    }
312}
313
314struct DocumentDeserializer<'a, 'de> {
315    cursor: &'a mut Cursor<'de>,
316}
317
318impl<'de> de::Deserializer<'de> for DocumentDeserializer<'_, 'de> {
319    type Error = Error;
320
321    fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Error> {
322        visitor.visit_map(DocumentMapAccess {
323            cursor: self.cursor,
324            kind: MapKind::Root,
325            pending: None,
326            seen_keys: SeenKeys::default(),
327        })
328    }
329
330    forward_to_deserialize_any! {
331        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string bytes
332        byte_buf option unit unit_struct newtype_struct seq tuple tuple_struct map
333        struct enum identifier ignored_any
334    }
335}
336
337#[derive(Clone, Copy, Debug, Eq, PartialEq)]
338enum MapKind {
339    Root,
340    Options,
341    OptionsExcludeNewerPackage,
342    Manifest,
343    ManifestDependencyGroups,
344    ManifestDependencyMetadata,
345    Package,
346    PackageOptionalDependencies,
347    PackageDevDependencies,
348    PackageMetadata,
349    PackageMetadataRequiresDev,
350}
351
352#[derive(Clone, Copy, Debug, Eq, PartialEq)]
353enum SequenceKind {
354    Packages,
355    ManifestDependencyMetadata,
356}
357
358#[derive(Clone, Copy, Debug, Eq, PartialEq)]
359enum Pending {
360    Value,
361    Map(MapKind),
362    Sequence(SequenceKind),
363}
364
365/// Keeps common maps allocation-free and wide maps linear in the number of keys.
366#[derive(Default)]
367struct SeenKeys<'de> {
368    inline: SmallVec<[&'de str; 8]>,
369    overflow: Option<FxHashSet<&'de str>>,
370}
371
372impl<'de> SeenKeys<'de> {
373    fn insert(&mut self, key: &'de str) -> bool {
374        if let Some(overflow) = &mut self.overflow {
375            return overflow.insert(key);
376        }
377
378        if self.inline.contains(&key) {
379            return false;
380        }
381
382        if self.inline.len() < self.inline.inline_size() {
383            self.inline.push(key);
384            return true;
385        }
386
387        let mut overflow =
388            FxHashSet::with_capacity_and_hasher(self.inline.len() + 1, FxBuildHasher);
389        overflow.extend(self.inline.iter().copied());
390        let inserted = overflow.insert(key);
391        self.overflow = Some(overflow);
392        inserted
393    }
394}
395
396struct DocumentMapAccess<'a, 'de> {
397    cursor: &'a mut Cursor<'de>,
398    kind: MapKind,
399    pending: Option<Pending>,
400    seen_keys: SeenKeys<'de>,
401}
402
403impl<'de> MapAccess<'de> for DocumentMapAccess<'_, 'de> {
404    type Error = Error;
405
406    fn next_key_seed<K: DeserializeSeed<'de>>(
407        &mut self,
408        seed: K,
409    ) -> Result<Option<K::Value>, Error> {
410        self.cursor.skip_whitespace();
411        match self.cursor.peek() {
412            None => Ok(None),
413            Some(b'[') => self.section_key(seed),
414            Some(b'#') => Err(self
415                .cursor
416                .unsupported("comments require the TOML fallback")),
417            Some(_) => {
418                let key = self.cursor.assignment_key()?;
419                self.track_key(key)?;
420                self.pending = Some(Pending::Value);
421                seed.deserialize(de::value::BorrowedStrDeserializer::new(key))
422                    .map(Some)
423            }
424        }
425    }
426
427    fn next_value_seed<V: DeserializeSeed<'de>>(&mut self, seed: V) -> Result<V::Value, Error> {
428        let Some(pending) = self.pending.take() else {
429            return Err(self.cursor.unsupported("map value has no matching key"));
430        };
431
432        match pending {
433            Pending::Value => {
434                let value = seed.deserialize(ValueDeserializer {
435                    cursor: self.cursor,
436                })?;
437                self.cursor.finish_assignment()?;
438                Ok(value)
439            }
440            Pending::Map(kind) => seed.deserialize(SectionDeserializer {
441                cursor: self.cursor,
442                kind,
443            }),
444            Pending::Sequence(kind) => seed.deserialize(SectionSequenceDeserializer {
445                cursor: self.cursor,
446                kind,
447            }),
448        }
449    }
450}
451
452impl<'de> DocumentMapAccess<'_, 'de> {
453    fn track_key(&mut self, key: &'de str) -> Result<(), Error> {
454        if !self.seen_keys.insert(key) {
455            return Err(self.cursor.unsupported("duplicate TOML key"));
456        }
457        Ok(())
458    }
459
460    fn section_key<K: DeserializeSeed<'de>>(&mut self, seed: K) -> Result<Option<K::Value>, Error> {
461        let header = self.cursor.header()?;
462        let child = match (self.kind, header) {
463            (MapKind::Root, "[options]") => {
464                Some(("options", Pending::Map(MapKind::Options), "[options]"))
465            }
466            (MapKind::Root, "[manifest]") => {
467                Some(("manifest", Pending::Map(MapKind::Manifest), "[manifest]"))
468            }
469            (MapKind::Root, "[[package]]") => Some((
470                "package",
471                Pending::Sequence(SequenceKind::Packages),
472                "[[package]]",
473            )),
474            (MapKind::Options, "[options.exclude-newer-package]") => Some((
475                "exclude-newer-package",
476                Pending::Map(MapKind::OptionsExcludeNewerPackage),
477                "[options.exclude-newer-package]",
478            )),
479            (MapKind::Manifest, "[manifest.dependency-groups]") => Some((
480                "dependency-groups",
481                Pending::Map(MapKind::ManifestDependencyGroups),
482                "[manifest.dependency-groups]",
483            )),
484            (MapKind::Manifest, "[[manifest.dependency-metadata]]") => Some((
485                "dependency-metadata",
486                Pending::Sequence(SequenceKind::ManifestDependencyMetadata),
487                "[[manifest.dependency-metadata]]",
488            )),
489            (MapKind::Package, "[package.optional-dependencies]") => Some((
490                "optional-dependencies",
491                Pending::Map(MapKind::PackageOptionalDependencies),
492                "[package.optional-dependencies]",
493            )),
494            (MapKind::Package, "[package.dev-dependencies]") => Some((
495                "dev-dependencies",
496                Pending::Map(MapKind::PackageDevDependencies),
497                "[package.dev-dependencies]",
498            )),
499            (MapKind::Package, "[package.metadata]") => Some((
500                "metadata",
501                Pending::Map(MapKind::PackageMetadata),
502                "[package.metadata]",
503            )),
504            (MapKind::PackageMetadata, "[package.metadata.requires-dev]") => Some((
505                "requires-dev",
506                Pending::Map(MapKind::PackageMetadataRequiresDev),
507                "[package.metadata.requires-dev]",
508            )),
509            (MapKind::Root, _) => {
510                return Err(self
511                    .cursor
512                    .unsupported("unknown or noncanonical lock table"));
513            }
514            _ => None,
515        };
516
517        let Some((key, pending, expected)) = child else {
518            return Ok(None);
519        };
520        self.track_key(key)?;
521        self.cursor.consume_header(expected)?;
522        self.pending = Some(pending);
523        seed.deserialize(de::value::BorrowedStrDeserializer::new(key))
524            .map(Some)
525    }
526}
527
528struct SectionDeserializer<'a, 'de> {
529    cursor: &'a mut Cursor<'de>,
530    kind: MapKind,
531}
532
533impl<'de> de::Deserializer<'de> for SectionDeserializer<'_, 'de> {
534    type Error = Error;
535
536    fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Error> {
537        visitor.visit_map(DocumentMapAccess {
538            cursor: self.cursor,
539            kind: self.kind,
540            pending: None,
541            seen_keys: SeenKeys::default(),
542        })
543    }
544
545    forward_to_deserialize_any! {
546        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string bytes
547        byte_buf option unit unit_struct newtype_struct seq tuple tuple_struct map
548        struct enum identifier ignored_any
549    }
550}
551
552struct SectionSequenceDeserializer<'a, 'de> {
553    cursor: &'a mut Cursor<'de>,
554    kind: SequenceKind,
555}
556
557impl<'de> de::Deserializer<'de> for SectionSequenceDeserializer<'_, 'de> {
558    type Error = Error;
559
560    fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Error> {
561        visitor.visit_seq(SectionSequenceAccess {
562            cursor: self.cursor,
563            kind: self.kind,
564            started: false,
565        })
566    }
567
568    forward_to_deserialize_any! {
569        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string bytes
570        byte_buf option unit unit_struct newtype_struct seq tuple tuple_struct map
571        struct enum identifier ignored_any
572    }
573}
574
575struct SectionSequenceAccess<'a, 'de> {
576    cursor: &'a mut Cursor<'de>,
577    kind: SequenceKind,
578    started: bool,
579}
580
581impl<'de> SeqAccess<'de> for SectionSequenceAccess<'_, 'de> {
582    type Error = Error;
583
584    fn next_element_seed<T: DeserializeSeed<'de>>(
585        &mut self,
586        seed: T,
587    ) -> Result<Option<T::Value>, Error> {
588        if self.started {
589            self.cursor.skip_whitespace();
590            if self.cursor.peek() != Some(b'[') {
591                return Ok(None);
592            }
593
594            let expected = match self.kind {
595                SequenceKind::Packages => "[[package]]",
596                SequenceKind::ManifestDependencyMetadata => "[[manifest.dependency-metadata]]",
597            };
598            if self.cursor.header()? != expected {
599                return Ok(None);
600            }
601            self.cursor.consume_header(expected)?;
602        }
603
604        self.started = true;
605        let kind = match self.kind {
606            SequenceKind::Packages => MapKind::Package,
607            SequenceKind::ManifestDependencyMetadata => MapKind::ManifestDependencyMetadata,
608        };
609        seed.deserialize(SectionDeserializer {
610            cursor: self.cursor,
611            kind,
612        })
613        .map(Some)
614    }
615}
616
617struct ValueDeserializer<'a, 'de> {
618    cursor: &'a mut Cursor<'de>,
619}
620
621impl<'de> de::Deserializer<'de> for ValueDeserializer<'_, 'de> {
622    type Error = Error;
623
624    fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Error> {
625        match self.cursor.peek() {
626            Some(b'"') => match self.cursor.string()? {
627                Cow::Borrowed(value) => visitor.visit_borrowed_str(value),
628                Cow::Owned(value) => visitor.visit_string(value),
629            },
630            Some(b'{') => self.cursor.with_container(|cursor| {
631                cursor.consume(b'{')?;
632                visitor.visit_map(InlineMapAccess {
633                    cursor,
634                    started: false,
635                    seen_keys: SeenKeys::default(),
636                })
637            }),
638            Some(b'[') => self.cursor.with_container(|cursor| {
639                cursor.consume(b'[')?;
640                visitor.visit_seq(InlineSequenceAccess {
641                    cursor,
642                    started: false,
643                })
644            }),
645            Some(b't') => {
646                self.cursor.literal("true")?;
647                visitor.visit_bool(true)
648            }
649            Some(b'f') => {
650                self.cursor.literal("false")?;
651                visitor.visit_bool(false)
652            }
653            Some(b'-' | b'0'..=b'9') => {
654                let number = self.cursor.number()?;
655                if number.contains(['.', 'e', 'E']) {
656                    let value = number
657                        .parse::<f64>()
658                        .map_err(|_| self.cursor.unsupported("invalid floating-point value"))?;
659                    visitor.visit_f64(value)
660                } else if number.starts_with('-') {
661                    let value = number
662                        .parse::<i64>()
663                        .map_err(|_| self.cursor.unsupported("invalid signed integer"))?;
664                    visitor.visit_i64(value)
665                } else {
666                    let value = number
667                        .parse::<u64>()
668                        .map_err(|_| self.cursor.unsupported("invalid unsigned integer"))?;
669                    visitor.visit_u64(value)
670                }
671            }
672            _ => Err(self.cursor.unsupported("unsupported canonical lock value")),
673        }
674    }
675
676    fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Error> {
677        visitor.visit_some(self)
678    }
679
680    fn deserialize_newtype_struct<V: Visitor<'de>>(
681        self,
682        _name: &'static str,
683        visitor: V,
684    ) -> Result<V::Value, Error> {
685        visitor.visit_newtype_struct(self)
686    }
687
688    fn deserialize_enum<V: Visitor<'de>>(
689        self,
690        _name: &'static str,
691        _variants: &'static [&'static str],
692        visitor: V,
693    ) -> Result<V::Value, Error> {
694        match self.cursor.peek() {
695            Some(b'"') => match self.cursor.string()? {
696                Cow::Borrowed(value) => {
697                    visitor.visit_enum(de::value::BorrowedStrDeserializer::new(value))
698                }
699                Cow::Owned(value) => visitor.visit_enum(de::value::StringDeserializer::new(value)),
700            },
701            Some(b'{') => self.cursor.with_container(|cursor| {
702                cursor.consume(b'{')?;
703                visitor.visit_enum(InlineEnumAccess { cursor })
704            }),
705            _ => Err(self.cursor.unsupported("expected a canonical enum value")),
706        }
707    }
708
709    forward_to_deserialize_any! {
710        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string bytes
711        byte_buf unit unit_struct seq tuple tuple_struct map struct identifier ignored_any
712    }
713}
714
715struct InlineMapAccess<'a, 'de> {
716    cursor: &'a mut Cursor<'de>,
717    started: bool,
718    seen_keys: SeenKeys<'de>,
719}
720
721impl<'de> MapAccess<'de> for InlineMapAccess<'_, 'de> {
722    type Error = Error;
723
724    fn next_key_seed<K: DeserializeSeed<'de>>(
725        &mut self,
726        seed: K,
727    ) -> Result<Option<K::Value>, Error> {
728        self.cursor.skip_whitespace();
729        if self.started {
730            if self.cursor.peek() == Some(b'}') {
731                self.cursor.consume(b'}')?;
732                return Ok(None);
733            }
734            self.cursor.consume(b',')?;
735            self.cursor.skip_whitespace();
736        } else if self.cursor.peek() == Some(b'}') {
737            self.cursor.consume(b'}')?;
738            return Ok(None);
739        }
740
741        let key = self.cursor.assignment_key()?;
742        if !self.seen_keys.insert(key) {
743            return Err(self.cursor.unsupported("duplicate TOML key"));
744        }
745        self.started = true;
746        seed.deserialize(de::value::BorrowedStrDeserializer::new(key))
747            .map(Some)
748    }
749
750    fn next_value_seed<V: DeserializeSeed<'de>>(&mut self, seed: V) -> Result<V::Value, Error> {
751        seed.deserialize(ValueDeserializer {
752            cursor: self.cursor,
753        })
754    }
755}
756
757struct InlineSequenceAccess<'a, 'de> {
758    cursor: &'a mut Cursor<'de>,
759    started: bool,
760}
761
762impl<'de> SeqAccess<'de> for InlineSequenceAccess<'_, 'de> {
763    type Error = Error;
764
765    fn next_element_seed<T: DeserializeSeed<'de>>(
766        &mut self,
767        seed: T,
768    ) -> Result<Option<T::Value>, Error> {
769        self.cursor.skip_whitespace();
770        if self.started {
771            if self.cursor.peek() == Some(b']') {
772                self.cursor.consume(b']')?;
773                return Ok(None);
774            }
775            self.cursor.consume(b',')?;
776            self.cursor.skip_whitespace();
777        }
778
779        if self.cursor.peek() == Some(b']') {
780            self.cursor.consume(b']')?;
781            return Ok(None);
782        }
783
784        self.started = true;
785        seed.deserialize(ValueDeserializer {
786            cursor: self.cursor,
787        })
788        .map(Some)
789    }
790}
791
792struct InlineEnumAccess<'a, 'de> {
793    cursor: &'a mut Cursor<'de>,
794}
795
796impl<'a, 'de> EnumAccess<'de> for InlineEnumAccess<'a, 'de> {
797    type Error = Error;
798    type Variant = InlineVariantAccess<'a, 'de>;
799
800    fn variant_seed<V: DeserializeSeed<'de>>(
801        self,
802        seed: V,
803    ) -> Result<(V::Value, Self::Variant), Error> {
804        self.cursor.skip_whitespace();
805        let key = self.cursor.assignment_key()?;
806        let variant = seed.deserialize(de::value::BorrowedStrDeserializer::new(key))?;
807        Ok((
808            variant,
809            InlineVariantAccess {
810                cursor: self.cursor,
811            },
812        ))
813    }
814}
815
816struct InlineVariantAccess<'a, 'de> {
817    cursor: &'a mut Cursor<'de>,
818}
819
820impl<'de> VariantAccess<'de> for InlineVariantAccess<'_, 'de> {
821    type Error = Error;
822
823    fn unit_variant(self) -> Result<(), Error> {
824        Err(self
825            .cursor
826            .unsupported("inline tables cannot contain unit variants"))
827    }
828
829    fn newtype_variant_seed<T: DeserializeSeed<'de>>(self, seed: T) -> Result<T::Value, Error> {
830        let value = seed.deserialize(ValueDeserializer {
831            cursor: self.cursor,
832        })?;
833        self.cursor.skip_whitespace();
834        self.cursor.consume(b'}')?;
835        Ok(value)
836    }
837
838    fn tuple_variant<V: Visitor<'de>>(self, length: usize, visitor: V) -> Result<V::Value, Error> {
839        let value = de::Deserializer::deserialize_tuple(
840            ValueDeserializer {
841                cursor: self.cursor,
842            },
843            length,
844            visitor,
845        )?;
846        self.cursor.skip_whitespace();
847        self.cursor.consume(b'}')?;
848        Ok(value)
849    }
850
851    fn struct_variant<V: Visitor<'de>>(
852        self,
853        fields: &'static [&'static str],
854        visitor: V,
855    ) -> Result<V::Value, Error> {
856        let value = de::Deserializer::deserialize_struct(
857            ValueDeserializer {
858                cursor: self.cursor,
859            },
860            "",
861            fields,
862            visitor,
863        )?;
864        self.cursor.skip_whitespace();
865        self.cursor.consume(b'}')?;
866        Ok(value)
867    }
868}
869
870#[cfg(test)]
871mod tests {
872    use std::assert_matches;
873    use std::fmt::Write as _;
874
875    use serde::Deserialize;
876
877    use super::super::{LockParseError, VERSION};
878    use super::{Cursor, Error, Lock, ValueDeserializer, from_str};
879
880    const CANONICAL_LOCK: &str = r#"version = 1
881revision = 3
882requires-python = ">=3.12"
883
884[[package]]
885name = "dependency"
886version = "1.0.0"
887source = { registry = "https://example.com/simple" }
888
889[[package]]
890name = "project"
891version = "0.1.0"
892source = { virtual = "." }
893dependencies = [
894    { name = "dependency" },
895]
896"#;
897
898    #[test]
899    fn canonical_lock_matches_toml() {
900        let expected: Lock = toml::from_str(CANONICAL_LOCK).expect("valid TOML lock");
901        let actual = from_str(CANONICAL_LOCK).expect("valid canonical lock");
902
903        assert_eq!(actual, expected);
904    }
905
906    #[test]
907    fn repository_lock_matches_toml() {
908        let input = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../uv.lock"));
909        let expected: Lock = toml::from_str(input).expect("valid repository lock");
910        let actual = from_str(input).expect("valid canonical repository lock");
911
912        assert_eq!(actual, expected);
913    }
914
915    #[test]
916    fn nested_lock_sections_match_toml() {
917        let input = r#"version = 1
918revision = 3
919requires-python = ">=3.12"
920
921[options]
922resolution-mode = "highest"
923
924[options.exclude-newer-package]
925dependency = false
926
927[manifest]
928members = ["project"]
929requirements = [{ name = "dependency", specifier = ">=1" }]
930
931[manifest.dependency-groups]
932dev = [{ name = "dependency", specifier = ">=1" }]
933
934[[manifest.dependency-metadata]]
935name = "dependency"
936version = "1.0.0"
937
938[[package]]
939name = "dependency"
940version = "1.0.0"
941source = { registry = "https://example.com/simple" }
942
943[[package]]
944name = "project"
945version = "0.1.0"
946source = { virtual = "." }
947dependencies = [{ name = "dependency" }]
948
949[package.optional-dependencies]
950feature = [{ name = "dependency" }]
951
952[package.dev-dependencies]
953dev = [{ name = "dependency" }]
954
955[package.metadata]
956requires-dist = [{ name = "dependency", specifier = ">=1" }]
957provides-extras = ["feature"]
958
959[package.metadata.requires-dev]
960dev = [{ name = "dependency", specifier = ">=1" }]
961"#;
962        let expected: Lock = toml::from_str(input).expect("valid nested TOML lock");
963        let actual = from_str(input).expect("valid nested canonical lock");
964
965        assert_eq!(actual, expected);
966    }
967
968    #[test]
969    fn escaped_strings_match_toml() {
970        let input = CANONICAL_LOCK.replace(
971            "https://example.com/simple",
972            "https://example.com/simple?name=quoted\\\"value",
973        );
974        let expected: Lock = toml::from_str(&input).expect("valid TOML lock");
975        let actual = from_str(&input).expect("valid canonical lock");
976
977        assert_eq!(actual, expected);
978    }
979
980    #[test]
981    fn long_basic_strings_deserialize_directly() {
982        let source = format!("https://example.com/{}", "a".repeat(512));
983        let input = CANONICAL_LOCK.replace("https://example.com/simple", &source);
984        let expected: Lock = toml::from_str(&input).expect("valid TOML lock with a long string");
985        let actual = from_str(&input).expect("long basic string uses the direct parser");
986
987        assert_eq!(actual, expected);
988    }
989
990    #[test]
991    fn empty_basic_string_deserializes_directly() {
992        let mut cursor = Cursor::new(r#""""#);
993        let actual = String::deserialize(ValueDeserializer {
994            cursor: &mut cursor,
995        })
996        .expect("empty basic string deserializes directly");
997
998        assert_eq!(actual, "");
999    }
1000
1001    #[test]
1002    fn noncanonical_lock_falls_back() {
1003        let input = CANONICAL_LOCK
1004            .replace("requires-python = \">=3.12\"", "requires-python = '>=3.12'")
1005            .replace("[[package]]", "# A hand-edited package.\n[[package]]");
1006        let expected: Lock = toml::from_str(&input).expect("valid noncanonical lock");
1007
1008        assert!(
1009            Lock::from_canonical_toml(&input).is_err(),
1010            "the canonical lock parser must not fall back"
1011        );
1012        assert_eq!(
1013            Lock::from_toml(&input).expect("noncanonical lock falls back"),
1014            expected
1015        );
1016    }
1017
1018    #[test]
1019    fn invalid_lock_preserves_toml_error() {
1020        let input = CANONICAL_LOCK.replace(
1021            "source = { registry = \"https://example.com/simple\" }",
1022            "source = { registry = \"https://example.com/simple\", registry = \"https://other.example/simple\" }",
1023        );
1024        let expected = toml::from_str::<Lock>(&input).expect_err("duplicate source is invalid");
1025        let actual = Lock::from_toml(&input).expect_err("duplicate source remains invalid");
1026
1027        assert_eq!(actual.to_string(), expected.to_string());
1028    }
1029
1030    #[test]
1031    fn unsupported_lock_version_is_rejected() {
1032        let version = VERSION + 1;
1033        let input = CANONICAL_LOCK.replacen("version = 1", &format!("version = {version}"), 1);
1034        let error = Lock::from_toml(&input).expect_err("unsupported lock versions are rejected");
1035
1036        assert_matches!(
1037            error,
1038            LockParseError::UnsupportedVersion {
1039                supported: VERSION,
1040                version: actual,
1041            } if actual == version
1042        );
1043    }
1044
1045    #[test]
1046    fn unparsable_unsupported_lock_version_is_identified() {
1047        let version = VERSION + 1;
1048        let input = CANONICAL_LOCK
1049            .replacen("version = 1", &format!("version = {version}"), 1)
1050            .replacen("name = \"dependency\"", "name = false", 1);
1051        let error =
1052            Lock::from_toml(&input).expect_err("unparsable unsupported lock versions are rejected");
1053
1054        assert_matches!(
1055            error,
1056            LockParseError::UnparsableVersion {
1057                supported: VERSION,
1058                version: actual,
1059                ..
1060            } if actual == version
1061        );
1062    }
1063
1064    #[test]
1065    fn syntax_errors_preserve_real_byte_offsets() {
1066        let input =
1067            CANONICAL_LOCK.replace("requires-python = \">=3.12\"", "requires-python = '>=3.12'");
1068        let unsupported = from_str(&input).expect_err("literal strings require the TOML fallback");
1069
1070        assert_matches!(&unsupported, Error::Unsupported { offset, .. } if *offset > 0,
1071            "unsupported syntax must retain its real byte offset: {unsupported}"
1072        );
1073
1074        let input = CANONICAL_LOCK.replace("revision = 3", "revision = 03");
1075        let invalid = from_str(&input).expect_err("TOML rejects a leading zero");
1076
1077        assert_matches!(&invalid, Error::Invalid { offset, .. } if *offset > 0,
1078            "invalid TOML scalars must retain their real byte offset: {invalid}"
1079        );
1080    }
1081
1082    #[test]
1083    fn inline_unit_variants_without_values_preserve_toml_error() {
1084        for variant in ["highest", "lowest", "lowest-direct"] {
1085            let input = CANONICAL_LOCK.replacen(
1086                "\n\n[[package]]",
1087                &format!("\n\n[options]\nresolution-mode = {{ {variant} = }}\n\n[[package]]"),
1088                1,
1089            );
1090            let expected = toml::from_str::<Lock>(&input)
1091                .expect_err("TOML rejects an inline unit variant without a value");
1092
1093            assert!(
1094                from_str(&input).is_err(),
1095                "the direct parser must reject an inline `{variant}` without a value"
1096            );
1097
1098            let actual = Lock::from_toml(&input)
1099                .expect_err("the lock reader rejects an inline unit variant without a value");
1100
1101            assert_eq!(actual.to_string(), expected.to_string());
1102        }
1103    }
1104
1105    #[test]
1106    fn leading_zero_integers_preserve_toml_error() {
1107        for (valid, invalid) in [
1108            ("version = 1", "version = 01"),
1109            ("revision = 3", "revision = 03"),
1110        ] {
1111            let input = CANONICAL_LOCK.replace(valid, invalid);
1112            let expected = toml::from_str::<Lock>(&input)
1113                .expect_err("TOML rejects decimal integers with leading zeros");
1114
1115            assert!(
1116                from_str(&input).is_err(),
1117                "the direct parser must reject `{invalid}`"
1118            );
1119
1120            let actual = Lock::from_toml(&input)
1121                .expect_err("the lock reader rejects decimal integers with leading zeros");
1122
1123            assert_eq!(actual.to_string(), expected.to_string());
1124        }
1125    }
1126
1127    #[test]
1128    fn invalid_numeric_syntax_preserves_toml_error() {
1129        for number in [
1130            "-01", "00.1", "1.", "1.e2", "1e", "1e+", "1__0", "1_", "0_1",
1131        ] {
1132            let input = CANONICAL_LOCK.replace(
1133                "requires-python = \">=3.12\"",
1134                &format!("requires-python = \">=3.12\"\nunknown-number = {number}"),
1135            );
1136            let expected = toml::from_str::<Lock>(&input)
1137                .expect_err("TOML rejects invalid decimal number syntax");
1138
1139            assert!(
1140                from_str(&input).is_err(),
1141                "the direct parser must reject `{number}`"
1142            );
1143
1144            let actual = Lock::from_toml(&input)
1145                .expect_err("the lock reader rejects invalid decimal number syntax");
1146
1147            assert_eq!(actual.to_string(), expected.to_string());
1148        }
1149    }
1150
1151    #[test]
1152    fn json_only_string_escapes_preserve_toml_error() {
1153        for source in [
1154            r"https:\/\/example.com\/simple",
1155            r"https://example.com/\uD83D\uDE00",
1156        ] {
1157            let input = CANONICAL_LOCK.replace("https://example.com/simple", source);
1158            let expected =
1159                toml::from_str::<Lock>(&input).expect_err("TOML rejects JSON-only string escapes");
1160
1161            assert!(
1162                from_str(&input).is_err(),
1163                "the direct parser must reject JSON-only escapes in `{source}`"
1164            );
1165
1166            let actual = Lock::from_toml(&input)
1167                .expect_err("the lock reader rejects JSON-only string escapes");
1168
1169            assert_eq!(actual.to_string(), expected.to_string());
1170        }
1171    }
1172
1173    #[test]
1174    fn unicode_escapes_match_toml() {
1175        let input = CANONICAL_LOCK.replace(
1176            "https://example.com/simple",
1177            r"https://example.com/\u0073imple",
1178        );
1179        let expected: Lock = toml::from_str(&input).expect("valid TOML Unicode escape");
1180        let actual = from_str(&input).expect("valid canonical Unicode escape");
1181
1182        assert_eq!(actual, expected);
1183    }
1184
1185    #[test]
1186    fn toml_only_string_escapes_deserialize_directly() {
1187        for source in [
1188            r"https://example.com/simpl\x65",
1189            r"https://example.com/simpl\U00000065",
1190        ] {
1191            let input = CANONICAL_LOCK.replace("https://example.com/simple", source);
1192            let expected: Lock = toml::from_str(&input).expect("valid TOML-only string escape");
1193
1194            assert_eq!(
1195                from_str(&input).expect("valid TOML-only string escape uses the direct parser"),
1196                expected,
1197                "the TOML decoder correctly handles escapes in `{source}`"
1198            );
1199        }
1200    }
1201
1202    #[test]
1203    fn underscored_integers_deserialize_directly() {
1204        let input = CANONICAL_LOCK.replace("revision = 3", "revision = 3_0");
1205        let expected: Lock = toml::from_str(&input).expect("valid TOML integer separator");
1206        let actual = from_str(&input).expect("valid TOML integer separator uses the direct parser");
1207
1208        assert_eq!(actual, expected);
1209    }
1210
1211    #[test]
1212    fn toml_escape_character_deserializes_directly() {
1213        let input = CANONICAL_LOCK.replace(
1214            "revision = 3\n",
1215            "revision = 3\nignored = \"TOML\\eescape\"\n",
1216        );
1217        let expected: Lock = toml::from_str(&input).expect("valid TOML escape character");
1218        let actual = from_str(&input).expect("valid TOML escape character uses the direct parser");
1219
1220        assert_eq!(actual, expected);
1221    }
1222
1223    #[test]
1224    fn canonical_relative_exclude_newer_uses_fast_path() {
1225        let input = CANONICAL_LOCK.replace(
1226            "requires-python = \">=3.12\"\n",
1227            concat!(
1228                "requires-python = \">=3.12\"\n\n",
1229                "[options]\n",
1230                "exclude-newer = \"0001-01-01T00:00:00Z\" ",
1231                "# This has no effect and is included for backwards compatibility ",
1232                "when using relative exclude-newer values.\n",
1233                "exclude-newer-span = \"P3W\"\n",
1234            ),
1235        );
1236        let expected: Lock =
1237            toml::from_str(&input).expect("canonical relative exclude-newer lock is valid TOML");
1238        let actual =
1239            from_str(&input).expect("canonical relative exclude-newer lock uses the direct parser");
1240
1241        assert_eq!(actual, expected);
1242    }
1243
1244    #[test]
1245    fn inline_comments_match_toml() {
1246        let input = CANONICAL_LOCK
1247            .replace("version = 1\n", "version = 1 # root comment\n")
1248            .replace(
1249                "source = { virtual = \".\" }\n",
1250                "source = { virtual = \".\" } # package comment\n",
1251            );
1252        let expected: Lock = toml::from_str(&input).expect("inline comments are valid TOML");
1253        let actual = from_str(&input).expect("inline comments use the direct parser");
1254
1255        assert_eq!(actual, expected);
1256    }
1257
1258    #[test]
1259    fn invalid_comment_characters_preserve_toml_error() {
1260        for character in ['\u{0}', '\u{7}', '\u{b}', '\u{7f}'] {
1261            let input = CANONICAL_LOCK.replace(
1262                "version = 1\n",
1263                &format!("version = 1 # invalid{character}comment\n"),
1264            );
1265            let expected = toml::from_str::<Lock>(&input)
1266                .expect_err("TOML rejects control characters in comments");
1267
1268            assert!(
1269                from_str(&input).is_err(),
1270                "the direct parser must reject U+{:04X} in a comment",
1271                u32::from(character)
1272            );
1273
1274            let actual = Lock::from_toml(&input)
1275                .expect_err("the lock reader rejects control characters in comments");
1276
1277            assert_eq!(actual.to_string(), expected.to_string());
1278        }
1279    }
1280
1281    #[test]
1282    fn bare_carriage_returns_preserve_toml_error() {
1283        for input in [
1284            format!("\r{CANONICAL_LOCK}"),
1285            format!("{CANONICAL_LOCK}\r"),
1286            CANONICAL_LOCK.replace("revision = 3\n", "revision = 3\n\r"),
1287            CANONICAL_LOCK.replace("dependencies = [\n", "dependencies = [\r"),
1288            format!("{CANONICAL_LOCK}[options]\r"),
1289        ] {
1290            let expected = toml::from_str::<Lock>(&input)
1291                .expect_err("TOML rejects standalone carriage returns");
1292
1293            assert!(
1294                from_str(&input).is_err(),
1295                "the direct parser must reject standalone carriage returns"
1296            );
1297
1298            let actual = Lock::from_toml(&input)
1299                .expect_err("the lock reader rejects standalone carriage returns");
1300
1301            assert_eq!(actual.to_string(), expected.to_string());
1302        }
1303    }
1304
1305    #[test]
1306    fn unescaped_delete_preserves_toml_error() {
1307        let input = CANONICAL_LOCK.replace(
1308            "requires-python = \">=3.12\"\n",
1309            "requires-python = \">=3.12\"\nunknown = \"invalid\u{7f}string\"\n",
1310        );
1311        let expected =
1312            toml::from_str::<Lock>(&input).expect_err("TOML rejects unescaped ASCII DELETE");
1313
1314        assert!(
1315            from_str(&input).is_err(),
1316            "the direct parser must reject unescaped ASCII DELETE"
1317        );
1318
1319        let actual =
1320            Lock::from_toml(&input).expect_err("the lock reader rejects unescaped ASCII DELETE");
1321
1322        assert_eq!(actual.to_string(), expected.to_string());
1323    }
1324
1325    #[test]
1326    fn unescaped_string_control_characters_preserve_toml_error() {
1327        for prefix_length in [0, 7, 8, 15, 16, 31, 32, 63, 64] {
1328            let prefix = "a".repeat(prefix_length);
1329
1330            for character in ['\u{0}', '\u{7}', '\u{8}', '\n', '\r', '\u{1f}'] {
1331                let input = CANONICAL_LOCK.replace(
1332                    "requires-python = \">=3.12\"\n",
1333                    &format!(
1334                        "requires-python = \">=3.12\"\nunknown = \"{prefix}{character}string\"\n"
1335                    ),
1336                );
1337                let expected = toml::from_str::<Lock>(&input)
1338                    .expect_err("TOML rejects unescaped control characters in basic strings");
1339
1340                assert!(
1341                    from_str(&input).is_err(),
1342                    "the direct parser must reject U+{:04X} after {prefix_length} bytes",
1343                    u32::from(character)
1344                );
1345
1346                let actual = Lock::from_toml(&input)
1347                    .expect_err("the lock reader rejects unescaped string control characters");
1348
1349                assert_eq!(actual.to_string(), expected.to_string());
1350            }
1351        }
1352    }
1353
1354    #[test]
1355    fn duplicate_keys_preserve_toml_error() {
1356        for (kind, input) in [
1357            (
1358                "unknown root keys",
1359                CANONICAL_LOCK.replace(
1360                    "requires-python = \">=3.12\"\n",
1361                    "requires-python = \">=3.12\"\nunknown = 1\nunknown = 2\n",
1362                ),
1363            ),
1364            (
1365                "unknown inline-table keys",
1366                CANONICAL_LOCK.replace(
1367                    "requires-python = \">=3.12\"\n",
1368                    "requires-python = \">=3.12\"\nunknown = { nested = 1, nested = 2 }\n",
1369                ),
1370            ),
1371            (
1372                "dependency-group keys",
1373                format!("{CANONICAL_LOCK}\n[package.dev-dependencies]\ndev = []\ndev = []\n"),
1374            ),
1375            (
1376                "section headers",
1377                format!(
1378                    "{CANONICAL_LOCK}\n[package.metadata]\nunknown = 1\n\n[package.metadata]\nunknown = 2\n"
1379                ),
1380            ),
1381        ] {
1382            let expected = toml::from_str::<Lock>(&input).expect_err("TOML rejects duplicate keys");
1383
1384            assert!(
1385                from_str(&input).is_err(),
1386                "the direct parser must reject duplicate {kind}"
1387            );
1388
1389            let actual =
1390                Lock::from_toml(&input).expect_err("the lock reader rejects duplicate keys");
1391
1392            assert_eq!(actual.to_string(), expected.to_string());
1393        }
1394    }
1395
1396    #[test]
1397    fn wide_maps_match_toml() {
1398        let mut assignments = String::new();
1399        let mut inline_entries = String::new();
1400
1401        for index in 0..4_096 {
1402            writeln!(assignments, "ignored-{index:04} = true")
1403                .expect("writing to a string cannot fail");
1404
1405            if !inline_entries.is_empty() {
1406                inline_entries.push_str(", ");
1407            }
1408            write!(inline_entries, "ignored-{index:04} = true")
1409                .expect("writing to a string cannot fail");
1410        }
1411
1412        for (kind, entries) in [
1413            ("root", assignments),
1414            ("inline", format!("ignored = {{ {inline_entries} }}\n")),
1415        ] {
1416            let input = CANONICAL_LOCK.replace(
1417                "requires-python = \">=3.12\"\n",
1418                &format!("requires-python = \">=3.12\"\n{entries}"),
1419            );
1420            let expected: Lock = toml::from_str(&input).expect("wide lock is valid TOML");
1421            let actual = from_str(&input).expect("wide lock uses the direct parser");
1422
1423            assert_eq!(actual, expected, "wide {kind} map matches TOML");
1424        }
1425    }
1426
1427    #[test]
1428    fn duplicates_after_inline_capacity_preserve_toml_error() {
1429        let mut assignments = String::new();
1430        let mut inline_entries = String::new();
1431
1432        for index in 0..16 {
1433            writeln!(assignments, "ignored-{index:02} = {index}")
1434                .expect("writing to a string cannot fail");
1435
1436            if !inline_entries.is_empty() {
1437                inline_entries.push_str(", ");
1438            }
1439            write!(inline_entries, "ignored-{index:02} = {index}")
1440                .expect("writing to a string cannot fail");
1441        }
1442
1443        assignments.push_str("ignored-08 = 8\n");
1444        inline_entries.push_str(", ignored-08 = 8");
1445
1446        for (kind, entries) in [
1447            ("root", assignments),
1448            ("inline", format!("ignored = {{ {inline_entries} }}\n")),
1449        ] {
1450            let input = CANONICAL_LOCK.replace(
1451                "requires-python = \">=3.12\"\n",
1452                &format!("requires-python = \">=3.12\"\n{entries}"),
1453            );
1454            let expected = toml::from_str::<Lock>(&input)
1455                .expect_err("TOML rejects duplicate keys in wide maps");
1456
1457            assert!(
1458                from_str(&input).is_err(),
1459                "the direct parser rejects duplicate keys in a wide {kind} map"
1460            );
1461
1462            let actual = Lock::from_toml(&input)
1463                .expect_err("the lock reader rejects duplicate keys in wide maps");
1464
1465            assert_eq!(actual.to_string(), expected.to_string());
1466        }
1467    }
1468
1469    #[test]
1470    fn canonical_mutations_match_toml() {
1471        const MUTATIONS: &[char] = &[
1472            '\0', '\t', '\n', '\r', ' ', '#', '"', '\'', '\\', '=', ',', '.', '{', '}', '[', ']',
1473            '_', '0', 'é', '🦀', '\u{85}', '\u{2028}',
1474        ];
1475
1476        for offset in 0..CANONICAL_LOCK.len() {
1477            for &mutation in MUTATIONS {
1478                let mut input = CANONICAL_LOCK.to_owned();
1479                input.insert(offset, mutation);
1480
1481                let Ok(actual) = from_str(&input) else {
1482                    continue;
1483                };
1484
1485                let expected = toml::from_str::<Lock>(&input);
1486                assert!(
1487                    expected.is_ok(),
1488                    "direct parser accepted invalid TOML after inserting {mutation:?} at {offset}: {expected:?}"
1489                );
1490
1491                assert_eq!(
1492                    actual,
1493                    expected.expect("direct parser accepted valid TOML"),
1494                    "direct parser disagreed with TOML after inserting {mutation:?} at {offset}"
1495                );
1496            }
1497        }
1498    }
1499
1500    #[test]
1501    fn excessive_container_depth_preserves_toml_error() {
1502        for nested in [
1503            format!("{}0{}", "[".repeat(81), "]".repeat(81)),
1504            (0..81).fold(String::from("0"), |nested, index| {
1505                if index % 2 == 0 {
1506                    format!("[{nested}]")
1507                } else {
1508                    format!("{{ nested = {nested} }}")
1509                }
1510            }),
1511        ] {
1512            let input = CANONICAL_LOCK.replace(
1513                "requires-python = \">=3.12\"\n",
1514                &format!("requires-python = \">=3.12\"\nunknown = {nested}\n"),
1515            );
1516            let expected =
1517                toml::from_str::<Lock>(&input).expect_err("TOML rejects excessive nesting");
1518
1519            assert!(
1520                from_str(&input).is_err(),
1521                "the direct parser must reject excessive inline-container nesting"
1522            );
1523
1524            let actual = Lock::from_toml(&input)
1525                .expect_err("the lock reader rejects excessive inline-container nesting");
1526
1527            assert_eq!(actual.to_string(), expected.to_string());
1528        }
1529    }
1530
1531    #[test]
1532    fn supported_container_depth_matches_toml() {
1533        let nested = format!("{}0{}", "[".repeat(80), "]".repeat(80));
1534        let input = CANONICAL_LOCK.replace(
1535            "requires-python = \">=3.12\"\n",
1536            &format!("requires-python = \">=3.12\"\nunknown = {nested}\n"),
1537        );
1538        let expected: Lock = toml::from_str(&input).expect("TOML supports 80 nested containers");
1539        let actual = from_str(&input).expect("the direct parser supports 80 nested containers");
1540
1541        assert_eq!(actual, expected);
1542    }
1543
1544    #[test]
1545    fn canonical_round_trip_uses_fast_path() {
1546        let lock: Lock = toml::from_str(CANONICAL_LOCK).expect("valid TOML lock");
1547        let canonical = lock.to_toml().expect("lock serializes canonically");
1548
1549        assert_eq!(
1550            Lock::from_canonical_toml(&canonical).expect("writer output uses fast path"),
1551            lock
1552        );
1553    }
1554}