Skip to main content

libverify_core/
license.rs

1/// SPDX license classification for compliance checking.
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3pub enum LicenseCategory {
4    /// Permissive (MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, Unlicense, etc.)
5    Permissive,
6    /// Weak copyleft (LGPL-2.1, LGPL-3.0, MPL-2.0, EPL-2.0)
7    WeakCopyleft,
8    /// Strong copyleft (GPL-2.0, GPL-3.0, AGPL-3.0, SSPL-1.0, EUPL-1.2)
9    StrongCopyleft,
10    /// Unknown/unrecognized license
11    Unknown,
12}
13
14/// Classify an SPDX license identifier.
15pub fn classify_license(spdx_id: &str) -> LicenseCategory {
16    // Normalize: trim whitespace, compare case-insensitively
17    let id = spdx_id.trim();
18
19    // Strong copyleft
20    let strong_copyleft = [
21        "GPL-2.0",
22        "GPL-2.0-only",
23        "GPL-2.0-or-later",
24        "GPL-3.0",
25        "GPL-3.0-only",
26        "GPL-3.0-or-later",
27        "AGPL-3.0",
28        "AGPL-3.0-only",
29        "AGPL-3.0-or-later",
30        "SSPL-1.0",
31        "EUPL-1.2",
32        "EUPL-1.1",
33        "CECILL-2.1",
34        "OSL-3.0",
35        "RPL-1.5",
36    ];
37    for known in &strong_copyleft {
38        if id.eq_ignore_ascii_case(known) {
39            return LicenseCategory::StrongCopyleft;
40        }
41    }
42
43    // Weak copyleft
44    let weak_copyleft = [
45        "LGPL-2.1",
46        "LGPL-2.1-only",
47        "LGPL-2.1-or-later",
48        "LGPL-3.0",
49        "LGPL-3.0-only",
50        "LGPL-3.0-or-later",
51        "MPL-2.0",
52        "EPL-2.0",
53        "EPL-1.0",
54        "CDDL-1.0",
55        "CDDL-1.1",
56        "CPL-1.0",
57    ];
58    for known in &weak_copyleft {
59        if id.eq_ignore_ascii_case(known) {
60            return LicenseCategory::WeakCopyleft;
61        }
62    }
63
64    // Permissive
65    let permissive = [
66        "MIT",
67        "Apache-2.0",
68        "BSD-2-Clause",
69        "BSD-3-Clause",
70        "ISC",
71        "Unlicense",
72        "0BSD",
73        "CC0-1.0",
74        "Zlib",
75        "BSL-1.0",
76        "PSF-2.0",
77        "Unicode-3.0",
78        "Unicode-DFS-2016",
79        "BlueOak-1.0.0",
80        "MIT-0",
81    ];
82    for known in &permissive {
83        if id.eq_ignore_ascii_case(known) {
84            return LicenseCategory::Permissive;
85        }
86    }
87
88    LicenseCategory::Unknown
89}
90
91/// Returns true if the license is copyleft (weak or strong).
92pub fn is_copyleft(spdx_id: &str) -> bool {
93    matches!(
94        classify_license(spdx_id),
95        LicenseCategory::WeakCopyleft | LicenseCategory::StrongCopyleft
96    )
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn permissive_licenses() {
105        assert_eq!(classify_license("MIT"), LicenseCategory::Permissive);
106        assert_eq!(classify_license("Apache-2.0"), LicenseCategory::Permissive);
107        assert_eq!(
108            classify_license("BSD-3-Clause"),
109            LicenseCategory::Permissive
110        );
111        assert_eq!(classify_license("ISC"), LicenseCategory::Permissive);
112        assert_eq!(classify_license("Unlicense"), LicenseCategory::Permissive);
113        assert_eq!(classify_license("0BSD"), LicenseCategory::Permissive);
114    }
115
116    #[test]
117    fn weak_copyleft_licenses() {
118        assert_eq!(classify_license("LGPL-2.1"), LicenseCategory::WeakCopyleft);
119        assert_eq!(classify_license("MPL-2.0"), LicenseCategory::WeakCopyleft);
120        assert_eq!(classify_license("EPL-2.0"), LicenseCategory::WeakCopyleft);
121        assert_eq!(
122            classify_license("LGPL-3.0-only"),
123            LicenseCategory::WeakCopyleft
124        );
125    }
126
127    #[test]
128    fn strong_copyleft_licenses() {
129        assert_eq!(classify_license("GPL-2.0"), LicenseCategory::StrongCopyleft);
130        assert_eq!(classify_license("GPL-3.0"), LicenseCategory::StrongCopyleft);
131        assert_eq!(
132            classify_license("AGPL-3.0"),
133            LicenseCategory::StrongCopyleft
134        );
135        assert_eq!(
136            classify_license("SSPL-1.0"),
137            LicenseCategory::StrongCopyleft
138        );
139        assert_eq!(
140            classify_license("AGPL-3.0-or-later"),
141            LicenseCategory::StrongCopyleft
142        );
143    }
144
145    #[test]
146    fn unknown_license() {
147        assert_eq!(classify_license("PROPRIETARY"), LicenseCategory::Unknown);
148        assert_eq!(classify_license(""), LicenseCategory::Unknown);
149    }
150
151    #[test]
152    fn case_insensitive() {
153        assert_eq!(classify_license("mit"), LicenseCategory::Permissive);
154        assert_eq!(classify_license("gpl-3.0"), LicenseCategory::StrongCopyleft);
155    }
156
157    #[test]
158    fn is_copyleft_checks() {
159        assert!(!is_copyleft("MIT"));
160        assert!(!is_copyleft("Apache-2.0"));
161        assert!(is_copyleft("GPL-3.0"));
162        assert!(is_copyleft("LGPL-2.1"));
163        assert!(is_copyleft("AGPL-3.0"));
164        assert!(!is_copyleft("UNKNOWN"));
165    }
166
167    #[test]
168    fn whitespace_trimmed() {
169        assert_eq!(classify_license("  MIT  "), LicenseCategory::Permissive);
170    }
171}