1mod minimize;
2mod parser;
3
4use crate::{error::ParseError, LicenseReq};
5pub use minimize::MinimizeError;
6use smallvec::SmallVec;
7use 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)]
12pub struct ExpressionReq {
13 pub req: LicenseReq,
14 pub span: std::ops::Range<u32>,
15}
16
17impl 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)]
25pub enum Operator {
26 And,
27 Or,
28}
29
30#[derive(Debug, Clone, PartialEq)]
31pub 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)]
69pub 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
75impl 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
204impl AsRef<str> for Expression {
205 fn as_ref(&self) -> &str {
206 &self.original
207 }
208}
209
210impl 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
228impl fmt::Display for Expression {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 f.write_str(&self.original)
231 }
232}
233
234impl 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
241impl 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)]
255mod 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