Skip to main content

nextest_runner/build_scope/
package_spec.rs

1// Copyright (c) The nextest Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::errors::PackageSpecError;
5use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
6use std::{
7    fmt,
8    hash::{Hash, Hasher},
9    str::FromStr,
10};
11
12/// A package specifier provided after `-p` or `--exclude` on the command line.
13#[derive(Clone, Debug)]
14pub struct PackageSpec {
15    raw: String,
16}
17
18// Equality and hashing use `raw` only.
19impl PartialEq for PackageSpec {
20    fn eq(&self, other: &Self) -> bool {
21        self.raw == other.raw
22    }
23}
24
25impl Eq for PackageSpec {}
26
27impl Hash for PackageSpec {
28    fn hash<H: Hasher>(&self, state: &mut H) {
29        self.raw.hash(state);
30    }
31}
32
33impl PackageSpec {
34    /// Creates a new `PackageSpec` from the given string.
35    pub fn new(spec: &str) -> Result<Self, PackageSpecError> {
36        Ok(Self {
37            raw: spec.to_owned(),
38        })
39    }
40
41    /// Returns the string representation of this package spec.
42    pub fn as_str(&self) -> &str {
43        &self.raw
44    }
45}
46
47impl fmt::Display for PackageSpec {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        f.write_str(&self.raw)
50    }
51}
52
53impl FromStr for PackageSpec {
54    type Err = PackageSpecError;
55
56    fn from_str(spec: &str) -> Result<Self, Self::Err> {
57        Self::new(spec)
58    }
59}
60
61impl Serialize for PackageSpec {
62    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
63    where
64        S: Serializer,
65    {
66        serializer.serialize_str(&self.raw)
67    }
68}
69
70impl<'de> Deserialize<'de> for PackageSpec {
71    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
72    where
73        D: Deserializer<'de>,
74    {
75        deserializer.deserialize_str(PackageSpecVisitor)
76    }
77}
78
79struct PackageSpecVisitor;
80
81impl de::Visitor<'_> for PackageSpecVisitor {
82    type Value = PackageSpec;
83
84    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        f.write_str("a Cargo package spec (a package name, name@version, URL, or glob pattern)")
86    }
87
88    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
89    where
90        E: de::Error,
91    {
92        PackageSpec::new(value).map_err(E::custom)
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn serde_round_trip() {
102        let inputs = [
103            "foo",
104            "foo@1.2",
105            "https://github.com/rust-lang/cargo#0.52.0",
106            "nextest-*",
107        ];
108
109        for input in inputs {
110            let spec = PackageSpec::new(input).expect("spec is valid");
111            assert_eq!(spec.as_str(), input, "as_str matches {input}");
112            assert_eq!(spec.to_string(), input, "Display matches {input}");
113            assert_eq!(
114                input.parse::<PackageSpec>().expect("spec parses"),
115                spec,
116                "FromStr matches new for {input}"
117            );
118            let json = serde_json::to_string(&spec).expect("serialized spec");
119            assert_eq!(
120                json,
121                serde_json::to_string(input).expect("serialized input string"),
122                "spec serializes as a plain string for {input}"
123            );
124            let deserialized: PackageSpec = serde_json::from_str(&json).expect("deserialized spec");
125            assert_eq!(deserialized, spec, "round-trip matches for {input}");
126        }
127    }
128
129    #[test]
130    fn deserialize_non_string() {
131        let error =
132            serde_json::from_str::<PackageSpec>("42").expect_err("non-string fails to deserialize");
133        assert_eq!(
134            error.to_string(),
135            "invalid type: integer `42`, expected a Cargo package spec \
136             (a package name, name@version, URL, or glob pattern) at line 1 column 2",
137        );
138    }
139}