welcome to 2018

This commit is contained in:
Rob Ede 2020-09-28 23:48:26 +01:00
parent 67bb58710b
commit 1d5fdd5dc5
No known key found for this signature in database
GPG key ID: C2A3B36E841A91E6
21 changed files with 783 additions and 765 deletions

1213
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,7 @@
name = "shiny-robots"
version = "0.1.0"
authors = ["Sam Rijs <srijs@airpost.net>"]
edition = "2018"
[workspace]
members = [
@ -35,6 +36,7 @@ tokio-core = "0.1.12"
tokio-service = "0.1.0"
toml = "0.4.5"
try_future = "0.1.1"
once_cell = "1.4"
[build-dependencies]

View file

@ -2,13 +2,13 @@ use failure::Error;
use futures::{Future, Poll, Stream};
use futures::stream::futures_unordered;
use ::models::crates::{AnalyzedDependencies, CrateDeps};
use crate::models::crates::{AnalyzedDependencies, CrateDeps};
use super::super::Engine;
use super::super::machines::analyzer::DependencyAnalyzer;
pub struct AnalyzeDependenciesFuture {
inner: Box<Future<Item=AnalyzedDependencies, Error=Error>>
inner: Box<dyn Future<Item=AnalyzedDependencies, Error=Error>>
}
impl AnalyzeDependenciesFuture {

View file

@ -1,11 +1,11 @@
use std::mem;
use failure::Error;
use futures::{Async, Future, Poll, Stream};
use futures::{Async, Future, Poll, Stream, try_ready};
use futures::stream::FuturesOrdered;
use relative_path::RelativePathBuf;
use ::models::repo::RepoPath;
use crate::models::repo::RepoPath;
use super::super::Engine;
use super::super::machines::crawler::ManifestCrawler;
@ -15,12 +15,12 @@ pub struct CrawlManifestFuture {
repo_path: RepoPath,
engine: Engine,
crawler: ManifestCrawler,
futures: FuturesOrdered<Box<Future<Item=(RelativePathBuf, String), Error=Error>>>
futures: FuturesOrdered<Box<dyn Future<Item=(RelativePathBuf, String), Error=Error>>>
}
impl CrawlManifestFuture {
pub fn new(engine: &Engine, repo_path: RepoPath, entry_point: RelativePathBuf) -> Self {
let future: Box<Future<Item=_, Error=_>> = Box::new(engine.retrieve_manifest_at_path(&repo_path, &entry_point)
let future: Box<dyn Future<Item=_, Error=_>> = Box::new(engine.retrieve_manifest_at_path(&repo_path, &entry_point)
.map(move |contents| (entry_point, contents)));
let engine = engine.clone();
let crawler = ManifestCrawler::new();
@ -46,7 +46,7 @@ impl Future for CrawlManifestFuture {
Some((path, raw_manifest)) => {
let output = self.crawler.step(path, raw_manifest)?;
for path in output.paths_of_interest.into_iter() {
let future: Box<Future<Item=_, Error=_>> = Box::new(self.engine.retrieve_manifest_at_path(&self.repo_path, &path)
let future: Box<dyn Future<Item=_, Error=_>> = Box::new(self.engine.retrieve_manifest_at_path(&self.repo_path, &path)
.map(move |contents| (path, contents)));
self.futures.push(future);
}

View file

@ -3,7 +3,7 @@ use std::sync::Arc;
use rustsec::db::AdvisoryDatabase;
use semver::Version;
use ::models::crates::{CrateDeps, CrateRelease, CrateName, AnalyzedDependency, AnalyzedDependencies};
use crate::models::crates::{CrateDeps, CrateRelease, CrateName, AnalyzedDependency, AnalyzedDependencies};
pub struct DependencyAnalyzer {
deps: AnalyzedDependencies,

View file

@ -4,8 +4,8 @@ use failure::Error;
use relative_path::RelativePathBuf;
use indexmap::IndexMap;
use ::parsers::manifest::parse_manifest_toml;
use ::models::crates::{CrateDep, CrateDeps, CrateName, CrateManifest};
use crate::parsers::manifest::parse_manifest_toml;
use crate::models::crates::{CrateDep, CrateDeps, CrateName, CrateManifest};
pub struct ManifestCrawlerOutput {
pub crates: IndexMap<CrateName, CrateDeps>

View file

@ -5,32 +5,33 @@ use std::time::{Duration, Instant};
use cadence::prelude::*;
use cadence::{MetricSink, NopMetricSink, StatsdClient};
use failure::Error;
use futures::{Future, future};
use futures::future::join_all;
use hyper::Client;
use futures::{future, Future};
use hyper::client::HttpConnector;
use hyper::Client;
use hyper_tls::HttpsConnector;
use once_cell::sync::Lazy;
use relative_path::{RelativePath, RelativePathBuf};
use rustsec::db::AdvisoryDatabase;
use semver::VersionReq;
use slog::Logger;
use tokio_service::Service;
use crate::utils::cache::Cache;
use crate::models::crates::{AnalyzedDependencies, CrateName, CratePath, CrateRelease};
use crate::models::repo::{RepoPath, Repository};
use crate::interactors::crates::{GetPopularCrates, QueryCrate};
use crate::interactors::github::GetPopularRepos;
use crate::interactors::rustsec::FetchAdvisoryDatabase;
use crate::interactors::RetrieveFileAtPath;
mod fut;
mod machines;
mod futures;
use ::utils::cache::Cache;
use ::models::repo::{Repository, RepoPath};
use ::models::crates::{CrateName, CratePath, CrateRelease, AnalyzedDependencies};
use ::interactors::crates::{QueryCrate, GetPopularCrates};
use ::interactors::RetrieveFileAtPath;
use ::interactors::github::GetPopularRepos;
use ::interactors::rustsec::FetchAdvisoryDatabase;
use self::futures::AnalyzeDependenciesFuture;
use self::futures::CrawlManifestFuture;
use self::fut::AnalyzeDependenciesFuture;
use self::fut::CrawlManifestFuture;
type HttpClient = Client<HttpsConnector<HttpConnector>>;
@ -44,7 +45,7 @@ pub struct Engine {
get_popular_crates: Arc<Cache<GetPopularCrates<HttpClient>>>,
get_popular_repos: Arc<Cache<GetPopularRepos<HttpClient>>>,
retrieve_file_at_path: Arc<RetrieveFileAtPath<HttpClient>>,
fetch_advisory_db: Arc<Cache<FetchAdvisoryDatabase<HttpClient>>>
fetch_advisory_db: Arc<Cache<FetchAdvisoryDatabase<HttpClient>>>,
}
impl Engine {
@ -52,18 +53,26 @@ impl Engine {
let metrics = StatsdClient::from_sink("engine", NopMetricSink);
let query_crate = Cache::new(QueryCrate(client.clone()), Duration::from_secs(300), 500);
let get_popular_crates = Cache::new(GetPopularCrates(client.clone()), Duration::from_secs(10), 1);
let get_popular_repos = Cache::new(GetPopularRepos(client.clone()), Duration::from_secs(10), 1);
let fetch_advisory_db = Cache::new(FetchAdvisoryDatabase(client.clone()), Duration::from_secs(300), 1);
let get_popular_crates =
Cache::new(GetPopularCrates(client.clone()), Duration::from_secs(10), 1);
let get_popular_repos =
Cache::new(GetPopularRepos(client.clone()), Duration::from_secs(10), 1);
let fetch_advisory_db = Cache::new(
FetchAdvisoryDatabase(client.clone()),
Duration::from_secs(300),
1,
);
Engine {
client: client.clone(), logger, metrics,
client: client.clone(),
logger,
metrics,
query_crate: Arc::new(query_crate),
get_popular_crates: Arc::new(get_popular_crates),
get_popular_repos: Arc::new(get_popular_repos),
retrieve_file_at_path: Arc::new(RetrieveFileAtPath(client)),
fetch_advisory_db: Arc::new(fetch_advisory_db)
fetch_advisory_db: Arc::new(fetch_advisory_db),
}
}
@ -74,7 +83,7 @@ impl Engine {
pub struct AnalyzeDependenciesOutcome {
pub crates: Vec<(CrateName, AnalyzedDependencies)>,
pub duration: Duration
pub duration: Duration,
}
impl AnalyzeDependenciesOutcome {
@ -83,38 +92,42 @@ impl AnalyzeDependenciesOutcome {
}
pub fn any_insecure(&self) -> bool {
self.crates.iter().any(|&(_, ref deps)| deps.count_insecure() > 0)
self.crates
.iter()
.any(|&(_, ref deps)| deps.count_insecure() > 0)
}
pub fn outdated_ratio(&self) -> (usize, usize) {
self.crates.iter().fold((0, 0), |(outdated, total), &(_, ref deps)| {
(outdated + deps.count_outdated(), total + deps.count_total())
})
self.crates
.iter()
.fold((0, 0), |(outdated, total), &(_, ref deps)| {
(outdated + deps.count_outdated(), total + deps.count_total())
})
}
}
impl Engine {
pub fn get_popular_repos(&self) ->
impl Future<Item=Vec<Repository>, Error=Error>
{
self.get_popular_repos.call(())
.from_err().map(|repos| {
repos.iter()
.filter(|repo| !POPULAR_REPOS_BLACKLIST.contains(&repo.path))
.cloned().collect()
})
pub fn get_popular_repos(&self) -> impl Future<Item = Vec<Repository>, Error = Error> {
self.get_popular_repos.call(()).from_err().map(|repos| {
repos
.iter()
.filter(|repo| !POPULAR_REPO_BLOCK_LIST.contains(&repo.path))
.cloned()
.collect()
})
}
pub fn get_popular_crates(&self) ->
impl Future<Item=Vec<CratePath>, Error=Error>
{
self.get_popular_crates.call(())
.from_err().map(|crates| crates.clone())
pub fn get_popular_crates(&self) -> impl Future<Item = Vec<CratePath>, Error = Error> {
self.get_popular_crates
.call(())
.from_err()
.map(|crates| crates.clone())
}
pub fn analyze_repo_dependencies(&self, repo_path: RepoPath) ->
impl Future<Item=AnalyzeDependenciesOutcome, Error=Error>
{
pub fn analyze_repo_dependencies(
&self,
repo_path: RepoPath,
) -> impl Future<Item = AnalyzeDependenciesOutcome, Error = Error> {
let start = Instant::now();
let entry_point = RelativePath::new("/").to_relative_path_buf();
@ -123,99 +136,125 @@ impl Engine {
let engine = self.clone();
manifest_future.and_then(move |manifest_output| {
let engine_for_analyze = engine.clone();
let futures = manifest_output.crates.into_iter().map(move |(crate_name, deps)| {
let analyzed_deps_future = AnalyzeDependenciesFuture::new(engine_for_analyze.clone(), deps);
let futures = manifest_output
.crates
.into_iter()
.map(move |(crate_name, deps)| {
let analyzed_deps_future =
AnalyzeDependenciesFuture::new(engine_for_analyze.clone(), deps);
analyzed_deps_future.map(move |analyzed_deps| (crate_name, analyzed_deps))
});
analyzed_deps_future.map(move |analyzed_deps| (crate_name, analyzed_deps))
});
join_all(futures).and_then(move |crates| {
let duration = start.elapsed();
engine.metrics.time_duration_with_tags("analyze_duration", duration)
engine
.metrics
.time_duration_with_tags("analyze_duration", duration)
.with_tag("repo_site", repo_path.site.as_ref())
.with_tag("repo_qual", repo_path.qual.as_ref())
.with_tag("repo_name", repo_path.name.as_ref())
.send()?;
Ok(AnalyzeDependenciesOutcome {
crates, duration
})
Ok(AnalyzeDependenciesOutcome { crates, duration })
})
})
}
pub fn analyze_crate_dependencies(&self, crate_path: CratePath) ->
impl Future<Item=AnalyzeDependenciesOutcome, Error=Error>
{
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))),
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.clone(), release.deps.clone());
let analyzed_deps_future =
AnalyzeDependenciesFuture::new(engine.clone(), 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
}
AnalyzeDependenciesOutcome { crates, duration }
}))
}
}
})
}
pub fn find_latest_crate_release(&self, name: CrateName, req: VersionReq) ->
impl Future<Item=Option<CrateRelease>, Error=Error>
{
self.query_crate.call(name).from_err().map(move |query_response| {
query_response.releases.iter()
.filter(|release| req.matches(&release.version))
.max_by(|r1, r2| r1.version.cmp(&r2.version))
.cloned()
})
pub fn find_latest_crate_release(
&self,
name: CrateName,
req: VersionReq,
) -> impl Future<Item = Option<CrateRelease>, Error = Error> {
self.query_crate
.call(name)
.from_err()
.map(move |query_response| {
query_response
.releases
.iter()
.filter(|release| req.matches(&release.version))
.max_by(|r1, r2| r1.version.cmp(&r2.version))
.cloned()
})
}
fn fetch_releases<I: IntoIterator<Item=CrateName>>(&self, names: I) ->
impl Iterator<Item=impl Future<Item=Vec<CrateRelease>, Error=Error>>
{
fn fetch_releases<I: IntoIterator<Item = CrateName>>(
&self,
names: I,
) -> impl Iterator<Item = impl Future<Item = Vec<CrateRelease>, Error = Error>> {
let engine = self.clone();
names.into_iter().map(move |name| {
engine.query_crate.call(name)
engine
.query_crate
.call(name)
.from_err()
.map(|resp| resp.releases.clone())
})
}
fn retrieve_manifest_at_path(&self, repo_path: &RepoPath, path: &RelativePathBuf) ->
impl Future<Item=String, Error=Error>
{
fn retrieve_manifest_at_path(
&self,
repo_path: &RepoPath,
path: &RelativePathBuf,
) -> impl Future<Item = String, Error = Error> {
let manifest_path = path.join(RelativePath::new("Cargo.toml"));
self.retrieve_file_at_path.call((repo_path.clone(), manifest_path))
self.retrieve_file_at_path
.call((repo_path.clone(), manifest_path))
}
fn fetch_advisory_db(&self) ->
impl Future<Item=Arc<AdvisoryDatabase>, Error=Error>
{
self.fetch_advisory_db.call(()).from_err().map(|db| db.clone())
fn fetch_advisory_db(&self) -> impl Future<Item = Arc<AdvisoryDatabase>, Error = Error> {
self.fetch_advisory_db
.call(())
.from_err()
.map(|db| db.clone())
}
}
lazy_static! {
static ref POPULAR_REPOS_BLACKLIST: HashSet<RepoPath> = {
vec![
RepoPath::from_parts("github", "rust-lang", "rust"),
RepoPath::from_parts("github", "google", "xi-editor"),
RepoPath::from_parts("github", "lk-geimfari", "awesomo"),
RepoPath::from_parts("github", "redox-os", "tfs"),
RepoPath::from_parts("github", "carols10cents", "rustlings"),
RepoPath::from_parts("github", "rust-unofficial", "awesome-rust")
].into_iter().collect::<Result<HashSet<_>, _>>().unwrap()
};
}
static POPULAR_REPO_BLOCK_LIST: Lazy<HashSet<RepoPath>> = Lazy::new(|| {
vec![
RepoPath::from_parts("github", "rust-lang", "rust"),
RepoPath::from_parts("github", "google", "xi-editor"),
RepoPath::from_parts("github", "lk-geimfari", "awesomo"),
RepoPath::from_parts("github", "redox-os", "tfs"),
RepoPath::from_parts("github", "carols10cents", "rustlings"),
RepoPath::from_parts("github", "rust-unofficial", "awesome-rust"),
]
.into_iter()
.collect::<Result<HashSet<_>, _>>()
.unwrap()
});

View file

@ -2,7 +2,7 @@ use failure::Error;
use hyper::Uri;
use relative_path::RelativePathBuf;
use ::models::repo::RepoPath;
use crate::models::repo::RepoPath;
const BITBUCKET_USER_CONTENT_BASE_URI: &'static str = "https://bitbucket.org";
@ -15,4 +15,3 @@ pub fn get_manifest_uri(repo_path: &RepoPath, path: &RelativePathBuf) -> Result<
path_str
).parse::<Uri>()?)
}

View file

@ -7,7 +7,7 @@ use tokio_service::Service;
use semver::{Version, VersionReq};
use serde_json;
use ::models::crates::{CrateName, CrateRelease, CrateDeps, CrateDep, CratePath};
use crate::models::crates::{CrateName, CrateRelease, CrateDeps, CrateDep, CratePath};
const CRATES_INDEX_BASE_URI: &str = "https://raw.githubusercontent.com/rust-lang/crates.io-index";
const CRATES_API_BASE_URI: &str = "https://crates.io/api/v1";
@ -68,7 +68,7 @@ impl<S> Service for QueryCrate<S>
type Request = CrateName;
type Response = QueryCrateResponse;
type Error = Error;
type Future = Box<Future<Item=Self::Response, Error=Self::Error>>;
type Future = Box<dyn Future<Item=Self::Response, Error=Self::Error>>;
fn call(&self, crate_name: CrateName) -> Self::Future {
let lower_name = crate_name.as_ref().to_lowercase();
@ -137,7 +137,7 @@ impl<S> Service for GetPopularCrates<S>
type Request = ();
type Response = Vec<CratePath>;
type Error = Error;
type Future = Box<Future<Item=Self::Response, Error=Self::Error>>;
type Future = Box<dyn Future<Item=Self::Response, Error=Self::Error>>;
fn call(&self, _req: ()) -> Self::Future {
let service = self.0.clone();

View file

@ -6,7 +6,7 @@ use relative_path::RelativePathBuf;
use tokio_service::Service;
use serde_json;
use ::models::repo::{Repository, RepoPath};
use crate::models::repo::{Repository, RepoPath};
const GITHUB_API_BASE_URI: &'static str = "https://api.github.com";
const GITHUB_USER_CONTENT_BASE_URI: &'static str = "https://raw.githubusercontent.com";
@ -48,7 +48,7 @@ impl<S> Service for GetPopularRepos<S>
type Request = ();
type Response = Vec<Repository>;
type Error = Error;
type Future = Box<Future<Item=Self::Response, Error=Self::Error>>;
type Future = Box<dyn Future<Item=Self::Response, Error=Self::Error>>;
fn call(&self, _req: ()) -> Self::Future {
let uri = try_future_box!(format!("{}/search/repositories?q=language:rust&sort=stars", GITHUB_API_BASE_URI)

View file

@ -2,7 +2,7 @@ use hyper::Uri;
use relative_path::RelativePathBuf;
use failure::Error;
use ::models::repo::RepoPath;
use crate::models::repo::RepoPath;
const GITLAB_USER_CONTENT_BASE_URI: &'static str = "https://gitlab.com";

View file

@ -4,7 +4,7 @@ use hyper::{Error as HyperError, Method, Request, Response};
use relative_path::RelativePathBuf;
use tokio_service::Service;
use ::models::repo::{RepoSite, RepoPath};
use crate::models::repo::{RepoSite, RepoPath};
pub mod bitbucket;
pub mod crates;
@ -22,7 +22,7 @@ impl<S> Service for RetrieveFileAtPath<S>
type Request = (RepoPath, RelativePathBuf);
type Response = String;
type Error = Error;
type Future = Box<Future<Item=Self::Response, Error=Self::Error>>;
type Future = Box<dyn Future<Item=Self::Response, Error=Self::Error>>;
fn call(&self, req: Self::Request) -> Self::Future {
let (repo_path, path) = req;
@ -54,5 +54,3 @@ impl<S> Service for RetrieveFileAtPath<S>
}))
}
}

View file

@ -18,7 +18,7 @@ impl<S> Service for FetchAdvisoryDatabase<S>
type Request = ();
type Response = Arc<AdvisoryDatabase>;
type Error = Error;
type Future = Box<Future<Item=Self::Response, Error=Self::Error>>;
type Future = Box<dyn Future<Item=Self::Response, Error=Self::Error>>;
fn call(&self, _req: ()) -> Self::Future {
let service = self.0.clone();

View file

@ -1,43 +1,17 @@
#![allow(bare_trait_objects)]
#![deny(bare_trait_objects)]
#![allow(unused)]
extern crate badge;
extern crate cadence;
#[macro_use]
extern crate failure;
#[macro_use]
extern crate futures;
extern crate hyper;
extern crate hyper_tls;
extern crate indexmap;
#[macro_use]
extern crate lazy_static;
extern crate lru_cache;
extern crate maud;
extern crate relative_path;
extern crate route_recognizer;
extern crate rustsec;
extern crate semver;
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;
extern crate shared_failure;
#[macro_use]
extern crate slog;
extern crate slog_json;
extern crate tokio_core;
extern crate tokio_service;
extern crate toml;
#[macro_use]
extern crate try_future;
mod engine;
mod interactors;
mod models;
mod parsers;
mod server;
mod utils;
use std::env;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
use std::sync::Mutex;
@ -50,6 +24,13 @@ use hyper_tls::HttpsConnector;
use slog::Drain;
use tokio_core::reactor::Core;
mod engine;
mod interactors;
mod models;
mod parsers;
mod server;
mod utils;
use self::engine::Engine;
use self::server::Server;

View file

@ -4,7 +4,7 @@ use relative_path::RelativePathBuf;
use semver::VersionReq;
use toml;
use ::models::crates::{CrateName, CrateDep, CrateDeps, CrateManifest};
use crate::models::crates::{CrateName, CrateDep, CrateDeps, CrateManifest};
#[derive(Serialize, Deserialize, Debug)]
struct CargoTomlComplexDependency {

View file

@ -12,10 +12,10 @@ use tokio_service::Service;
mod assets;
mod views;
use ::engine::{Engine, AnalyzeDependenciesOutcome};
use ::models::crates::{CrateName, CratePath};
use ::models::repo::RepoPath;
use ::models::SubjectPath;
use crate::engine::{Engine, AnalyzeDependenciesOutcome};
use crate::models::crates::{CrateName, CratePath};
use crate::models::repo::RepoPath;
use crate::models::SubjectPath;
#[derive(Clone, Copy, PartialEq)]
enum StatusFormat {
@ -68,7 +68,7 @@ impl Service for Server {
type Request = Request;
type Response = Response;
type Error = HyperError;
type Future = Box<Future<Item=Response, Error=HyperError>>;
type Future = Box<dyn Future<Item=Response, Error=HyperError>>;
fn call(&self, req: Request) -> Self::Future {
let logger = self.logger.new(o!("http_path" => req.uri().path().to_owned()));

View file

@ -2,7 +2,7 @@ use badge::{Badge, BadgeOptions};
use hyper::Response;
use hyper::header::ContentType;
use ::engine::AnalyzeDependenciesOutcome;
use crate::engine::AnalyzeDependenciesOutcome;
pub fn badge(analysis_outcome: Option<&AnalyzeDependenciesOutcome>) -> Badge {
let opts = match analysis_outcome {

View file

@ -1,8 +1,8 @@
use hyper::Response;
use maud::{Markup, html};
use ::models::repo::Repository;
use ::models::crates::CratePath;
use crate::models::repo::Repository;
use crate::models::crates::CratePath;
fn popular_table(popular_repos: Vec<Repository>, popular_crates: Vec<CratePath>) -> Markup {
html! {

View file

@ -2,10 +2,10 @@ use hyper::Response;
use maud::{Markup, html};
use indexmap::IndexMap;
use ::engine::AnalyzeDependenciesOutcome;
use ::models::crates::{CrateName, AnalyzedDependency, AnalyzedDependencies};
use ::models::SubjectPath;
use ::models::repo::RepoSite;
use crate::engine::AnalyzeDependenciesOutcome;
use crate::models::crates::{CrateName, AnalyzedDependency, AnalyzedDependencies};
use crate::models::SubjectPath;
use crate::models::repo::RepoSite;
use super::super::badge;

View file

@ -88,7 +88,7 @@ impl<F: Future<Error=Error>> Future for Cached<F> {
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
self.0.poll()
.map_err(|err| (*err).clone())
.map(|async| async.map(CachedItem))
.map(|_async| _async.map(CachedItem))
}
}