mod dart_dependency;
use self::dart_dependency::{DartDependencies, DartDependency};
use crate::retriever::{self, hosted::Retriever};
use futures::prelude::*;
use licensebat_core::{
collector::{RetrievedDependencyStream, RetrievedDependencyStreamResult},
Collector, Comment, Dependency, FileCollector, RetrievedDependency,
};
use std::sync::Arc;
use tracing::instrument;
#[derive(Debug, Clone)]
pub struct Dart<R: Retriever> {
retriever: R,
}
impl Default for Dart<retriever::Hosted> {
fn default() -> Self {
let retriever = retriever::Hosted::default();
Self::new(retriever)
}
}
impl<R: Retriever> Dart<R> {
#[must_use]
pub fn new(hosted_retriever: R) -> Self {
Self {
retriever: hosted_retriever,
}
}
}
impl Dart<retriever::Hosted> {
#[must_use]
pub fn with_hosted_retriever(
client: reqwest::Client,
store: Arc<Option<askalono::Store>>,
) -> Self {
Self::new(retriever::Hosted::new(client, store))
}
}
impl<R: Retriever> Collector for Dart<R> {
fn get_name(&self) -> String {
crate::DART.to_string()
}
}
impl<R: Retriever> FileCollector for Dart<R> {
fn get_dependency_filename(&self) -> String {
"pubspec.lock".to_string()
}
#[instrument(skip(self), level = "debug")]
fn get_dependencies(&self, dependency_file_content: &str) -> RetrievedDependencyStreamResult {
let dependencies = serde_yaml::from_str::<DartDependencies>(dependency_file_content)?
.into_vec_collection();
let futures = dependencies
.into_iter()
.map(|dep| get_dependency(dep, &self.retriever).boxed())
.collect();
Ok(RetrievedDependencyStream::new(futures))
}
}
async fn get_dependency<R: Retriever>(
dependency: DartDependency,
retriever: &R,
) -> RetrievedDependency {
match dependency.source.as_ref() {
"sdk" => resolve_sdk_dependency(&dependency),
"hosted" => resolve_hosted_dependency(dependency, retriever).await,
"git" => resolve_git_dependency(&dependency),
_ => resolve_unknown_dependency(&dependency),
}
}
fn resolve_sdk_dependency(dependency: &DartDependency) -> RetrievedDependency {
retrieved_dependency(
dependency,
Some(vec!["BSD-3-Clause".to_owned()]),
None,
Some("https://github.com/flutter/flutter".to_string()),
Some(Comment::removable("SDK dependency. **You should accept this dependency**. Consider adding **BSD-3-Clause** to the **.licrc** configuration file.")),
None,
)
}
#[allow(clippy::too_many_lines, clippy::single_match_else)]
async fn resolve_hosted_dependency<R: Retriever>(
dependency: DartDependency,
retriever: &R,
) -> RetrievedDependency {
if let Ok(dep) = TryInto::<Dependency>::try_into(dependency.clone()) {
let dep_name = dep.name.clone();
retriever.get_dependency(dep).await.unwrap_or_else(|e| {
let url = format!(
"https://pub.dev/packages/{}/versions/{}",
dep_name, &dependency.version,
);
retrieved_dependency(
&dependency,
None,
Some(e.to_string()),
Some(url),
None,
None,
)
})
} else {
retrieved_dependency(
&dependency,
None,
Some("No name found for this dependency".to_owned()),
None,
None,
None,
)
}
}
fn resolve_git_dependency(dependency: &DartDependency) -> RetrievedDependency {
retrieved_dependency(
dependency,
None,
Some("Git source is not supported".to_string()),
dependency.description.url.clone(),
Some(Comment::removable("Git projects are not supported yet. We're working on it but there are too many different git hosting providers and supporting private repos is hard. We're marking this as **invalid by default** so you check for yourself the validity of the license. Consider **adding this dependency to the ignored list** in the **.licrc** configuration file if you trust the source.")),
None,
)
}
fn resolve_unknown_dependency(dependency: &DartDependency) -> RetrievedDependency {
retrieved_dependency(
dependency,
None,
Some(format!("Not supported source {}", dependency.source)),
None,
None,
None,
)
}
fn retrieved_dependency(
dependency: &DartDependency,
licenses: Option<Vec<String>>,
error: Option<String>,
url: Option<String>,
comment: Option<Comment>,
suggested_licenses: Option<Vec<(String, f32)>>,
) -> RetrievedDependency {
RetrievedDependency::new(
dependency
.description
.name
.as_ref()
.map_or("unknown".to_string(), std::borrow::ToOwned::to_owned),
dependency.version.clone(),
crate::DART.to_string(),
url,
licenses,
error,
comment,
suggested_licenses,
None,
None,
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::collector::dart_dependency::Description;
use crate::retriever;
const LICENSE_CACHE: &[u8] =
std::include_bytes!("../../../licensebat-cli/license-cache.bin.zstd");
#[tokio::test]
async fn integration_check_dependency_supports_license_map_dart_retriever() {
let dependency_name = "flutter_local_notifications_platform_interface";
let dep = DartDependency {
version: "1.0.1".to_string(),
source: "hosted".to_string(), dependency: "direct main".to_string(), description: Description {
path: None,
reference: None,
url: None,
name: Some(dependency_name.to_string()),
},
is_dev: None,
is_optional: None,
};
let store = Arc::new(askalono::Store::from_cache(LICENSE_CACHE).ok());
let retriever = retriever::Hosted::new(reqwest::Client::new(), store);
let res = get_dependency(dep, &retriever).await;
assert_eq!(res.name, dependency_name);
assert!(res.licenses.is_some());
}
}