blob: 7daa54d91c20142e68acf0581fbb162843901b1f [file] [log] [blame] [edit]
mod minimize;
mod parser;
use crate::{error::ParseError, LicenseReq};
pub use minimize::MinimizeError;
use smallvec::SmallVec;
use std::fmt;
/// A license requirement inside an SPDX license expression, including
/// the span in the expression where it is located
#[derive(Debug, Clone)]
pub struct ExpressionReq {
pub req: LicenseReq,
pub span: std::ops::Range<u32>,
}
impl PartialEq for ExpressionReq {
fn eq(&self, o: &Self) -> bool {
self.req == o.req
}
}
/// The joining operators supported by SPDX 2.1
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
pub enum Operator {
And,
Or,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ExprNode {
Op(Operator),
Req(ExpressionReq),
}
/// An SPDX license expression that is both syntactically and semantically valid,
/// and can be evaluated
///
/// ```
/// use spdx::Expression;
///
/// let this_is_fine = Expression::parse("MIT OR Apache-2.0").unwrap();
/// assert!(this_is_fine.evaluate(|req| {
/// if let spdx::LicenseItem::Spdx { id, .. } = req.license {
/// // Both MIT and Apache-2.0 are OSI approved, so this expression
/// // evaluates to true
/// return id.is_osi_approved();
/// }
///
/// false
/// }));
///
/// assert!(!this_is_fine.evaluate(|req| {
/// if let spdx::LicenseItem::Spdx { id, .. } = req.license {
/// // This is saying we don't accept any licenses that are OSI approved
/// // so the expression will evaluate to false as both sides of the OR
/// // are now rejected
/// return !id.is_osi_approved();
/// }
///
/// false
/// }));
///
/// // `NOPE` is not a valid SPDX license identifier, so this expression
/// // will fail to parse
/// let _this_is_not = Expression::parse("MIT OR NOPE").unwrap_err();
/// ```
#[derive(Clone)]
pub struct Expression {
pub(crate) expr: SmallVec<[ExprNode; 5]>,
// We keep the original string around for display purposes only
pub(crate) original: String,
}
impl Expression {
/// Returns each of the license requirements in the license expression,
/// but not the operators that join them together
///
/// ```
/// let expr = spdx::Expression::parse("MIT AND BSD-2-Clause").unwrap();
///
/// assert_eq!(
/// &expr.requirements().map(|er| er.req.license.id()).collect::<Vec<_>>(), &[
/// spdx::license_id("MIT"),
/// spdx::license_id("BSD-2-Clause")
/// ]
/// );
/// ```
pub fn requirements(&self) -> impl Iterator<Item = &ExpressionReq> {
self.expr.iter().filter_map(|item| match item {
ExprNode::Req(req) => Some(req),
ExprNode::Op(_op) => None,
})
}
/// Returns both the license requirements and the operators that join them
/// together. Note that the expression is returned in post fix order.
///
/// ```
/// use spdx::expression::{ExprNode, Operator};
/// let expr = spdx::Expression::parse("Apache-2.0 OR MIT").unwrap();
///
/// let mut ei = expr.iter();
///
/// assert!(ei.next().is_some()); // Apache
/// assert!(ei.next().is_some()); // MIT
/// assert_eq!(*ei.next().unwrap(), ExprNode::Op(Operator::Or));
/// ```
pub fn iter(&self) -> impl Iterator<Item = &ExprNode> {
self.expr.iter()
}
/// Evaluates the expression, using the provided function to determine if the
/// licensee meets the requirements for each license term. If enough requirements are
/// satisfied the evaluation will return true.
///
/// ```
/// use spdx::Expression;
///
/// let this_is_fine = Expression::parse("MIT OR Apache-2.0").unwrap();
/// assert!(this_is_fine.evaluate(|req| {
/// // If we find MIT, then we're happy!
/// req.license.id() == spdx::license_id("MIT")
/// }));
/// ```
pub fn evaluate<AF: FnMut(&LicenseReq) -> bool>(&self, mut allow_func: AF) -> bool {
let mut result_stack = SmallVec::<[bool; 8]>::new();
// We store the expression as postfix, so just evaluate each license
// requirement in the order it comes, and then combining the previous
// results according to each operator as it comes
for node in self.expr.iter() {
match node {
ExprNode::Req(req) => {
let allowed = allow_func(&req.req);
result_stack.push(allowed);
}
ExprNode::Op(Operator::Or) => {
let a = result_stack.pop().unwrap();
let b = result_stack.pop().unwrap();
result_stack.push(a || b);
}
ExprNode::Op(Operator::And) => {
let a = result_stack.pop().unwrap();
let b = result_stack.pop().unwrap();
result_stack.push(a && b);
}
}
}
result_stack.pop().unwrap()
}
/// Just as with evaluate, the license expression is evaluated to see if
/// enough license requirements in the expression are met for the evaluation
/// to succeed, except this method also keeps track of each failed requirement
/// and returns them, allowing for more detailed error reporting about precisely
/// what terms in the expression caused the overall failure
pub fn evaluate_with_failures<AF: FnMut(&LicenseReq) -> bool>(
&self,
mut allow_func: AF,
) -> Result<(), Vec<&ExpressionReq>> {
let mut result_stack = SmallVec::<[bool; 8]>::new();
let mut failures = Vec::new();
// We store the expression as postfix, so just evaluate each license
// requirement in the order it comes, and then combining the previous
// results according to each operator as it comes
for node in self.expr.iter() {
match node {
ExprNode::Req(req) => {
let allowed = allow_func(&req.req);
result_stack.push(allowed);
if !allowed {
failures.push(req);
}
}
ExprNode::Op(Operator::Or) => {
let a = result_stack.pop().unwrap();
let b = result_stack.pop().unwrap();
result_stack.push(a || b);
}
ExprNode::Op(Operator::And) => {
let a = result_stack.pop().unwrap();
let b = result_stack.pop().unwrap();
result_stack.push(a && b);
}
}
}
if let Some(false) = result_stack.pop() {
Err(failures)
} else {
Ok(())
}
}
}
impl AsRef<str> for Expression {
fn as_ref(&self) -> &str {
&self.original
}
}
impl fmt::Debug for Expression {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, node) in self.expr.iter().enumerate() {
if i > 0 {
f.write_str(" ")?;
}
match node {
ExprNode::Req(req) => write!(f, "{}", req.req)?,
ExprNode::Op(Operator::And) => f.write_str("AND")?,
ExprNode::Op(Operator::Or) => f.write_str("OR")?,
}
}
Ok(())
}
}
impl fmt::Display for Expression {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.original)
}
}
impl std::str::FromStr for Expression {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
impl PartialEq for Expression {
fn eq(&self, o: &Self) -> bool {
// The expressions can be semantically the same but not
// syntactically the same, if the user wants to compare
// the raw expressions they can just do a string compare
if self.expr.len() != o.expr.len() {
return false;
}
!self.expr.iter().zip(o.expr.iter()).any(|(a, b)| a != b)
}
}
#[cfg(test)]
mod test {
use super::Expression;
#[test]
#[allow(clippy::eq_op)]
fn eq() {
let normal = Expression::parse("MIT OR Apache-2.0").unwrap();
let extra_parens = Expression::parse("(MIT OR (Apache-2.0))").unwrap();
let llvm_exc = Expression::parse("MIT OR Apache-2.0 WITH LLVM-exception").unwrap();
assert_eq!(normal, normal);
assert_eq!(extra_parens, extra_parens);
assert_eq!(llvm_exc, llvm_exc);
assert_eq!(normal, extra_parens);
assert_ne!(normal, llvm_exc);
}
}