use merge::Merge;
use serde::Deserialize;
#[derive(Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CrossRef {
pub r#match: String,
pub url: String,
pub is_valid: bool,
pub is_live: bool,
pub timestamp: String,
pub is_way_back_link: bool,
pub order: u32,
}
#[derive(Merge, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct License {
#[merge(skip)]
pub name: String,
#[merge(skip)]
pub license_id: String,
#[merge(skip)]
pub see_also: Vec<String>,
#[merge(skip)]
pub is_osi_approved: bool,
#[merge(skip)]
pub is_fsf_libre: Option<bool>,
#[merge(skip)]
pub is_deprecated_license_id: bool,
pub reference: Option<String>,
pub details_url: Option<String>,
pub reference_number: Option<u32>,
pub license_text: Option<String>,
pub standard_license_template: Option<String>,
pub license_text_html: Option<String>,
pub cross_ref: Option<Vec<CrossRef>>,
}
#[derive(Default, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Licenses {
pub license_list_version: String,
pub licenses: Vec<License>,
pub release_date: String,
}
impl Licenses {
pub fn match_license(&self, id: &str) -> Option<License> {
self.match_licenses(id, None).first().cloned()
}
pub fn match_licenses(&self, id: &str, n: Option<usize>) -> Vec<License> {
let mut similar: Vec<_> = self
.licenses
.iter()
.map(|license| {
(
strsim::jaro_winkler(
&license.license_id.clone().to_lowercase(),
&id.to_lowercase(),
),
license,
)
})
.collect();
similar.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap());
let count = n.unwrap_or(similar.len());
similar[0..count]
.iter()
.map(|(_, license)| license.to_owned().clone())
.collect()
}
pub fn get_license(&self, id: &str) -> Option<&License> {
self.licenses
.iter()
.find(|license| license.license_id == id)
}
pub fn get_license_case_insensitive(&self, id: &str) -> Option<&License> {
self.licenses
.iter()
.find(|&license| license.license_id.to_lowercase() == id.to_lowercase())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_merge_license_with_details() {
let mut license = License {
name: String::from("MIT License"),
license_id: String::from("MIT"),
see_also: vec![String::from("https://opensource.org/licenses/MIT")],
is_osi_approved: true,
is_fsf_libre: Some(true),
is_deprecated_license_id: false,
license_text: None,
standard_license_template: None,
license_text_html: None,
cross_ref: None,
reference: None,
details_url: None,
reference_number: None,
};
let details = License {
name: String::from("MIT License"),
license_id: String::from("MIT"),
see_also: vec![String::from("https://opensource.org/licenses/MIT")],
is_osi_approved: true,
is_fsf_libre: Some(true),
is_deprecated_license_id: false,
license_text: Some(String::from("...")),
standard_license_template: Some(String::from("...")),
license_text_html: Some(String::from("...")),
cross_ref: Some(vec![CrossRef {
r#match: String::from("N/A"),
url: String::from("https://opensource.org/licenses/MIT"),
is_valid: true,
is_live: false,
timestamp: String::from("2023-10-05T15:11:54Z"),
is_way_back_link: false,
order: 0,
}]),
reference: Some(String::from("https://spdx.org/licenses/MIT.html")),
details_url: Some(String::from("https://spdx.org/licenses/MIT.json")),
reference_number: Some(246),
};
license.merge(details);
let expected = License {
name: String::from("MIT License"),
license_id: String::from("MIT"),
see_also: vec![String::from("https://opensource.org/licenses/MIT")],
is_osi_approved: true,
is_fsf_libre: Some(true),
is_deprecated_license_id: false,
license_text: Some(String::from("...")),
standard_license_template: Some(String::from("...")),
license_text_html: Some(String::from("...")),
cross_ref: Some(vec![CrossRef {
r#match: String::from("N/A"),
url: String::from("https://opensource.org/licenses/MIT"),
is_valid: true,
is_live: false,
timestamp: String::from("2023-10-05T15:11:54Z"),
is_way_back_link: false,
order: 0,
}]),
reference: Some(String::from("https://spdx.org/licenses/MIT.html")),
details_url: Some(String::from("https://spdx.org/licenses/MIT.json")),
reference_number: Some(246),
};
assert_eq!(license, expected);
}
#[test]
fn test_mit_license_serde() {
let data = r#"
{
"isDeprecatedLicenseId": false,
"isFsfLibre": true,
"licenseText": "...",
"standardLicenseTemplate": "...",
"name": "MIT License",
"licenseId": "MIT",
"crossRef": [
{
"match": "N/A",
"url": "https://opensource.org/licenses/MIT",
"isValid": true,
"isLive": false,
"timestamp": "2023-10-05T15:11:54Z",
"isWayBackLink": false,
"order": 0
}
],
"seeAlso": [ "https://opensource.org/licenses/MIT" ],
"isOsiApproved": true,
"licenseTextHtml": "..."
}"#;
let deserialized: License = serde_json::from_str(data).unwrap();
let expected = License {
name: String::from("MIT License"),
license_id: String::from("MIT"),
see_also: vec![String::from("https://opensource.org/licenses/MIT")],
is_osi_approved: true,
is_fsf_libre: Some(true),
is_deprecated_license_id: false,
license_text: Some(String::from("...")),
standard_license_template: Some(String::from("...")),
license_text_html: Some(String::from("...")),
cross_ref: Some(vec![CrossRef {
r#match: String::from("N/A"),
url: String::from("https://opensource.org/licenses/MIT"),
is_valid: true,
is_live: false,
timestamp: String::from("2023-10-05T15:11:54Z"),
is_way_back_link: false,
order: 0,
}]),
reference: None,
details_url: None,
reference_number: None,
};
assert_eq!(deserialized, expected);
}
#[test]
fn test_licenses() {
let data = r#"
{
"licenseListVersion": "3.16",
"licenses": [
{
"reference": "https://spdx.org/licenses/MIT.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/MIT.json",
"referenceNumber": 246,
"name": "MIT License",
"licenseId": "MIT",
"seeAlso": [
"https://opensource.org/licenses/MIT"
],
"isOsiApproved": true,
"isFsfLibre": true
}
],
"releaseDate": "2022-02-06"
}"#;
let deserialized: Licenses = serde_json::from_str(data).unwrap();
let expected_license = License {
name: String::from("MIT License"),
license_id: String::from("MIT"),
see_also: vec![String::from("https://opensource.org/licenses/MIT")],
is_osi_approved: true,
is_fsf_libre: Some(true),
is_deprecated_license_id: false,
license_text: None,
standard_license_template: None,
license_text_html: None,
cross_ref: None,
reference: Some(String::from("https://spdx.org/licenses/MIT.html")),
details_url: Some(String::from("https://spdx.org/licenses/MIT.json")),
reference_number: Some(246),
};
let expected = Licenses {
license_list_version: String::from("3.16"),
licenses: vec![expected_license],
release_date: String::from("2022-02-06"),
};
assert_eq!(deserialized, expected);
}
}