From 0f01d9bd4ddafbb33317a841bab368556d360951 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Fri, 18 Nov 2016 16:49:55 -0500 Subject: [PATCH 1/4] Upload categories specified in the manifest --- src/cargo/core/manifest.rs | 1 + src/cargo/ops/registry.rs | 2 ++ src/cargo/util/toml.rs | 2 ++ src/crates-io/lib.rs | 1 + src/doc/manifest.md | 10 ++++++++-- 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index 93fac21fdf9..e9bc79609e0 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -47,6 +47,7 @@ pub struct VirtualManifest { pub struct ManifestMetadata { pub authors: Vec, pub keywords: Vec, + pub categories: Vec, pub license: Option, pub license_file: Option, pub description: Option, // not markdown diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index d937976f867..af80760b63b 100644 --- a/src/cargo/ops/registry.rs +++ b/src/cargo/ops/registry.rs @@ -112,6 +112,7 @@ fn transmit(config: &Config, let ManifestMetadata { ref authors, ref description, ref homepage, ref documentation, ref keywords, ref readme, ref repository, ref license, ref license_file, + ref categories, } = *manifest.metadata(); let readme = match *readme { Some(ref readme) => Some(paths::read(&pkg.root().join(readme))?), @@ -142,6 +143,7 @@ fn transmit(config: &Config, homepage: homepage.clone(), documentation: documentation.clone(), keywords: keywords.clone(), + categories: categories.clone(), readme: readme, repository: repository.clone(), license: license.clone(), diff --git a/src/cargo/util/toml.rs b/src/cargo/util/toml.rs index 5e5dc93a669..bcac3d2da09 100644 --- a/src/cargo/util/toml.rs +++ b/src/cargo/util/toml.rs @@ -304,6 +304,7 @@ pub struct TomlProject { documentation: Option, readme: Option, keywords: Option>, + categories: Option>, license: Option, license_file: Option, repository: Option, @@ -640,6 +641,7 @@ impl TomlManifest { license_file: project.license_file.clone(), repository: project.repository.clone(), keywords: project.keywords.clone().unwrap_or(Vec::new()), + categories: project.categories.clone().unwrap_or(Vec::new()), }; let workspace_config = match (self.workspace.as_ref(), diff --git a/src/crates-io/lib.rs b/src/crates-io/lib.rs index 884460df290..4d8d889f3b4 100644 --- a/src/crates-io/lib.rs +++ b/src/crates-io/lib.rs @@ -78,6 +78,7 @@ pub struct NewCrate { pub homepage: Option, pub readme: Option, pub keywords: Vec, + pub categories: Vec, pub license: Option, pub license_file: Option, pub repository: Option, diff --git a/src/doc/manifest.md b/src/doc/manifest.md index 9aaab670738..326cd628cc4 100644 --- a/src/doc/manifest.md +++ b/src/doc/manifest.md @@ -118,10 +118,16 @@ repository = "..." # contents of this file are stored and indexed in the registry. readme = "..." -# This is a small list of keywords used to categorize and search for this -# package. +# This is a list of up to five keywords that describe this crate. Keywords +# are searchable on crates.io, and you may choose any words that would +# help someone find this crate. keywords = ["...", "..."] +# This is a list of up to five categories where this crate would fit. +# Categories are a fixed list available at crates.io/categories, and +# they must match exactly. +categories = ["...", "..."] + # This is a string description of the license for this package. Currently # crates.io will validate the license provided against a whitelist of known # license identifiers from http://spdx.org/licenses/. Multiple licenses can be From 154cc0aa54500789613fbeb0434fadcdee29f5a5 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Tue, 29 Nov 2016 13:19:07 -0500 Subject: [PATCH 2/4] Warn when crates.io sends back invalid category slugs --- src/cargo/ops/registry.rs | 21 +++++++++++++++++---- src/crates-io/lib.rs | 28 +++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index af80760b63b..2b444760959 100644 --- a/src/cargo/ops/registry.rs +++ b/src/cargo/ops/registry.rs @@ -133,7 +133,7 @@ fn transmit(config: &Config, return Ok(()); } - registry.publish(&NewCrate { + let publish = registry.publish(&NewCrate { name: pkg.name().to_string(), vers: pkg.version().to_string(), deps: deps, @@ -148,9 +148,22 @@ fn transmit(config: &Config, repository: repository.clone(), license: license.clone(), license_file: license_file.clone(), - }, tarball).map_err(|e| { - human(e.to_string()) - }) + }, tarball); + + match publish { + Ok(invalid_categories) => { + if !invalid_categories.is_empty() { + let msg = format!("\ + the following are not valid category slugs and were \ + ignored: {}. Please see https://crates.io/category_slugs \ + for the list of all category slugs. \ + ", invalid_categories.join(", ")); + config.shell().warn(&msg)?; + } + Ok(()) + }, + Err(e) => Err(human(e.to_string())), + } } pub fn registry_configuration(config: &Config) -> CargoResult { diff --git a/src/crates-io/lib.rs b/src/crates-io/lib.rs index 4d8d889f3b4..12c195edbc5 100644 --- a/src/crates-io/lib.rs +++ b/src/crates-io/lib.rs @@ -10,7 +10,7 @@ use std::io::{self, Cursor}; use std::result; use curl::easy::{Easy, List}; -use rustc_serialize::json; +use rustc_serialize::json::{self, Json}; use url::percent_encoding::{percent_encode, QUERY_ENCODE_SET}; @@ -39,6 +39,7 @@ pub enum Error { NotFound, JsonEncodeError(json::EncoderError), JsonDecodeError(json::DecoderError), + JsonParseError(json::ParserError), } impl From for Error { @@ -53,6 +54,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: json::ParserError) -> Error { + Error::JsonParseError(err) + } +} + impl From for Error { fn from(err: curl::Error) -> Error { Error::Curl(err) @@ -111,7 +118,6 @@ pub struct User { #[derive(RustcDecodable)] struct Users { users: Vec } #[derive(RustcDecodable)] struct TotalCrates { total: u32 } #[derive(RustcDecodable)] struct Crates { crates: Vec, meta: TotalCrates } - impl Registry { pub fn new(host: String, token: Option) -> Registry { Registry::new_handle(host, token, Easy::new()) @@ -148,7 +154,8 @@ impl Registry { Ok(json::decode::(&body)?.users) } - pub fn publish(&mut self, krate: &NewCrate, tarball: &File) -> Result<()> { + pub fn publish(&mut self, krate: &NewCrate, tarball: &File) + -> Result> { let json = json::encode(krate)?; // Prepare the body. The format of the upload request is: // @@ -191,10 +198,20 @@ impl Registry { headers.append(&format!("Authorization: {}", token))?; self.handle.http_headers(headers)?; - let _body = handle(&mut self.handle, &mut |buf| { + let body = handle(&mut self.handle, &mut |buf| { body.read(buf).unwrap_or(0) })?; - Ok(()) + // Can't derive RustcDecodable because JSON has a key named "crate" :( + let response = Json::from_str(&body)?; + let invalid_categories: Vec = + response + .find_path(&["warnings", "invalid_categories"]) + .and_then(Json::as_array) + .map(|x| { + x.iter().flat_map(Json::as_string).map(Into::into).collect() + }) + .unwrap_or_else(Vec::new); + Ok(invalid_categories) } pub fn search(&mut self, query: &str, limit: u8) -> Result<(Vec, u32)> { @@ -329,6 +346,7 @@ impl fmt::Display for Error { Error::NotFound => write!(f, "cannot find crate"), Error::JsonEncodeError(ref e) => write!(f, "json encode error: {}", e), Error::JsonDecodeError(ref e) => write!(f, "json decode error: {}", e), + Error::JsonParseError(ref e) => write!(f, "json parse error: {}", e), } } } From 7dd0f932a864d97bef5da26a0e148fca3f06d448 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Tue, 29 Nov 2016 16:09:00 -0500 Subject: [PATCH 3/4] Have a fallback for an empty response This happens in tests. --- src/crates-io/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/crates-io/lib.rs b/src/crates-io/lib.rs index 12c195edbc5..e35d8244033 100644 --- a/src/crates-io/lib.rs +++ b/src/crates-io/lib.rs @@ -202,7 +202,11 @@ impl Registry { body.read(buf).unwrap_or(0) })?; // Can't derive RustcDecodable because JSON has a key named "crate" :( - let response = Json::from_str(&body)?; + let response = if body.len() > 0 { + Json::from_str(&body)? + } else { + Json::from_str("{}")? + }; let invalid_categories: Vec = response .find_path(&["warnings", "invalid_categories"]) From f697b8c68d56fb2acb66accacb449b7bcb49d838 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Mon, 5 Dec 2016 12:36:44 -0500 Subject: [PATCH 4/4] Add more structure to the warnings returned from crates.io publish --- src/cargo/ops/registry.rs | 6 +++--- src/crates-io/lib.rs | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index 2b444760959..4c9bad817ac 100644 --- a/src/cargo/ops/registry.rs +++ b/src/cargo/ops/registry.rs @@ -151,13 +151,13 @@ fn transmit(config: &Config, }, tarball); match publish { - Ok(invalid_categories) => { - if !invalid_categories.is_empty() { + Ok(warnings) => { + if !warnings.invalid_categories.is_empty() { let msg = format!("\ the following are not valid category slugs and were \ ignored: {}. Please see https://crates.io/category_slugs \ for the list of all category slugs. \ - ", invalid_categories.join(", ")); + ", warnings.invalid_categories.join(", ")); config.shell().warn(&msg)?; } Ok(()) diff --git a/src/crates-io/lib.rs b/src/crates-io/lib.rs index e35d8244033..00637be3dc2 100644 --- a/src/crates-io/lib.rs +++ b/src/crates-io/lib.rs @@ -111,6 +111,10 @@ pub struct User { pub name: Option, } +pub struct Warnings { + pub invalid_categories: Vec, +} + #[derive(RustcDecodable)] struct R { ok: bool } #[derive(RustcDecodable)] struct ApiErrorList { errors: Vec } #[derive(RustcDecodable)] struct ApiError { detail: String } @@ -155,7 +159,7 @@ impl Registry { } pub fn publish(&mut self, krate: &NewCrate, tarball: &File) - -> Result> { + -> Result { let json = json::encode(krate)?; // Prepare the body. The format of the upload request is: // @@ -215,7 +219,7 @@ impl Registry { x.iter().flat_map(Json::as_string).map(Into::into).collect() }) .unwrap_or_else(Vec::new); - Ok(invalid_categories) + Ok(Warnings { invalid_categories: invalid_categories }) } pub fn search(&mut self, query: &str, limit: u8) -> Result<(Vec, u32)> {