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