use crate::io::api::{DatabasePersistence, RemoteResource, INCLUDED_ENDPOINTS};
use crate::io::database::schema::{LicenseRow, Table};
use crate::io::database::{Database, Operations};
use crate::io::{with_progress, ApiResult, ProgressType};
use crate::schema::validate::{is_semantic_version, is_urls};
use crate::util::{Label, Searchable};
use async_trait::async_trait;
use color_eyre::eyre::{eyre, Report};
use serde::{Deserialize, Serialize};
use validator::Validate;
#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)]
#[serde(rename_all = "camelCase")]
pub struct LicenseMetadata {
pub license_id: String,
pub name: String,
#[validate(url)]
pub details_url: String,
#[validate(url)]
pub reference: String,
pub reference_number: u32,
#[validate(custom(function = "is_urls"))]
pub see_also: Vec<String>,
#[serde(rename = "isDeprecatedLicenseId")]
pub is_deprecated: bool,
#[serde(default, rename = "isFsfLibre")]
pub is_free_software: bool,
#[serde(rename = "isOsiApproved")]
pub is_osi: bool,
}
#[derive(Clone, Debug, Deserialize, Serialize, Validate)]
#[serde(rename_all = "camelCase")]
#[derive(Default)]
pub struct LicensesResponse {
#[validate(custom(function = "is_semantic_version"))]
pub license_list_version: Option<String>,
#[validate(nested)]
pub licenses: Vec<LicenseMetadata>,
}
impl From<LicenseMetadata> for LicenseRow {
fn from(meta: LicenseMetadata) -> Self {
let LicenseMetadata {
license_id,
name,
is_deprecated,
is_free_software,
is_osi,
..
} = meta;
LicenseRow::init()
.identifier(license_id)
.name(name)
.is_deprecated(is_deprecated)
.is_free_software(is_free_software)
.is_open_source(is_osi)
.build()
}
}
impl LicenseMetadata {
pub fn is_open_source(&self) -> bool {
self.is_osi
}
}
#[async_trait]
impl DatabasePersistence for LicensesResponse {
async fn persist(self, database: Database<Table>) -> ApiResult<usize> {
let Self { licenses, .. } = self;
let message: fn(&LicenseMetadata) -> String = |item| format!("Saving \"{}\" license metadata", item.license_id);
let operation = |item| async { database.insert(LicenseRow::from(item)) };
let finish = |count| format!("{}Saved metadata for {count} SPDX licenses", Label::CHECKMARK);
with_progress(licenses, message, operation, finish, None, ProgressType::Bar)
.await
.map(|counts| counts.into_iter().sum())
.map_err(Report::msg)
}
}
pub async fn download() -> ApiResult<LicensesResponse> {
match INCLUDED_ENDPOINTS.find_by_name("spdx") {
| Some(endpoint) => {
let response = endpoint.invoke("licenses", None).await;
endpoint.handle::<LicensesResponse>(response)
}
| None => Err(eyre!("No SPDX endpoint found")),
}
}