|
8 | 8 |
|
9 | 9 | use glob::Pattern; |
10 | 10 | use owo_colors::OwoColorize; |
11 | | -use serde::{de::IntoDeserializer, Deserialize, Serialize}; |
| 11 | +use serde::{de::IntoDeserializer, Deserialize, Deserializer, Serialize}; |
12 | 12 | use std::ops::Deref; |
13 | 13 | use std::path::{Path, PathBuf}; |
14 | 14 | use std::str::FromStr; |
@@ -455,7 +455,7 @@ enum SourcesWire { |
455 | 455 | impl<'de> serde::de::Deserialize<'de> for SourcesWire { |
456 | 456 | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
457 | 457 | where |
458 | | - D: serde::de::Deserializer<'de>, |
| 458 | + D: Deserializer<'de>, |
459 | 459 | { |
460 | 460 | serde_untagged::UntaggedEnumVisitor::new() |
461 | 461 | .map(|map| map.deserialize().map(SourcesWire::One)) |
@@ -520,31 +520,30 @@ impl TryFrom<SourcesWire> for Sources { |
520 | 520 | } |
521 | 521 |
|
522 | 522 | /// A `tool.uv.sources` value. |
523 | | -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] |
524 | | -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] |
525 | | -#[serde(rename_all = "kebab-case", untagged, deny_unknown_fields)] |
| 523 | +#[derive(Serialize, Debug, Clone, PartialEq, Eq)] |
| 524 | +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema), schemars(rename_all = "kebab-case", untagged, deny_unknown_fields))] |
526 | 525 | pub enum Source { |
527 | 526 | /// A remote Git repository, available over HTTPS or SSH. |
528 | 527 | /// |
529 | 528 | /// Example: |
530 | 529 | /// ```toml |
531 | 530 | /// flask = { git = "https://github.com/pallets/flask", tag = "3.0.0" } |
532 | 531 | /// ``` |
533 | | - Git { |
| 532 | + Git{ |
534 | 533 | /// The repository URL (without the `git+` prefix). |
535 | | - git: Url, |
| 534 | + git: Url, |
536 | 535 | /// The path to the directory with the `pyproject.toml`, if it's not in the archive root. |
537 | | - subdirectory: Option<PortablePathBuf>, |
| 536 | + subdirectory: Option<PortablePathBuf>, |
538 | 537 | // Only one of the three may be used; we'll validate this later and emit a custom error. |
539 | | - rev: Option<String>, |
540 | | - tag: Option<String>, |
541 | | - branch: Option<String>, |
| 538 | + rev: Option<String>, |
| 539 | + tag: Option<String>, |
| 540 | + branch: Option<String>, |
542 | 541 | #[serde( |
543 | 542 | skip_serializing_if = "pep508_rs::marker::ser::is_empty", |
544 | 543 | serialize_with = "pep508_rs::marker::ser::serialize", |
545 | 544 | default |
546 | 545 | )] |
547 | | - marker: MarkerTree, |
| 546 | + marker: MarkerTree, |
548 | 547 | }, |
549 | 548 | /// A remote `http://` or `https://` URL, either a wheel (`.whl`) or a source distribution |
550 | 549 | /// (`.zip`, `.tar.gz`). |
@@ -602,24 +601,202 @@ pub enum Source { |
602 | 601 | )] |
603 | 602 | marker: MarkerTree, |
604 | 603 | }, |
605 | | - /// A catch-all variant used to emit precise error messages when deserializing. |
606 | | - CatchAll { |
607 | | - git: String, |
608 | | - subdirectory: Option<PortablePathBuf>, |
609 | | - rev: Option<String>, |
610 | | - tag: Option<String>, |
611 | | - branch: Option<String>, |
612 | | - url: String, |
613 | | - path: PortablePathBuf, |
614 | | - index: String, |
615 | | - workspace: bool, |
616 | | - #[serde( |
617 | | - skip_serializing_if = "pep508_rs::marker::ser::is_empty", |
618 | | - serialize_with = "pep508_rs::marker::ser::serialize", |
619 | | - default |
620 | | - )] |
621 | | - marker: MarkerTree, |
622 | | - }, |
| 604 | +} |
| 605 | + |
| 606 | +/// A custom deserialization implementation for [`Source`]. This is roughly equivalent to |
| 607 | +/// `#[serde(untagged)]`, but provides more detailed error messages. |
| 608 | +impl<'de> Deserialize<'de> for Source { |
| 609 | + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
| 610 | + where |
| 611 | + D: Deserializer<'de>, |
| 612 | + { |
| 613 | + #[derive(Deserialize, Debug, Clone)] |
| 614 | + #[serde(rename_all = "kebab-case", deny_unknown_fields)] |
| 615 | + struct CatchAll { |
| 616 | + git: Option<Url>, |
| 617 | + subdirectory: Option<PortablePathBuf>, |
| 618 | + rev: Option<String>, |
| 619 | + tag: Option<String>, |
| 620 | + branch: Option<String>, |
| 621 | + url: Option<Url>, |
| 622 | + path: Option<PortablePathBuf>, |
| 623 | + editable: Option<bool>, |
| 624 | + index: Option<String>, |
| 625 | + workspace: Option<bool>, |
| 626 | + #[serde( |
| 627 | + skip_serializing_if = "pep508_rs::marker::ser::is_empty", |
| 628 | + serialize_with = "pep508_rs::marker::ser::serialize", |
| 629 | + default |
| 630 | + )] |
| 631 | + marker: MarkerTree, |
| 632 | + } |
| 633 | + |
| 634 | + // Attempt to deserialize as `CatchAll`. |
| 635 | + let CatchAll { git, subdirectory, rev, tag, branch, url, path, editable, index, workspace, marker } = CatchAll::deserialize(deserializer)?; |
| 636 | + |
| 637 | + // If the `git` field is set, we're dealing with a Git source. |
| 638 | + if let Some(git) = git { |
| 639 | + if index.is_some() { |
| 640 | + return Err(serde::de::Error::custom("cannot specify both `git` and `index`")); |
| 641 | + } |
| 642 | + if workspace.is_some() { |
| 643 | + return Err(serde::de::Error::custom("cannot specify both `git` and `workspace`")); |
| 644 | + } |
| 645 | + if path.is_some() { |
| 646 | + return Err(serde::de::Error::custom("cannot specify both `git` and `path`")); |
| 647 | + } |
| 648 | + if url.is_some() { |
| 649 | + return Err(serde::de::Error::custom("cannot specify both `git` and `url`")); |
| 650 | + } |
| 651 | + if editable.is_some() { |
| 652 | + return Err(serde::de::Error::custom("cannot specify both `git` and `editable`")); |
| 653 | + } |
| 654 | + |
| 655 | + return Ok(Source::Git { |
| 656 | + git, |
| 657 | + subdirectory, |
| 658 | + rev, |
| 659 | + tag, |
| 660 | + branch, |
| 661 | + marker, |
| 662 | + }); |
| 663 | + } |
| 664 | + |
| 665 | + // If the `url` field is set, we're dealing with a URL source. |
| 666 | + if let Some(url) = url { |
| 667 | + if index.is_some() { |
| 668 | + return Err(serde::de::Error::custom("cannot specify both `url` and `index`")); |
| 669 | + } |
| 670 | + if workspace.is_some() { |
| 671 | + return Err(serde::de::Error::custom("cannot specify both `url` and `workspace`")); |
| 672 | + } |
| 673 | + if path.is_some() { |
| 674 | + return Err(serde::de::Error::custom("cannot specify both `url` and `path`")); |
| 675 | + } |
| 676 | + if git.is_some() { |
| 677 | + return Err(serde::de::Error::custom("cannot specify both `url` and `git`")); |
| 678 | + } |
| 679 | + if rev.is_some() { |
| 680 | + return Err(serde::de::Error::custom("cannot specify both `url` and `rev`")); |
| 681 | + } |
| 682 | + if tag.is_some() { |
| 683 | + return Err(serde::de::Error::custom("cannot specify both `url` and `tag`")); |
| 684 | + } |
| 685 | + if branch.is_some() { |
| 686 | + return Err(serde::de::Error::custom("cannot specify both `url` and `branch`")); |
| 687 | + } |
| 688 | + if editable.is_some() { |
| 689 | + return Err(serde::de::Error::custom("cannot specify both `url` and `editable`")); |
| 690 | + } |
| 691 | + |
| 692 | + return Ok(Source::Url { |
| 693 | + url, |
| 694 | + subdirectory, |
| 695 | + marker, |
| 696 | + }); |
| 697 | + } |
| 698 | + |
| 699 | + // If the `path` field is set, we're dealing with a path source. |
| 700 | + if let Some(path) = path { |
| 701 | + if index.is_some() { |
| 702 | + return Err(serde::de::Error::custom("cannot specify both `path` and `index`")); |
| 703 | + } |
| 704 | + if workspace.is_some() { |
| 705 | + return Err(serde::de::Error::custom("cannot specify both `path` and `workspace`")); |
| 706 | + } |
| 707 | + if git.is_some() { |
| 708 | + return Err(serde::de::Error::custom("cannot specify both `path` and `git`")); |
| 709 | + } |
| 710 | + if url.is_some() { |
| 711 | + return Err(serde::de::Error::custom("cannot specify both `path` and `url`")); |
| 712 | + } |
| 713 | + if rev.is_some() { |
| 714 | + return Err(serde::de::Error::custom("cannot specify both `path` and `rev`")); |
| 715 | + } |
| 716 | + if tag.is_some() { |
| 717 | + return Err(serde::de::Error::custom("cannot specify both `path` and `tag`")); |
| 718 | + } |
| 719 | + if branch.is_some() { |
| 720 | + return Err(serde::de::Error::custom("cannot specify both `path` and `branch`")); |
| 721 | + } |
| 722 | + |
| 723 | + return Ok(Source::Path { |
| 724 | + path, |
| 725 | + editable, |
| 726 | + marker, |
| 727 | + }); |
| 728 | + } |
| 729 | + |
| 730 | + // If the `index` field is set, we're dealing with a registry source. |
| 731 | + if let Some(index) = index { |
| 732 | + if workspace.is_some() { |
| 733 | + return Err(serde::de::Error::custom("cannot specify both `index` and `workspace`")); |
| 734 | + } |
| 735 | + if git.is_some() { |
| 736 | + return Err(serde::de::Error::custom("cannot specify both `index` and `git`")); |
| 737 | + } |
| 738 | + if url.is_some() { |
| 739 | + return Err(serde::de::Error::custom("cannot specify both `index` and `url`")); |
| 740 | + } |
| 741 | + if path.is_some() { |
| 742 | + return Err(serde::de::Error::custom("cannot specify both `index` and `path`")); |
| 743 | + } |
| 744 | + if rev.is_some() { |
| 745 | + return Err(serde::de::Error::custom("cannot specify both `index` and `rev`")); |
| 746 | + } |
| 747 | + if tag.is_some() { |
| 748 | + return Err(serde::de::Error::custom("cannot specify both `index` and `tag`")); |
| 749 | + } |
| 750 | + if branch.is_some() { |
| 751 | + return Err(serde::de::Error::custom("cannot specify both `index` and `branch`")); |
| 752 | + } |
| 753 | + if editable.is_some() { |
| 754 | + return Err(serde::de::Error::custom("cannot specify both `index` and `editable`")); |
| 755 | + } |
| 756 | + |
| 757 | + return Ok(Source::Registry { |
| 758 | + index, |
| 759 | + marker, |
| 760 | + }); |
| 761 | + } |
| 762 | + |
| 763 | + // If the `workspace` field is set, we're dealing with a workspace source. |
| 764 | + if let Some(workspace) = workspace { |
| 765 | + if index.is_some() { |
| 766 | + return Err(serde::de::Error::custom("cannot specify both `index` and `index`")); |
| 767 | + } |
| 768 | + if git.is_some() { |
| 769 | + return Err(serde::de::Error::custom("cannot specify both `index` and `git`")); |
| 770 | + } |
| 771 | + if url.is_some() { |
| 772 | + return Err(serde::de::Error::custom("cannot specify both `index` and `url`")); |
| 773 | + } |
| 774 | + if path.is_some() { |
| 775 | + return Err(serde::de::Error::custom("cannot specify both `index` and `path`")); |
| 776 | + } |
| 777 | + if rev.is_some() { |
| 778 | + return Err(serde::de::Error::custom("cannot specify both `index` and `rev`")); |
| 779 | + } |
| 780 | + if tag.is_some() { |
| 781 | + return Err(serde::de::Error::custom("cannot specify both `index` and `tag`")); |
| 782 | + } |
| 783 | + if branch.is_some() { |
| 784 | + return Err(serde::de::Error::custom("cannot specify both `index` and `branch`")); |
| 785 | + } |
| 786 | + if editable.is_some() { |
| 787 | + return Err(serde::de::Error::custom("cannot specify both `index` and `editable`")); |
| 788 | + } |
| 789 | + |
| 790 | + return Ok(Source::Workspace { |
| 791 | + workspace, |
| 792 | + marker, |
| 793 | + }); |
| 794 | + } |
| 795 | + |
| 796 | + // If none of the fields are set, we're dealing with an error. |
| 797 | + Err(serde::de::Error::custom("expected one of `git`, `url`, `path`, `index`, or `workspace`")) |
| 798 | + |
| 799 | + } |
623 | 800 | } |
624 | 801 |
|
625 | 802 | #[derive(Error, Debug)] |
@@ -763,7 +940,6 @@ impl Source { |
763 | 940 | Source::Path { marker, .. } => marker.clone(), |
764 | 941 | Source::Registry { marker, .. } => marker.clone(), |
765 | 942 | Source::Workspace { marker, .. } => marker.clone(), |
766 | | - Source::CatchAll { marker, .. } => marker.clone(), |
767 | 943 | } |
768 | 944 | } |
769 | 945 | } |
|
0 commit comments