diff --git a/app/models/crate.js b/app/models/crate.js
index 02e32bfd3cb..75fb5b64acb 100644
--- a/app/models/crate.js
+++ b/app/models/crate.js
@@ -20,4 +20,5 @@ export default DS.Model.extend({
owners: DS.hasMany('users', {async: true}),
version_downloads: DS.hasMany('version-download', {async: true}),
keywords: DS.hasMany('keywords', {async: true}),
+ reverse_dependencies: DS.hasMany('reverse-dependency', {async: true}),
});
diff --git a/app/models/reverse-dependency.js b/app/models/reverse-dependency.js
new file mode 100644
index 00000000000..1f44a3b5d42
--- /dev/null
+++ b/app/models/reverse-dependency.js
@@ -0,0 +1,3 @@
+import Dependency from 'cargo/models/dependency';
+
+export default Dependency;
diff --git a/app/router.js b/app/router.js
index d725d9df23b..4aa49567b8a 100644
--- a/app/router.js
+++ b/app/router.js
@@ -14,6 +14,7 @@ Router.map(function() {
this.resource('crate', { path: '/crates/*crate_id' }, function() {
this.route('download');
this.route('versions');
+ this.route('reverse_dependencies');
});
this.route('me', function() {
this.route('crates');
diff --git a/app/routes/crate/reverse-dependencies.js b/app/routes/crate/reverse-dependencies.js
new file mode 100644
index 00000000000..5a765b1ebf2
--- /dev/null
+++ b/app/routes/crate/reverse-dependencies.js
@@ -0,0 +1,20 @@
+import Ember from 'ember';
+import Crate from 'cargo/models/crate';
+
+export default Ember.Route.extend({
+ afterModel: function(data) {
+ console.log("afterModel");
+ if (data instanceof Crate) {
+ return data.get('reverse_dependencies');
+ } else {
+ return data.crate.get('reverse_dependencies');
+ }
+ },
+
+ setupController: function(controller, data) {
+ if (data instanceof Crate) {
+ data = {crate: data, reverse_dependencies: null};
+ }
+ this._super(controller, data.crate);
+ },
+});
diff --git a/app/templates/crate/reverse-dependencies.hbs b/app/templates/crate/reverse-dependencies.hbs
new file mode 100644
index 00000000000..bcd37b17a1d
--- /dev/null
+++ b/app/templates/crate/reverse-dependencies.hbs
@@ -0,0 +1,24 @@
+
+ {{#link-to 'crate' this}}⬅ Back to Main Page{{/link-to}}
+
+
+
+
+ All {{ reverse_dependencies.length }}
+ reverse dependencies of {{ name }}
+
+
+
+
+ {{#each model.reverse_dependencies}}
+
+
+ {{#link-to 'crate' crate_id}}{{crate_id}}{{/link-to}} requires {{req}}
+ {{#link-to 'crate' this}}{{ num }}{{/link-to}}
+
+ {{#link-to 'crate' this class='arrow'}}
+

+ {{/link-to}}
+
+ {{/each}}
+
diff --git a/src/krate.rs b/src/krate.rs
index f15ab7e529b..3340273dc31 100644
--- a/src/krate.rs
+++ b/src/krate.rs
@@ -17,6 +17,7 @@ use url::{mod, Url};
use {Model, User, Keyword, Version};
use app::{App, RequestApp};
use db::{Connection, RequestTransaction};
+use dependency::{Dependency, EncodableDependency};
use download::{VersionDownload, EncodableVersionDownload};
use git;
use keyword::EncodableKeyword;
@@ -68,6 +69,7 @@ pub struct CrateLinks {
pub version_downloads: String,
pub versions: Option,
pub owners: Option,
+ pub reverse_dependencies: String,
}
impl Crate {
@@ -253,6 +255,7 @@ impl Crate {
version_downloads: format!("/api/v1/crates/{}/downloads", name),
versions: versions_link,
owners: Some(format!("/api/v1/crates/{}/owners", name)),
+ reverse_dependencies: format!("/api/v1/crates/{}/reverse_dependencies", name)
},
}
}
@@ -333,6 +336,22 @@ impl Crate {
let rows = try!(stmt.query(&[&self.id]));
Ok(rows.map(|r| Model::from_row(&r)).collect())
}
+
+ /// Returns (dependency, dependent crate name)
+ pub fn reverse_dependencies(&self, conn: &Connection) -> CargoResult> {
+ let stmt = try!(conn.prepare("SELECT dependencies.*,
+ crates.name AS crate_name
+ FROM dependencies
+ INNER JOIN versions
+ ON versions.id = dependencies.version_id
+ INNER JOIN crates
+ ON crates.id = versions.crate_id
+ WHERE dependencies.crate_id = $1
+ AND versions.num = crates.max_version"));
+ Ok(try!(stmt.query(&[&self.id])).map(|r| {
+ (Model::from_row(&r), r.get("crate_name"))
+ }).collect())
+ }
}
impl Model for Crate {
@@ -860,3 +879,18 @@ fn modify_owners(req: &mut Request, add: bool) -> CargoResult {
struct R { ok: bool }
Ok(req.json(&R{ ok: true }))
}
+
+pub fn reverse_dependencies(req: &mut Request) -> CargoResult {
+ let name = &req.params()["crate_id"];
+ let conn = try!(req.tx());
+ let krate = try!(Crate::find_by_name(conn, name.as_slice()));
+ let tx = try!(req.tx());
+ let rev_deps = try!(krate.reverse_dependencies(tx));
+ let rev_deps = rev_deps.into_iter().map(|(dep, crate_name)| {
+ dep.encodable(crate_name.as_slice())
+ }).collect();
+
+ #[deriving(Encodable)]
+ struct R { reverse_dependencies: Vec }
+ Ok(req.json(&R{ reverse_dependencies: rev_deps }))
+}
diff --git a/src/lib.rs b/src/lib.rs
old mode 100644
new mode 100755
index 92b1b131783..015b91218b0
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -89,6 +89,7 @@ pub fn middleware(app: Arc) -> MiddlewareBuilder {
api_router.delete("/crates/:crate_id/owners", C(krate::remove_owners));
api_router.delete("/crates/:crate_id/:version/yank", C(version::yank));
api_router.put("/crates/:crate_id/:version/unyank", C(version::unyank));
+ api_router.get("/crates/:crate_id/reverse_dependencies", C(krate::reverse_dependencies));
api_router.get("/versions", C(version::index));
api_router.get("/versions/:version_id", C(version::show));
api_router.get("/keywords", C(keyword::index));
diff --git a/src/tests/all.rs b/src/tests/all.rs
old mode 100644
new mode 100755
index 3af9818b389..55f9a824663
--- a/src/tests/all.rs
+++ b/src/tests/all.rs
@@ -24,6 +24,7 @@ use conduit_test::MockRequest;
use cargo_registry::app::App;
use cargo_registry::db::{mod, RequestTransaction};
use cargo_registry::{User, Crate, Version, Keyword};
+use cargo_registry::util::CargoResult;
macro_rules! t( ($e:expr) => (
match $e {
@@ -213,8 +214,14 @@ fn mock_user(req: &mut Request, u: User) -> User {
}
fn mock_crate(req: &mut Request, krate: Crate) -> Crate {
+ let (c, v) = mock_crate_vers(req, krate, &semver::Version::parse("1.0.0").unwrap());
+ v.unwrap();
+ c
+}
+fn mock_crate_vers(req: &mut Request, krate: Crate, v: &semver::Version)
+ -> (Crate, CargoResult) {
let user = req.extensions().find::().unwrap();
- let krate = Crate::find_or_insert(req.tx().unwrap(), krate.name.as_slice(),
+ let mut krate = Crate::find_or_insert(req.tx().unwrap(), krate.name.as_slice(),
user.id, &krate.description,
&krate.homepage,
&krate.documentation,
@@ -224,10 +231,8 @@ fn mock_crate(req: &mut Request, krate: Crate) -> Crate {
&krate.license).unwrap();
Keyword::update_crate(req.tx().unwrap(), &krate,
krate.keywords.as_slice()).unwrap();
- Version::insert(req.tx().unwrap(), krate.id,
- &semver::Version::parse("1.0.0").unwrap(),
- &HashMap::new(), &[]).unwrap();
- return krate;
+ let v = krate.add_version(req.tx().unwrap(), v, &HashMap::new(), &[]);
+ (krate, v)
}
fn mock_keyword(req: &mut Request, name: &str) -> Keyword {
diff --git a/src/tests/krate.rs b/src/tests/krate.rs
index 96515508921..3c0f446670e 100644
--- a/src/tests/krate.rs
+++ b/src/tests/krate.rs
@@ -32,6 +32,8 @@ struct CrateResponse { krate: EncodableCrate, versions: Vec }
#[deriving(Decodable)]
struct Deps { dependencies: Vec }
#[deriving(Decodable)]
+struct RevDeps { reverse_dependencies: Vec }
+#[deriving(Decodable)]
struct Downloads { version_downloads: Vec }
#[test]
@@ -504,7 +506,7 @@ fn dependencies() {
let c1 = ::krate("foo");
let c2 = ::krate("bar");
middle.add(::middleware::MockUser(user.clone()));
- middle.add(::middleware::MockDependency(c1.clone(), c2.clone()));
+ middle.add(::middleware::MockDependency(c1.clone(), "1.0.0", c2.clone()));
let rel = format!("/api/v1/crates/{}/1.0.0/dependencies", c1.name);
let mut req = MockRequest::new(conduit::Get, rel.as_slice());
let mut response = ok_resp!(middle.call(&mut req));
@@ -700,3 +702,32 @@ fn bad_keywords() {
::json::<::Bad>(&mut response);
}
}
+
+#[test]
+fn reverse_dependencies() {
+ let (_b, _app, mut middle) = ::app();
+ let user = ::user("foo");
+ let c1 = ::krate("foo");
+ let c2 = ::krate("bar");
+ middle.add(::middleware::MockUser(user.clone()));
+ // multiple dependencies of c1 on c2, to detect we handle this
+ // correctly.
+ middle.add(::middleware::MockDependency(c1.clone(), "1.0.0", c2.clone()));
+ middle.add(::middleware::MockDependency(c1.clone(), "1.1.0", c2.clone()));
+
+ let rel = format!("/api/v1/crates/{}/reverse_dependencies", c2.name);
+ let mut req = MockRequest::new(conduit::Get, rel.as_slice());
+ let mut response = ok_resp!(middle.call(&mut req));
+ let deps = ::json::(&mut response);
+ assert_eq!(deps.reverse_dependencies.len(), 1);
+ assert_eq!(deps.reverse_dependencies[0].crate_id.as_slice(), &*c1.name);
+ drop(req);
+
+ // c1 has no dependent crates.
+ let rel = format!("/api/v1/crates/{}/reverse_dependencies", c1.name);
+ let mut req = MockRequest::new(conduit::Get, rel.as_slice());
+ let mut response = ok_resp!(middle.call(&mut req));
+ let deps = ::json::(&mut response);
+ assert_eq!(deps.reverse_dependencies.len(), 0);
+ drop(req);
+}
diff --git a/src/tests/middleware.rs b/src/tests/middleware.rs
index 1ea0472b8a6..557189c49ec 100644
--- a/src/tests/middleware.rs
+++ b/src/tests/middleware.rs
@@ -28,15 +28,19 @@ impl Middleware for MockCrate {
}
}
-pub struct MockDependency(pub Crate, pub Crate);
+pub struct MockDependency(pub Crate, pub &'static str, pub Crate);
impl Middleware for MockDependency {
fn before(&self, req: &mut Request) -> Result<(), Box> {
- let MockDependency(ref a, ref b) = *self;
- let crate_a = ::mock_crate(req, a.clone());
- let crate_b = ::mock_crate(req, b.clone());
- let va = crate_a.versions(req.tx().unwrap()).unwrap()[0].id;
- Dependency::insert(req.tx().unwrap(), va, crate_b.id,
+ let MockDependency(ref a, version, ref b) = *self;
+ let vers = semver::Version::parse(version).unwrap();
+ let (_crate_a, va) = ::mock_crate_vers(req, a.clone(), &vers);
+
+ // don't panic on duplicate uploads
+ let (crate_b, _) = ::mock_crate_vers(req, b.clone(),
+ &semver::Version::parse("1.0.0").unwrap());
+
+ Dependency::insert(req.tx().unwrap(), va.unwrap().id, crate_b.id,
&semver::VersionReq::parse(">= 0").unwrap(),
Kind::Normal,
false, true, &[], &None).unwrap();