// SPDX-FileCopyrightText: 2022 HH Partners // // SPDX-License-Identifier: MIT //! Private inner structs for [`crate::SpdxExpression`]. use std::{collections::HashSet, fmt::Display}; use nom::Finish; use serde::{de::Visitor, Deserialize, Serialize}; use crate::{ error::SpdxExpressionError, parser::{parse_expression, simple_expression}, }; /// Simple SPDX license expression. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct SimpleExpression { /// The license identifier. pub identifier: String, /// Optional DocumentRef for the expression. pub document_ref: Option, /// `true` if the expression is a user defined license reference. pub license_ref: bool, } impl Serialize for SimpleExpression { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.collect_str(self) } } struct SimpleExpressionVisitor; impl<'de> Visitor<'de> for SimpleExpressionVisitor { type Value = SimpleExpression; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("a syntactically valid SPDX simple expression") } fn visit_str(self, v: &str) -> Result where E: serde::de::Error, { SimpleExpression::parse(v) .map_err(|err| E::custom(format!("error parsing the expression: {}", err))) } fn visit_borrowed_str(self, v: &'de str) -> Result where E: serde::de::Error, { self.visit_str(v) } fn visit_string(self, v: String) -> Result where E: serde::de::Error, { self.visit_str(&v) } } impl<'de> Deserialize<'de> for SimpleExpression { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { deserializer.deserialize_str(SimpleExpressionVisitor) } } impl Display for SimpleExpression { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let document_ref = match &self.document_ref { Some(document_ref) => { format!("DocumentRef-{}:", document_ref) } None => "".to_string(), }; let license_ref = if self.license_ref { "LicenseRef-" } else { "" }; write!( f, "{document_ref}{license_ref}{identifier}", identifier = self.identifier ) } } impl SimpleExpression { /// Create a new simple expression. pub const fn new(identifier: String, document_ref: Option, license_ref: bool) -> Self { Self { identifier, document_ref, license_ref, } } /// Parse a simple expression. /// /// # Examples /// /// ``` /// # use spdx_expression::SimpleExpression; /// # use spdx_expression::SpdxExpressionError; /// # /// let expression = SimpleExpression::parse("MIT")?; /// # Ok::<(), SpdxExpressionError>(()) /// ``` /// /// The function will only accept simple expressions, compound expressions will fail. /// /// ``` /// # use spdx_expression::SimpleExpression; /// # /// let expression = SimpleExpression::parse("MIT OR ISC"); /// assert!(expression.is_err()); /// ``` /// /// # Errors /// /// Fails if parsing fails. pub fn parse(expression: &str) -> Result { let (remaining, result) = simple_expression(expression)?; if remaining.is_empty() { Ok(result) } else { Err(SpdxExpressionError::Parse(expression.to_string())) } } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct WithExpression { pub license: SimpleExpression, pub exception: String, } impl WithExpression { pub const fn new(license: SimpleExpression, exception: String) -> Self { Self { license, exception } } } impl Display for WithExpression { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{license} WITH {exception}", license = self.license, exception = self.exception ) } } #[derive(Debug, PartialEq, Clone, Eq)] pub enum ExpressionVariant { Simple(SimpleExpression), With(WithExpression), And(Box, Box), Or(Box, Box), Parens(Box), } impl Display for ExpressionVariant { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use self::ExpressionVariant::{And, Or, Parens, Simple, With}; match self { Simple(expression) => write!(f, "{expression}"), With(expression) => write!(f, "{expression}"), And(left, right) => write!(f, "{left} AND {right}"), Or(left, right) => write!(f, "{left} OR {right}"), Parens(expression) => write!(f, "({expression})"), } } } impl ExpressionVariant { pub fn parse(i: &str) -> Result { let (remaining, expression) = parse_expression(i) .finish() .map_err(|_| SpdxExpressionError::Parse(i.to_string()))?; if remaining.is_empty() { Ok(expression) } else { Err(SpdxExpressionError::Parse(i.to_string())) } } pub fn licenses(&self) -> HashSet<&SimpleExpression> { let mut expressions = HashSet::new(); match self { ExpressionVariant::Simple(expression) => { expressions.insert(expression); } ExpressionVariant::With(expression) => { expressions.insert(&expression.license); } ExpressionVariant::And(left, right) | ExpressionVariant::Or(left, right) => { expressions.extend(left.licenses()); expressions.extend(right.licenses()); } ExpressionVariant::Parens(expression) => { expressions.extend(expression.licenses()); } } expressions } pub fn exceptions(&self) -> HashSet<&str> { let mut expressions = HashSet::new(); match self { ExpressionVariant::Simple(_) => {} ExpressionVariant::With(expression) => { expressions.insert(expression.exception.as_str()); } ExpressionVariant::And(left, right) | ExpressionVariant::Or(left, right) => { expressions.extend(left.exceptions()); expressions.extend(right.exceptions()); } ExpressionVariant::Parens(expression) => { expressions.extend(expression.exceptions()); } } expressions } } #[cfg(test)] mod tests { use std::iter::FromIterator; use serde_json::Value; use super::*; #[test] fn display_simple_correctly() { let expression = ExpressionVariant::Simple(SimpleExpression::new("MIT".to_string(), None, false)); assert_eq!(expression.to_string(), "MIT".to_string()); } #[test] fn display_licenseref_correctly() { let expression = ExpressionVariant::Simple(SimpleExpression::new("license".to_string(), None, true)); assert_eq!(expression.to_string(), "LicenseRef-license".to_string()); } #[test] fn display_documentref_correctly() { let expression = ExpressionVariant::Simple(SimpleExpression::new( "license".to_string(), Some("document".to_string()), true, )); assert_eq!( expression.to_string(), "DocumentRef-document:LicenseRef-license".to_string() ); } #[test] fn display_with_expression_correctly() { let expression = ExpressionVariant::With(WithExpression::new( SimpleExpression::new("license".to_string(), None, false), "exception".to_string(), )); assert_eq!(expression.to_string(), "license WITH exception".to_string()); } #[test] fn display_and_expression_correctly() { let expression = ExpressionVariant::And( Box::new(ExpressionVariant::And( Box::new(ExpressionVariant::Simple(SimpleExpression::new( "license1".to_string(), None, false, ))), Box::new(ExpressionVariant::Simple(SimpleExpression::new( "license2".to_string(), None, false, ))), )), Box::new(ExpressionVariant::Simple(SimpleExpression::new( "license3".to_string(), None, false, ))), ); assert_eq!( expression.to_string(), "license1 AND license2 AND license3".to_string() ); } #[test] fn display_or_expression_correctly() { let expression = ExpressionVariant::Or( Box::new(ExpressionVariant::Or( Box::new(ExpressionVariant::Simple(SimpleExpression::new( "license1".to_string(), None, false, ))), Box::new(ExpressionVariant::Simple(SimpleExpression::new( "license2".to_string(), None, false, ))), )), Box::new(ExpressionVariant::Simple(SimpleExpression::new( "license3".to_string(), None, false, ))), ); assert_eq!( expression.to_string(), "license1 OR license2 OR license3".to_string() ); } #[test] fn get_licenses_correctly() { let expression = ExpressionVariant::And( Box::new(ExpressionVariant::Simple(SimpleExpression::new( "license1+".to_string(), None, false, ))), Box::new(ExpressionVariant::Parens(Box::new(ExpressionVariant::Or( Box::new(ExpressionVariant::Parens(Box::new( ExpressionVariant::With(WithExpression::new( SimpleExpression::new("license2".to_string(), None, false), "exception1".to_string(), )), ))), Box::new(ExpressionVariant::And( Box::new(ExpressionVariant::Simple(SimpleExpression::new( "license3+".to_string(), None, false, ))), Box::new(ExpressionVariant::With(WithExpression::new( SimpleExpression::new("license4".to_string(), None, false), "exception2".to_string(), ))), )), )))), ); assert_eq!( expression.licenses(), HashSet::from_iter([ &SimpleExpression::new("license1+".to_string(), None, false), &SimpleExpression::new("license2".to_string(), None, false), &SimpleExpression::new("license3+".to_string(), None, false), &SimpleExpression::new("license4".to_string(), None, false), ]) ); } #[test] fn get_exceptions_correctly() { let expression = ExpressionVariant::And( Box::new(ExpressionVariant::Simple(SimpleExpression::new( "license1+".to_string(), None, false, ))), Box::new(ExpressionVariant::Parens(Box::new(ExpressionVariant::Or( Box::new(ExpressionVariant::Parens(Box::new( ExpressionVariant::With(WithExpression::new( SimpleExpression::new("license2".to_string(), None, false), "exception1".to_string(), )), ))), Box::new(ExpressionVariant::And( Box::new(ExpressionVariant::Simple(SimpleExpression::new( "license3+".to_string(), None, false, ))), Box::new(ExpressionVariant::With(WithExpression::new( SimpleExpression::new("license4".to_string(), None, false), "exception2".to_string(), ))), )), )))), ); assert_eq!( expression.exceptions(), HashSet::from_iter(["exception1", "exception2"]) ); } #[test] fn parse_simple_expression() { let expression = SimpleExpression::parse("MIT").unwrap(); assert_eq!( expression, SimpleExpression::new("MIT".to_string(), None, false) ); let expression = SimpleExpression::parse("MIT OR ISC"); assert!(expression.is_err()); let expression = SimpleExpression::parse("GPL-2.0-only WITH Classpath-exception-2.0"); assert!(expression.is_err()); } #[test] fn serialize_simple_expression_correctly() { let expression = SimpleExpression::parse("MIT").unwrap(); let value = serde_json::to_value(expression).unwrap(); assert_eq!(value, Value::String("MIT".to_string())); } #[test] fn deserialize_simple_expression_correctly() { let expected = SimpleExpression::parse("LicenseRef-license1").unwrap(); let value = Value::String("LicenseRef-license1".to_string()); let actual: SimpleExpression = serde_json::from_value(value).unwrap(); assert_eq!(actual, expected); } }