Repository: oli-obk/cargo_metadata
Pull request #61 (2019-01-05 21:24:06)

Add some more fields.
This is to help assist with stabilizing alternative registries.

- `Dependency::rename` — Stabilized in 1.31.
- `Dependency::registry` — Recently added in https://github.com/rust-lang/cargo/pull/6500
- `Dependency::target` — Add typed as suggested in https://github.com/oli-obk/cargo_metadata/pull/41#issuecomment-403742363.  Parsing this is rather complex (see https://github.com/rust-lang/cargo/blob/dcb4360c50574483dc96e8862afcb1e581c7b95c/src/cargo/util/cfg.rs).
- `Package::links` — Recently added in https://github.com/rust-lang/cargo/pull/6480

The `registry` field could use a URL type (with `url` and `url_serde` crates) if you want it to be more strictly typed (ala #59).


oli-obk: Please make all of the new fields `#[serde(default)]` so it doesn't break using a newer `cargo_metadata` version for older compilers

Patch:
diff --git a/src/dependency.rs b/src/dependency.rs
index 9bdb251..71ca10d 100644
--- a/src/dependency.rs
+++ b/src/dependency.rs
@@ -2,6 +2,7 @@
 
 use serde::{Deserialize, Deserializer};
 use semver::VersionReq;
+use std::fmt;
 
 #[derive(PartialEq, Clone, Debug, Copy, Serialize, Deserialize)]
 /// Dependencies can come in three kinds
@@ -51,8 +52,30 @@ pub struct Dependency {
     /// The list of features enabled for this dependency.
     pub features: Vec<String>,
     /// The target this dependency is specific to.
-    target: Option<String>,
+    ///
+    /// Use the [`Display`] trait to access the contents.
+    ///
+    /// [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html
+    pub target: Option<Platform>,
+    /// If the dependency is renamed, this is the new name for the dependency
+    /// as a string.  None if it is not renamed.
+    pub rename: Option<String>,
+    /// The URL of the index of the registry where this dependency is from.
+    ///
+    /// If None, the dependency is from crates.io.
+    pub registry: Option<String>,
     #[doc(hidden)]
     #[serde(skip)]
     __do_not_match_exhaustively: (),
 }
+
+/// A target platform.
+#[derive(Clone, Serialize, Deserialize, Debug)]
+#[serde(transparent)]
+pub struct Platform(String);
+
+impl fmt::Display for Platform {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str(&self.0)
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 7d865c9..c6e31f4 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -231,7 +231,8 @@ pub struct Node {
 #[derive(Clone, Serialize, Deserialize, Debug)]
 /// A dependency in a node
 pub struct NodeDep {
-    /// Crate name. If the crate was renamed, it's the new name.
+    /// The name of the dependency's library target.
+    /// If the crate was renamed, it is the new name.
     pub name: String,
     /// Package ID (opaque unique identifier)
     pub pkg: PackageId,
@@ -311,6 +312,8 @@ pub struct Package {
     /// ```
     #[serde(default)]
     pub metadata: serde_json::Value,
+    /// The name of a native library the package is linking to.
+    pub links: Option<String>,
     #[doc(hidden)]
     #[serde(skip)]
     __do_not_match_exhaustively: (),


Test patch:
diff --git a/tests/all/Cargo.lock b/tests/all/Cargo.lock
new file mode 100644
index 0000000..7d75f5b
--- /dev/null
+++ b/tests/all/Cargo.lock
@@ -0,0 +1,49 @@
+[[package]]
+name = "all"
+version = "0.1.0"
+dependencies = [
+ "bdep 0.1.0",
+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "devdep 0.1.0",
+ "featdep 0.1.0",
+ "namedep 0.1.0",
+ "oldname 0.1.0",
+ "path-dep 0.1.0",
+ "windep 0.1.0",
+]
+
+[[package]]
+name = "bdep"
+version = "0.1.0"
+
+[[package]]
+name = "bitflags"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "devdep"
+version = "0.1.0"
+
+[[package]]
+name = "featdep"
+version = "0.1.0"
+
+[[package]]
+name = "namedep"
+version = "0.1.0"
+
+[[package]]
+name = "oldname"
+version = "0.1.0"
+
+[[package]]
+name = "path-dep"
+version = "0.1.0"
+
+[[package]]
+name = "windep"
+version = "0.1.0"
+
+[metadata]
+"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
diff --git a/tests/all/Cargo.toml b/tests/all/Cargo.toml
new file mode 100644
index 0000000..2430120
--- /dev/null
+++ b/tests/all/Cargo.toml
@@ -0,0 +1,50 @@
+[package]
+name = "all"
+version = "0.1.0"
+authors = ["Jane Doe <user@example.com>"]
+edition = "2018"
+license = "MIT/Apache-2.0"
+license-file = "LICENSE"
+description = "Package description."
+categories = ["command-line-utilities"]
+keywords = ["cli"]
+readme = "README.md"
+repository = "https://github.com/oli-obk/cargo_metadata/"
+links = "foo"
+
+[package.metadata.docs.rs]
+all-features = true
+default-target = "x86_64-unknown-linux-gnu"
+rustc-args = [ "--example-rustc-arg" ]
+
+[dependencies]
+path-dep = { path = "path-dep" }
+namedep = { path = "namedep" }
+bitflags = { version = "1.0", optional = true }
+featdep = { path = "featdep", features = ["i128"], default-features = false }
+newname = { path = "oldname", package = "oldname" }
+
+[dev-dependencies]
+devdep = { path = "devdep" }
+
+[build-dependencies]
+bdep = { path = "bdep" }
+
+[target.'cfg(windows)'.dependencies]
+windep = { path = "windep" }
+
+[features]
+default = ["feat1", "bitflags"]
+feat1 = []
+feat2 = []
+
+[lib]
+crate-type = ["rlib", "cdylib", "dylib", "staticlib"]
+
+[[bin]]
+name = "otherbin"
+edition = '2015'
+
+[[bin]]
+name = "reqfeat"
+required-features = ["feat2"]
diff --git a/tests/all/bdep/Cargo.toml b/tests/all/bdep/Cargo.toml
new file mode 100644
index 0000000..245eb22
--- /dev/null
+++ b/tests/all/bdep/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bdep"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
diff --git a/tests/all/bdep/src/lib.rs b/tests/all/bdep/src/lib.rs
new file mode 100644
index 0000000..e69de29
diff --git a/tests/all/benches/b1.rs b/tests/all/benches/b1.rs
new file mode 100644
index 0000000..e69de29
diff --git a/tests/all/build.rs b/tests/all/build.rs
new file mode 100644
index 0000000..f328e4d
--- /dev/null
+++ b/tests/all/build.rs
@@ -0,0 +1,1 @@
+fn main() {}
diff --git a/tests/all/devdep/Cargo.toml b/tests/all/devdep/Cargo.toml
new file mode 100644
index 0000000..f729c92
--- /dev/null
+++ b/tests/all/devdep/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "devdep"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
diff --git a/tests/all/devdep/src/lib.rs b/tests/all/devdep/src/lib.rs
new file mode 100644
index 0000000..e69de29
diff --git a/tests/all/examples/ex1.rs b/tests/all/examples/ex1.rs
new file mode 100644
index 0000000..e69de29
diff --git a/tests/all/featdep/Cargo.toml b/tests/all/featdep/Cargo.toml
new file mode 100644
index 0000000..6f5d460
--- /dev/null
+++ b/tests/all/featdep/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "featdep"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
+
+[features]
+i128 = []
diff --git a/tests/all/featdep/src/lib.rs b/tests/all/featdep/src/lib.rs
new file mode 100644
index 0000000..e69de29
diff --git a/tests/all/namedep/Cargo.toml b/tests/all/namedep/Cargo.toml
new file mode 100644
index 0000000..b77ffcb
--- /dev/null
+++ b/tests/all/namedep/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "namedep"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
+
+[lib]
+name = "different_name"
diff --git a/tests/all/namedep/src/lib.rs b/tests/all/namedep/src/lib.rs
new file mode 100644
index 0000000..e69de29
diff --git a/tests/all/oldname/Cargo.toml b/tests/all/oldname/Cargo.toml
new file mode 100644
index 0000000..6d639ef
--- /dev/null
+++ b/tests/all/oldname/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "oldname"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
diff --git a/tests/all/oldname/src/lib.rs b/tests/all/oldname/src/lib.rs
new file mode 100644
index 0000000..e69de29
diff --git a/tests/all/path-dep/Cargo.toml b/tests/all/path-dep/Cargo.toml
new file mode 100644
index 0000000..446a68b
--- /dev/null
+++ b/tests/all/path-dep/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "path-dep"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
diff --git a/tests/all/path-dep/src/lib.rs b/tests/all/path-dep/src/lib.rs
new file mode 100644
index 0000000..e69de29
diff --git a/tests/all/src/bin/otherbin.rs b/tests/all/src/bin/otherbin.rs
new file mode 100644
index 0000000..f328e4d
--- /dev/null
+++ b/tests/all/src/bin/otherbin.rs
@@ -0,0 +1,1 @@
+fn main() {}
diff --git a/tests/all/src/bin/reqfeat.rs b/tests/all/src/bin/reqfeat.rs
new file mode 100644
index 0000000..f328e4d
--- /dev/null
+++ b/tests/all/src/bin/reqfeat.rs
@@ -0,0 +1,1 @@
+fn main() {}
diff --git a/tests/all/src/lib.rs b/tests/all/src/lib.rs
new file mode 100644
index 0000000..e69de29
diff --git a/tests/all/src/main.rs b/tests/all/src/main.rs
new file mode 100644
index 0000000..e7a11a9
--- /dev/null
+++ b/tests/all/src/main.rs
@@ -0,0 +1,3 @@
+fn main() {
+    println!("Hello, world!");
+}
diff --git a/tests/all/tests/t1.rs b/tests/all/tests/t1.rs
new file mode 100644
index 0000000..e69de29
diff --git a/tests/all/windep/Cargo.toml b/tests/all/windep/Cargo.toml
new file mode 100644
index 0000000..d45ef25
--- /dev/null
+++ b/tests/all/windep/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "windep"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
diff --git a/tests/all/windep/src/lib.rs b/tests/all/windep/src/lib.rs
new file mode 100644
index 0000000..e69de29
diff --git a/tests/selftest.rs b/tests/selftest.rs
index 2fe9c42..ede3238 100644
--- a/tests/selftest.rs
+++ b/tests/selftest.rs
@@ -27,16 +27,18 @@ fn metadata() {
         Path::new(&metadata.target_directory)
     );
 
-    assert_eq!(metadata.packages[0].name, "cargo_metadata");
-    assert_eq!(metadata.packages[0].targets.len(), 2);
+    let this = &metadata.packages[0];
+    assert_eq!(this.name, "cargo_metadata");
+    assert_eq!(this.targets.len(), 3);
 
-    assert_eq!(metadata.packages[0].targets[0].name, "cargo_metadata");
-    assert_eq!(metadata.packages[0].targets[0].kind[0], "lib");
-    assert_eq!(metadata.packages[0].targets[0].crate_types[0], "lib");
+    let lib = this.targets.iter().find(|t| t.name == "cargo_metadata").unwrap();
+    assert_eq!(lib.kind[0], "lib");
+    assert_eq!(lib.crate_types[0], "lib");
 
-    assert_eq!(metadata.packages[0].targets[1].name, "selftest");
-    assert_eq!(metadata.packages[0].targets[1].kind[0], "test");
-    assert_eq!(metadata.packages[0].targets[1].crate_types[0], "bin");
+    let selftest = this.targets.iter().find(|t| t.name == "selftest").unwrap();
+    assert_eq!(selftest.name, "selftest");
+    assert_eq!(selftest.kind[0], "test");
+    assert_eq!(selftest.crate_types[0], "bin");
 
     let package_metadata = &metadata.packages[0].metadata.as_object()
         .expect("package.metadata must be a table.");
@@ -113,15 +115,15 @@ fn metadata_deps() {
     let this = &metadata[this_id];
 
     assert_eq!(this.name, "cargo_metadata");
-    assert_eq!(this.targets.len(), 2);
 
-    assert_eq!(this.targets[0].name, "cargo_metadata");
-    assert_eq!(this.targets[0].kind[0], "lib");
-    assert_eq!(this.targets[0].crate_types[0], "lib");
+    let lib = this.targets.iter().find(|t| t.name == "cargo_metadata").unwrap();
+    assert_eq!(lib.kind[0], "lib");
+    assert_eq!(lib.crate_types[0], "lib");
 
-    assert_eq!(this.targets[1].name, "selftest");
-    assert_eq!(this.targets[1].kind[0], "test");
-    assert_eq!(this.targets[1].crate_types[0], "bin");
+    let selftest = this.targets.iter().find(|t| t.name == "selftest").unwrap();
+    assert_eq!(selftest.name, "selftest");
+    assert_eq!(selftest.kind[0], "test");
+    assert_eq!(selftest.crate_types[0], "bin");
 
     let dependencies = &this.dependencies;
 
diff --git a/tests/test_samples.rs b/tests/test_samples.rs
new file mode 100644
index 0000000..eb68361
--- /dev/null
+++ b/tests/test_samples.rs
@@ -0,0 +1,476 @@
+extern crate cargo_metadata;
+extern crate semver;
+#[macro_use]
+extern crate serde_json;
+
+use cargo_metadata::{Metadata, MetadataCommand};
+use std::path::PathBuf;
+
+#[test]
+fn old_minimal() {
+    // Output from oldest supported version (1.24).
+    // This intentionally has as many null fields as possible.
+    // 1.8 is when metadata was introduced.
+    // Older versions not supported because the following are required:
+    // - `workspace_members` added in 1.13
+    // - `target_directory` added in 1.19
+    // - `workspace_root` added in 1.24
+    let json = r#"
+{
+  "packages": [
+    {
+      "name": "foo",
+      "version": "0.1.0",
+      "id": "foo 0.1.0 (path+file:///foo)",
+      "license": null,
+      "license_file": null,
+      "description": null,
+      "source": null,
+      "dependencies": [
+        {
+          "name": "somedep",
+          "source": null,
+          "req": "^1.0",
+          "kind": null,
+          "optional": false,
+          "uses_default_features": true,
+          "features": [],
+          "target": null
+        }
+      ],
+      "targets": [
+        {
+          "kind": [
+            "bin"
+          ],
+          "crate_types": [
+            "bin"
+          ],
+          "name": "foo",
+          "src_path": "/foo/src/main.rs"
+        }
+      ],
+      "features": {},
+      "manifest_path": "/foo/Cargo.toml"
+    }
+  ],
+  "workspace_members": [
+    "foo 0.1.0 (path+file:///foo)"
+  ],
+  "resolve": null,
+  "target_directory": "/foo/target",
+  "version": 1,
+  "workspace_root": "/foo"
+}
+"#;
+    let meta: Metadata = serde_json::from_str(json).unwrap();
+    assert_eq!(meta.packages.len(), 1);
+    let pkg = &meta.packages[0];
+    assert_eq!(pkg.name, "foo");
+    assert_eq!(pkg.version, semver::Version::parse("0.1.0").unwrap());
+    assert_eq!(pkg.authors.len(), 0);
+    assert_eq!(pkg.id.to_string(), "foo 0.1.0 (path+file:///foo)");
+    assert_eq!(pkg.description, None);
+    assert_eq!(pkg.license, None);
+    assert_eq!(pkg.license_file, None);
+    assert_eq!(pkg.dependencies.len(), 1);
+    let dep = &pkg.dependencies[0];
+    assert_eq!(dep.name, "somedep");
+    assert_eq!(dep.source, None);
+    assert_eq!(dep.req, semver::VersionReq::parse("^1.0").unwrap());
+    assert_eq!(dep.kind, cargo_metadata::DependencyKind::Normal);
+    assert_eq!(dep.optional, false);
+    assert_eq!(dep.uses_default_features, true);
+    assert_eq!(dep.features.len(), 0);
+    assert!(dep.target.is_none());
+    assert_eq!(dep.rename, None);
+    assert_eq!(dep.registry, None);
+    assert_eq!(pkg.targets.len(), 1);
+    let target = &pkg.targets[0];
+    assert_eq!(target.name, "foo");
+    assert_eq!(target.kind, vec!["bin"]);
+    assert_eq!(target.crate_types, vec!["bin"]);
+    assert_eq!(target.required_features.len(), 0);
+    assert_eq!(target.src_path, PathBuf::from("/foo/src/main.rs"));
+    assert_eq!(target.edition, "2015");
+    assert_eq!(pkg.features.len(), 0);
+    assert_eq!(pkg.manifest_path, PathBuf::from("/foo/Cargo.toml"));
+    assert_eq!(pkg.categories.len(), 0);
+    assert_eq!(pkg.keywords.len(), 0);
+    assert_eq!(pkg.readme, None);
+    assert_eq!(pkg.repository, None);
+    assert_eq!(pkg.edition, "2015");
+    assert_eq!(pkg.metadata, serde_json::Value::Null);
+    assert_eq!(pkg.links, None);
+    assert_eq!(meta.workspace_members.len(), 1);
+    assert_eq!(
+        meta.workspace_members[0].to_string(),
+        "foo 0.1.0 (path+file:///foo)"
+    );
+    assert!(meta.resolve.is_none());
+    assert_eq!(meta.workspace_root, PathBuf::from("/foo"));
+    assert_eq!(meta.target_directory, PathBuf::from("/foo/target"));
+}
+
+macro_rules! sorted {
+    ($e:expr) => {{
+        let mut v = $e.clone();
+        v.sort();
+        v
+    }};
+}
+
+fn cargo_version() -> semver::Version {
+    let output = std::process::Command::new("cargo")
+        .arg("-V")
+        .output()
+        .expect("Failed to exec cargo.");
+    let out = std::str::from_utf8(&output.stdout).expect("invalid utf8");
+    let split: Vec<&str> = out.split_whitespace().collect();
+    assert!(split.len() > 2, "cargo -V output is unexpected: {}", out);
+    semver::Version::parse(split[1]).expect("cargo -V semver could not be parsed")
+}
+
+#[test]
+fn all_the_fields() {
+    // All the fields currently generated as of 1.31. This tries to exercise as
+    // much as possible.
+    let ver = cargo_version();
+    let minimum = semver::Version::parse("1.31.0").unwrap();
+    if ver < minimum {
+        // edition added in 1.30
+        // rename added in 1.31
+        eprintln!("Skipping all_the_fields test, cargo {} is too old.", ver);
+        return;
+    }
+    let meta = MetadataCommand::new()
+        .manifest_path("tests/all/Cargo.toml")
+        .exec()
+        .unwrap();
+    assert_eq!(
+        meta.workspace_root.file_name().unwrap(),
+        PathBuf::from("all")
+    );
+    assert_eq!(
+        meta.target_directory.file_name().unwrap(),
+        PathBuf::from("target")
+    );
+    assert_eq!(meta.workspace_members.len(), 1);
+    assert!(meta.workspace_members[0].to_string().starts_with("all"));
+
+    assert_eq!(meta.packages.len(), 9);
+    let all = meta.packages.iter().find(|p| p.name == "all").unwrap();
+    assert_eq!(all.version, semver::Version::parse("0.1.0").unwrap());
+    assert_eq!(all.authors, vec!["Jane Doe <user@example.com>"]);
+    assert!(all.id.to_string().starts_with("all"));
+    assert_eq!(all.description, Some("Package description.".to_string()));
+    assert_eq!(all.license, Some("MIT/Apache-2.0".to_string()));
+    assert_eq!(all.license_file, Some(PathBuf::from("LICENSE")));
+
+    assert_eq!(all.dependencies.len(), 8);
+    let bitflags = all
+        .dependencies
+        .iter()
+        .find(|d| d.name == "bitflags")
+        .unwrap();
+    assert_eq!(
+        bitflags.source,
+        Some("registry+https://github.com/rust-lang/crates.io-index".to_string())
+    );
+    assert_eq!(bitflags.optional, true);
+    assert_eq!(bitflags.req, semver::VersionReq::parse("^1.0").unwrap());
+
+    let path_dep = all
+        .dependencies
+        .iter()
+        .find(|d| d.name == "path-dep")
+        .unwrap();
+    assert_eq!(path_dep.source, None);
+    assert_eq!(path_dep.kind, cargo_metadata::DependencyKind::Normal);
+    assert_eq!(path_dep.req, semver::VersionReq::parse("*").unwrap());
+
+    all.dependencies
+        .iter()
+        .find(|d| d.name == "namedep")
+        .unwrap();
+
+    let featdep = all
+        .dependencies
+        .iter()
+        .find(|d| d.name == "featdep")
+        .unwrap();
+    assert_eq!(featdep.features, vec!["i128"]);
+    assert_eq!(featdep.uses_default_features, false);
+
+    let renamed = all
+        .dependencies
+        .iter()
+        .find(|d| d.name == "oldname")
+        .unwrap();
+    assert_eq!(renamed.rename, Some("newname".to_string()));
+
+    let devdep = all
+        .dependencies
+        .iter()
+        .find(|d| d.name == "devdep")
+        .unwrap();
+    assert_eq!(devdep.kind, cargo_metadata::DependencyKind::Development);
+
+    let bdep = all.dependencies.iter().find(|d| d.name == "bdep").unwrap();
+    assert_eq!(bdep.kind, cargo_metadata::DependencyKind::Build);
+
+    let windep = all
+        .dependencies
+        .iter()
+        .find(|d| d.name == "windep")
+        .unwrap();
+    assert_eq!(
+        windep.target.as_ref().map(|x| x.to_string()),
+        Some("cfg(windows)".to_string())
+    );
+
+    macro_rules! get_file_name {
+        ($v:expr) => {
+            all.targets
+                .iter()
+                .find(|t| t.src_path.file_name().unwrap() == $v)
+                .unwrap()
+        };
+    }
+    assert_eq!(all.targets.len(), 8);
+    let lib = get_file_name!("lib.rs");
+    assert_eq!(lib.name, "all");
+    assert_eq!(
+        sorted!(lib.kind),
+        vec!["cdylib", "dylib", "rlib", "staticlib"]
+    );
+    assert_eq!(
+        sorted!(lib.crate_types),
+        vec!["cdylib", "dylib", "rlib", "staticlib"]
+    );
+    assert_eq!(lib.required_features.len(), 0);
+    assert_eq!(lib.edition, "2018");
+
+    let main = get_file_name!("main.rs");
+    assert_eq!(main.crate_types, vec!["bin"]);
+    assert_eq!(main.kind, vec!["bin"]);
+
+    let otherbin = get_file_name!("otherbin.rs");
+    assert_eq!(otherbin.edition, "2015");
+
+    let reqfeat = get_file_name!("reqfeat.rs");
+    assert_eq!(reqfeat.required_features, vec!["feat2"]);
+
+    let ex1 = get_file_name!("ex1.rs");
+    assert_eq!(ex1.kind, vec!["example"]);
+
+    let t1 = get_file_name!("t1.rs");
+    assert_eq!(t1.kind, vec!["test"]);
+
+    let b1 = get_file_name!("b1.rs");
+    assert_eq!(b1.kind, vec!["bench"]);
+
+    let build = get_file_name!("build.rs");
+    assert_eq!(build.kind, vec!["custom-build"]);
+
+    assert_eq!(all.features.len(), 3);
+    assert_eq!(all.features["feat1"].len(), 0);
+    assert_eq!(all.features["feat2"].len(), 0);
+    assert_eq!(sorted!(all.features["default"]), vec!["bitflags", "feat1"]);
+
+    assert!(all.manifest_path.ends_with("all/Cargo.toml"));
+    assert_eq!(all.categories, vec!["command-line-utilities"]);
+    assert_eq!(all.keywords, vec!["cli"]);
+    assert_eq!(all.readme, Some("README.md".to_string()));
+    assert_eq!(
+        all.repository,
+        Some("https://github.com/oli-obk/cargo_metadata/".to_string())
+    );
+    assert_eq!(all.edition, "2018");
+    assert_eq!(
+        all.metadata,
+        json!({
+            "docs": {
+                "rs": {
+                    "all-features": true,
+                    "default-target": "x86_64-unknown-linux-gnu",
+                    "rustc-args": ["--example-rustc-arg"]
+                }
+            }
+        })
+    );
+
+    let resolve = meta.resolve.as_ref().unwrap();
+    assert!(resolve
+        .root
+        .as_ref()
+        .unwrap()
+        .to_string()
+        .starts_with("all"));
+
+    assert_eq!(resolve.nodes.len(), 9);
+    let path_dep = resolve
+        .nodes
+        .iter()
+        .find(|n| n.id.to_string().starts_with("path-dep"))
+        .unwrap();
+    assert_eq!(path_dep.deps.len(), 0);
+    assert_eq!(path_dep.dependencies.len(), 0);
+    assert_eq!(path_dep.features.len(), 0);
+
+    let bitflags = resolve
+        .nodes
+        .iter()
+        .find(|n| n.id.to_string().starts_with("bitflags"))
+        .unwrap();
+    assert_eq!(bitflags.features, vec!["default"]);
+
+    let featdep = resolve
+        .nodes
+        .iter()
+        .find(|n| n.id.to_string().starts_with("featdep"))
+        .unwrap();
+    assert_eq!(featdep.features, vec!["i128"]);
+
+    let all = resolve
+        .nodes
+        .iter()
+        .find(|n| n.id.to_string().starts_with("all"))
+        .unwrap();
+    assert_eq!(all.dependencies.len(), 8);
+    assert_eq!(all.deps.len(), 8);
+    let newname = all.deps.iter().find(|d| d.name == "newname").unwrap();
+    assert!(newname.pkg.to_string().starts_with("oldname"));
+    // Note the underscore here.
+    let path_dep = all.deps.iter().find(|d| d.name == "path_dep").unwrap();
+    assert!(path_dep.pkg.to_string().starts_with("path-dep"));
+    let namedep = all
+        .deps
+        .iter()
+        .find(|d| d.name == "different_name")
+        .unwrap();
+    assert!(namedep.pkg.to_string().starts_with("namedep"));
+    assert_eq!(sorted!(all.features), vec!["bitflags", "default", "feat1"]);
+}
+
+#[test]
+fn alt_registry() {
+    // This is difficult to test (would need to set up a custom index).
+    // Just manually check the JSON is handled.
+    let json = r#"
+{
+  "packages": [
+    {
+      "name": "alt",
+      "version": "0.1.0",
+      "id": "alt 0.1.0 (path+file:///alt)",
+      "source": null,
+      "dependencies": [
+        {
+          "name": "alt2",
+          "source": "registry+https://example.com",
+          "req": "^0.1",
+          "kind": null,
+          "rename": null,
+          "optional": false,
+          "uses_default_features": true,
+          "features": [],
+          "target": null,
+          "registry": "https://example.com"
+        }
+      ],
+      "targets": [
+        {
+          "kind": [
+            "lib"
+          ],
+          "crate_types": [
+            "lib"
+          ],
+          "name": "alt",
+          "src_path": "/alt/src/lib.rs",
+          "edition": "2018"
+        }
+      ],
+      "features": {},
+      "manifest_path": "/alt/Cargo.toml",
+      "metadata": null,
+      "authors": [],
+      "categories": [],
+      "keywords": [],
+      "readme": null,
+      "repository": null,
+      "edition": "2018",
+      "links": null
+    }
+  ],
+  "workspace_members": [
+    "alt 0.1.0 (path+file:///alt)"
+  ],
+  "resolve": null,
+  "target_directory": "/alt/target",
+  "version": 1,
+  "workspace_root": "/alt"
+}
+"#;
+    let meta: Metadata = serde_json::from_str(json).unwrap();
+    assert_eq!(meta.packages.len(), 1);
+    let alt = &meta.packages[0];
+    let deps = &alt.dependencies;
+    assert_eq!(deps.len(), 1);
+    let dep = &deps[0];
+    assert_eq!(dep.registry, Some("https://example.com".to_string()));
+}
+
+#[test]
+fn links() {
+    // This should be moved to all_the_fields once it is available on stable (1.33).
+    let json = r#"
+{
+  "packages": [
+    {
+      "name": "foo",
+      "version": "0.1.0",
+      "id": "foo 0.1.0 (path+file:///foo)",
+      "license": null,
+      "license_file": null,
+      "description": null,
+      "source": null,
+      "dependencies": [],
+      "targets": [
+        {
+          "kind": [
+            "lib"
+          ],
+          "crate_types": [
+            "lib"
+          ],
+          "name": "foo",
+          "src_path": "/foo/src/lib.rs",
+          "edition": "2018"
+        }
+      ],
+      "features": {},
+      "manifest_path": "/foo/Cargo.toml",
+      "metadata": null,
+      "authors": [],
+      "categories": [],
+      "keywords": [],
+      "readme": null,
+      "repository": null,
+      "edition": "2018",
+      "links": "somelib"
+    }
+  ],
+  "workspace_members": [
+    "foo 0.1.0 (path+file:///foo)"
+  ],
+  "resolve": null,
+  "target_directory": "/foo/target",
+  "version": 1,
+  "workspace_root": "/foo"
+}
+"#;
+    let meta: Metadata = serde_json::from_str(json).unwrap();
+    assert_eq!(meta.packages[0].links, Some("somelib".to_string()));
+}