1 | mod minimize; |
2 | mod parser; |
3 | |
4 | use crate::{error::ParseError, LicenseReq}; |
5 | pub use minimize::MinimizeError; |
6 | use smallvec::SmallVec; |
7 | use std::fmt; |
8 | |
9 | /// A license requirement inside an SPDX license expression, including |
10 | /// the span in the expression where it is located |
11 | #[derive (Debug, Clone)] |
12 | pub struct ExpressionReq { |
13 | pub req: LicenseReq, |
14 | pub span: std::ops::Range<u32>, |
15 | } |
16 | |
17 | impl PartialEq for ExpressionReq { |
18 | fn eq(&self, o: &Self) -> bool { |
19 | self.req == o.req |
20 | } |
21 | } |
22 | |
23 | /// The joining operators supported by SPDX 2.1 |
24 | #[derive (Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] |
25 | pub enum Operator { |
26 | And, |
27 | Or, |
28 | } |
29 | |
30 | #[derive (Debug, Clone, PartialEq)] |
31 | pub enum ExprNode { |
32 | Op(Operator), |
33 | Req(ExpressionReq), |
34 | } |
35 | |
36 | /// An SPDX license expression that is both syntactically and semantically valid, |
37 | /// and can be evaluated |
38 | /// |
39 | /// ``` |
40 | /// use spdx::Expression; |
41 | /// |
42 | /// let this_is_fine = Expression::parse("MIT OR Apache-2.0" ).unwrap(); |
43 | /// assert!(this_is_fine.evaluate(|req| { |
44 | /// if let spdx::LicenseItem::Spdx { id, .. } = req.license { |
45 | /// // Both MIT and Apache-2.0 are OSI approved, so this expression |
46 | /// // evaluates to true |
47 | /// return id.is_osi_approved(); |
48 | /// } |
49 | /// |
50 | /// false |
51 | /// })); |
52 | /// |
53 | /// assert!(!this_is_fine.evaluate(|req| { |
54 | /// if let spdx::LicenseItem::Spdx { id, .. } = req.license { |
55 | /// // This is saying we don't accept any licenses that are OSI approved |
56 | /// // so the expression will evaluate to false as both sides of the OR |
57 | /// // are now rejected |
58 | /// return !id.is_osi_approved(); |
59 | /// } |
60 | /// |
61 | /// false |
62 | /// })); |
63 | /// |
64 | /// // `NOPE` is not a valid SPDX license identifier, so this expression |
65 | /// // will fail to parse |
66 | /// let _this_is_not = Expression::parse("MIT OR NOPE" ).unwrap_err(); |
67 | /// ``` |
68 | #[derive (Clone)] |
69 | pub struct Expression { |
70 | pub(crate) expr: SmallVec<[ExprNode; 5]>, |
71 | // We keep the original string around for display purposes only |
72 | pub(crate) original: String, |
73 | } |
74 | |
75 | impl Expression { |
76 | /// Returns each of the license requirements in the license expression, |
77 | /// but not the operators that join them together |
78 | /// |
79 | /// ``` |
80 | /// let expr = spdx::Expression::parse("MIT AND BSD-2-Clause" ).unwrap(); |
81 | /// |
82 | /// assert_eq!( |
83 | /// &expr.requirements().map(|er| er.req.license.id()).collect::<Vec<_>>(), &[ |
84 | /// spdx::license_id("MIT" ), |
85 | /// spdx::license_id("BSD-2-Clause" ) |
86 | /// ] |
87 | /// ); |
88 | /// ``` |
89 | pub fn requirements(&self) -> impl Iterator<Item = &ExpressionReq> { |
90 | self.expr.iter().filter_map(|item| match item { |
91 | ExprNode::Req(req) => Some(req), |
92 | ExprNode::Op(_op) => None, |
93 | }) |
94 | } |
95 | |
96 | /// Returns both the license requirements and the operators that join them |
97 | /// together. Note that the expression is returned in post fix order. |
98 | /// |
99 | /// ``` |
100 | /// use spdx::expression::{ExprNode, Operator}; |
101 | /// let expr = spdx::Expression::parse("Apache-2.0 OR MIT" ).unwrap(); |
102 | /// |
103 | /// let mut ei = expr.iter(); |
104 | /// |
105 | /// assert!(ei.next().is_some()); // Apache |
106 | /// assert!(ei.next().is_some()); // MIT |
107 | /// assert_eq!(*ei.next().unwrap(), ExprNode::Op(Operator::Or)); |
108 | /// ``` |
109 | pub fn iter(&self) -> impl Iterator<Item = &ExprNode> { |
110 | self.expr.iter() |
111 | } |
112 | |
113 | /// Evaluates the expression, using the provided function to determine if the |
114 | /// licensee meets the requirements for each license term. If enough requirements are |
115 | /// satisfied the evaluation will return true. |
116 | /// |
117 | /// ``` |
118 | /// use spdx::Expression; |
119 | /// |
120 | /// let this_is_fine = Expression::parse("MIT OR Apache-2.0" ).unwrap(); |
121 | /// assert!(this_is_fine.evaluate(|req| { |
122 | /// // If we find MIT, then we're happy! |
123 | /// req.license.id() == spdx::license_id("MIT" ) |
124 | /// })); |
125 | /// ``` |
126 | pub fn evaluate<AF: FnMut(&LicenseReq) -> bool>(&self, mut allow_func: AF) -> bool { |
127 | let mut result_stack = SmallVec::<[bool; 8]>::new(); |
128 | |
129 | // We store the expression as postfix, so just evaluate each license |
130 | // requirement in the order it comes, and then combining the previous |
131 | // results according to each operator as it comes |
132 | for node in self.expr.iter() { |
133 | match node { |
134 | ExprNode::Req(req) => { |
135 | let allowed = allow_func(&req.req); |
136 | result_stack.push(allowed); |
137 | } |
138 | ExprNode::Op(Operator::Or) => { |
139 | let a = result_stack.pop().unwrap(); |
140 | let b = result_stack.pop().unwrap(); |
141 | |
142 | result_stack.push(a || b); |
143 | } |
144 | ExprNode::Op(Operator::And) => { |
145 | let a = result_stack.pop().unwrap(); |
146 | let b = result_stack.pop().unwrap(); |
147 | |
148 | result_stack.push(a && b); |
149 | } |
150 | } |
151 | } |
152 | |
153 | result_stack.pop().unwrap() |
154 | } |
155 | |
156 | /// Just as with evaluate, the license expression is evaluated to see if |
157 | /// enough license requirements in the expression are met for the evaluation |
158 | /// to succeed, except this method also keeps track of each failed requirement |
159 | /// and returns them, allowing for more detailed error reporting about precisely |
160 | /// what terms in the expression caused the overall failure |
161 | pub fn evaluate_with_failures<AF: FnMut(&LicenseReq) -> bool>( |
162 | &self, |
163 | mut allow_func: AF, |
164 | ) -> Result<(), Vec<&ExpressionReq>> { |
165 | let mut result_stack = SmallVec::<[bool; 8]>::new(); |
166 | let mut failures = Vec::new(); |
167 | |
168 | // We store the expression as postfix, so just evaluate each license |
169 | // requirement in the order it comes, and then combining the previous |
170 | // results according to each operator as it comes |
171 | for node in self.expr.iter() { |
172 | match node { |
173 | ExprNode::Req(req) => { |
174 | let allowed = allow_func(&req.req); |
175 | result_stack.push(allowed); |
176 | |
177 | if !allowed { |
178 | failures.push(req); |
179 | } |
180 | } |
181 | ExprNode::Op(Operator::Or) => { |
182 | let a = result_stack.pop().unwrap(); |
183 | let b = result_stack.pop().unwrap(); |
184 | |
185 | result_stack.push(a || b); |
186 | } |
187 | ExprNode::Op(Operator::And) => { |
188 | let a = result_stack.pop().unwrap(); |
189 | let b = result_stack.pop().unwrap(); |
190 | |
191 | result_stack.push(a && b); |
192 | } |
193 | } |
194 | } |
195 | |
196 | if let Some(false) = result_stack.pop() { |
197 | Err(failures) |
198 | } else { |
199 | Ok(()) |
200 | } |
201 | } |
202 | } |
203 | |
204 | impl AsRef<str> for Expression { |
205 | fn as_ref(&self) -> &str { |
206 | &self.original |
207 | } |
208 | } |
209 | |
210 | impl fmt::Debug for Expression { |
211 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
212 | for (i: usize, node: &ExprNode) in self.expr.iter().enumerate() { |
213 | if i > 0 { |
214 | f.write_str(data:" " )?; |
215 | } |
216 | |
217 | match node { |
218 | ExprNode::Req(req: &ExpressionReq) => write!(f, " {}" , req.req)?, |
219 | ExprNode::Op(Operator::And) => f.write_str(data:"AND" )?, |
220 | ExprNode::Op(Operator::Or) => f.write_str(data:"OR" )?, |
221 | } |
222 | } |
223 | |
224 | Ok(()) |
225 | } |
226 | } |
227 | |
228 | impl fmt::Display for Expression { |
229 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
230 | f.write_str(&self.original) |
231 | } |
232 | } |
233 | |
234 | impl std::str::FromStr for Expression { |
235 | type Err = ParseError; |
236 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
237 | Self::parse(original:s) |
238 | } |
239 | } |
240 | |
241 | impl PartialEq for Expression { |
242 | fn eq(&self, o: &Self) -> bool { |
243 | // The expressions can be semantically the same but not |
244 | // syntactically the same, if the user wants to compare |
245 | // the raw expressions they can just do a string compare |
246 | if self.expr.len() != o.expr.len() { |
247 | return false; |
248 | } |
249 | |
250 | !self.expr.iter().zip(o.expr.iter()).any(|(a: &ExprNode, b: &ExprNode)| a != b) |
251 | } |
252 | } |
253 | |
254 | #[cfg (test)] |
255 | mod test { |
256 | use super::Expression; |
257 | |
258 | #[test ] |
259 | #[allow (clippy::eq_op)] |
260 | fn eq() { |
261 | let normal = Expression::parse("MIT OR Apache-2.0" ).unwrap(); |
262 | let extra_parens = Expression::parse("(MIT OR (Apache-2.0))" ).unwrap(); |
263 | let llvm_exc = Expression::parse("MIT OR Apache-2.0 WITH LLVM-exception" ).unwrap(); |
264 | |
265 | assert_eq!(normal, normal); |
266 | assert_eq!(extra_parens, extra_parens); |
267 | assert_eq!(llvm_exc, llvm_exc); |
268 | |
269 | assert_eq!(normal, extra_parens); |
270 | |
271 | assert_ne!(normal, llvm_exc); |
272 | } |
273 | } |
274 | |