spdx_toolkit/
license_list.rs1use serde::{Deserialize, Serialize};
6
7use crate::error::Error;
8
9#[derive(Serialize, Deserialize, Debug)]
10#[serde(rename_all = "camelCase", deny_unknown_fields)]
11pub struct LicenseList {
12 pub license_list_version: String,
13 #[serde(default)]
14 pub licenses: Vec<License>,
15 #[serde(default)]
16 pub exceptions: Vec<Exception>,
17 pub release_date: String,
18}
19
20impl LicenseList {
21 pub fn from_github(version: Option<&str>) -> Result<Self, Error> {
29 let version = version.unwrap_or("master");
30
31 let licenses_url = format!(
32 "https://raw.githubusercontent.com/spdx/license-list-data/{version}/json/licenses.json"
33 );
34 let body = reqwest::blocking::get(licenses_url)?.text()?;
35 let mut license_list: Self = serde_json::from_str(&body)?;
36
37 let exceptions_url =
38 format!("https://raw.githubusercontent.com/spdx/license-list-data/{version}/json/exceptions.json");
39 let body = reqwest::blocking::get(exceptions_url)?.text()?;
40 let exceptions_list: Self = serde_json::from_str(&body)?;
41 license_list.exceptions = exceptions_list.exceptions;
42 Ok(license_list)
43 }
44
45 pub fn includes_license(&self, spdx_id: &str) -> bool {
46 self.licenses
47 .iter()
48 .any(|license| license.license_id == spdx_id)
49 }
50
51 pub fn includes_exception(&self, exception_id: &str) -> bool {
52 self.exceptions
53 .iter()
54 .any(|exception| exception.license_exception_id == exception_id)
55 }
56
57 #[allow(clippy::doc_markdown)]
58 pub fn is_valid_license(&self, expression: &str) -> bool {
61 expression == "NOASSERTION"
62 || expression == "NONE"
63 || expression.starts_with("LicenseRef-")
64 || expression.starts_with("DocumentRef-")
65 || self.includes_license(&expression.replace('+', ""))
66 || self.includes_exception(expression)
67 }
68}
69
70#[derive(Serialize, Deserialize, Debug)]
71#[serde(rename_all = "camelCase", deny_unknown_fields)]
72pub struct License {
73 pub reference: String,
74 pub is_deprecated_license_id: bool,
75 pub details_url: String,
76 pub reference_number: i32,
77 pub name: String,
78 pub license_id: String,
79 pub see_also: Vec<String>,
80 pub is_osi_approved: bool,
81 #[serde(default)]
82 pub is_fsf_libre: bool,
83}
84
85#[derive(Serialize, Deserialize, Debug)]
86#[serde(rename_all = "camelCase", deny_unknown_fields)]
87pub struct Exception {
88 pub reference: String,
89 pub is_deprecated_license_id: bool,
90 pub details_url: String,
91 pub reference_number: i32,
92 pub name: String,
93 pub license_exception_id: String,
94 pub see_also: Vec<String>,
95}
96
97#[cfg(test)]
98mod test_license_list {
99 use std::fs::read_to_string;
100
101 use super::*;
102
103 #[test]
104 fn licenses_deserialization_works() {
105 let licenses_file =
106 read_to_string("tests/data/licenses.json").expect("Should always exist");
107 let _license_list: LicenseList =
108 serde_json::from_str(&licenses_file).expect("Deseralization should work.");
109 }
110
111 #[test]
112 fn exceptions_deserialization_works() {
113 let licenses_file =
114 read_to_string("tests/data/exceptions.json").expect("Should always exist");
115 let _license_list: LicenseList =
116 serde_json::from_str(&licenses_file).expect("Deseralization should work.");
117 }
118
119 #[test]
120 fn from_github_works() {
121 let license_list = LicenseList::from_github(None).unwrap();
122
123 assert!(!license_list.licenses.is_empty());
124 assert!(!license_list.exceptions.is_empty());
125 }
126
127 #[test]
128 fn correctly_get_older_version_from_github() {
129 let license_list = LicenseList::from_github(Some("v3.14")).unwrap();
130
131 assert!(!license_list.licenses.is_empty());
132 assert!(!license_list.exceptions.is_empty());
133
134 assert_eq!(license_list.license_list_version, "3.14".to_string());
135 }
136
137 #[test]
138 fn bsd_works() {
139 let license_list = LicenseList::from_github(None).unwrap();
140
141 assert!(!license_list.includes_license("BSD"));
142 assert!(!license_list.includes_exception("BSD"));
143 }
144
145 #[test]
146 fn correctly_determine_validity_of_licenses() {
147 let license_list = LicenseList::from_github(None).unwrap();
148 assert!(license_list.is_valid_license("MIT"));
149 assert!(license_list.is_valid_license("GPL-2.0-or-later"));
150 assert!(!license_list.is_valid_license("invalid-license"));
151 assert!(license_list.is_valid_license("LicenseRef-license1"));
152 assert!(license_list.is_valid_license("DocumentRef-document:LicenseRef-license1"));
153 assert!(license_list.is_valid_license("NONE"));
154 assert!(license_list.is_valid_license("NOASSERTION"));
155 }
156}