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::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}