crate_index/
metadata.rs

1use semver::{Version, VersionReq};
2use serde::{Deserialize, Serialize};
3use std::{collections::HashMap, fmt};
4use url::Url;
5
6/// Rust crate metadata, as stored in the crate index.
7///
8/// *[See the documentation for details](https://doc.rust-lang.org/cargo/reference/registries.html)*
9#[derive(Debug, Serialize, Deserialize)]
10pub struct Metadata {
11    name: String,
12
13    vers: Version,
14
15    #[serde(skip_serializing_if = "Vec::is_empty", default)]
16    deps: Vec<Dependency>,
17
18    cksum: String,
19
20    #[serde(skip_serializing_if = "HashMap::is_empty", default)]
21    features: HashMap<String, Vec<String>>,
22
23    yanked: bool,
24
25    #[serde(skip_serializing_if = "Option::is_none", default)]
26    links: Option<String>,
27}
28
29impl Metadata {
30    /// Create a new metadata object.
31    ///
32    /// The method parameters are all required, optional parameters can be set
33    /// using the builder API.
34    pub fn new(name: impl Into<String>, version: Version, check_sum: impl Into<String>) -> Self {
35        let name = name.into();
36        let vers = version;
37        let deps = Vec::new();
38        let cksum = check_sum.into();
39        let features = HashMap::new();
40        let yanked = false;
41        let links = None;
42
43        Self {
44            name,
45            vers,
46            deps,
47            cksum,
48            features,
49            yanked,
50            links,
51        }
52    }
53
54    /// The name of the crate
55    #[must_use]
56    pub fn name(&self) -> &String {
57        &self.name
58    }
59
60    /// The version of the crate
61    #[must_use]
62    pub fn version(&self) -> &Version {
63        &self.vers
64    }
65
66    /// A vector of crate [`Dependency`]
67    #[must_use]
68    pub fn dependencies(&self) -> &Vec<Dependency> {
69        &self.deps
70    }
71
72    /// A SHA256 checksum of the `.crate` file.
73    #[must_use]
74    pub fn check_sum(&self) -> &String {
75        &self.cksum
76    }
77
78    /// Set of features defined for the package.
79    ///
80    /// Each feature maps to an array of features or dependencies it enables.
81    #[must_use]
82    pub fn features(&self) -> &HashMap<String, Vec<String>> {
83        &self.features
84    }
85
86    /// Whether or not this version has been yanked
87    #[must_use]
88    pub fn yanked(&self) -> bool {
89        self.yanked
90    }
91
92    /// The `links` string value from the package's manifest
93    #[must_use]
94    pub fn links(&self) -> Option<&String> {
95        self.links.as_ref()
96    }
97
98    /// Set the 'yanked' status of the crate version to 'true'
99    pub fn yank(&mut self) {
100        self.yanked = true;
101    }
102
103    /// Set the 'yanked' status of the crate version to 'false'
104    pub fn unyank(&mut self) {
105        self.yanked = false;
106    }
107}
108
109impl fmt::Display for Metadata {
110    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
111        write!(f, "{}", &serde_json::to_string(self).unwrap())
112    }
113}
114
115#[derive(Debug, Serialize, Deserialize)]
116pub struct Dependency {
117    /// Name of the dependency.
118    /// If the dependency is renamed from the original package name,
119    /// this is the new name. The original package name is stored in
120    /// the `package` field.
121    name: String,
122
123    /// The semver requirement for this dependency.
124    req: VersionReq,
125
126    /// Array of features (as strings) enabled for this dependency.
127    #[serde(skip_serializing_if = "Vec::is_empty", default)]
128    features: Vec<String>,
129
130    /// Boolean of whether or not this is an optional dependency.
131    optional: bool,
132
133    /// Boolean of whether or not default features are enabled.
134    default_features: bool,
135
136    /// The target platform for the dependency.
137    /// null if not a target dependency.
138    /// Otherwise, a string such as "cfg(windows)".
139    #[serde(skip_serializing_if = "Option::is_none", default)]
140    target: Option<String>,
141
142    /// The dependency kind.
143    /// "dev", "build", or "normal".
144    kind: DependencyKind,
145
146    /// The URL of the index of the registry where this dependency is
147    /// from as a string. If not specified or null, it is assumed the
148    /// dependency is in the current registry.
149    #[serde(skip_serializing_if = "Option::is_none", default)]
150    registry: Option<Url>,
151
152    /// If the dependency is renamed, this is a string of the actual
153    /// package name. If not specified or null, this dependency is not
154    /// renamed.
155    #[serde(skip_serializing_if = "Option::is_none", default)]
156    package: Option<String>,
157}
158
159#[derive(Debug, Serialize, Deserialize)]
160#[serde(rename_all = "snake_case")]
161enum DependencyKind {
162    /// A dependency used only during testing
163    Dev,
164
165    /// A dependency only used during building
166    Build,
167
168    /// A normal dependency of the crate
169    Normal,
170}
171
172#[cfg(test)]
173mod tests {
174    use super::Metadata;
175    use semver::Version;
176
177    #[test]
178    fn serialize() {
179        let name = "foo";
180        let version = Version::parse("0.1.0").unwrap();
181        let check_sum = "d867001db0e2b6e0496f9fac96930e2d42233ecd3ca0413e0753d4c7695d289c";
182
183        let metadata = Metadata::new(name, version, check_sum);
184
185        let expected = r#"{"name":"foo","vers":"0.1.0","cksum":"d867001db0e2b6e0496f9fac96930e2d42233ecd3ca0413e0753d4c7695d289c","yanked":false}"#.to_string();
186        let actual = metadata.to_string();
187
188        assert_eq!(expected, actual)
189    }
190
191    #[test]
192    fn deserialize() {
193        let example1 = r#"
194        {
195            "name": "foo",
196            "vers": "0.1.0",
197            "deps": [
198                {
199                    "name": "rand",
200                    "req": "^0.6",
201                    "features": ["i128_support"],
202                    "optional": false,
203                    "default_features": true,
204                    "target": null,
205                    "kind": "normal",
206                    "registry": null,
207                    "package": null
208                }
209            ],
210            "cksum": "d867001db0e2b6e0496f9fac96930e2d42233ecd3ca0413e0753d4c7695d289c",
211            "features": {
212                "extras": ["rand/simd_support"]
213            },
214            "yanked": false,
215            "links": null
216        }
217        "#;
218
219        let _: Metadata = serde_json::from_str(example1).unwrap();
220
221        let example2 = r#"
222        {
223            "name": "my_serde",
224            "vers": "1.0.11",
225            "deps": [
226                {
227                    "name": "serde",
228                    "req": "^1.0",
229                    "registry": "https://github.com/rust-lang/crates.io-index",
230                    "features": [],
231                    "optional": true,
232                    "default_features": true,
233                    "target": null,
234                    "kind": "normal"
235                }
236            ],
237            "cksum": "f7726f29ddf9731b17ff113c461e362c381d9d69433f79de4f3dd572488823e9",
238            "features": {
239                "default": [
240                    "std"
241                ],
242                "derive": [
243                    "serde_derive"
244                ],
245                "std": [
246        
247                ]
248            },
249            "yanked": false
250        }
251        "#;
252
253        let _: Metadata = serde_json::from_str(example2).unwrap();
254    }
255
256    #[test]
257    fn yank() {
258        let name = "foo";
259        let version = Version::parse("0.1.0").unwrap();
260        let check_sum = "CHECK_SUM";
261
262        let mut metadata = Metadata::new(name, version, check_sum);
263
264        assert!(!metadata.yanked());
265
266        metadata.yank();
267        assert!(metadata.yanked());
268
269        metadata.unyank();
270        assert!(!metadata.yanked());
271    }
272}