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