1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
use std::error::Error; use std::fmt; mod spdx; use self::LicenseExpr::*; #[derive(Debug, Clone, Copy)] pub enum LicenseExpr<'a> { License(&'a str), Exception(&'a str), And, Or, With, } impl<'a> fmt::Display for LicenseExpr<'a> { fn fmt(&self, format: &mut fmt::Formatter) -> Result<(), fmt::Error> { match *self { With => format.write_str("WITH"), And => format.write_str("AND"), Or => format.write_str("OR"), License(info) | Exception(info) => format.write_str(info), } } } #[derive(Debug, Clone, Copy)] pub enum ParseError<'a> { UnknownLicenseId(&'a str), InvalidStructure(LicenseExpr<'a>) } impl<'a> fmt::Display for ParseError<'a> { fn fmt(&self, format: &mut fmt::Formatter) -> Result<(), fmt::Error> { match *self { ParseError::UnknownLicenseId(info) => format.write_fmt(format_args!("{}: {}", self.description(), info)), ParseError::InvalidStructure(info) => format.write_fmt(format_args!("{}: {}", self.description(), info)), } } } impl<'a> Error for ParseError<'a> { fn description(&self) -> &str { match *self { ParseError::UnknownLicenseId(_) => "unknown license or other term", ParseError::InvalidStructure(_) => "invalid license expression", } } } pub fn validate_license_expr(license_expr: &str) -> Result<(), ParseError> { license_expr.split_whitespace().map(|word| match word { "AND" => Ok(And), "OR" => Ok(Or), "WITH" => Ok(With), _ if spdx::LICENSES.binary_search(&word.trim_right_matches('+')).is_ok() => Ok(License(word)), _ if spdx::EXCEPTIONS.binary_search(&word).is_ok() => Ok(Exception(word)), _ => Err(ParseError::UnknownLicenseId(word)) }).fold(Ok(Or), |prev, word| match (prev, word) { (err @ Err(_), _) | (_, err @ Err(_)) => err, (Ok(License(_)), Ok(With)) | (Ok(License(_)), Ok(And)) | (Ok(License(_)), Ok(Or)) | (Ok(Exception(_)), Ok(And)) | (Ok(Exception(_)), Ok(Or)) | (Ok(And), Ok(License(_))) | (Ok(Or), Ok(License(_))) | (Ok(With), Ok(Exception(_))) => word, _ => Err(ParseError::InvalidStructure(word.unwrap())) }).and(Ok(())) } #[cfg(test)] mod tests { #[test] fn single_license() { assert!(super::validate_license_expr("MIT").is_ok()); } #[test] fn compound_license() { assert!(super::validate_license_expr("GPL-3.0+ WITH Classpath-exception-2.0 OR MIT AND AAL") .is_ok()); } #[test] fn fails_invalid_license() { assert!(super::validate_license_expr("asdfghjkl").is_err()); assert!(super::validate_license_expr("MIT AND qwerty").is_err()) } #[test] fn fails_incorrect_structure() { assert!(super::validate_license_expr("WITH").is_err()); assert!(super::validate_license_expr("MIT OR WITH").is_err()); assert!(super::validate_license_expr("MIT AND Classpath-exception-2.0").is_err()); assert!(super::validate_license_expr("Classpath-exception-2.0").is_err()); } }