diff --git a/migrations/20170611165120_add_license_to_versions/down.sql b/migrations/20170611165120_add_license_to_versions/down.sql new file mode 100644 index 00000000000..ad8340aee04 --- /dev/null +++ b/migrations/20170611165120_add_license_to_versions/down.sql @@ -0,0 +1 @@ +ALTER TABLE versions DROP COLUMN license; diff --git a/migrations/20170611165120_add_license_to_versions/up.sql b/migrations/20170611165120_add_license_to_versions/up.sql new file mode 100644 index 00000000000..5e8e580a54d --- /dev/null +++ b/migrations/20170611165120_add_license_to_versions/up.sql @@ -0,0 +1 @@ +ALTER TABLE versions ADD COLUMN license VARCHAR; diff --git a/src/bin/populate.rs b/src/bin/populate.rs index 8dfa0514f6f..f87fed92e47 100644 --- a/src/bin/populate.rs +++ b/src/bin/populate.rs @@ -40,8 +40,8 @@ fn update(tx: &postgres::transaction::Transaction) -> postgres::Result<()> { dls += rng.gen_range(-100, 100); tx.execute( "INSERT INTO version_downloads \ - (version_id, downloads, date) \ - VALUES ($1, $2, $3)", + (version_id, downloads, date) \ + VALUES ($1, $2, $3)", &[&id, &dls, &moment], )?; } diff --git a/src/bin/update-licenses.rs b/src/bin/update-licenses.rs new file mode 100644 index 00000000000..0bcd6de7865 --- /dev/null +++ b/src/bin/update-licenses.rs @@ -0,0 +1,65 @@ +//! Updates all of the licenses from the existing crates into each of their +//! already existing versions. + +// +// Usage: +// cargo run --bin update-licenses + +extern crate cargo_registry; +extern crate postgres; + +use std::io::prelude::*; + +fn main() { + let conn = cargo_registry::db::connect_now(); + { + let tx = conn.transaction().unwrap(); + transfer(&tx); + tx.set_commit(); + tx.finish().unwrap(); + } +} + +fn transfer(tx: &postgres::transaction::Transaction) { + let stmt = tx.prepare("SELECT id, name, license FROM crates").unwrap(); + let rows = stmt.query(&[]).unwrap(); + + for row in rows.iter() { + let id: i32 = row.get("id"); + let name: String = row.get("name"); + let license: Option = row.get("license"); + + if let Some(license) = license { + println!( + "Setting the license for all versions of {} to {}.", + name, + license + ); + + let num_updated = tx.execute( + "UPDATE versions SET license = $1 WHERE crate_id = $2", + &[&license, &id], + ).unwrap(); + assert!(num_updated > 0); + } else { + println!( + "Ignoring crate `{}` because it doesn't have a license.", + name + ); + } + } + + get_confirm("Finish committing?"); +} + +fn get_confirm(msg: &str) { + print!("{} [y/N]: ", msg); + std::io::stdout().flush().unwrap(); + + let mut line = String::new(); + std::io::stdin().read_line(&mut line).unwrap(); + + if !line.starts_with("y") { + std::process::exit(0); + } +} diff --git a/src/categories.rs b/src/categories.rs index 6a8024cd7a4..1d6e16bdef8 100644 --- a/src/categories.rs +++ b/src/categories.rs @@ -101,11 +101,11 @@ pub fn sync() -> CargoResult<()> { for category in &categories { tx.execute( "\ - INSERT INTO categories (slug, category, description) \ - VALUES (LOWER($1), $2, $3) \ - ON CONFLICT (slug) DO UPDATE \ - SET category = EXCLUDED.category, \ - description = EXCLUDED.description;", + INSERT INTO categories (slug, category, description) \ + VALUES (LOWER($1), $2, $3) \ + ON CONFLICT (slug) DO UPDATE \ + SET category = EXCLUDED.category, \ + description = EXCLUDED.description;", &[&category.slug, &category.name, &category.description], )?; } @@ -119,8 +119,8 @@ pub fn sync() -> CargoResult<()> { tx.execute( &format!( "\ - DELETE FROM categories \ - WHERE slug NOT IN ({});", + DELETE FROM categories \ + WHERE slug NOT IN ({});", in_clause ), &[], diff --git a/src/category.rs b/src/category.rs index c1ea9677ed8..eec05b8d7d5 100644 --- a/src/category.rs +++ b/src/category.rs @@ -60,7 +60,7 @@ impl Category { pub fn find_by_category(conn: &GenericConnection, name: &str) -> CargoResult { let stmt = conn.prepare( "SELECT * FROM categories \ - WHERE category = $1", + WHERE category = $1", )?; let rows = stmt.query(&[&name])?; rows.iter().next().chain_error(|| NotFound).map(|row| { @@ -71,7 +71,7 @@ impl Category { pub fn find_by_slug(conn: &GenericConnection, slug: &str) -> CargoResult { let stmt = conn.prepare( "SELECT * FROM categories \ - WHERE slug = LOWER($1)", + WHERE slug = LOWER($1)", )?; let rows = stmt.query(&[&slug])?; rows.iter().next().chain_error(|| NotFound).map(|row| { @@ -168,8 +168,8 @@ impl Category { if !to_rm.is_empty() { conn.execute( "DELETE FROM crates_categories \ - WHERE category_id = ANY($1) \ - AND crate_id = $2", + WHERE category_id = ANY($1) \ + AND crate_id = $2", &[&to_rm, &krate.id], )?; } @@ -183,7 +183,7 @@ impl Category { conn.execute( &format!( "INSERT INTO crates_categories \ - (crate_id, category_id) VALUES {}", + (crate_id, category_id) VALUES {}", insert ), &[], @@ -196,9 +196,9 @@ impl Category { pub fn count_toplevel(conn: &GenericConnection) -> CargoResult { let sql = format!( "\ - SELECT COUNT(*) \ - FROM {} \ - WHERE category NOT LIKE '%::%'", + SELECT COUNT(*) \ + FROM {} \ + WHERE category NOT LIKE '%::%'", Model::table_name(None::) ); let stmt = conn.prepare(&sql)?; @@ -272,16 +272,16 @@ impl Category { pub fn subcategories(&self, conn: &GenericConnection) -> CargoResult> { let stmt = conn.prepare( "\ - SELECT c.id, c.category, c.slug, c.description, c.created_at, \ - COALESCE (( \ - SELECT sum(c2.crates_cnt)::int \ - FROM categories as c2 \ - WHERE c2.slug = c.slug \ - OR c2.slug LIKE c.slug || '::%' \ - ), 0) as crates_cnt \ - FROM categories as c \ - WHERE c.category ILIKE $1 || '::%' \ - AND c.category NOT ILIKE $1 || '::%::%'", + SELECT c.id, c.category, c.slug, c.description, c.created_at, \ + COALESCE (( \ + SELECT sum(c2.crates_cnt)::int \ + FROM categories as c2 \ + WHERE c2.slug = c.slug \ + OR c2.slug LIKE c.slug || '::%' \ + ), 0) as crates_cnt \ + FROM categories as c \ + WHERE c.category ILIKE $1 || '::%' \ + AND c.category NOT ILIKE $1 || '::%::%'", )?; let rows = stmt.query(&[&self.category])?; @@ -391,7 +391,7 @@ pub fn slugs(req: &mut Request) -> CargoResult { let conn = req.tx()?; let stmt = conn.prepare( "SELECT slug FROM categories \ - ORDER BY slug", + ORDER BY slug", )?; let rows = stmt.query(&[])?; diff --git a/src/dependency.rs b/src/dependency.rs index 39a148f49d5..3b33ab42b58 100644 --- a/src/dependency.rs +++ b/src/dependency.rs @@ -149,9 +149,9 @@ pub fn add_dependencies( if dep.version_req == semver::VersionReq::parse("*").unwrap() { return Err(human( "wildcard (`*`) dependency constraints are not allowed \ - on crates.io. See http://doc.crates.io/faq.html#can-\ - libraries-use--as-a-version-for-their-dependencies for more \ - information", + on crates.io. See http://doc.crates.io/faq.html#can-\ + libraries-use--as-a-version-for-their-dependencies for more \ + information", )); } let features: Vec<_> = dep.features.iter().map(|s| &**s).collect(); diff --git a/src/http.rs b/src/http.rs index 4767239958a..7d153a8dac3 100644 --- a/src/http.rs +++ b/src/http.rs @@ -49,19 +49,19 @@ pub fn parse_github_response(mut resp: Easy, data: &[u8]) -> Cargo 403 => { return Err(human( "It looks like you don't have permission \ - to query a necessary property from Github \ - to complete this request. \ - You may need to re-authenticate on \ - crates.io to grant permission to read \ - github org memberships. Just go to \ - https://crates.io/login", + to query a necessary property from Github \ + to complete this request. \ + You may need to re-authenticate on \ + crates.io to grant permission to read \ + github org memberships. Just go to \ + https://crates.io/login", )); } n => { let resp = String::from_utf8_lossy(data); return Err(internal(&format_args!( "didn't get a 200 result from \ - github, got {} with: {}", + github, got {} with: {}", n, resp ))); diff --git a/src/krate.rs b/src/krate.rs index 8c09683bc94..1c0d8f08aab 100644 --- a/src/krate.rs +++ b/src/krate.rs @@ -127,15 +127,15 @@ pub struct NewCrate<'a> { pub documentation: Option<&'a str>, pub readme: Option<&'a str>, pub repository: Option<&'a str>, - pub license: Option<&'a str>, pub max_upload_size: Option, + pub license: Option<&'a str>, } impl<'a> NewCrate<'a> { pub fn create_or_update( mut self, conn: &PgConnection, - license_file: Option<&str>, + license_file: Option<&'a str>, uploader: i32, ) -> CargoResult { use diesel::update; @@ -161,7 +161,7 @@ impl<'a> NewCrate<'a> { }) } - fn validate(&mut self, license_file: Option<&str>) -> CargoResult<()> { + fn validate(&mut self, license_file: Option<&'a str>) -> CargoResult<()> { fn validate_url(url: Option<&str>, field: &str) -> CargoResult<()> { let url = match url { Some(s) => s, @@ -175,7 +175,7 @@ impl<'a> NewCrate<'a> { s => { return Err(human(&format_args!( "`{}` has an invalid url \ - scheme: `{}`", + scheme: `{}`", field, s ))) @@ -184,7 +184,7 @@ impl<'a> NewCrate<'a> { if url.cannot_be_a_base() { return Err(human(&format_args!( "`{}` must have relative scheme \ - data: {}", + data: {}", field, url ))); @@ -205,8 +205,8 @@ impl<'a> NewCrate<'a> { license_exprs::validate_license_expr(part).map_err(|e| { human(&format_args!( "{}; see http://opensource.org/licenses \ - for options, and http://spdx.org/licenses/ \ - for their identifiers", + for options, and http://spdx.org/licenses/ \ + for their identifiers", e )) })?; @@ -412,7 +412,7 @@ impl Crate { s => { return Err(human(&format_args!( "`{}` has an invalid url \ - scheme: `{}`", + scheme: `{}`", field, s ))) @@ -421,7 +421,7 @@ impl Crate { if url.cannot_be_a_base() { return Err(human(&format_args!( "`{}` must have relative scheme \ - data: {}", + data: {}", field, url ))); @@ -439,8 +439,8 @@ impl Crate { .map_err(|e| { human(&format_args!( "{}; see http://opensource.org/licenses \ - for options, and http://spdx.org/licenses/ \ - for their identifiers", + for options, and http://spdx.org/licenses/ \ + for their identifiers", e )) }) @@ -503,8 +503,8 @@ impl Crate { description, homepage, documentation, - license, repository, + license, .. } = self; let versions_link = match versions { @@ -529,8 +529,8 @@ impl Crate { homepage: homepage, exact_match: exact_match, description: description, - license: license, repository: repository, + license: license, links: CrateLinks { version_downloads: format!("/api/v1/crates/{}/downloads", name), versions: versions_link, @@ -568,7 +568,7 @@ impl Crate { pub fn versions(&self, conn: &GenericConnection) -> CargoResult> { let stmt = conn.prepare( "SELECT * FROM versions \ - WHERE crate_id = $1", + WHERE crate_id = $1", )?; let rows = stmt.query(&[&self.id])?; let mut ret = rows.iter() @@ -640,7 +640,7 @@ impl Crate { } else { return Err(human(&format_args!( "only members of {} can add it as \ - an owner", + an owner", login ))); } @@ -714,10 +714,10 @@ impl Crate { pub fn categories(&self, conn: &GenericConnection) -> CargoResult> { let stmt = conn.prepare( "SELECT categories.* FROM categories \ - LEFT JOIN crates_categories \ - ON categories.id = \ - crates_categories.category_id \ - WHERE crates_categories.crate_id = $1", + LEFT JOIN crates_categories \ + ON categories.id = \ + crates_categories.category_id \ + WHERE crates_categories.crate_id = $1", )?; let rows = stmt.query(&[&self.id])?; Ok(rows.iter().map(|r| Model::from_row(&r)).collect()) @@ -1083,6 +1083,7 @@ pub fn new(req: &mut Request) -> CargoResult { license: new_crate.license.as_ref().map(|s| &**s), max_upload_size: None, }; + let license_file = new_crate.license_file.as_ref().map(|s| &**s); let krate = persist.create_or_update(&conn, license_file, user.id)?; @@ -1090,7 +1091,7 @@ pub fn new(req: &mut Request) -> CargoResult { if rights(req.app(), &owners, &user)? < Rights::Publish { return Err(human( "crate name has already been claimed by \ - another user", + another user", )); } @@ -1111,12 +1112,12 @@ pub fn new(req: &mut Request) -> CargoResult { return Err(human(&format_args!("max upload size is: {}", max))); } + // This is only redundant for now. Eventually the duplication will be removed. + let license = new_crate.license.clone(); + // Persist the new version of this crate - let version = NewVersion::new(krate.id, vers, &features)?.save( - &conn, - &new_crate - .authors, - )?; + let version = NewVersion::new(krate.id, vers, &features, license, license_file)? + .save(&conn, &new_crate.authors)?; // Link this new version to all dependencies let git_deps = dependency::add_dependencies(&conn, &new_crate.deps, version.id)?; @@ -1213,8 +1214,8 @@ fn parse_new_headers(req: &mut Request) -> CargoResult<(upload::NewCrate, User)> if !missing.is_empty() { return Err(human(&format_args!( "missing or empty metadata fields: {}. Please \ - see http://doc.crates.io/manifest.html#package-metadata for \ - how to upload metadata", + see http://doc.crates.io/manifest.html#package-metadata for \ + how to upload metadata", missing.join(", ") ))); } diff --git a/src/owner.rs b/src/owner.rs index 407527055c2..a28af6ae1bf 100644 --- a/src/owner.rs +++ b/src/owner.rs @@ -90,7 +90,7 @@ impl Team { let team = chunks.next().ok_or_else(|| { human( "missing github team argument; \ - format is github:org:team", + format is github:org:team", ) })?; Team::create_github_team(app, conn, login, org, team, req_user) @@ -98,7 +98,7 @@ impl Team { _ => { Err(human( "unknown organization handler, \ - only 'github:org:team' is supported", + only 'github:org:team' is supported", )) } } @@ -129,7 +129,7 @@ impl Team { if let Some(c) = org_name.chars().find(whitelist) { return Err(human(&format_args!( "organization cannot contain special \ - characters like {}", + characters like {}", c ))); } diff --git a/src/schema.rs b/src/schema.rs index 65ce72b7281..9f973a040ba 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -172,5 +172,6 @@ table! { downloads -> Int4, features -> Nullable, yanked -> Bool, + license -> Nullable, } } diff --git a/src/tests/all.rs b/src/tests/all.rs index 61b4255a5e7..9a270fd8f92 100644 --- a/src/tests/all.rs +++ b/src/tests/all.rs @@ -214,12 +214,54 @@ fn user(login: &str) -> User { use cargo_registry::util::CargoResult; +struct VersionBuilder<'a> { + version: semver::Version, + license: Option<&'a str>, + license_file: Option<&'a str>, + features: HashMap>, +} + +impl<'a> VersionBuilder<'a> { + fn new(version: &str) -> VersionBuilder { + let version = semver::Version::parse(version).unwrap_or_else(|e| { + panic!("The version {} is not valid: {}", version, e); + }); + + VersionBuilder { + version: version, + license: None, + license_file: None, + features: HashMap::new(), + } + } + + fn license(mut self, license: Option<&'a str>) -> Self { + self.license = license; + self + } + + fn build(self, connection: &PgConnection, crate_id: i32) -> CargoResult { + let license = match self.license { + Some(license) => Some(license.to_owned()), + None => None, + }; + + NewVersion::new( + crate_id, + &self.version, + &self.features, + license, + self.license_file, + )? + .save(connection, &[]) + } +} + struct CrateBuilder<'a> { owner_id: i32, krate: NewCrate<'a>, - license_file: Option<&'a str>, downloads: Option, - versions: Vec, + versions: Vec>, keywords: Vec<&'a str>, } @@ -231,7 +273,6 @@ impl<'a> CrateBuilder<'a> { name: name, ..NewCrate::default() }, - license_file: None, downloads: None, versions: Vec::new(), keywords: Vec::new(), @@ -268,10 +309,7 @@ impl<'a> CrateBuilder<'a> { self } - fn version(mut self, version: &str) -> Self { - let version = semver::Version::parse(version).unwrap_or_else(|e| { - panic!("The version {} is not valid: {}", version, e); - }); + fn version(mut self, version: VersionBuilder<'a>) -> Self { self.versions.push(version); self } @@ -284,11 +322,7 @@ impl<'a> CrateBuilder<'a> { fn build(mut self, connection: &PgConnection) -> CargoResult { use diesel::update; - let mut krate = self.krate.create_or_update( - connection, - self.license_file, - self.owner_id, - )?; + let mut krate = self.krate.create_or_update(connection, None, self.owner_id)?; // Since we are using `NewCrate`, we can't set all the // crate properties in a single DB call. @@ -298,14 +332,11 @@ impl<'a> CrateBuilder<'a> { } if self.versions.is_empty() { - self.versions.push("0.99.0".parse().expect( - "invalid version number", - )); + self.versions.push(VersionBuilder::new("0.99.0")); } - for version_num in &self.versions { - NewVersion::new(krate.id, version_num, &HashMap::new())? - .save(connection, &[])?; + for version in self.versions { + version.build(&connection, krate.id)?; } if !self.keywords.is_empty() { @@ -325,7 +356,7 @@ impl<'a> CrateBuilder<'a> { fn new_version(crate_id: i32, num: &str) -> NewVersion { let num = semver::Version::parse(num).unwrap(); - NewVersion::new(crate_id, &num, &HashMap::new()).unwrap() + NewVersion::new(crate_id, &num, &HashMap::new(), None, None).unwrap() } fn krate(name: &str) -> Crate { @@ -440,9 +471,9 @@ fn mock_category(req: &mut Request, name: &str, slug: &str) -> Category { let conn = req.tx().unwrap(); let stmt = conn.prepare( " \ - INSERT INTO categories (category, slug) \ - VALUES ($1, $2) \ - RETURNING *", + INSERT INTO categories (category, slug) \ + VALUES ($1, $2) \ + RETURNING *", ).unwrap(); let rows = stmt.query(&[&name, &slug]).unwrap(); Model::from_row(&rows.iter().next().unwrap()) diff --git a/src/tests/krate.rs b/src/tests/krate.rs index 6875d7b478b..612d87e7be5 100644 --- a/src/tests/krate.rs +++ b/src/tests/krate.rs @@ -357,9 +357,9 @@ fn show() { .description("description") .documentation("https://example.com") .homepage("http://example.com") - .version("1.0.0") - .version("0.5.0") - .version("0.5.1") + .version(::VersionBuilder::new("1.0.0")) + .version(::VersionBuilder::new("0.5.0")) + .version(::VersionBuilder::new("0.5.1")) .keyword("kw1") .expect_build(&conn); } @@ -763,7 +763,7 @@ fn new_krate_duplicate_version() { ::sign_in_as(&mut req, &user); ::CrateBuilder::new("foo_dupe", user.id) - .version("1.0.0") + .version(::VersionBuilder::new("1.0.0")) .expect_build(&conn); } let json = bad_resp!(middle.call(&mut req)); @@ -949,7 +949,7 @@ fn download() { let conn = app.diesel_database.get().unwrap(); let user = ::new_user("foo").create_or_update(&conn).unwrap(); ::CrateBuilder::new("foo_download", user.id) - .version("1.0.0") + .version(::VersionBuilder::new("1.0.0")) .expect_build(&conn); } let resp = t_resp!(middle.call(&mut req)); diff --git a/src/tests/user.rs b/src/tests/user.rs index 0e99b3d1246..c2f78b473ce 100644 --- a/src/tests/user.rs +++ b/src/tests/user.rs @@ -155,11 +155,11 @@ fn following() { ::sign_in_as(&mut req, &user); ::CrateBuilder::new("foo_fighters", user.id) - .version("1.0.0") + .version(::VersionBuilder::new("1.0.0")) .expect_build(&conn); ::CrateBuilder::new("bar_fighters", user.id) - .version("1.0.0") + .version(::VersionBuilder::new("1.0.0")) .expect_build(&conn); } diff --git a/src/tests/version.rs b/src/tests/version.rs index e6fe058299c..cf1df296a6f 100644 --- a/src/tests/version.rs +++ b/src/tests/version.rs @@ -29,8 +29,10 @@ fn index() { let conn = app.diesel_database.get().unwrap(); let u = ::new_user("foo").create_or_update(&conn).unwrap(); ::CrateBuilder::new("foo_vers_index", u.id) - .version("2.0.0") - .version("2.0.1") + .version(::VersionBuilder::new("2.0.0").license(Some("MIT"))) + .version(::VersionBuilder::new("2.0.1").license( + Some("MIT/Apache-2.0"), + )) .expect_build(&conn); let ids = versions::table .select(versions::id) @@ -42,6 +44,14 @@ fn index() { let mut response = ok_resp!(middle.call(&mut req)); let json: VersionList = ::json(&mut response); assert_eq!(json.versions.len(), 2); + + for v in &json.versions { + match v.num.as_ref() { + "2.0.0" => assert_eq!(v.license, Some(String::from("MIT"))), + "2.0.1" => assert_eq!(v.license, Some(String::from("MIT/Apache-2.0"))), + _ => panic!("unexpected version"), + } + } } #[test] diff --git a/src/upload.rs b/src/upload.rs index b450b1d600c..ff987cd3252 100644 --- a/src/upload.rs +++ b/src/upload.rs @@ -54,9 +54,9 @@ impl Decodable for CrateName { if !Crate::valid_name(&s) { return Err(d.error(&format!( "invalid crate name specified: {}. \ - Valid crate names must start with a letter; contain only \ - letters, numbers, hyphens, or underscores; and have {} or \ - fewer characters.", + Valid crate names must start with a letter; contain only \ + letters, numbers, hyphens, or underscores; and have {} or \ + fewer characters.", s, MAX_NAME_LENGTH ))); @@ -139,7 +139,7 @@ impl Decodable for KeywordList { if val.len() > 20 { return Err(d.error( "keywords must contain less than 20 \ - characters", + characters", )); } } @@ -167,7 +167,7 @@ impl Decodable for DependencyKind { s => { Err(d.error(&format!( "invalid dependency kind `{}`, must be \ - one of dev, build, or normal", + one of dev, build, or normal", s ))) } diff --git a/src/user/mod.rs b/src/user/mod.rs index d587577538f..902908956e4 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -106,7 +106,7 @@ impl User { pub fn find_by_api_token(conn: &GenericConnection, token: &str) -> CargoResult { let stmt = conn.prepare( "SELECT * FROM users \ - WHERE api_token = $1 LIMIT 1", + WHERE api_token = $1 LIMIT 1", )?; let rows = stmt.query(&[&token])?; rows.iter() @@ -328,7 +328,7 @@ pub fn reset_token(req: &mut Request) -> CargoResult { let conn = req.tx()?; let rows = conn.query( "UPDATE users SET api_token = DEFAULT \ - WHERE id = $1 RETURNING api_token", + WHERE id = $1 RETURNING api_token", &[&user.id], )?; let token = rows.iter().next().map(|r| r.get("api_token")).chain_error( diff --git a/src/util/io_util.rs b/src/util/io_util.rs index be25ddd14f0..9d7eb38d2be 100644 --- a/src/util/io_util.rs +++ b/src/util/io_util.rs @@ -15,10 +15,12 @@ impl LimitErrorReader { impl Read for LimitErrorReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { match self.inner.read(buf) { - Ok(0) if self.inner.limit() == 0 => Err(io::Error::new( - io::ErrorKind::Other, - "maximum limit reached when reading", - )), + Ok(0) if self.inner.limit() == 0 => { + Err(io::Error::new( + io::ErrorKind::Other, + "maximum limit reached when reading", + )) + } e => e, } } diff --git a/src/version.rs b/src/version.rs index 9656cb34e15..6097025335a 100644 --- a/src/version.rs +++ b/src/version.rs @@ -23,6 +23,7 @@ use user::RequestUser; use util::errors::CargoError; use util::{RequestUtils, CargoResult, ChainError, internal, human}; use {Model, Crate}; +use license_exprs; #[derive(Clone, Identifiable, Associations)] #[belongs_to(Crate)] @@ -35,6 +36,7 @@ pub struct Version { pub downloads: i32, pub features: HashMap>, pub yanked: bool, + pub license: Option, } #[derive(Insertable)] @@ -43,6 +45,7 @@ pub struct NewVersion { crate_id: i32, num: String, features: String, + license: Option, } pub struct Author { @@ -60,6 +63,7 @@ pub struct EncodableVersion { pub downloads: i32, pub features: HashMap>, pub yanked: bool, + pub license: Option, pub links: VersionLinks, } @@ -79,7 +83,7 @@ impl Version { let num = num.to_string(); let stmt = conn.prepare( "SELECT * FROM versions \ - WHERE crate_id = $1 AND num = $2", + WHERE crate_id = $1 AND num = $2", )?; let rows = stmt.query(&[&crate_id, &num])?; Ok(rows.iter().next().map(|r| Model::from_row(&r))) @@ -96,9 +100,9 @@ impl Version { let features = json::encode(features).unwrap(); let stmt = conn.prepare( "INSERT INTO versions \ - (crate_id, num, features) \ - VALUES ($1, $2, $3) \ - RETURNING *", + (crate_id, num, features) \ + VALUES ($1, $2, $3) \ + RETURNING *", )?; let rows = stmt.query(&[&crate_id, &num, &features])?; let ret: Version = Model::from_row(&rows.iter().next().chain_error( @@ -123,6 +127,7 @@ impl Version { downloads, features, yanked, + license, .. } = self; let num = num.to_string(); @@ -136,6 +141,7 @@ impl Version { downloads: downloads, features: features, yanked: yanked, + license: license, links: VersionLinks { dependencies: format!("/api/v1/crates/{}/{}/dependencies", crate_name, num), version_downloads: format!("/api/v1/crates/{}/{}/downloads", crate_name, num), @@ -205,13 +211,21 @@ impl NewVersion { crate_id: i32, num: &semver::Version, features: &HashMap>, + license: Option, + license_file: Option<&str>, ) -> CargoResult { let features = json::encode(features)?; - Ok(NewVersion { + + let mut new_version = NewVersion { crate_id: crate_id, num: num.to_string(), features: features, - }) + license: license, + }; + + new_version.validate_license(license_file)?; + + Ok(new_version) } pub fn save(&self, conn: &PgConnection, authors: &[String]) -> CargoResult { @@ -225,7 +239,7 @@ impl NewVersion { if select(exists(already_uploaded)).get_result(conn)? { return Err(human(&format_args!( "crate version `{}` is already \ - uploaded", + uploaded", self.num ))); } @@ -249,6 +263,27 @@ impl NewVersion { Ok(version) }) } + + fn validate_license(&mut self, license_file: Option<&str>) -> CargoResult<()> { + if let Some(ref license) = self.license { + for part in license.split('/') { + license_exprs::validate_license_expr(part).map_err(|e| { + human(&format_args!( + "{}; see http://opensource.org/licenses \ + for options, and http://spdx.org/licenses/ \ + for their identifiers", + e + )) + })?; + } + } else if license_file.is_some() { + // If no license is given, but a license file is given, flag this + // crate as having a nonstandard license. Note that we don't + // actually do anything else with license_file currently. + self.license = Some(String::from("non-standard")); + } + Ok(()) + } } #[derive(Insertable)] @@ -259,7 +294,7 @@ struct NewAuthor<'a> { } impl Queryable for Version { - type Row = (i32, i32, String, Timespec, Timespec, i32, Option, bool); + type Row = (i32, i32, String, Timespec, Timespec, i32, Option, bool, Option); fn build(row: Self::Row) -> Self { let features = row.6.map(|s| json::decode(&s).unwrap()).unwrap_or_else( @@ -274,6 +309,7 @@ impl Queryable for Version { downloads: row.5, features: features, yanked: row.7, + license: row.8, } } } @@ -294,6 +330,7 @@ impl Model for Version { downloads: row.get("downloads"), features: features, yanked: row.get("yanked"), + license: row.get("license"), } } fn table_name(_: Option) -> &'static str {