1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//! Module for working with SPDX license data
//!
//! See `https://github.com/spdx/license-list-data/blob/main/accessingLicenses.md` for more information on the SPDX license list and how to access it.
//!
//! See `https://spdx.org/rdf/terms/` for SPDX RDF terms.
use crate::io::{
api::{DatabasePersistence, RemoteResource, INCLUDED_ENDPOINTS},
database::{
schema::{LicenseRow, Table},
Database, Operations,
},
with_progress, ApiResult, ProgressType,
};
use acorn_core::util::Searchable;
use acorn_host::terminal::Label;
use acorn_schema::validation::Validate;
use async_trait::async_trait;
use color_eyre::eyre::{eyre, Report};
use serde::{Deserialize, Serialize};
/// SPDX license metadata
#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)]
#[serde(rename_all = "camelCase")]
pub struct LicenseMetadata {
/// SPDX license identifier
pub license_id: String,
/// Name of the license
pub name: String,
/// URL to a JSON file containing the license detailed information
#[validate(url)]
pub details_url: String,
/// Reference to the HTML format for the license file
#[validate(url)]
pub reference: String,
/// Deprecated - this field is generated and is no longer in use
pub reference_number: u32,
/// Cross reference URL(s) pointing to additional copies of the license
#[validate(each(url))]
pub see_also: Vec<String>,
/// Specifies whether the license is deprecated
/// ### Note
/// This isn't actually the best name for this particular field
#[serde(rename = "isDeprecatedLicenseId")]
pub is_deprecated: bool,
/// Specifies whether the License is listed as free by the Free Software Foundation (FSF)
///
/// See <https://www.fsf.org/licensing/compliance> for more information
#[serde(default, rename = "isFsfLibre")]
pub is_free_software: bool,
/// Specifies whether the license is approved by the Open Source Initiative (OSI)
#[serde(rename = "isOsiApproved")]
pub is_osi: bool,
}
/// Struct for SPDX license list data
#[derive(Clone, Debug, Deserialize, Serialize, Validate)]
#[serde(rename_all = "camelCase")]
#[derive(Default)]
pub struct LicensesResponse {
/// Version of the SPDX License List
#[validate(version)]
pub license_list_version: Option<String>,
/// Licenses
#[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 {
/// Check if the license is OSI approved
pub fn is_open_source(&self) -> bool {
self.is_osi
}
}
#[async_trait]
impl DatabasePersistence for LicensesResponse {
/// Persist SPDX license data to local database
/// ### Example
/// ```ignore
/// use acorn::io::api::spdx;
/// use acorn::io::database::schema::Table;
/// use acorn::io::database::{Database, Operations};
///
/// let licenses = spdx::download().await.ok().unwrap();
/// let database = Database::<Table>::from_path(database_path.clone());
/// let _ = database.persist(Some(licenses));
/// ```
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)
}
}
/// Download SPDX license data from homepage
/// ### Example
/// ```ignore
/// use acorn::io::api::spdx;
///
/// let result = spdx::download().await;
/// ```
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")),
}
}