whichlicense_classification/
lib.rs

1/*
2*   Copyright (c) 2023 Duart Snel
3*   All rights reserved.
4
5*   Licensed under the Apache License, Version 2.0 (the "License");
6*   you may not use this file except in compliance with the License.
7*   You may obtain a copy of the License at
8
9*   http://www.apache.org/licenses/LICENSE-2.0
10
11*   Unless required by applicable law or agreed to in writing, software
12*   distributed under the License is distributed on an "AS IS" BASIS,
13*   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*   See the License for the specific language governing permissions and
15*   limitations under the License.
16*/
17
18pub mod classification {
19    use std::collections::HashMap;
20
21    use serde::{Deserialize, Serialize};
22
23    #[derive(Debug, Clone, PartialEq, Eq)]
24    pub enum CompliancyStatus {
25        Compliant,
26        /// A leading license was found to be incompliant with the items in the supplied vector.
27        NonCompliant(Vec<CompatibilityEntry>),
28
29        /// The provided leading license is not known to the index.
30        UnknownLeading,
31
32        /// The license keys provided within the supplied vector are not known to the given license entry within index (i.e., no compatibility specified for the given subordinate license).
33        Unknown(Vec<String>),
34    }
35
36    #[derive(Debug, Deserialize, Serialize, Copy, Clone, PartialEq, Eq)]
37    pub enum CompatibilityStatus {
38        Compatible,
39        Incompatible,
40        Unknown,
41    }
42
43    #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
44    pub struct CompatibilityEntry {
45        pub name: String,
46        pub compatible: CompatibilityStatus,
47        pub explanation: String,
48    }
49
50    impl CompatibilityEntry {
51        pub fn new_unknown(key: &str) -> CompatibilityEntry {
52            CompatibilityEntry {
53                name: key.to_string(),
54                compatible: CompatibilityStatus::Unknown,
55                explanation: format!("No compliancy data found for {}", key),
56            }
57        }
58    }
59
60    #[derive(Debug, Deserialize, Serialize, Clone)]
61    pub struct LicenseEntry {
62        pub name: String,
63        pub compatibility: HashMap<String, CompatibilityEntry>,
64        pub spdx_license_key: Option<String>,
65    }
66
67    impl LicenseEntry {
68        pub fn check_compatibility(&self, other: &LicenseEntry) -> CompatibilityStatus {
69            if let Some(compatibility) = self.compatibility.get(&other.name) {
70                compatibility.compatible
71            } else {
72                CompatibilityStatus::Unknown
73            }
74        }
75
76        pub fn get_all(&self, keys: &Vec<&str>) -> Vec<Option<&CompatibilityEntry>> {
77            keys.iter()
78                .map(|key| self.compatibility.get(*key))
79                .collect()
80        }
81    }
82
83    #[derive(Debug, Deserialize, Serialize, Clone)]
84    pub struct CompatibilityIndex {
85        pub data: HashMap<String, LicenseEntry>,
86    }
87
88    impl CompatibilityIndex {
89        pub fn new() -> CompatibilityIndex {
90            CompatibilityIndex {
91                data: HashMap::new(),
92            }
93        }
94
95        pub fn add(&mut self, key: &str, classification: LicenseEntry) {
96            self.data.insert(key.to_owned(), classification);
97        }
98
99        pub fn get(&self, key: &str) -> Option<&LicenseEntry> {
100            self.data.get(key)
101        }
102
103        pub fn get_all(&self, keys: &Vec<&str>) -> Vec<Option<&LicenseEntry>> {
104            keys.iter().map(|key| self.get(key)).collect()
105        }
106
107        pub fn load_from_memory(&mut self, raw: &[u8]) {
108            self.data = bincode::deserialize(&raw[..]).unwrap();
109        }
110
111        pub fn from_memory(raw: &[u8]) -> CompatibilityIndex {
112            let mut classifier = CompatibilityIndex::new();
113            classifier.load_from_memory(raw);
114            classifier
115        }
116
117        pub fn load_from_file(&mut self, path: &str) {
118            let raw = std::fs::read(path).unwrap();
119            self.load_from_memory(&raw);
120        }
121
122        pub fn from_file(path: &str) -> CompatibilityIndex {
123            let mut classifier = CompatibilityIndex::new();
124            classifier.load_from_file(path);
125            classifier
126        }
127
128        pub fn save_to_file(&self, path: &str) {
129            let raw = bincode::serialize(&self.data).unwrap();
130            std::fs::write(path, raw).unwrap();
131        }
132
133        pub fn check_compliancy(
134            &self,
135            leading_license: &str,
136            subordinate_licenses: &Vec<&str>,
137        ) -> CompliancyStatus {
138            let host_classification = self.get(leading_license);
139            if host_classification.is_none() {
140                return CompliancyStatus::UnknownLeading;
141            }
142
143            let slimmed_matrix: Vec<CompatibilityEntry> = host_classification
144                .unwrap()
145                .get_all(subordinate_licenses)
146                .iter()
147                .map(|c| {
148                    if let Some(c) = c {
149                        c.clone().clone()
150                    } else {
151                        CompatibilityEntry::new_unknown(leading_license)
152                    }
153                })
154                .collect();
155
156            if slimmed_matrix
157                .iter()
158                .any(|classification| classification.compatible == CompatibilityStatus::Unknown)
159            {
160                CompliancyStatus::Unknown(
161                    slimmed_matrix
162                        .iter()
163                        .filter(|classification| {
164                            classification.compatible == CompatibilityStatus::Unknown
165                        })
166                        .map(|classification| classification.name.to_owned())
167                        .collect(),
168                )
169            } else if slimmed_matrix.iter().any(|classification| {
170                classification.compatible == CompatibilityStatus::Incompatible
171            }) {
172                CompliancyStatus::NonCompliant(
173                    slimmed_matrix
174                        .iter()
175                        .filter(|classification| {
176                            classification.compatible == CompatibilityStatus::Incompatible
177                        })
178                        .map(|classification| classification.clone())
179                        .collect(),
180                )
181            } else {
182                CompliancyStatus::Compliant
183            }
184        }
185    }
186}