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::fmt::Write as _;
873
874    use serde::Deserialize;
875
876    use super::super::{LockParseError, VERSION};
877    use super::{Cursor, Error, Lock, ValueDeserializer, from_str};
878
879    const CANONICAL_LOCK: &str = r#"version = 1
880revision = 3
881requires-python = ">=3.12"
882
883[[package]]
884name = "dependency"
885version = "1.0.0"
886source = { registry = "https://example.com/simple" }
887
888[[package]]
889name = "project"
890version = "0.1.0"
891source = { virtual = "." }
892dependencies = [
893    { name = "dependency" },
894]
895"#;
896
897    #[test]
898    fn canonical_lock_matches_toml() {
899        let expected: Lock = toml::from_str(CANONICAL_LOCK).expect("valid TOML lock");
900        let actual = from_str(CANONICAL_LOCK).expect("valid canonical lock");
901
902        assert_eq!(actual, expected);
903    }
904
905    #[test]
906    fn repository_lock_matches_toml() {
907        let input = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../uv.lock"));
908        let expected: Lock = toml::from_str(input).expect("valid repository lock");
909        let actual = from_str(input).expect("valid canonical repository lock");
910
911        assert_eq!(actual, expected);
912    }
913
914    #[test]
915    fn nested_lock_sections_match_toml() {
916        let input = r#"version = 1
917revision = 3
918requires-python = ">=3.12"
919
920[options]
921resolution-mode = "highest"
922
923[options.exclude-newer-package]
924dependency = false
925
926[manifest]
927members = ["project"]
928requirements = [{ name = "dependency", specifier = ">=1" }]
929
930[manifest.dependency-groups]
931dev = [{ name = "dependency", specifier = ">=1" }]
932
933[[manifest.dependency-metadata]]
934name = "dependency"
935version = "1.0.0"
936
937[[package]]
938name = "dependency"
939version = "1.0.0"
940source = { registry = "https://example.com/simple" }
941
942[[package]]
943name = "project"
944version = "0.1.0"
945source = { virtual = "." }
946dependencies = [{ name = "dependency" }]
947
948[package.optional-dependencies]
949feature = [{ name = "dependency" }]
950
951[package.dev-dependencies]
952dev = [{ name = "dependency" }]
953
954[package.metadata]
955requires-dist = [{ name = "dependency", specifier = ">=1" }]
956provides-extras = ["feature"]
957
958[package.metadata.requires-dev]
959dev = [{ name = "dependency", specifier = ">=1" }]
960"#;
961        let expected: Lock = toml::from_str(input).expect("valid nested TOML lock");
962        let actual = from_str(input).expect("valid nested canonical lock");
963
964        assert_eq!(actual, expected);
965    }
966
967    #[test]
968    fn escaped_strings_match_toml() {
969        let input = CANONICAL_LOCK.replace(
970            "https://example.com/simple",
971            "https://example.com/simple?name=quoted\\\"value",
972        );
973        let expected: Lock = toml::from_str(&input).expect("valid TOML lock");
974        let actual = from_str(&input).expect("valid canonical lock");
975
976        assert_eq!(actual, expected);
977    }
978
979    #[test]
980    fn long_basic_strings_deserialize_directly() {
981        let source = format!("https://example.com/{}", "a".repeat(512));
982        let input = CANONICAL_LOCK.replace("https://example.com/simple", &source);
983        let expected: Lock = toml::from_str(&input).expect("valid TOML lock with a long string");
984        let actual = from_str(&input).expect("long basic string uses the direct parser");
985
986        assert_eq!(actual, expected);
987    }
988
989    #[test]
990    fn empty_basic_string_deserializes_directly() {
991        let mut cursor = Cursor::new(r#""""#);
992        let actual = String::deserialize(ValueDeserializer {
993            cursor: &mut cursor,
994        })
995        .expect("empty basic string deserializes directly");
996
997        assert_eq!(actual, "");
998    }
999
1000    #[test]
1001    fn noncanonical_lock_falls_back() {
1002        let input = CANONICAL_LOCK
1003            .replace("requires-python = \">=3.12\"", "requires-python = '>=3.12'")
1004            .replace("[[package]]", "# A hand-edited package.\n[[package]]");
1005        let expected: Lock = toml::from_str(&input).expect("valid noncanonical lock");
1006
1007        assert!(
1008            Lock::from_canonical_toml(&input).is_err(),
1009            "the canonical lock parser must not fall back"
1010        );
1011        assert_eq!(
1012            Lock::from_toml(&input).expect("noncanonical lock falls back"),
1013            expected
1014        );
1015    }
1016
1017    #[test]
1018    fn invalid_lock_preserves_toml_error() {
1019        let input = CANONICAL_LOCK.replace(
1020            "source = { registry = \"https://example.com/simple\" }",
1021            "source = { registry = \"https://example.com/simple\", registry = \"https://other.example/simple\" }",
1022        );
1023        let expected = toml::from_str::<Lock>(&input).expect_err("duplicate source is invalid");
1024        let actual = Lock::from_toml(&input).expect_err("duplicate source remains invalid");
1025
1026        assert_eq!(actual.to_string(), expected.to_string());
1027    }
1028
1029    #[test]
1030    fn unsupported_lock_version_is_rejected() {
1031        let version = VERSION + 1;
1032        let input = CANONICAL_LOCK.replacen("version = 1", &format!("version = {version}"), 1);
1033        let error = Lock::from_toml(&input).expect_err("unsupported lock versions are rejected");
1034
1035        assert!(matches!(
1036            error,
1037            LockParseError::UnsupportedVersion {
1038                supported: VERSION,
1039                version: actual,
1040            } if actual == version
1041        ));
1042    }
1043
1044    #[test]
1045    fn unparsable_unsupported_lock_version_is_identified() {
1046        let version = VERSION + 1;
1047        let input = CANONICAL_LOCK
1048            .replacen("version = 1", &format!("version = {version}"), 1)
1049            .replacen("name = \"dependency\"", "name = false", 1);
1050        let error =
1051            Lock::from_toml(&input).expect_err("unparsable unsupported lock versions are rejected");
1052
1053        assert!(matches!(
1054            error,
1055            LockParseError::UnparsableVersion {
1056                supported: VERSION,
1057                version: actual,
1058                ..
1059            } if actual == version
1060        ));
1061    }
1062
1063    #[test]
1064    fn syntax_errors_preserve_real_byte_offsets() {
1065        let input =
1066            CANONICAL_LOCK.replace("requires-python = \">=3.12\"", "requires-python = '>=3.12'");
1067        let unsupported = from_str(&input).expect_err("literal strings require the TOML fallback");
1068
1069        assert!(
1070            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!(
1078            matches!(&invalid, Error::Invalid { offset, .. } if *offset > 0),
1079            "invalid TOML scalars must retain their real byte offset: {invalid}"
1080        );
1081    }
1082
1083    #[test]
1084    fn inline_unit_variants_without_values_preserve_toml_error() {
1085        for variant in ["highest", "lowest", "lowest-direct"] {
1086            let input = CANONICAL_LOCK.replacen(
1087                "\n\n[[package]]",
1088                &format!("\n\n[options]\nresolution-mode = {{ {variant} = }}\n\n[[package]]"),
1089                1,
1090            );
1091            let expected = toml::from_str::<Lock>(&input)
1092                .expect_err("TOML rejects an inline unit variant without a value");
1093
1094            assert!(
1095                from_str(&input).is_err(),
1096                "the direct parser must reject an inline `{variant}` without a value"
1097            );
1098
1099            let actual = Lock::from_toml(&input)
1100                .expect_err("the lock reader rejects an inline unit variant without a value");
1101
1102            assert_eq!(actual.to_string(), expected.to_string());
1103        }
1104    }
1105
1106    #[test]
1107    fn leading_zero_integers_preserve_toml_error() {
1108        for (valid, invalid) in [
1109            ("version = 1", "version = 01"),
1110            ("revision = 3", "revision = 03"),
1111        ] {
1112            let input = CANONICAL_LOCK.replace(valid, invalid);
1113            let expected = toml::from_str::<Lock>(&input)
1114                .expect_err("TOML rejects decimal integers with leading zeros");
1115
1116            assert!(
1117                from_str(&input).is_err(),
1118                "the direct parser must reject `{invalid}`"
1119            );
1120
1121            let actual = Lock::from_toml(&input)
1122                .expect_err("the lock reader rejects decimal integers with leading zeros");
1123
1124            assert_eq!(actual.to_string(), expected.to_string());
1125        }
1126    }
1127
1128    #[test]
1129    fn invalid_numeric_syntax_preserves_toml_error() {
1130        for number in [
1131            "-01", "00.1", "1.", "1.e2", "1e", "1e+", "1__0", "1_", "0_1",
1132        ] {
1133            let input = CANONICAL_LOCK.replace(
1134                "requires-python = \">=3.12\"",
1135                &format!("requires-python = \">=3.12\"\nunknown-number = {number}"),
1136            );
1137            let expected = toml::from_str::<Lock>(&input)
1138                .expect_err("TOML rejects invalid decimal number syntax");
1139
1140            assert!(
1141                from_str(&input).is_err(),
1142                "the direct parser must reject `{number}`"
1143            );
1144
1145            let actual = Lock::from_toml(&input)
1146                .expect_err("the lock reader rejects invalid decimal number syntax");
1147
1148            assert_eq!(actual.to_string(), expected.to_string());
1149        }
1150    }
1151
1152    #[test]
1153    fn json_only_string_escapes_preserve_toml_error() {
1154        for source in [
1155            r"https:\/\/example.com\/simple",
1156            r"https://example.com/\uD83D\uDE00",
1157        ] {
1158            let input = CANONICAL_LOCK.replace("https://example.com/simple", source);
1159            let expected =
1160                toml::from_str::<Lock>(&input).expect_err("TOML rejects JSON-only string escapes");
1161
1162            assert!(
1163                from_str(&input).is_err(),
1164                "the direct parser must reject JSON-only escapes in `{source}`"
1165            );
1166
1167            let actual = Lock::from_toml(&input)
1168                .expect_err("the lock reader rejects JSON-only string escapes");
1169
1170            assert_eq!(actual.to_string(), expected.to_string());
1171        }
1172    }
1173
1174    #[test]
1175    fn unicode_escapes_match_toml() {
1176        let input = CANONICAL_LOCK.replace(
1177            "https://example.com/simple",
1178            r"https://example.com/\u0073imple",
1179        );
1180        let expected: Lock = toml::from_str(&input).expect("valid TOML Unicode escape");
1181        let actual = from_str(&input).expect("valid canonical Unicode escape");
1182
1183        assert_eq!(actual, expected);
1184    }
1185
1186    #[test]
1187    fn toml_only_string_escapes_deserialize_directly() {
1188        for source in [
1189            r"https://example.com/simpl\x65",
1190            r"https://example.com/simpl\U00000065",
1191        ] {
1192            let input = CANONICAL_LOCK.replace("https://example.com/simple", source);
1193            let expected: Lock = toml::from_str(&input).expect("valid TOML-only string escape");
1194
1195            assert_eq!(
1196                from_str(&input).expect("valid TOML-only string escape uses the direct parser"),
1197                expected,
1198                "the TOML decoder correctly handles escapes in `{source}`"
1199            );
1200        }
1201    }
1202
1203    #[test]
1204    fn underscored_integers_deserialize_directly() {
1205        let input = CANONICAL_LOCK.replace("revision = 3", "revision = 3_0");
1206        let expected: Lock = toml::from_str(&input).expect("valid TOML integer separator");
1207        let actual = from_str(&input).expect("valid TOML integer separator uses the direct parser");
1208
1209        assert_eq!(actual, expected);
1210    }
1211
1212    #[test]
1213    fn toml_escape_character_deserializes_directly() {
1214        let input = CANONICAL_LOCK.replace(
1215            "revision = 3\n",
1216            "revision = 3\nignored = \"TOML\\eescape\"\n",
1217        );
1218        let expected: Lock = toml::from_str(&input).expect("valid TOML escape character");
1219        let actual = from_str(&input).expect("valid TOML escape character uses the direct parser");
1220
1221        assert_eq!(actual, expected);
1222    }
1223
1224    #[test]
1225    fn canonical_relative_exclude_newer_uses_fast_path() {
1226        let input = CANONICAL_LOCK.replace(
1227            "requires-python = \">=3.12\"\n",
1228            concat!(
1229                "requires-python = \">=3.12\"\n\n",
1230                "[options]\n",
1231                "exclude-newer = \"0001-01-01T00:00:00Z\" ",
1232                "# This has no effect and is included for backwards compatibility ",
1233                "when using relative exclude-newer values.\n",
1234                "exclude-newer-span = \"P3W\"\n",
1235            ),
1236        );
1237        let expected: Lock =
1238            toml::from_str(&input).expect("canonical relative exclude-newer lock is valid TOML");
1239        let actual =
1240            from_str(&input).expect("canonical relative exclude-newer lock uses the direct parser");
1241
1242        assert_eq!(actual, expected);
1243    }
1244
1245    #[test]
1246    fn inline_comments_match_toml() {
1247        let input = CANONICAL_LOCK
1248            .replace("version = 1\n", "version = 1 # root comment\n")
1249            .replace(
1250                "source = { virtual = \".\" }\n",
1251                "source = { virtual = \".\" } # package comment\n",
1252            );
1253        let expected: Lock = toml::from_str(&input).expect("inline comments are valid TOML");
1254        let actual = from_str(&input).expect("inline comments use the direct parser");
1255
1256        assert_eq!(actual, expected);
1257    }
1258
1259    #[test]
1260    fn invalid_comment_characters_preserve_toml_error() {
1261        for character in ['\u{0}', '\u{7}', '\u{b}', '\u{7f}'] {
1262            let input = CANONICAL_LOCK.replace(
1263                "version = 1\n",
1264                &format!("version = 1 # invalid{character}comment\n"),
1265            );
1266            let expected = toml::from_str::<Lock>(&input)
1267                .expect_err("TOML rejects control characters in comments");
1268
1269            assert!(
1270                from_str(&input).is_err(),
1271                "the direct parser must reject U+{:04X} in a comment",
1272                u32::from(character)
1273            );
1274
1275            let actual = Lock::from_toml(&input)
1276                .expect_err("the lock reader rejects control characters in comments");
1277
1278            assert_eq!(actual.to_string(), expected.to_string());
1279        }
1280    }
1281
1282    #[test]
1283    fn bare_carriage_returns_preserve_toml_error() {
1284        for input in [
1285            format!("\r{CANONICAL_LOCK}"),
1286            format!("{CANONICAL_LOCK}\r"),
1287            CANONICAL_LOCK.replace("revision = 3\n", "revision = 3\n\r"),
1288            CANONICAL_LOCK.replace("dependencies = [\n", "dependencies = [\r"),
1289            format!("{CANONICAL_LOCK}[options]\r"),
1290        ] {
1291            let expected = toml::from_str::<Lock>(&input)
1292                .expect_err("TOML rejects standalone carriage returns");
1293
1294            assert!(
1295                from_str(&input).is_err(),
1296                "the direct parser must reject standalone carriage returns"
1297            );
1298
1299            let actual = Lock::from_toml(&input)
1300                .expect_err("the lock reader rejects standalone carriage returns");
1301
1302            assert_eq!(actual.to_string(), expected.to_string());
1303        }
1304    }
1305
1306    #[test]
1307    fn unescaped_delete_preserves_toml_error() {
1308        let input = CANONICAL_LOCK.replace(
1309            "requires-python = \">=3.12\"\n",
1310            "requires-python = \">=3.12\"\nunknown = \"invalid\u{7f}string\"\n",
1311        );
1312        let expected =
1313            toml::from_str::<Lock>(&input).expect_err("TOML rejects unescaped ASCII DELETE");
1314
1315        assert!(
1316            from_str(&input).is_err(),
1317            "the direct parser must reject unescaped ASCII DELETE"
1318        );
1319
1320        let actual =
1321            Lock::from_toml(&input).expect_err("the lock reader rejects unescaped ASCII DELETE");
1322
1323        assert_eq!(actual.to_string(), expected.to_string());
1324    }
1325
1326    #[test]
1327    fn unescaped_string_control_characters_preserve_toml_error() {
1328        for prefix_length in [0, 7, 8, 15, 16, 31, 32, 63, 64] {
1329            let prefix = "a".repeat(prefix_length);
1330
1331            for character in ['\u{0}', '\u{7}', '\u{8}', '\n', '\r', '\u{1f}'] {
1332                let input = CANONICAL_LOCK.replace(
1333                    "requires-python = \">=3.12\"\n",
1334                    &format!(
1335                        "requires-python = \">=3.12\"\nunknown = \"{prefix}{character}string\"\n"
1336                    ),
1337                );
1338                let expected = toml::from_str::<Lock>(&input)
1339                    .expect_err("TOML rejects unescaped control characters in basic strings");
1340
1341                assert!(
1342                    from_str(&input).is_err(),
1343                    "the direct parser must reject U+{:04X} after {prefix_length} bytes",
1344                    u32::from(character)
1345                );
1346
1347                let actual = Lock::from_toml(&input)
1348                    .expect_err("the lock reader rejects unescaped string control characters");
1349
1350                assert_eq!(actual.to_string(), expected.to_string());
1351            }
1352        }
1353    }
1354
1355    #[test]
1356    fn duplicate_keys_preserve_toml_error() {
1357        for (kind, input) in [
1358            (
1359                "unknown root keys",
1360                CANONICAL_LOCK.replace(
1361                    "requires-python = \">=3.12\"\n",
1362                    "requires-python = \">=3.12\"\nunknown = 1\nunknown = 2\n",
1363                ),
1364            ),
1365            (
1366                "unknown inline-table keys",
1367                CANONICAL_LOCK.replace(
1368                    "requires-python = \">=3.12\"\n",
1369                    "requires-python = \">=3.12\"\nunknown = { nested = 1, nested = 2 }\n",
1370                ),
1371            ),
1372            (
1373                "dependency-group keys",
1374                format!("{CANONICAL_LOCK}\n[package.dev-dependencies]\ndev = []\ndev = []\n"),
1375            ),
1376            (
1377                "section headers",
1378                format!(
1379                    "{CANONICAL_LOCK}\n[package.metadata]\nunknown = 1\n\n[package.metadata]\nunknown = 2\n"
1380                ),
1381            ),
1382        ] {
1383            let expected = toml::from_str::<Lock>(&input).expect_err("TOML rejects duplicate keys");
1384
1385            assert!(
1386                from_str(&input).is_err(),
1387                "the direct parser must reject duplicate {kind}"
1388            );
1389
1390            let actual =
1391                Lock::from_toml(&input).expect_err("the lock reader rejects duplicate keys");
1392
1393            assert_eq!(actual.to_string(), expected.to_string());
1394        }
1395    }
1396
1397    #[test]
1398    fn wide_maps_match_toml() {
1399        let mut assignments = String::new();
1400        let mut inline_entries = String::new();
1401
1402        for index in 0..4_096 {
1403            writeln!(assignments, "ignored-{index:04} = true")
1404                .expect("writing to a string cannot fail");
1405
1406            if !inline_entries.is_empty() {
1407                inline_entries.push_str(", ");
1408            }
1409            write!(inline_entries, "ignored-{index:04} = true")
1410                .expect("writing to a string cannot fail");
1411        }
1412
1413        for (kind, entries) in [
1414            ("root", assignments),
1415            ("inline", format!("ignored = {{ {inline_entries} }}\n")),
1416        ] {
1417            let input = CANONICAL_LOCK.replace(
1418                "requires-python = \">=3.12\"\n",
1419                &format!("requires-python = \">=3.12\"\n{entries}"),
1420            );
1421            let expected: Lock = toml::from_str(&input).expect("wide lock is valid TOML");
1422            let actual = from_str(&input).expect("wide lock uses the direct parser");
1423
1424            assert_eq!(actual, expected, "wide {kind} map matches TOML");
1425        }
1426    }
1427
1428    #[test]
1429    fn duplicates_after_inline_capacity_preserve_toml_error() {
1430        let mut assignments = String::new();
1431        let mut inline_entries = String::new();
1432
1433        for index in 0..16 {
1434            writeln!(assignments, "ignored-{index:02} = {index}")
1435                .expect("writing to a string cannot fail");
1436
1437            if !inline_entries.is_empty() {
1438                inline_entries.push_str(", ");
1439            }
1440            write!(inline_entries, "ignored-{index:02} = {index}")
1441                .expect("writing to a string cannot fail");
1442        }
1443
1444        assignments.push_str("ignored-08 = 8\n");
1445        inline_entries.push_str(", ignored-08 = 8");
1446
1447        for (kind, entries) in [
1448            ("root", assignments),
1449            ("inline", format!("ignored = {{ {inline_entries} }}\n")),
1450        ] {
1451            let input = CANONICAL_LOCK.replace(
1452                "requires-python = \">=3.12\"\n",
1453                &format!("requires-python = \">=3.12\"\n{entries}"),
1454            );
1455            let expected = toml::from_str::<Lock>(&input)
1456                .expect_err("TOML rejects duplicate keys in wide maps");
1457
1458            assert!(
1459                from_str(&input).is_err(),
1460                "the direct parser rejects duplicate keys in a wide {kind} map"
1461            );
1462
1463            let actual = Lock::from_toml(&input)
1464                .expect_err("the lock reader rejects duplicate keys in wide maps");
1465
1466            assert_eq!(actual.to_string(), expected.to_string());
1467        }
1468    }
1469
1470    #[test]
1471    fn canonical_mutations_match_toml() {
1472        const MUTATIONS: &[char] = &[
1473            '\0', '\t', '\n', '\r', ' ', '#', '"', '\'', '\\', '=', ',', '.', '{', '}', '[', ']',
1474            '_', '0', 'é', '🦀', '\u{85}', '\u{2028}',
1475        ];
1476
1477        for offset in 0..CANONICAL_LOCK.len() {
1478            for &mutation in MUTATIONS {
1479                let mut input = CANONICAL_LOCK.to_owned();
1480                input.insert(offset, mutation);
1481
1482                let Ok(actual) = from_str(&input) else {
1483                    continue;
1484                };
1485
1486                let expected = toml::from_str::<Lock>(&input);
1487                assert!(
1488                    expected.is_ok(),
1489                    "direct parser accepted invalid TOML after inserting {mutation:?} at {offset}: {expected:?}"
1490                );
1491
1492                assert_eq!(
1493                    actual,
1494                    expected.expect("direct parser accepted valid TOML"),
1495                    "direct parser disagreed with TOML after inserting {mutation:?} at {offset}"
1496                );
1497            }
1498        }
1499    }
1500
1501    #[test]
1502    fn excessive_container_depth_preserves_toml_error() {
1503        for nested in [
1504            format!("{}0{}", "[".repeat(81), "]".repeat(81)),
1505            (0..81).fold(String::from("0"), |nested, index| {
1506                if index % 2 == 0 {
1507                    format!("[{nested}]")
1508                } else {
1509                    format!("{{ nested = {nested} }}")
1510                }
1511            }),
1512        ] {
1513            let input = CANONICAL_LOCK.replace(
1514                "requires-python = \">=3.12\"\n",
1515                &format!("requires-python = \">=3.12\"\nunknown = {nested}\n"),
1516            );
1517            let expected =
1518                toml::from_str::<Lock>(&input).expect_err("TOML rejects excessive nesting");
1519
1520            assert!(
1521                from_str(&input).is_err(),
1522                "the direct parser must reject excessive inline-container nesting"
1523            );
1524
1525            let actual = Lock::from_toml(&input)
1526                .expect_err("the lock reader rejects excessive inline-container nesting");
1527
1528            assert_eq!(actual.to_string(), expected.to_string());
1529        }
1530    }
1531
1532    #[test]
1533    fn supported_container_depth_matches_toml() {
1534        let nested = format!("{}0{}", "[".repeat(80), "]".repeat(80));
1535        let input = CANONICAL_LOCK.replace(
1536            "requires-python = \">=3.12\"\n",
1537            &format!("requires-python = \">=3.12\"\nunknown = {nested}\n"),
1538        );
1539        let expected: Lock = toml::from_str(&input).expect("TOML supports 80 nested containers");
1540        let actual = from_str(&input).expect("the direct parser supports 80 nested containers");
1541
1542        assert_eq!(actual, expected);
1543    }
1544
1545    #[test]
1546    fn canonical_round_trip_uses_fast_path() {
1547        let lock: Lock = toml::from_str(CANONICAL_LOCK).expect("valid TOML lock");
1548        let canonical = lock.to_toml().expect("lock serializes canonically");
1549
1550        assert_eq!(
1551            Lock::from_canonical_toml(&canonical).expect("writer output uses fast path"),
1552            lock
1553        );
1554    }
1555}