mirror of
https://github.com/deps-rs/deps.rs.git
synced 2024-11-23 18:56:30 +00:00
Compare commits
2 commits
13f5983c0a
...
dcd41ac5e3
Author | SHA1 | Date | |
---|---|---|---|
|
dcd41ac5e3 | ||
|
85a077e80d |
28 changed files with 1205 additions and 805 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -4,3 +4,6 @@
|
|||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# local environment variables
|
||||
.env
|
||||
|
|
3
.rustfmt.toml
Normal file
3
.rustfmt.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
imports_granularity = "Crate"
|
||||
group_imports = "StdExternalCrate"
|
||||
use_field_init_shorthand = true
|
781
Cargo.lock
generated
781
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
15
Cargo.toml
15
Cargo.toml
|
@ -14,30 +14,33 @@ edition = "2021"
|
|||
[dependencies]
|
||||
badge = { path = "./libs/badge" }
|
||||
|
||||
actix-web = "4"
|
||||
actix-web-lab = "0.20"
|
||||
anyhow = "1"
|
||||
cadence = "1"
|
||||
crates-index = { version = "2", default-features = false, features = ["git"] }
|
||||
derive_more = "0.99"
|
||||
dotenvy = "0.15"
|
||||
font-awesome-as-a-crate = "0.3"
|
||||
futures-util = { version = "0.3", default-features = false, features = ["std"] }
|
||||
hyper = { version = "0.14.10", features = ["full"] }
|
||||
error_reporter = "1"
|
||||
indexmap = { version = "2", features = ["serde"] }
|
||||
lru_time_cache = "0.11"
|
||||
maud = "0.26"
|
||||
mime = "0.3"
|
||||
once_cell = "1"
|
||||
parking_lot = "0.12"
|
||||
pulldown-cmark = "0.11"
|
||||
relative-path = { version = "1", features = ["serde"] }
|
||||
reqwest = { version = "0.12", features = ["json"] }
|
||||
route-recognizer = "0.3"
|
||||
rustsec = "0.29"
|
||||
semver = { version = "1.0", features = ["serde"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_urlencoded = "0.7"
|
||||
slog = "2"
|
||||
slog-async = "2"
|
||||
slog-term = "2"
|
||||
tokio = { version = "1.24.2", features = ["rt-multi-thread", "macros", "sync", "time"] }
|
||||
tokio = { version = "1.24.2", features = ["rt", "macros", "sync", "time"] }
|
||||
toml = "0.8"
|
||||
tracing = "0.1.30"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
|
||||
[target.'cfg(any())'.dependencies]
|
||||
gix = { version = "0.63", default-features = false, features = ["blocking-http-transport-reqwest-rust-tls"] }
|
||||
|
|
4
build.rs
4
build.rs
|
@ -1,6 +1,4 @@
|
|||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::{env, fs, path::Path};
|
||||
|
||||
use sha1::{Digest, Sha1};
|
||||
|
||||
|
|
|
@ -235,8 +235,8 @@ mod tests {
|
|||
#[test]
|
||||
#[ignore]
|
||||
fn test_to_svg() {
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::{fs::File, io::Write as _};
|
||||
|
||||
let mut file = File::create("test.svg").unwrap();
|
||||
let options = BadgeOptions {
|
||||
subject: "latest".to_owned(),
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
use anyhow::Error;
|
||||
use futures_util::{future::BoxFuture, stream::FuturesOrdered, FutureExt as _, StreamExt as _};
|
||||
use futures_util::{
|
||||
future::LocalBoxFuture, stream::FuturesOrdered, FutureExt as _, StreamExt as _,
|
||||
};
|
||||
use relative_path::RelativePathBuf;
|
||||
|
||||
use crate::models::repo::RepoPath;
|
||||
|
||||
use crate::engine::{
|
||||
use crate::{
|
||||
engine::{
|
||||
machines::crawler::{ManifestCrawler, ManifestCrawlerOutput},
|
||||
Engine,
|
||||
},
|
||||
models::repo::RepoPath,
|
||||
};
|
||||
|
||||
pub async fn crawl_manifest(
|
||||
|
@ -15,8 +18,9 @@ pub async fn crawl_manifest(
|
|||
entry_point: RelativePathBuf,
|
||||
) -> anyhow::Result<ManifestCrawlerOutput> {
|
||||
let mut crawler = ManifestCrawler::new();
|
||||
let mut futures: FuturesOrdered<BoxFuture<'static, Result<(RelativePathBuf, String), Error>>> =
|
||||
FuturesOrdered::new();
|
||||
let mut futures: FuturesOrdered<
|
||||
LocalBoxFuture<'static, Result<(RelativePathBuf, String), Error>>,
|
||||
> = FuturesOrdered::new();
|
||||
|
||||
let engine2 = engine.clone();
|
||||
let repo_path2 = repo_path.clone();
|
||||
|
@ -27,7 +31,7 @@ pub async fn crawl_manifest(
|
|||
.await?;
|
||||
Ok((entry_point, contents))
|
||||
}
|
||||
.boxed();
|
||||
.boxed_local();
|
||||
|
||||
futures.push_back(fut);
|
||||
|
||||
|
@ -46,7 +50,7 @@ pub async fn crawl_manifest(
|
|||
let contents = engine.retrieve_manifest_at_path(&repo_path, &path).await?;
|
||||
Ok((path, contents))
|
||||
}
|
||||
.boxed();
|
||||
.boxed_local();
|
||||
|
||||
futures.push_back(fut);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
mod analyze;
|
||||
mod crawl;
|
||||
|
||||
pub use self::analyze::analyze_dependencies;
|
||||
pub use self::crawl::crawl_manifest;
|
||||
pub use self::{analyze::analyze_dependencies, crawl::crawl_manifest};
|
||||
|
|
|
@ -101,9 +101,8 @@ impl DependencyAnalyzer {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::models::crates::{CrateDep, CrateDeps, CrateRelease};
|
||||
|
||||
use super::*;
|
||||
use crate::models::crates::{CrateDep, CrateDeps, CrateRelease};
|
||||
|
||||
#[test]
|
||||
fn tracks_latest_without_matching() {
|
||||
|
|
|
@ -4,8 +4,10 @@ use anyhow::Error;
|
|||
use indexmap::IndexMap;
|
||||
use relative_path::RelativePathBuf;
|
||||
|
||||
use crate::models::crates::{CrateDep, CrateDeps, CrateManifest, CrateName};
|
||||
use crate::parsers::manifest::parse_manifest_toml;
|
||||
use crate::{
|
||||
models::crates::{CrateDep, CrateDeps, CrateManifest, CrateName},
|
||||
parsers::manifest::parse_manifest_toml,
|
||||
};
|
||||
|
||||
pub struct ManifestCrawlerOutput {
|
||||
pub crates: IndexMap<CrateName, CrateDeps>,
|
||||
|
@ -118,9 +120,8 @@ mod tests {
|
|||
use relative_path::RelativePath;
|
||||
use semver::VersionReq;
|
||||
|
||||
use crate::models::crates::CrateDep;
|
||||
|
||||
use super::*;
|
||||
use crate::models::crates::CrateDep;
|
||||
|
||||
#[test]
|
||||
fn simple_package_manifest() {
|
||||
|
|
|
@ -5,36 +5,40 @@ use std::{
|
|||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use actix_web::dev::Service;
|
||||
use anyhow::{anyhow, Error};
|
||||
use cadence::{MetricSink, NopMetricSink, StatsdClient};
|
||||
|
||||
use futures_util::{
|
||||
future::try_join_all,
|
||||
stream::{self, BoxStream},
|
||||
stream::{self, LocalBoxStream},
|
||||
StreamExt as _,
|
||||
};
|
||||
use hyper::service::Service;
|
||||
use once_cell::sync::Lazy;
|
||||
use relative_path::{RelativePath, RelativePathBuf};
|
||||
use rustsec::database::Database;
|
||||
use semver::VersionReq;
|
||||
use slog::Logger;
|
||||
|
||||
use crate::interactors::crates::{GetPopularCrates, QueryCrate};
|
||||
use crate::interactors::github::GetPopularRepos;
|
||||
use crate::interactors::rustsec::FetchAdvisoryDatabase;
|
||||
use crate::interactors::RetrieveFileAtPath;
|
||||
use crate::models::crates::{AnalyzedDependencies, CrateName, CratePath, CrateRelease};
|
||||
use crate::models::repo::{RepoPath, Repository};
|
||||
use crate::utils::cache::Cache;
|
||||
use crate::ManagedIndex;
|
||||
use crate::{
|
||||
interactors::{
|
||||
crates::{GetPopularCrates, QueryCrate},
|
||||
github::GetPopularRepos,
|
||||
rustsec::FetchAdvisoryDatabase,
|
||||
RetrieveFileAtPath,
|
||||
},
|
||||
models::{
|
||||
crates::{AnalyzedDependencies, CrateName, CratePath, CrateRelease},
|
||||
repo::{RepoPath, Repository},
|
||||
},
|
||||
utils::cache::Cache,
|
||||
ManagedIndex,
|
||||
};
|
||||
|
||||
mod fut;
|
||||
mod machines;
|
||||
|
||||
use self::fut::{analyze_dependencies, crawl_manifest};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Engine {
|
||||
metrics: Arc<StatsdClient>,
|
||||
query_crate: Cache<QueryCrate, CrateName>,
|
||||
|
@ -45,33 +49,25 @@ pub struct Engine {
|
|||
}
|
||||
|
||||
impl Engine {
|
||||
pub fn new(client: reqwest::Client, index: ManagedIndex, logger: Logger) -> Engine {
|
||||
pub fn new(client: reqwest::Client, index: ManagedIndex) -> Engine {
|
||||
let metrics = Arc::new(StatsdClient::from_sink("engine", NopMetricSink));
|
||||
|
||||
let query_crate = Cache::new(
|
||||
QueryCrate::new(index),
|
||||
Duration::from_secs(10),
|
||||
500,
|
||||
logger.clone(),
|
||||
);
|
||||
let query_crate = Cache::new(QueryCrate::new(index), Duration::from_secs(10), 500);
|
||||
let get_popular_crates = Cache::new(
|
||||
GetPopularCrates::new(client.clone()),
|
||||
Duration::from_secs(15 * 60),
|
||||
1,
|
||||
logger.clone(),
|
||||
);
|
||||
let get_popular_repos = Cache::new(
|
||||
GetPopularRepos::new(client.clone()),
|
||||
Duration::from_secs(5 * 60),
|
||||
1,
|
||||
logger.clone(),
|
||||
);
|
||||
let retrieve_file_at_path = RetrieveFileAtPath::new(client.clone());
|
||||
let fetch_advisory_db = Cache::new(
|
||||
FetchAdvisoryDatabase::new(client),
|
||||
Duration::from_secs(30 * 60),
|
||||
1,
|
||||
logger,
|
||||
);
|
||||
|
||||
Engine {
|
||||
|
@ -259,7 +255,10 @@ impl Engine {
|
|||
Ok(latest)
|
||||
}
|
||||
|
||||
fn fetch_releases<'a, I>(&'a self, names: I) -> BoxStream<'a, anyhow::Result<Vec<CrateRelease>>>
|
||||
fn fetch_releases<'a, I>(
|
||||
&'a self,
|
||||
names: I,
|
||||
) -> LocalBoxStream<'a, anyhow::Result<Vec<CrateRelease>>>
|
||||
where
|
||||
I: IntoIterator<Item = CrateName>,
|
||||
<I as IntoIterator>::IntoIter: Send + 'a,
|
||||
|
@ -281,7 +280,7 @@ impl Engine {
|
|||
) -> Result<String, Error> {
|
||||
let manifest_path = path.join(RelativePath::new("Cargo.toml"));
|
||||
|
||||
let mut service = self.retrieve_file_at_path.clone();
|
||||
let service = self.retrieve_file_at_path.clone();
|
||||
service.call((repo_path.clone(), manifest_path)).await
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
use std::{fmt, str, task::Context, task::Poll};
|
||||
use std::{fmt, str};
|
||||
|
||||
use actix_web::dev::Service;
|
||||
use anyhow::{anyhow, Error};
|
||||
use crates_index::{Crate, DependencyKind};
|
||||
use futures_util::FutureExt as _;
|
||||
use hyper::service::Service;
|
||||
use futures_util::{future::LocalBoxFuture, FutureExt as _};
|
||||
use semver::{Version, VersionReq};
|
||||
use serde::Deserialize;
|
||||
|
||||
use tokio::task::spawn_blocking;
|
||||
|
||||
use crate::{
|
||||
models::crates::{CrateDep, CrateDeps, CrateName, CratePath, CrateRelease},
|
||||
BoxFuture, ManagedIndex,
|
||||
ManagedIndex,
|
||||
};
|
||||
|
||||
const CRATES_API_BASE_URI: &str = "https://crates.io/api/v1";
|
||||
|
@ -84,13 +83,11 @@ impl fmt::Debug for QueryCrate {
|
|||
impl Service<CrateName> for QueryCrate {
|
||||
type Response = QueryCrateResponse;
|
||||
type Error = Error;
|
||||
type Future = BoxFuture<Result<Self::Response, Self::Error>>;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
actix_web::dev::always_ready!();
|
||||
|
||||
fn call(&mut self, crate_name: CrateName) -> Self::Future {
|
||||
fn call(&self, crate_name: CrateName) -> Self::Future {
|
||||
let index = self.index.clone();
|
||||
Self::query(index, crate_name).boxed()
|
||||
}
|
||||
|
@ -148,13 +145,11 @@ impl fmt::Debug for GetPopularCrates {
|
|||
impl Service<()> for GetPopularCrates {
|
||||
type Response = Vec<CratePath>;
|
||||
type Error = Error;
|
||||
type Future = BoxFuture<Result<Self::Response, Self::Error>>;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
actix_web::dev::always_ready!();
|
||||
|
||||
fn call(&mut self, _req: ()) -> Self::Future {
|
||||
fn call(&self, _req: ()) -> Self::Future {
|
||||
let client = self.client.clone();
|
||||
Self::query(client).boxed()
|
||||
}
|
||||
|
|
|
@ -1,18 +1,11 @@
|
|||
use std::{
|
||||
fmt,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
use actix_web::dev::Service;
|
||||
use anyhow::Error;
|
||||
|
||||
use futures_util::FutureExt as _;
|
||||
use hyper::service::Service;
|
||||
use futures_util::{future::LocalBoxFuture, FutureExt as _};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
models::repo::{RepoPath, Repository},
|
||||
BoxFuture,
|
||||
};
|
||||
use crate::models::repo::{RepoPath, Repository};
|
||||
|
||||
const GITHUB_API_BASE_URI: &str = "https://api.github.com";
|
||||
|
||||
|
@ -73,13 +66,11 @@ impl fmt::Debug for GetPopularRepos {
|
|||
impl Service<()> for GetPopularRepos {
|
||||
type Response = Vec<Repository>;
|
||||
type Error = Error;
|
||||
type Future = BoxFuture<Result<Self::Response, Self::Error>>;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
actix_web::dev::always_ready!();
|
||||
|
||||
fn call(&mut self, _req: ()) -> Self::Future {
|
||||
fn call(&self, _req: ()) -> Self::Future {
|
||||
let client = self.client.clone();
|
||||
Self::query(client).boxed()
|
||||
}
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
use std::{
|
||||
fmt,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
use actix_web::dev::Service;
|
||||
use anyhow::{anyhow, Error};
|
||||
use futures_util::FutureExt as _;
|
||||
use hyper::service::Service;
|
||||
use futures_util::{future::LocalBoxFuture, FutureExt as _};
|
||||
use relative_path::RelativePathBuf;
|
||||
|
||||
use crate::{models::repo::RepoPath, BoxFuture};
|
||||
use crate::models::repo::RepoPath;
|
||||
|
||||
pub mod crates;
|
||||
pub mod github;
|
||||
|
@ -43,13 +40,11 @@ impl RetrieveFileAtPath {
|
|||
impl Service<(RepoPath, RelativePathBuf)> for RetrieveFileAtPath {
|
||||
type Response = String;
|
||||
type Error = Error;
|
||||
type Future = BoxFuture<Result<Self::Response, Self::Error>>;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
actix_web::dev::always_ready!();
|
||||
|
||||
fn call(&mut self, (repo_path, path): (RepoPath, RelativePathBuf)) -> Self::Future {
|
||||
fn call(&self, (repo_path, path): (RepoPath, RelativePathBuf)) -> Self::Future {
|
||||
let client = self.client.clone();
|
||||
Self::query(client, repo_path, path).boxed()
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
use std::{fmt, sync::Arc, task::Context, task::Poll};
|
||||
use std::{fmt, sync::Arc};
|
||||
|
||||
use actix_web::dev::Service;
|
||||
use anyhow::Error;
|
||||
use futures_util::FutureExt as _;
|
||||
use hyper::service::Service;
|
||||
use futures_util::{future::LocalBoxFuture, FutureExt as _};
|
||||
use rustsec::database::Database;
|
||||
|
||||
use crate::BoxFuture;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FetchAdvisoryDatabase {
|
||||
client: reqwest::Client,
|
||||
|
@ -26,20 +24,19 @@ impl FetchAdvisoryDatabase {
|
|||
impl Service<()> for FetchAdvisoryDatabase {
|
||||
type Response = Arc<Database>;
|
||||
type Error = Error;
|
||||
type Future = BoxFuture<Result<Self::Response, Self::Error>>;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
actix_web::dev::always_ready!();
|
||||
|
||||
fn call(&mut self, _req: ()) -> Self::Future {
|
||||
fn call(&self, _req: ()) -> Self::Future {
|
||||
let client = self.client.clone();
|
||||
Self::fetch(client).boxed()
|
||||
Self::fetch(client).boxed_local()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for FetchAdvisoryDatabase {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("FetchAdvisoryDatabase")
|
||||
f.debug_struct("FetchAdvisoryDatabase")
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
|
91
src/main.rs
91
src/main.rs
|
@ -3,21 +3,14 @@
|
|||
|
||||
use std::{
|
||||
env,
|
||||
future::Future,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
|
||||
pin::Pin,
|
||||
net::{Ipv4Addr, UdpSocket},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use actix_web::{middleware::Logger, web};
|
||||
use actix_web_lab::{extract::ThinData, middleware::NormalizePath};
|
||||
use cadence::{QueuingMetricSink, UdpMetricSink};
|
||||
use hyper::{
|
||||
server::conn::AddrStream,
|
||||
service::{make_service_fn, service_fn},
|
||||
Server,
|
||||
};
|
||||
|
||||
use reqwest::redirect::Policy as RedirectPolicy;
|
||||
use slog::{error, info, o, Drain, Logger};
|
||||
|
||||
mod engine;
|
||||
mod interactors;
|
||||
|
@ -26,12 +19,7 @@ mod parsers;
|
|||
mod server;
|
||||
mod utils;
|
||||
|
||||
use self::engine::Engine;
|
||||
use self::server::App;
|
||||
use self::utils::index::ManagedIndex;
|
||||
|
||||
/// Future crate's BoxFuture without the explicit lifetime parameter.
|
||||
pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
|
||||
use self::{engine::Engine, utils::index::ManagedIndex};
|
||||
|
||||
const DEPS_RS_UA: &str = "deps.rs";
|
||||
|
||||
|
@ -43,18 +31,29 @@ fn init_metrics() -> QueuingMetricSink {
|
|||
QueuingMetricSink::from(sink)
|
||||
}
|
||||
|
||||
fn init_root_logger() -> Logger {
|
||||
let decorator = slog_term::TermDecorator::new().build();
|
||||
let drain = slog_term::FullFormat::new(decorator).build().fuse();
|
||||
let drain = slog_async::Async::new(drain).build().fuse();
|
||||
fn init_tracing_subscriber() {
|
||||
use tracing::level_filters::LevelFilter;
|
||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||
|
||||
Logger::root(drain, o!())
|
||||
let stdout_logger = match env::var("RUST_LOG_TIME").as_deref() {
|
||||
Ok("false") => fmt::layer().without_time().boxed(),
|
||||
_ => fmt::layer().boxed(),
|
||||
};
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
EnvFilter::builder()
|
||||
.with_default_directive(LevelFilter::INFO.into())
|
||||
.from_env_lossy(),
|
||||
)
|
||||
.with(stdout_logger)
|
||||
.init();
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() {
|
||||
let logger = init_root_logger();
|
||||
|
||||
dotenvy::dotenv().ok();
|
||||
init_tracing_subscriber();
|
||||
let metrics = init_metrics();
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
|
@ -69,9 +68,7 @@ async fn main() {
|
|||
.parse()
|
||||
.expect("could not read port");
|
||||
|
||||
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port);
|
||||
|
||||
let index = ManagedIndex::new(logger.clone());
|
||||
let index = ManagedIndex::new();
|
||||
|
||||
{
|
||||
let index = index.clone();
|
||||
|
@ -81,27 +78,31 @@ async fn main() {
|
|||
});
|
||||
}
|
||||
|
||||
let mut engine = Engine::new(client.clone(), index, logger.new(o!()));
|
||||
let mut engine = Engine::new(client.clone(), index);
|
||||
engine.set_metrics(metrics);
|
||||
|
||||
let svc_logger = logger.new(o!());
|
||||
let make_svc = make_service_fn(move |_socket: &AddrStream| {
|
||||
let engine = engine.clone();
|
||||
let logger = svc_logger.clone();
|
||||
let server = actix_web::HttpServer::new(move || {
|
||||
actix_web::App::new()
|
||||
.app_data(ThinData(engine.clone()))
|
||||
.service(server::index)
|
||||
.service(server::crate_redirect)
|
||||
.service(server::crate_latest_status_svg)
|
||||
.service(server::crate_status_svg)
|
||||
.service(server::crate_status_html)
|
||||
.service(server::repo_status_svg)
|
||||
.service(server::repo_status_html)
|
||||
.configure(server::static_files)
|
||||
.default_service(web::to(server::not_found))
|
||||
.wrap(NormalizePath::trim())
|
||||
.wrap(Logger::default())
|
||||
})
|
||||
.bind_auto_h2c((Ipv4Addr::UNSPECIFIED, port))
|
||||
.unwrap()
|
||||
.run();
|
||||
|
||||
async move {
|
||||
let server = App::new(logger.clone(), engine.clone());
|
||||
Ok::<_, hyper::Error>(service_fn(move |req| {
|
||||
let server = server.clone();
|
||||
async move { server.handle(req).await }
|
||||
}))
|
||||
}
|
||||
});
|
||||
let server = Server::bind(&addr).serve(make_svc);
|
||||
tracing::info!("Server running on port {port}");
|
||||
|
||||
info!(logger, "Server running on port {}", port);
|
||||
|
||||
if let Err(e) = server.await {
|
||||
error!(logger, "server error: {}", e);
|
||||
if let Err(err) = server.await {
|
||||
tracing::error!("server error: {err}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ impl FromStr for RepoSite {
|
|||
if let Some((site, domain)) = input.split_once('/') {
|
||||
match site {
|
||||
"gitea" => Ok(RepoSite::Gitea(domain.parse()?)),
|
||||
_ => Err(anyhow!("unknown repo site identifier")),
|
||||
site => Err(anyhow!("unknown repo site identifier: {site}")),
|
||||
}
|
||||
} else {
|
||||
match input {
|
||||
|
@ -109,7 +109,7 @@ impl FromStr for RepoSite {
|
|||
"bitbucket" => Ok(RepoSite::Bitbucket),
|
||||
"sourcehut" => Ok(RepoSite::Sourcehut),
|
||||
"codeberg" => Ok(RepoSite::Codeberg),
|
||||
_ => Err(anyhow!("unknown repo site identifier")),
|
||||
site => Err(anyhow!("unknown repo site identifier: {site}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -135,9 +135,8 @@ pub fn parse_manifest_toml(input: &str) -> Result<CrateManifest, Error> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::models::crates::CrateManifest;
|
||||
|
||||
use super::*;
|
||||
use crate::models::crates::CrateManifest;
|
||||
|
||||
#[test]
|
||||
fn parse_workspace_without_members_declaration() {
|
||||
|
|
|
@ -4,11 +4,9 @@ pub const STATIC_STYLE_CSS_PATH: &str = concat!(
|
|||
include_str!(concat!(env!("OUT_DIR"), "/style.css.sha1")),
|
||||
".css"
|
||||
);
|
||||
pub const STATIC_STYLE_CSS_ETAG: &str = concat!(
|
||||
"\"",
|
||||
include_str!(concat!(env!("OUT_DIR"), "/style.css.sha1")),
|
||||
"\""
|
||||
);
|
||||
pub const STATIC_STYLE_CSS_ETAG: &str = include_str!(concat!(env!("OUT_DIR"), "/style.css.sha1"));
|
||||
|
||||
pub const STATIC_FAVICON_PATH: &str = "/static/logo.svg";
|
||||
pub static STATIC_FAVICON: &[u8] = include_bytes!("../../assets/logo.svg");
|
||||
|
||||
pub static STATIC_LINKS_JS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/links.js"));
|
||||
|
@ -17,8 +15,4 @@ pub const STATIC_LINKS_JS_PATH: &str = concat!(
|
|||
include_str!(concat!(env!("OUT_DIR"), "/links.js.sha1")),
|
||||
".js"
|
||||
);
|
||||
pub const STATIC_LINKS_JS_ETAG: &str = concat!(
|
||||
"\"",
|
||||
include_str!(concat!(env!("OUT_DIR"), "/links.js.sha1")),
|
||||
"\""
|
||||
);
|
||||
pub const STATIC_LINKS_JS_ETAG: &str = include_str!(concat!(env!("OUT_DIR"), "/links.js.sha1"));
|
||||
|
|
79
src/server/error.rs
Normal file
79
src/server/error.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
use actix_web::{
|
||||
http::{header::ContentType, StatusCode},
|
||||
HttpResponse, ResponseError,
|
||||
};
|
||||
use derive_more::Display;
|
||||
use maud::Markup;
|
||||
|
||||
use crate::server::views::html::error::{render, render_404};
|
||||
|
||||
#[derive(Debug, Display)]
|
||||
pub(crate) enum ServerError {
|
||||
#[display(fmt = "Could not retrieve popular items")]
|
||||
PopularItemsFailed,
|
||||
|
||||
#[display(fmt = "Crate not found")]
|
||||
CrateNotFound,
|
||||
|
||||
#[display(fmt = "Could not parse crate path")]
|
||||
BadCratePath,
|
||||
|
||||
#[display(fmt = "Could not fetch crate information")]
|
||||
CrateFetchFailed,
|
||||
|
||||
#[display(fmt = "Could not parse repository path")]
|
||||
BadRepoPath,
|
||||
|
||||
#[display(fmt = "Crate/repo analysis failed")]
|
||||
AnalysisFailed(Markup),
|
||||
}
|
||||
|
||||
impl ResponseError for ServerError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
ServerError::PopularItemsFailed => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
ServerError::CrateNotFound => StatusCode::NOT_FOUND,
|
||||
ServerError::BadCratePath => StatusCode::BAD_REQUEST,
|
||||
ServerError::CrateFetchFailed => StatusCode::NOT_FOUND,
|
||||
ServerError::BadRepoPath => StatusCode::BAD_REQUEST,
|
||||
ServerError::AnalysisFailed(_) => StatusCode::BAD_REQUEST,
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
let mut res = HttpResponse::build(self.status_code());
|
||||
let res = res.insert_header(ContentType::html());
|
||||
|
||||
match self {
|
||||
ServerError::PopularItemsFailed => res.body(render(self.to_string(), "").0),
|
||||
|
||||
ServerError::CrateNotFound => res.body(render_404().0),
|
||||
|
||||
ServerError::BadCratePath => res.body(
|
||||
render(
|
||||
self.to_string(),
|
||||
"Please make sure to provide a valid crate name and version.",
|
||||
)
|
||||
.0,
|
||||
),
|
||||
|
||||
ServerError::CrateFetchFailed => res.body(
|
||||
render(
|
||||
self.to_string(),
|
||||
"Please make sure to provide a valid crate name.",
|
||||
)
|
||||
.0,
|
||||
),
|
||||
|
||||
ServerError::BadRepoPath => res.body(
|
||||
render(
|
||||
self.to_string(),
|
||||
"Please make sure to provide a valid repository path.",
|
||||
)
|
||||
.0,
|
||||
),
|
||||
|
||||
Self::AnalysisFailed(html) => res.body(html.0.clone()),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +1,44 @@
|
|||
use std::{env, sync::Arc, time::Instant};
|
||||
use std::env;
|
||||
|
||||
use actix_web::{
|
||||
get,
|
||||
http::{
|
||||
header::{ContentType, ETag, EntityTag},
|
||||
Uri,
|
||||
},
|
||||
web::{Redirect, ServiceConfig},
|
||||
Either, HttpResponse, Resource, Responder,
|
||||
};
|
||||
use actix_web_lab::{
|
||||
extract::{Path, ThinData},
|
||||
header::{CacheControl, CacheDirective},
|
||||
respond::Html,
|
||||
};
|
||||
use assets::STATIC_FAVICON_PATH;
|
||||
use badge::BadgeStyle;
|
||||
use futures_util::future;
|
||||
use hyper::{
|
||||
header::{CACHE_CONTROL, CONTENT_TYPE, ETAG, LOCATION},
|
||||
Body, Error as HyperError, Method, Request, Response, StatusCode,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use route_recognizer::{Params, Router};
|
||||
use semver::VersionReq;
|
||||
use serde::Deserialize;
|
||||
use slog::{error, info, o, Logger};
|
||||
|
||||
mod assets;
|
||||
mod error;
|
||||
mod views;
|
||||
|
||||
use self::assets::{
|
||||
use self::{
|
||||
assets::{
|
||||
STATIC_LINKS_JS_ETAG, STATIC_LINKS_JS_PATH, STATIC_STYLE_CSS_ETAG, STATIC_STYLE_CSS_PATH,
|
||||
},
|
||||
error::ServerError,
|
||||
};
|
||||
use crate::{
|
||||
engine::{AnalyzeDependenciesOutcome, Engine},
|
||||
models::{
|
||||
crates::{CrateName, CratePath},
|
||||
repo::RepoPath,
|
||||
SubjectPath,
|
||||
},
|
||||
};
|
||||
use crate::engine::{AnalyzeDependenciesOutcome, Engine};
|
||||
use crate::models::crates::{CrateName, CratePath};
|
||||
use crate::models::repo::RepoPath;
|
||||
use crate::models::SubjectPath;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum StatusFormat {
|
||||
|
@ -29,255 +46,109 @@ enum StatusFormat {
|
|||
Svg,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum StaticFile {
|
||||
StyleCss,
|
||||
FaviconPng,
|
||||
LinksJs,
|
||||
}
|
||||
|
||||
enum Route {
|
||||
Index,
|
||||
Static(StaticFile),
|
||||
RepoStatus(StatusFormat),
|
||||
CrateRedirect,
|
||||
CrateStatus(StatusFormat),
|
||||
LatestCrateBadge,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct App {
|
||||
logger: Logger,
|
||||
engine: Engine,
|
||||
router: Arc<Router<Route>>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new(logger: Logger, engine: Engine) -> App {
|
||||
let mut router = Router::new();
|
||||
|
||||
router.add("/", Route::Index);
|
||||
|
||||
router.add(STATIC_STYLE_CSS_PATH, Route::Static(StaticFile::StyleCss));
|
||||
router.add("/static/logo.svg", Route::Static(StaticFile::FaviconPng));
|
||||
router.add(STATIC_LINKS_JS_PATH, Route::Static(StaticFile::LinksJs));
|
||||
|
||||
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", Route::CrateRedirect);
|
||||
router.add(
|
||||
"/crate/:name/:version",
|
||||
Route::CrateStatus(StatusFormat::Html),
|
||||
);
|
||||
router.add("/crate/:name/latest/status.svg", Route::LatestCrateBadge);
|
||||
router.add(
|
||||
"/crate/:name/:version/status.svg",
|
||||
Route::CrateStatus(StatusFormat::Svg),
|
||||
);
|
||||
|
||||
App {
|
||||
logger,
|
||||
engine,
|
||||
router: Arc::new(router),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle(&self, req: Request<Body>) -> Result<Response<Body>, HyperError> {
|
||||
let logger = self.logger.new(o!("path" => req.uri().path().to_owned()));
|
||||
let logger2 = logger.clone();
|
||||
let start = Instant::now();
|
||||
|
||||
// allows `/path/` to also match `/path`
|
||||
let normalized_path = req.uri().path().trim_end_matches('/');
|
||||
|
||||
let res = if let Ok(route_match) = self.router.recognize(normalized_path) {
|
||||
match (req.method(), route_match.handler()) {
|
||||
(&Method::GET, Route::Index) => {
|
||||
self.index(req, route_match.params().clone(), logger).await
|
||||
}
|
||||
|
||||
(&Method::GET, Route::RepoStatus(format)) => {
|
||||
self.repo_status(req, route_match.params().clone(), logger, *format)
|
||||
.await
|
||||
}
|
||||
|
||||
(&Method::GET, Route::CrateStatus(format)) => {
|
||||
self.crate_status(req, route_match.params().clone(), logger, *format)
|
||||
.await
|
||||
}
|
||||
|
||||
(&Method::GET, Route::LatestCrateBadge) => {
|
||||
self.crate_status(req, route_match.params().clone(), logger, StatusFormat::Svg)
|
||||
.await
|
||||
}
|
||||
|
||||
(&Method::GET, Route::CrateRedirect) => {
|
||||
self.crate_redirect(req, route_match.params().clone(), logger)
|
||||
.await
|
||||
}
|
||||
|
||||
(&Method::GET, Route::Static(file)) => Ok(App::static_file(*file)),
|
||||
|
||||
_ => Ok(not_found()),
|
||||
}
|
||||
} else {
|
||||
Ok(not_found())
|
||||
};
|
||||
|
||||
let end = Instant::now();
|
||||
let diff = end - start;
|
||||
|
||||
match &res {
|
||||
Ok(res) => info!(
|
||||
logger2, "";
|
||||
"status" => res.status().to_string(),
|
||||
"time" => format!("{}ms", diff.as_millis())
|
||||
),
|
||||
Err(err) => error!(logger2, ""; "error" => err.to_string()),
|
||||
};
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
async fn index(
|
||||
&self,
|
||||
_req: Request<Body>,
|
||||
_params: Params,
|
||||
logger: Logger,
|
||||
) -> Result<Response<Body>, HyperError> {
|
||||
let engine = self.engine.clone();
|
||||
|
||||
let popular =
|
||||
future::try_join(engine.get_popular_repos(), engine.get_popular_crates()).await;
|
||||
#[get("/")]
|
||||
pub(crate) async fn index(ThinData(engine): ThinData<Engine>) -> actix_web::Result<impl Responder> {
|
||||
let popular = future::try_join(engine.get_popular_repos(), engine.get_popular_crates()).await;
|
||||
|
||||
match popular {
|
||||
Err(err) => {
|
||||
error!(logger, "error: {}", err);
|
||||
let mut response =
|
||||
views::html::error::render("Could not retrieve popular items", "");
|
||||
*response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
Ok(response)
|
||||
tracing::error!(%err);
|
||||
Err(ServerError::PopularItemsFailed.into())
|
||||
}
|
||||
Ok((popular_repos, popular_crates)) => {
|
||||
Ok(views::html::index::render(popular_repos, popular_crates))
|
||||
Ok((popular_repos, popular_crates)) => Ok(Html::new(
|
||||
views::html::index::render(popular_repos, popular_crates).0,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/repo/{site:.+?}/{qual}/{name}/status.svg")]
|
||||
pub(crate) async fn repo_status_svg(
|
||||
ThinData(engine): ThinData<Engine>,
|
||||
uri: Uri,
|
||||
Path(params): Path<(String, String, String)>,
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
repo_status(engine, uri, params, StatusFormat::Svg).await
|
||||
}
|
||||
|
||||
#[get("/repo/{site:.+?}/{qual}/{name}")]
|
||||
pub(crate) async fn repo_status_html(
|
||||
ThinData(engine): ThinData<Engine>,
|
||||
uri: Uri,
|
||||
Path(params): Path<(String, String, String)>,
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
repo_status(engine, uri, params, StatusFormat::Html).await
|
||||
}
|
||||
|
||||
async fn repo_status(
|
||||
&self,
|
||||
req: Request<Body>,
|
||||
params: Params,
|
||||
logger: Logger,
|
||||
engine: Engine,
|
||||
uri: Uri,
|
||||
(site, qual, name): (String, String, String),
|
||||
format: StatusFormat,
|
||||
) -> Result<Response<Body>, HyperError> {
|
||||
let server = self.clone();
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
let extra_knobs = ExtraConfig::from_query_string(uri.query());
|
||||
|
||||
let site = params.find("site").expect("route param 'site' not found");
|
||||
let qual = params.find("qual").expect("route param 'qual' not found");
|
||||
let name = params.find("name").expect("route param 'name' not found");
|
||||
let repo_path_result = RepoPath::from_parts(&site, &qual, &name);
|
||||
|
||||
let extra_knobs = ExtraConfig::from_query_string(req.uri().query());
|
||||
|
||||
let repo_path_result = RepoPath::from_parts(site, qual, name);
|
||||
|
||||
match repo_path_result {
|
||||
let repo_path = match repo_path_result {
|
||||
Ok(repo_path) => repo_path,
|
||||
Err(err) => {
|
||||
error!(logger, "error: {}", err);
|
||||
let mut response = views::html::error::render(
|
||||
"Could not parse repository path",
|
||||
"Please make sure to provide a valid repository path.",
|
||||
);
|
||||
*response.status_mut() = StatusCode::BAD_REQUEST;
|
||||
Ok(response)
|
||||
tracing::error!(%err);
|
||||
return Err(ServerError::BadRepoPath.into());
|
||||
}
|
||||
};
|
||||
|
||||
Ok(repo_path) => {
|
||||
let analyze_result = server
|
||||
.engine
|
||||
let analyze_result = engine
|
||||
.analyze_repo_dependencies(repo_path.clone(), &extra_knobs.path)
|
||||
.await;
|
||||
|
||||
match analyze_result {
|
||||
Err(err) => {
|
||||
error!(logger, "error: {}", err);
|
||||
let response = App::status_format_analysis(
|
||||
None,
|
||||
format,
|
||||
SubjectPath::Repo(repo_path),
|
||||
extra_knobs,
|
||||
);
|
||||
tracing::error!(%err);
|
||||
let response =
|
||||
status_format_analysis(None, format, SubjectPath::Repo(repo_path), extra_knobs);
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
Ok(analysis_outcome) => {
|
||||
let response = App::status_format_analysis(
|
||||
let response = status_format_analysis(
|
||||
Some(analysis_outcome),
|
||||
format,
|
||||
SubjectPath::Repo(repo_path),
|
||||
extra_knobs,
|
||||
);
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/crate/:name")]
|
||||
async fn crate_redirect(
|
||||
&self,
|
||||
_req: Request<Body>,
|
||||
params: Params,
|
||||
logger: Logger,
|
||||
) -> Result<Response<Body>, HyperError> {
|
||||
let engine = self.engine.clone();
|
||||
|
||||
let name = params.find("name").expect("route param 'name' not found");
|
||||
ThinData(engine): ThinData<Engine>,
|
||||
Path((name,)): Path<(String,)>,
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
let crate_name_result = name.parse::<CrateName>();
|
||||
|
||||
match crate_name_result {
|
||||
let crate_name = match crate_name_result {
|
||||
Ok(crate_name) => crate_name,
|
||||
Err(err) => {
|
||||
error!(logger, "error: {}", err);
|
||||
let mut response = views::html::error::render(
|
||||
"Could not parse crate name",
|
||||
"Please make sure to provide a valid crate name.",
|
||||
);
|
||||
*response.status_mut() = StatusCode::BAD_REQUEST;
|
||||
Ok(response)
|
||||
tracing::error!(%err);
|
||||
return Err(ServerError::BadCratePath.into());
|
||||
}
|
||||
};
|
||||
|
||||
Ok(crate_name) => {
|
||||
let release_result = engine
|
||||
.find_latest_stable_crate_release(crate_name, VersionReq::STAR)
|
||||
.await;
|
||||
.await
|
||||
.inspect_err(|err| {
|
||||
tracing::error!(%err);
|
||||
});
|
||||
|
||||
let Ok(Some(release)) = release_result else {
|
||||
return Err(ServerError::CrateFetchFailed.into());
|
||||
};
|
||||
|
||||
match release_result {
|
||||
Err(err) => {
|
||||
error!(logger, "error: {}", err);
|
||||
let mut response = views::html::error::render(
|
||||
"Could not fetch crate information",
|
||||
"Please make sure to provide a valid crate name.",
|
||||
);
|
||||
*response.status_mut() = StatusCode::NOT_FOUND;
|
||||
Ok(response)
|
||||
}
|
||||
Ok(None) => {
|
||||
let mut response = views::html::error::render(
|
||||
"Could not fetch crate information",
|
||||
"Please make sure to provide a valid crate name.",
|
||||
);
|
||||
*response.status_mut() = StatusCode::NOT_FOUND;
|
||||
Ok(response)
|
||||
}
|
||||
Ok(Some(release)) => {
|
||||
let redirect_url = format!(
|
||||
"{}/crate/{}/{}",
|
||||
&SELF_BASE_URL as &str,
|
||||
|
@ -285,98 +156,86 @@ impl App {
|
|||
release.version
|
||||
);
|
||||
|
||||
let res = Response::builder()
|
||||
.status(StatusCode::TEMPORARY_REDIRECT)
|
||||
.header(LOCATION, redirect_url)
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
Ok(Redirect::to(redirect_url))
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
#[get("/crate/{name}/{version}")]
|
||||
async fn crate_status_html(
|
||||
ThinData(engine): ThinData<Engine>,
|
||||
uri: Uri,
|
||||
Path((name, version)): Path<(String, String)>,
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
crate_status(engine, uri, (name, Some(version)), StatusFormat::Html).await
|
||||
}
|
||||
|
||||
#[get("/crate/{name}/latest/status.svg")]
|
||||
async fn crate_latest_status_svg(
|
||||
ThinData(engine): ThinData<Engine>,
|
||||
uri: Uri,
|
||||
Path((name,)): Path<(String,)>,
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
crate_status(engine, uri, (name, None), StatusFormat::Svg).await
|
||||
}
|
||||
|
||||
#[get("/crate/{name}/{version}/status.svg")]
|
||||
async fn crate_status_svg(
|
||||
ThinData(engine): ThinData<Engine>,
|
||||
uri: Uri,
|
||||
Path((name, version)): Path<(String, String)>,
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
crate_status(engine, uri, (name, Some(version)), StatusFormat::Svg).await
|
||||
}
|
||||
|
||||
async fn crate_status(
|
||||
&self,
|
||||
req: Request<Body>,
|
||||
params: Params,
|
||||
logger: Logger,
|
||||
engine: Engine,
|
||||
uri: Uri,
|
||||
(name, version): (String, Option<String>),
|
||||
format: StatusFormat,
|
||||
) -> Result<Response<Body>, HyperError> {
|
||||
let server = self.clone();
|
||||
|
||||
let name = params.find("name").expect("route param 'name' not found");
|
||||
|
||||
let version = match params.find("version") {
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
let version = match version {
|
||||
Some(ver) => ver.to_owned(),
|
||||
None => {
|
||||
let crate_name = match name.parse() {
|
||||
Ok(name) => name,
|
||||
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.status_mut() = StatusCode::BAD_REQUEST;
|
||||
return Ok(response);
|
||||
}
|
||||
Err(_) => return Err(ServerError::BadCratePath.into()),
|
||||
};
|
||||
|
||||
match server
|
||||
.engine
|
||||
match engine
|
||||
.find_latest_stable_crate_release(crate_name, VersionReq::STAR)
|
||||
.await
|
||||
{
|
||||
Ok(Some(latest_rel)) => latest_rel.version.to_string(),
|
||||
Ok(None) => return Ok(not_found()),
|
||||
|
||||
Ok(None) => return Err(ServerError::CrateNotFound.into()),
|
||||
|
||||
Err(err) => {
|
||||
error!(logger, "error: {}", err);
|
||||
let mut response = views::html::error::render(
|
||||
"Could not fetch crate information",
|
||||
"Please make sure to provide a valid crate name.",
|
||||
);
|
||||
*response.status_mut() = StatusCode::NOT_FOUND;
|
||||
return Ok(response);
|
||||
tracing::error!(%err);
|
||||
return Err(ServerError::CrateFetchFailed.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let crate_path_result = CratePath::from_parts(name, &version);
|
||||
let badge_knobs = ExtraConfig::from_query_string(req.uri().query());
|
||||
let crate_path_result = CratePath::from_parts(&name, &version);
|
||||
let badge_knobs = ExtraConfig::from_query_string(uri.query());
|
||||
|
||||
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.status_mut() = StatusCode::BAD_REQUEST;
|
||||
Ok(response)
|
||||
tracing::error!(%err);
|
||||
Err(ServerError::BadCratePath.into())
|
||||
}
|
||||
|
||||
Ok(crate_path) => {
|
||||
let analyze_result = server
|
||||
.engine
|
||||
let analysis_outcome = engine
|
||||
.analyze_crate_dependencies(crate_path.clone())
|
||||
.await;
|
||||
.await
|
||||
.inspect_err(|err| {
|
||||
tracing::error!(%err);
|
||||
})
|
||||
.ok();
|
||||
|
||||
match analyze_result {
|
||||
Err(err) => {
|
||||
error!(logger, "error: {}", err);
|
||||
let response = App::status_format_analysis(
|
||||
None,
|
||||
format,
|
||||
SubjectPath::Crate(crate_path),
|
||||
badge_knobs,
|
||||
);
|
||||
Ok(response)
|
||||
}
|
||||
Ok(analysis_outcome) => {
|
||||
let response = App::status_format_analysis(
|
||||
Some(analysis_outcome),
|
||||
let response = status_format_analysis(
|
||||
analysis_outcome,
|
||||
format,
|
||||
SubjectPath::Crate(crate_path),
|
||||
badge_knobs,
|
||||
|
@ -386,47 +245,61 @@ impl App {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn status_format_analysis(
|
||||
analysis_outcome: Option<AnalyzeDependenciesOutcome>,
|
||||
format: StatusFormat,
|
||||
subject_path: SubjectPath,
|
||||
badge_knobs: ExtraConfig,
|
||||
) -> Response<Body> {
|
||||
) -> impl Responder {
|
||||
match format {
|
||||
StatusFormat::Svg => views::badge::response(analysis_outcome.as_ref(), badge_knobs),
|
||||
StatusFormat::Html => {
|
||||
views::html::status::render(analysis_outcome, subject_path, badge_knobs)
|
||||
}
|
||||
StatusFormat::Svg => Either::Left(views::badge::response(
|
||||
analysis_outcome.as_ref(),
|
||||
badge_knobs,
|
||||
)),
|
||||
|
||||
StatusFormat::Html => Either::Right(views::html::status::response(
|
||||
analysis_outcome,
|
||||
subject_path,
|
||||
badge_knobs,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn static_file(file: StaticFile) -> Response<Body> {
|
||||
match file {
|
||||
StaticFile::StyleCss => Response::builder()
|
||||
.header(CONTENT_TYPE, "text/css; charset=utf-8")
|
||||
.header(ETAG, STATIC_STYLE_CSS_ETAG)
|
||||
.header(CACHE_CONTROL, "public, max-age=365000000, immutable")
|
||||
.body(Body::from(assets::STATIC_STYLE_CSS))
|
||||
.unwrap(),
|
||||
StaticFile::FaviconPng => Response::builder()
|
||||
.header(CONTENT_TYPE, "image/svg+xml")
|
||||
.body(Body::from(assets::STATIC_FAVICON))
|
||||
.unwrap(),
|
||||
StaticFile::LinksJs => Response::builder()
|
||||
.header(CONTENT_TYPE, "text/javascript; charset=utf-8")
|
||||
.header(ETAG, STATIC_LINKS_JS_ETAG)
|
||||
.header(CACHE_CONTROL, "public, max-age=365000000, immutable")
|
||||
.body(Body::from(assets::STATIC_LINKS_JS))
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
pub(crate) fn static_files(cfg: &mut ServiceConfig) {
|
||||
cfg.service(Resource::new(STATIC_STYLE_CSS_PATH).to(|| async {
|
||||
HttpResponse::Ok()
|
||||
.insert_header(ContentType(mime::TEXT_CSS_UTF_8))
|
||||
.insert_header(ETag(EntityTag::new_strong(
|
||||
STATIC_STYLE_CSS_ETAG.to_owned(),
|
||||
)))
|
||||
.insert_header(CacheControl(vec![
|
||||
CacheDirective::Public,
|
||||
CacheDirective::MaxAge(365000000),
|
||||
CacheDirective::Immutable,
|
||||
]))
|
||||
.body(assets::STATIC_STYLE_CSS)
|
||||
}))
|
||||
.service(Resource::new(STATIC_FAVICON_PATH).to(|| async {
|
||||
HttpResponse::Ok()
|
||||
.insert_header(ContentType(mime::IMAGE_SVG))
|
||||
.body(assets::STATIC_FAVICON)
|
||||
}))
|
||||
.service(Resource::new(STATIC_LINKS_JS_PATH).get(|| async {
|
||||
HttpResponse::Ok()
|
||||
.insert_header(ContentType(mime::APPLICATION_JAVASCRIPT_UTF_8))
|
||||
.insert_header(ETag(EntityTag::new_strong(STATIC_LINKS_JS_ETAG.to_owned())))
|
||||
.insert_header(CacheControl(vec![
|
||||
CacheDirective::Public,
|
||||
CacheDirective::MaxAge(365000000),
|
||||
CacheDirective::Immutable,
|
||||
]))
|
||||
.body(assets::STATIC_LINKS_JS)
|
||||
}));
|
||||
}
|
||||
|
||||
fn not_found() -> Response<Body> {
|
||||
views::html::error::render_404()
|
||||
pub(crate) async fn not_found() -> impl Responder {
|
||||
Html::new(views::html::error::render_404().0)
|
||||
}
|
||||
|
||||
static SELF_BASE_URL: Lazy<String> =
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
use actix_web::{http::header::ContentType, HttpResponse};
|
||||
use badge::{Badge, BadgeOptions};
|
||||
use hyper::header::CONTENT_TYPE;
|
||||
use hyper::{Body, Response};
|
||||
|
||||
use crate::engine::AnalyzeDependenciesOutcome;
|
||||
use crate::server::ExtraConfig;
|
||||
use crate::{engine::AnalyzeDependenciesOutcome, server::ExtraConfig};
|
||||
|
||||
pub fn badge(
|
||||
analysis_outcome: Option<&AnalyzeDependenciesOutcome>,
|
||||
|
@ -75,11 +73,10 @@ pub fn badge(
|
|||
pub fn response(
|
||||
analysis_outcome: Option<&AnalyzeDependenciesOutcome>,
|
||||
badge_knobs: ExtraConfig,
|
||||
) -> Response<Body> {
|
||||
) -> HttpResponse {
|
||||
let badge = badge(analysis_outcome, badge_knobs).to_svg();
|
||||
|
||||
Response::builder()
|
||||
.header(CONTENT_TYPE, "image/svg+xml; charset=utf-8")
|
||||
.body(Body::from(badge))
|
||||
.unwrap()
|
||||
HttpResponse::Ok()
|
||||
.insert_header(ContentType(mime::IMAGE_SVG))
|
||||
.body(badge)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
use hyper::{
|
||||
header::{CACHE_CONTROL, CONTENT_TYPE},
|
||||
Body, Response, StatusCode,
|
||||
};
|
||||
use maud::html;
|
||||
use maud::{html, Markup};
|
||||
|
||||
use crate::server::assets::STATIC_STYLE_CSS_PATH;
|
||||
|
||||
pub fn render(title: &str, descr: &str) -> Response<Body> {
|
||||
pub fn render(title: impl Into<String>, desc: &str) -> Markup {
|
||||
let title = title.into();
|
||||
|
||||
super::render_html(
|
||||
title,
|
||||
title.clone(),
|
||||
html! {
|
||||
section class="hero is-light" {
|
||||
div class="hero-head" { (super::render_navbar()) }
|
||||
|
@ -17,7 +15,7 @@ pub fn render(title: &str, descr: &str) -> Response<Body> {
|
|||
div class="container" {
|
||||
div class="notification is-danger" {
|
||||
p class="title is-3" { (title) }
|
||||
p { (descr) }
|
||||
p { (desc) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,8 +24,8 @@ pub fn render(title: &str, descr: &str) -> Response<Body> {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn render_404() -> Response<Body> {
|
||||
let rendered = html! {
|
||||
pub fn render_404() -> Markup {
|
||||
html! {
|
||||
html {
|
||||
head {
|
||||
meta charset="utf-8";
|
||||
|
@ -53,12 +51,5 @@ pub fn render_404() -> Response<Body> {
|
|||
(super::render_footer(None))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.header(CONTENT_TYPE, "text/html; charset=utf-8")
|
||||
.header(CACHE_CONTROL, "public, max-age=300, immutable")
|
||||
.body(Body::from(rendered.0))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
use hyper::{Body, Response};
|
||||
use maud::{html, Markup};
|
||||
|
||||
use crate::models::crates::CratePath;
|
||||
use crate::models::repo::Repository;
|
||||
|
||||
use crate::server::assets::STATIC_LINKS_JS_PATH;
|
||||
use crate::{
|
||||
models::{crates::CratePath, repo::Repository},
|
||||
server::assets::STATIC_LINKS_JS_PATH,
|
||||
};
|
||||
|
||||
fn link_forms() -> Markup {
|
||||
html! {
|
||||
|
@ -161,7 +160,7 @@ fn popular_table(popular_repos: Vec<Repository>, popular_crates: Vec<CratePath>)
|
|||
}
|
||||
}
|
||||
|
||||
pub fn render(popular_repos: Vec<Repository>, popular_crates: Vec<CratePath>) -> Response<Body> {
|
||||
pub fn render(popular_repos: Vec<Repository>, popular_crates: Vec<CratePath>) -> Markup {
|
||||
super::render_html(
|
||||
"Keep your dependencies up-to-date",
|
||||
html! {
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use hyper::header::CONTENT_TYPE;
|
||||
use hyper::{Body, Response};
|
||||
use maud::{html, Markup, Render, DOCTYPE};
|
||||
|
||||
pub mod error;
|
||||
pub mod index;
|
||||
pub mod status;
|
||||
|
||||
use crate::server::assets::STATIC_STYLE_CSS_PATH;
|
||||
use crate::server::SELF_BASE_URL;
|
||||
use crate::server::{assets::STATIC_STYLE_CSS_PATH, SELF_BASE_URL};
|
||||
|
||||
fn render_html<B: Render>(title: &str, body: B) -> Response<Body> {
|
||||
let rendered = html! {
|
||||
fn render_html<B: Render>(title: impl Into<String>, body: B) -> Markup {
|
||||
let title = title.into();
|
||||
|
||||
html! {
|
||||
(DOCTYPE)
|
||||
html {
|
||||
head {
|
||||
|
@ -26,12 +25,7 @@ fn render_html<B: Render>(title: &str, body: B) -> Response<Body> {
|
|||
}
|
||||
body { (body) }
|
||||
}
|
||||
};
|
||||
|
||||
Response::builder()
|
||||
.header(CONTENT_TYPE, "text/html; charset=utf-8")
|
||||
.body(Body::from(rendered.0))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn render_navbar() -> Markup {
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
use actix_web::Responder;
|
||||
use actix_web_lab::respond::Html;
|
||||
use font_awesome_as_a_crate::{svg as fa, Type as FaType};
|
||||
use hyper::{Body, Response};
|
||||
use indexmap::IndexMap;
|
||||
use maud::{html, Markup, PreEscaped};
|
||||
use pulldown_cmark::{html, Parser};
|
||||
use rustsec::advisory::Advisory;
|
||||
use semver::Version;
|
||||
|
||||
use crate::engine::AnalyzeDependenciesOutcome;
|
||||
use crate::models::crates::{AnalyzedDependencies, AnalyzedDependency, CrateName};
|
||||
use crate::models::repo::RepoSite;
|
||||
use crate::models::SubjectPath;
|
||||
use crate::server::views::badge;
|
||||
use crate::server::ExtraConfig;
|
||||
use crate::{
|
||||
engine::AnalyzeDependenciesOutcome,
|
||||
models::{
|
||||
crates::{AnalyzedDependencies, AnalyzedDependency, CrateName},
|
||||
repo::RepoSite,
|
||||
SubjectPath,
|
||||
},
|
||||
server::{error::ServerError, views::badge, ExtraConfig},
|
||||
};
|
||||
|
||||
use super::render_html;
|
||||
|
||||
fn get_crates_url(name: impl AsRef<str>) -> String {
|
||||
format!("https://crates.io/crates/{}", name.as_ref())
|
||||
|
@ -450,11 +456,11 @@ fn render_success(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
pub fn response(
|
||||
analysis_outcome: Option<AnalyzeDependenciesOutcome>,
|
||||
subject_path: SubjectPath,
|
||||
extra_config: ExtraConfig,
|
||||
) -> Response<Body> {
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
let title = match subject_path {
|
||||
SubjectPath::Repo(ref repo_path) => {
|
||||
format!("{} / {}", repo_path.qual.as_ref(), repo_path.name.as_ref())
|
||||
|
@ -465,8 +471,12 @@ pub fn render(
|
|||
};
|
||||
|
||||
if let Some(outcome) = analysis_outcome {
|
||||
super::render_html(&title, render_success(outcome, subject_path, extra_config))
|
||||
Ok(Html::new(render_html(
|
||||
&title,
|
||||
render_success(outcome, subject_path, extra_config),
|
||||
)))
|
||||
} else {
|
||||
super::render_html(&title, render_failure(subject_path))
|
||||
let html = render_html(&title, render_failure(subject_path));
|
||||
Err(ServerError::AnalysisFailed(html).into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use std::{fmt, sync::Arc, time::Duration};
|
||||
|
||||
use actix_web::dev::Service;
|
||||
use derive_more::{Display, Error, From};
|
||||
use hyper::service::Service;
|
||||
use lru_time_cache::LruCache;
|
||||
use slog::{debug, Logger};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[derive(Debug, Clone, Display, From, Error)]
|
||||
|
@ -18,7 +17,6 @@ where
|
|||
{
|
||||
inner: S,
|
||||
cache: Arc<Mutex<LruCache<Req, S::Response>>>,
|
||||
logger: Logger,
|
||||
}
|
||||
|
||||
impl<S, Req> fmt::Debug for Cache<S, Req>
|
||||
|
@ -38,13 +36,12 @@ where
|
|||
S::Response: Clone,
|
||||
Req: Clone + Eq + Ord + fmt::Debug,
|
||||
{
|
||||
pub fn new(service: S, ttl: Duration, capacity: usize, logger: Logger) -> Cache<S, Req> {
|
||||
pub fn new(service: S, ttl: Duration, capacity: usize) -> Cache<S, Req> {
|
||||
let cache = LruCache::with_expiry_duration_and_capacity(ttl, capacity);
|
||||
|
||||
Cache {
|
||||
inner: service,
|
||||
cache: Arc::new(Mutex::new(cache)),
|
||||
logger,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,22 +50,22 @@ where
|
|||
let mut cache = self.cache.lock().await;
|
||||
|
||||
if let Some(cached_response) = cache.get(&req) {
|
||||
debug!(
|
||||
self.logger, "cache hit";
|
||||
"svc" => format!("{:?}", self.inner),
|
||||
"req" => format!("{:?}", &req)
|
||||
tracing::debug!(
|
||||
svc = ?self.inner,
|
||||
req = ?req,
|
||||
cache = "hit",
|
||||
);
|
||||
return Ok(cached_response.clone());
|
||||
}
|
||||
}
|
||||
|
||||
debug!(
|
||||
self.logger, "cache miss";
|
||||
"svc" => format!("{:?}", self.inner),
|
||||
"req" => format!("{:?}", &req)
|
||||
tracing::debug!(
|
||||
svc = ?self.inner,
|
||||
req = ?req,
|
||||
cache = "miss",
|
||||
);
|
||||
|
||||
let mut service = self.inner.clone();
|
||||
let service = self.inner.clone();
|
||||
let fresh = service.call(req.clone()).await?;
|
||||
|
||||
{
|
||||
|
|
|
@ -1,32 +1,30 @@
|
|||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use anyhow::Result;
|
||||
use crates_index::{Crate, GitIndex};
|
||||
use parking_lot::Mutex;
|
||||
use tokio::{
|
||||
task::spawn_blocking,
|
||||
time::{self, MissedTickBehavior},
|
||||
};
|
||||
|
||||
use crate::models::crates::CrateName;
|
||||
use anyhow::Result;
|
||||
use crates_index::Crate;
|
||||
use crates_index::GitIndex;
|
||||
use slog::{error, Logger};
|
||||
use tokio::task::spawn_blocking;
|
||||
use tokio::time::{self, MissedTickBehavior};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ManagedIndex {
|
||||
index: Arc<Mutex<GitIndex>>,
|
||||
logger: Logger,
|
||||
}
|
||||
|
||||
impl ManagedIndex {
|
||||
pub fn new(logger: Logger) -> Self {
|
||||
pub fn new() -> Self {
|
||||
// the index path is configurable through the `CARGO_HOME` env variable
|
||||
let index = Arc::new(Mutex::new(GitIndex::new_cargo_default().unwrap()));
|
||||
Self { index, logger }
|
||||
|
||||
Self { index }
|
||||
}
|
||||
|
||||
pub fn crate_(&self, crate_name: CrateName) -> Option<Crate> {
|
||||
let index = self.index.lock().unwrap();
|
||||
|
||||
index.crate_(crate_name.as_ref())
|
||||
self.index.lock().crate_(crate_name.as_ref())
|
||||
}
|
||||
|
||||
pub async fn refresh_at_interval(&self, update_interval: Duration) {
|
||||
|
@ -34,25 +32,23 @@ impl ManagedIndex {
|
|||
update_interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
|
||||
|
||||
loop {
|
||||
if let Err(e) = self.refresh().await {
|
||||
error!(
|
||||
self.logger,
|
||||
"failed refreshing the crates.io-index, the operation will be retried: {}", e
|
||||
if let Err(err) = self.refresh().await {
|
||||
tracing::error!(
|
||||
"failed refreshing the crates.io-index, the operation will be retried: {}",
|
||||
error_reporter::Report::new(err),
|
||||
);
|
||||
}
|
||||
update_interval.tick().await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn refresh(&self) -> Result<()> {
|
||||
async fn refresh(&self) -> Result<(), crates_index::Error> {
|
||||
let index = Arc::clone(&self.index);
|
||||
|
||||
spawn_blocking(move || {
|
||||
let mut index = index.lock().unwrap();
|
||||
spawn_blocking(move || index.lock().update())
|
||||
.await
|
||||
.expect("blocking index update task should never panic")?;
|
||||
|
||||
index.update()
|
||||
})
|
||||
.await??;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue