citeworks_cff/
license.rs

1use std::hash::Hash;
2
3use serde::{Deserialize, Serialize};
4use spdx::Expression;
5
6/// License field value.
7///
8/// This may either be a single SPDX license expression, or a list of licenses
9/// or expressions.
10///
11/// A list should be interpreted as being a single expression with members
12/// joined with `OR`; this library does no such interpretation immediately, so
13/// as to keep the format of the original document. However, the
14/// [`License::to_expression`] method does this for convenience.
15///
16/// Note that `Hash`, `PartialEq`, and `Eq` are implemented in term of the
17/// original strings for the expression. That is, the list of `Apache-2.0` and
18/// `MIT` may not be equal or hash to the same as `Apache-2.0 OR MIT`.
19#[derive(Debug, Clone, Serialize, Deserialize)]
20#[serde(untagged, try_from = "ExprInternal", into = "ExprInternal")]
21pub enum License {
22	/// A single SPDX license expression.
23	Single(Box<Expression>),
24
25	/// A set of SPDX license expressions (interpreted as joined by `OR`).
26	AnyOf(Vec<Expression>),
27}
28
29impl License {
30	/// Get a single SPDX expression for this License value.
31	pub fn to_expression(&self) -> Expression {
32		match self {
33			Self::Single(exp) => *exp.clone(),
34			Self::AnyOf(exps) => Expression::parse(
35				&exps
36					.iter()
37					.map(|exp| format!("({exp})"))
38					.collect::<Vec<_>>()
39					.join(" OR "),
40			)
41			.expect("if the original expressions parsed, this one will too"),
42		}
43	}
44}
45
46impl Hash for License {
47	fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
48		self.to_expression().to_string().hash(state)
49	}
50}
51
52impl PartialEq for License {
53	fn eq(&self, other: &Self) -> bool {
54		self.to_expression().eq(&other.to_expression())
55	}
56}
57
58impl Eq for License {}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(untagged)]
62enum ExprInternal {
63	Single(String),
64	AnyOf(Vec<String>),
65}
66
67impl TryFrom<ExprInternal> for License {
68	type Error = spdx::ParseError;
69
70	fn try_from(value: ExprInternal) -> Result<Self, Self::Error> {
71		match value {
72			ExprInternal::Single(expr) => {
73				let expr = Expression::parse(&expr)?;
74				Ok(Self::Single(Box::new(expr)))
75			}
76			ExprInternal::AnyOf(exprs) => {
77				let mut exps = Vec::with_capacity(exprs.len());
78				for exp in exprs {
79					exps.push(Expression::parse(&exp)?);
80				}
81				Ok(Self::AnyOf(exps))
82			}
83		}
84	}
85}
86
87impl From<License> for ExprInternal {
88	fn from(license: License) -> Self {
89		match license {
90			License::Single(exp) => Self::Single(exp.to_string()),
91			License::AnyOf(exps) => Self::AnyOf(exps.into_iter().map(|e| e.to_string()).collect()),
92		}
93	}
94}