1use crate::io::api::{DatabasePersistence, RemoteResource, INCLUDED_ENDPOINTS};
7use crate::io::database::schema::{LicenseRow, Table};
8use crate::io::database::{Database, Operations};
9use crate::io::{with_progress, ApiResult, ProgressType};
10use crate::schema::validate::{is_semantic_version, is_urls};
11use crate::util::{Label, Searchable};
12use async_trait::async_trait;
13use color_eyre::eyre::{eyre, Report};
14use serde::{Deserialize, Serialize};
15use validator::Validate;
16
17#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)]
19#[serde(rename_all = "camelCase")]
20pub struct LicenseMetadata {
21 pub license_id: String,
23 pub name: String,
25 #[validate(url)]
27 pub details_url: String,
28 #[validate(url)]
30 pub reference: String,
31 pub reference_number: u32,
33 #[validate(custom(function = "is_urls"))]
35 pub see_also: Vec<String>,
36 #[serde(rename = "isDeprecatedLicenseId")]
40 pub is_deprecated: bool,
41 #[serde(default, rename = "isFsfLibre")]
45 pub is_free_software: bool,
46 #[serde(rename = "isOsiApproved")]
48 pub is_osi: bool,
49}
50#[derive(Clone, Debug, Deserialize, Serialize, Validate)]
52#[serde(rename_all = "camelCase")]
53#[derive(Default)]
54pub struct LicensesResponse {
55 #[validate(custom(function = "is_semantic_version"))]
57 pub license_list_version: Option<String>,
58 #[validate(nested)]
60 pub licenses: Vec<LicenseMetadata>,
61}
62impl From<LicenseMetadata> for LicenseRow {
63 fn from(meta: LicenseMetadata) -> Self {
64 let LicenseMetadata {
65 license_id,
66 name,
67 is_deprecated,
68 is_free_software,
69 is_osi,
70 ..
71 } = meta;
72 LicenseRow::init()
73 .identifier(license_id)
74 .name(name)
75 .is_deprecated(is_deprecated)
76 .is_free_software(is_free_software)
77 .is_open_source(is_osi)
78 .build()
79 }
80}
81impl LicenseMetadata {
82 pub fn is_open_source(&self) -> bool {
84 self.is_osi
85 }
86}
87#[async_trait]
88impl DatabasePersistence for LicensesResponse {
89 async fn persist(self, database: Database<Table>) -> ApiResult<usize> {
101 let Self { licenses, .. } = self;
102 let message: fn(&LicenseMetadata) -> String = |item| format!("Saving \"{}\" license metadata", item.license_id);
103 let operation = |item| async { database.insert(LicenseRow::from(item)) };
104 let finish = |count| format!("{}Saved metadata for {count} SPDX licenses", Label::CHECKMARK);
105 with_progress(licenses, message, operation, finish, None, ProgressType::Bar)
106 .await
107 .map(|counts| counts.into_iter().sum())
108 .map_err(Report::msg)
109 }
110}
111pub async fn download() -> ApiResult<LicensesResponse> {
119 match INCLUDED_ENDPOINTS.find_by_name("spdx") {
120 | Some(endpoint) => {
121 let response = endpoint.invoke("licenses", None).await;
122 endpoint.handle::<LicensesResponse>(response)
123 }
124 | None => Err(eyre!("No SPDX endpoint found")),
125 }
126}