mirror of
https://github.com/deps-rs/deps.rs.git
synced 2024-11-22 10:26:30 +00:00
first simple version of crate dependency reports
This commit is contained in:
parent
12e4d7df51
commit
7fff95203e
6 changed files with 182 additions and 41 deletions
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
use failure::Error;
|
||||
use futures::Future;
|
||||
use futures::{Future, future};
|
||||
use futures::future::join_all;
|
||||
use hyper::Client;
|
||||
use hyper::client::HttpConnector;
|
||||
|
@ -18,7 +18,7 @@ mod futures;
|
|||
use ::utils::cache::Cache;
|
||||
|
||||
use ::models::repo::{Repository, RepoPath};
|
||||
use ::models::crates::{CrateName, CrateRelease, AnalyzedDependencies};
|
||||
use ::models::crates::{CrateName, CratePath, CrateRelease, AnalyzedDependencies};
|
||||
|
||||
use ::interactors::crates::QueryCrate;
|
||||
use ::interactors::RetrieveFileAtPath;
|
||||
|
@ -83,7 +83,7 @@ impl Engine {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn analyze_dependencies(&self, repo_path: RepoPath) ->
|
||||
pub fn analyze_repo_dependencies(&self, repo_path: RepoPath) ->
|
||||
impl Future<Item=AnalyzeDependenciesOutcome, Error=Error>
|
||||
{
|
||||
let start = Instant::now();
|
||||
|
@ -109,6 +109,33 @@ impl Engine {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn analyze_crate_dependencies(&self, crate_path: CratePath) ->
|
||||
impl Future<Item=AnalyzeDependenciesOutcome, Error=Error>
|
||||
{
|
||||
let start = Instant::now();
|
||||
|
||||
let query_future = self.query_crate.call(crate_path.name.clone()).from_err();
|
||||
|
||||
let engine = self.clone();
|
||||
query_future.and_then(move |query_response| {
|
||||
match query_response.releases.iter().find(|release| release.version == crate_path.version) {
|
||||
None => future::Either::A(future::err(format_err!("could not find crate release with version {}", crate_path.version))),
|
||||
Some(release) => {
|
||||
let analyzed_deps_future = AnalyzeDependenciesFuture::new(&engine, release.deps.clone());
|
||||
|
||||
future::Either::B(analyzed_deps_future.map(move |analyzed_deps| {
|
||||
let crates = vec![(crate_path.name, analyzed_deps)].into_iter().collect();
|
||||
let duration = start.elapsed();
|
||||
|
||||
AnalyzeDependenciesOutcome {
|
||||
crates, duration
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn fetch_releases<I: IntoIterator<Item=CrateName>>(&self, names: I) ->
|
||||
impl Iterator<Item=impl Future<Item=Vec<CrateRelease>, Error=Error>>
|
||||
{
|
||||
|
|
|
@ -4,28 +4,49 @@ use failure::Error;
|
|||
use futures::{Future, Stream, IntoFuture, future};
|
||||
use hyper::{Error as HyperError, Method, Request, Response, Uri};
|
||||
use tokio_service::Service;
|
||||
use semver::Version;
|
||||
use semver::{Version, VersionReq};
|
||||
use serde_json;
|
||||
|
||||
use ::models::crates::{CrateName, CrateRelease};
|
||||
use ::models::crates::{CrateName, CrateRelease, CrateDeps, CrateDep};
|
||||
|
||||
const CRATES_INDEX_BASE_URI: &str = "https://raw.githubusercontent.com/rust-lang/crates.io-index";
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct RegistryPackageDep {
|
||||
name: String,
|
||||
req: VersionReq,
|
||||
#[serde(default)]
|
||||
kind: Option<String>
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct RegistryPackage {
|
||||
vers: Version,
|
||||
#[serde(default)]
|
||||
deps: Vec<RegistryPackageDep>,
|
||||
#[serde(default)]
|
||||
yanked: bool
|
||||
}
|
||||
|
||||
fn convert_pkgs(name: &CrateName, packages: Vec<RegistryPackage>) -> Result<QueryCrateResponse, Error> {
|
||||
let releases = packages.into_iter().map(|package| {
|
||||
CrateRelease {
|
||||
let mut deps = CrateDeps::default();
|
||||
for dep in package.deps {
|
||||
match dep.kind.map(|k| k.clone()).unwrap_or_else(|| "normal".into()).as_ref() {
|
||||
"normal" =>
|
||||
deps.main.insert(dep.name.parse()?, CrateDep::External(dep.req)),
|
||||
"dev" =>
|
||||
deps.dev.insert(dep.name.parse()?, CrateDep::External(dep.req)),
|
||||
_ => None
|
||||
};
|
||||
}
|
||||
Ok(CrateRelease {
|
||||
name: name.clone(),
|
||||
version: package.vers,
|
||||
deps: deps,
|
||||
yanked: package.yanked
|
||||
}
|
||||
}).collect();
|
||||
})
|
||||
}).collect::<Result<_, Error>>()?;
|
||||
|
||||
Ok(QueryCrateResponse {
|
||||
releases: releases
|
||||
|
@ -72,7 +93,7 @@ impl<S> Service for QueryCrate<S>
|
|||
future::Either::A(future::err(format_err!("Status code {} for URI {}", status, uri)))
|
||||
} else {
|
||||
let body_future = response.body().concat2().from_err();
|
||||
let decode_future = body_future.and_then(|body| {
|
||||
let decode_future = body_future.and_then(move |body| {
|
||||
let string_body = str::from_utf8(body.as_ref())?;
|
||||
let packages = string_body.lines()
|
||||
.map(|s| s.trim())
|
||||
|
|
|
@ -6,6 +6,21 @@ use ordermap::OrderMap;
|
|||
use relative_path::RelativePathBuf;
|
||||
use semver::{Version, VersionReq};
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct CratePath {
|
||||
pub name: CrateName,
|
||||
pub version: Version
|
||||
}
|
||||
|
||||
impl CratePath {
|
||||
pub fn from_parts(name: &str, version: &str) -> Result<CratePath, Error> {
|
||||
Ok(CratePath {
|
||||
name: name.parse()?,
|
||||
version: version.parse()?
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct CrateName(String);
|
||||
|
||||
|
@ -47,6 +62,7 @@ impl FromStr for CrateName {
|
|||
pub struct CrateRelease {
|
||||
pub name: CrateName,
|
||||
pub version: Version,
|
||||
pub deps: CrateDeps,
|
||||
pub yanked: bool
|
||||
}
|
||||
|
||||
|
|
|
@ -1,2 +1,7 @@
|
|||
pub mod crates;
|
||||
pub mod repo;
|
||||
|
||||
pub enum SubjectPath {
|
||||
Repo(self::repo::RepoPath),
|
||||
Crate(self::crates::CratePath)
|
||||
}
|
||||
|
|
|
@ -11,7 +11,9 @@ mod assets;
|
|||
mod views;
|
||||
|
||||
use ::engine::{Engine, AnalyzeDependenciesOutcome};
|
||||
use ::models::crates::CratePath;
|
||||
use ::models::repo::RepoPath;
|
||||
use ::models::SubjectPath;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum StatusFormat {
|
||||
|
@ -28,7 +30,8 @@ enum StaticFile {
|
|||
enum Route {
|
||||
Index,
|
||||
Static(StaticFile),
|
||||
Status(StatusFormat)
|
||||
RepoStatus(StatusFormat),
|
||||
CrateStatus(StatusFormat)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -47,8 +50,11 @@ impl Server {
|
|||
router.add("/static/style.css", Route::Static(StaticFile::StyleCss));
|
||||
router.add("/static/favicon.png", Route::Static(StaticFile::FaviconPng));
|
||||
|
||||
router.add("/repo/:site/:qual/:name", Route::Status(StatusFormat::Html));
|
||||
router.add("/repo/:site/:qual/:name/status.svg", Route::Status(StatusFormat::Svg));
|
||||
router.add("/repo/:site/:qual/:name", Route::RepoStatus(StatusFormat::Html));
|
||||
router.add("/repo/:site/:qual/:name/status.svg", Route::RepoStatus(StatusFormat::Svg));
|
||||
|
||||
router.add("/crate/:name/:version", Route::CrateStatus(StatusFormat::Html));
|
||||
router.add("/crate/:name/:version/status.svg", Route::CrateStatus(StatusFormat::Svg));
|
||||
|
||||
Server { logger, engine, router: Arc::new(router) }
|
||||
}
|
||||
|
@ -70,9 +76,14 @@ impl Service for Server {
|
|||
return Box::new(self.index(req, route_match.params, logger));
|
||||
}
|
||||
},
|
||||
&Route::Status(format) => {
|
||||
&Route::RepoStatus(format) => {
|
||||
if *req.method() == Method::Get {
|
||||
return Box::new(self.status(req, route_match.params, logger, format));
|
||||
return Box::new(self.repo_status(req, route_match.params, logger, format));
|
||||
}
|
||||
},
|
||||
&Route::CrateStatus(format) => {
|
||||
if *req.method() == Method::Get {
|
||||
return Box::new(self.crate_status(req, route_match.params, logger, format));
|
||||
}
|
||||
},
|
||||
&Route::Static(file) => {
|
||||
|
@ -108,7 +119,7 @@ impl Server {
|
|||
})
|
||||
}
|
||||
|
||||
fn status(&self, _req: Request, params: Params, logger: Logger, format: StatusFormat) ->
|
||||
fn repo_status(&self, _req: Request, params: Params, logger: Logger, format: StatusFormat) ->
|
||||
impl Future<Item=Response, Error=HyperError>
|
||||
{
|
||||
let server = self.clone();
|
||||
|
@ -127,15 +138,15 @@ impl Server {
|
|||
future::Either::A(future::ok(response))
|
||||
},
|
||||
Ok(repo_path) => {
|
||||
future::Either::B(server.engine.analyze_dependencies(repo_path.clone()).then(move |analyze_result| {
|
||||
future::Either::B(server.engine.analyze_repo_dependencies(repo_path.clone()).then(move |analyze_result| {
|
||||
match analyze_result {
|
||||
Err(err) => {
|
||||
error!(logger, "error: {}", err);
|
||||
let response = Server::status_format_analysis(None, format, repo_path);
|
||||
let response = Server::status_format_analysis(None, format, SubjectPath::Repo(repo_path));
|
||||
future::ok(response)
|
||||
},
|
||||
Ok(analysis_outcome) => {
|
||||
let response = Server::status_format_analysis(Some(analysis_outcome), format, repo_path);
|
||||
let response = Server::status_format_analysis(Some(analysis_outcome), format, SubjectPath::Repo(repo_path));
|
||||
future::ok(response)
|
||||
}
|
||||
}
|
||||
|
@ -145,12 +156,48 @@ impl Server {
|
|||
})
|
||||
}
|
||||
|
||||
fn status_format_analysis(analysis_outcome: Option<AnalyzeDependenciesOutcome>, format: StatusFormat, repo_path: RepoPath) -> Response {
|
||||
fn crate_status(&self, _req: Request, params: Params, logger: Logger, format: StatusFormat) ->
|
||||
impl Future<Item=Response, Error=HyperError>
|
||||
{
|
||||
let server = self.clone();
|
||||
|
||||
let name = params.find("name").expect("route param 'name' not found");
|
||||
let version = params.find("version").expect("route param 'version' not found");
|
||||
|
||||
CratePath::from_parts(name, version).into_future().then(move |crate_path_result| {
|
||||
match crate_path_result {
|
||||
Err(err) => {
|
||||
error!(logger, "error: {}", err);
|
||||
let mut response = views::html::error::render("Could not parse crate path",
|
||||
"Please make sure to provide a valid crate name and version.");
|
||||
response.set_status(StatusCode::BadRequest);
|
||||
future::Either::A(future::ok(response))
|
||||
},
|
||||
Ok(crate_path) => {
|
||||
future::Either::B(server.engine.analyze_crate_dependencies(crate_path.clone()).then(move |analyze_result| {
|
||||
match analyze_result {
|
||||
Err(err) => {
|
||||
error!(logger, "error: {}", err);
|
||||
let response = Server::status_format_analysis(None, format, SubjectPath::Crate(crate_path));
|
||||
future::ok(response)
|
||||
},
|
||||
Ok(analysis_outcome) => {
|
||||
let response = Server::status_format_analysis(Some(analysis_outcome), format, SubjectPath::Crate(crate_path));
|
||||
future::ok(response)
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn status_format_analysis(analysis_outcome: Option<AnalyzeDependenciesOutcome>, format: StatusFormat, subject_path: SubjectPath) -> Response {
|
||||
match format {
|
||||
StatusFormat::Svg =>
|
||||
views::badge::response(analysis_outcome.as_ref()),
|
||||
StatusFormat::Html =>
|
||||
views::html::status::render(analysis_outcome, repo_path)
|
||||
views::html::status::render(analysis_outcome, subject_path)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,8 @@ use ordermap::OrderMap;
|
|||
|
||||
use ::engine::AnalyzeDependenciesOutcome;
|
||||
use ::models::crates::{CrateName, AnalyzedDependency, AnalyzedDependencies};
|
||||
use ::models::repo::{RepoSite, RepoPath};
|
||||
use ::models::SubjectPath;
|
||||
use ::models::repo::RepoSite;
|
||||
|
||||
use super::super::badge;
|
||||
|
||||
|
@ -84,26 +85,44 @@ fn dependency_table(title: &str, deps: OrderMap<CrateName, AnalyzedDependency>)
|
|||
}
|
||||
}
|
||||
|
||||
fn get_site_icon(repo_site: &RepoSite) -> &'static str {
|
||||
match *repo_site {
|
||||
fn get_site_icon(site: &RepoSite) -> &'static str {
|
||||
match *site {
|
||||
RepoSite::Github => "fa-github",
|
||||
RepoSite::Gitlab => "fa-gitlab",
|
||||
RepoSite::Bitbucket => "fa-bitbucket",
|
||||
RepoSite::Bitbucket => "fa-bitbucket"
|
||||
}
|
||||
}
|
||||
|
||||
fn render_failure(repo_path: RepoPath) -> Markup {
|
||||
let site_icon = get_site_icon(&repo_path.site);
|
||||
fn render_title(subject_path: &SubjectPath) -> Markup {
|
||||
match *subject_path {
|
||||
SubjectPath::Repo(ref repo_path) => {
|
||||
let site_icon = get_site_icon(&repo_path.site);
|
||||
html! {
|
||||
a href=(format!("{}/{}/{}", repo_path.site.to_base_uri(), repo_path.qual.as_ref(), repo_path.name.as_ref())) {
|
||||
i class=(format!("fa {}", site_icon)) ""
|
||||
(format!(" {} / {}", repo_path.qual.as_ref(), repo_path.name.as_ref()))
|
||||
}
|
||||
}
|
||||
},
|
||||
SubjectPath::Crate(ref crate_path) => {
|
||||
html! {
|
||||
a href=(format!("https://crates.io/crates/{}/{}", crate_path.name.as_ref(), crate_path.version)) {
|
||||
i class="fa fa-cube" ""
|
||||
(format!(" {} {}", crate_path.name.as_ref(), crate_path.version))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_failure(subject_path: SubjectPath) -> Markup {
|
||||
html! {
|
||||
section class="hero is-light" {
|
||||
div class="hero-head" (super::render_navbar())
|
||||
div class="hero-body" {
|
||||
div class="container" {
|
||||
h1 class="title is-1" {
|
||||
a href=(format!("{}/{}/{}", repo_path.site.to_base_uri(), repo_path.qual.as_ref(), repo_path.name.as_ref())) {
|
||||
i class=(format!("fa {}", site_icon)) ""
|
||||
(format!(" {} / {}", repo_path.qual.as_ref(), repo_path.name.as_ref()))
|
||||
}
|
||||
(render_title(&subject_path))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -120,10 +139,14 @@ fn render_failure(repo_path: RepoPath) -> Markup {
|
|||
}
|
||||
}
|
||||
|
||||
fn render_success(analysis_outcome: AnalyzeDependenciesOutcome, repo_path: RepoPath) -> Markup {
|
||||
let self_path = format!("repo/{}/{}/{}", repo_path.site.as_ref(), repo_path.qual.as_ref(), repo_path.name.as_ref());
|
||||
fn render_success(analysis_outcome: AnalyzeDependenciesOutcome, subject_path: SubjectPath) -> Markup {
|
||||
let self_path = match subject_path {
|
||||
SubjectPath::Repo(ref repo_path) =>
|
||||
format!("repo/{}/{}/{}", repo_path.site.as_ref(), repo_path.qual.as_ref(), repo_path.name.as_ref()),
|
||||
SubjectPath::Crate(ref crate_path) =>
|
||||
format!("crate/{}/{}", crate_path.name.as_ref(), crate_path.version)
|
||||
};
|
||||
let status_base_url = format!("{}/{}", &super::SELF_BASE_URL as &str, self_path);
|
||||
let site_icon = get_site_icon(&repo_path.site);
|
||||
|
||||
let status_data_uri = badge::badge(Some(&analysis_outcome)).to_svg_data_uri();
|
||||
|
||||
|
@ -139,10 +162,7 @@ fn render_success(analysis_outcome: AnalyzeDependenciesOutcome, repo_path: RepoP
|
|||
div class="hero-body" {
|
||||
div class="container" {
|
||||
h1 class="title is-1" {
|
||||
a href=(format!("{}/{}/{}", repo_path.site.to_base_uri(), repo_path.qual.as_ref(), repo_path.name.as_ref())) {
|
||||
i class=(format!("fa {}", site_icon)) ""
|
||||
(format!(" {} / {}", repo_path.qual.as_ref(), repo_path.name.as_ref()))
|
||||
}
|
||||
(render_title(&subject_path))
|
||||
}
|
||||
|
||||
img src=(status_data_uri);
|
||||
|
@ -167,12 +187,17 @@ fn render_success(analysis_outcome: AnalyzeDependenciesOutcome, repo_path: RepoP
|
|||
}
|
||||
}
|
||||
|
||||
pub fn render(analysis_outcome: Option<AnalyzeDependenciesOutcome>, repo_path: RepoPath) -> Response {
|
||||
let title = format!("{} / {}", repo_path.qual.as_ref(), repo_path.name.as_ref());
|
||||
pub fn render(analysis_outcome: Option<AnalyzeDependenciesOutcome>, subject_path: SubjectPath) -> Response {
|
||||
let title = match subject_path {
|
||||
SubjectPath::Repo(ref repo_path) =>
|
||||
format!("{} / {}", repo_path.qual.as_ref(), repo_path.name.as_ref()),
|
||||
SubjectPath::Crate(ref crate_path) =>
|
||||
format!("{} {}", crate_path.name.as_ref(), crate_path.version)
|
||||
};
|
||||
|
||||
if let Some(outcome) = analysis_outcome {
|
||||
super::render_html(&title, render_success(outcome, repo_path))
|
||||
super::render_html(&title, render_success(outcome, subject_path))
|
||||
} else {
|
||||
super::render_html(&title, render_failure(repo_path))
|
||||
super::render_html(&title, render_failure(subject_path))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue