rattler_build/recipe/parser/
about.rs1use std::{
2 fmt::{Display, Formatter},
3 str::FromStr,
4};
5
6use serde::{Deserialize, Serialize};
7use serde_with::{DeserializeFromStr, SerializeDisplay};
8use spdx::Expression;
9use url::Url;
10
11use crate::{
12 _partialerror,
13 recipe::{
14 custom_yaml::{
15 HasSpan, RenderedMappingNode, RenderedNode, RenderedScalarNode, TryConvertNode,
16 },
17 error::{ErrorKind, PartialParsingError},
18 },
19 validate_keys,
20};
21
22use super::{FlattenErrors, GlobVec};
23
24#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
26pub struct About {
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub homepage: Option<Url>,
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub repository: Option<Url>,
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub documentation: Option<Url>,
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub license: Option<License>,
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub license_family: Option<String>,
42 #[serde(default, skip_serializing_if = "GlobVec::is_empty")]
44 pub license_file: GlobVec,
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub license_url: Option<Url>,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub summary: Option<String>,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub description: Option<String>,
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub prelink_message: Option<String>,
57}
58
59impl About {
60 pub fn is_default(&self) -> bool {
62 self == &Self::default()
63 }
64}
65
66impl TryConvertNode<About> for RenderedNode {
67 fn try_convert(&self, name: &str) -> Result<About, Vec<PartialParsingError>> {
68 self.as_mapping()
69 .ok_or_else(|| vec![_partialerror!(*self.span(), ErrorKind::ExpectedMapping,)])
70 .and_then(|m| m.try_convert(name))
71 }
72}
73
74impl TryConvertNode<About> for RenderedMappingNode {
75 fn try_convert(&self, _name: &str) -> Result<About, Vec<PartialParsingError>> {
76 let mut about = About::default();
77
78 validate_keys!(
79 about,
80 self.iter(),
81 homepage,
82 repository,
83 documentation,
84 license,
85 license_family,
86 license_file,
87 license_url,
88 summary,
89 description,
90 prelink_message
91 );
92
93 Ok(about)
94 }
95}
96
97#[derive(Debug, Clone, SerializeDisplay, DeserializeFromStr)]
99pub struct License {
100 pub original: String,
101 pub expr: spdx::Expression,
102}
103
104impl PartialEq for License {
105 fn eq(&self, other: &Self) -> bool {
106 self.expr == other.expr
107 }
108}
109
110impl Display for License {
111 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
112 write!(f, "{}", &self.original)
113 }
114}
115
116impl FromStr for License {
117 type Err = spdx::ParseError;
118
119 fn from_str(s: &str) -> Result<Self, Self::Err> {
120 Ok(License {
121 original: s.to_owned(),
122 expr: Expression::parse(s)?,
123 })
124 }
125}
126
127impl TryConvertNode<License> for RenderedNode {
128 fn try_convert(&self, name: &str) -> Result<License, Vec<PartialParsingError>> {
129 self.as_scalar()
130 .ok_or_else(|| vec![_partialerror!(*self.span(), ErrorKind::ExpectedScalar,)])
131 .and_then(|m| m.try_convert(name))
132 }
133}
134
135impl TryConvertNode<License> for RenderedScalarNode {
136 fn try_convert(&self, name: &str) -> Result<License, Vec<PartialParsingError>> {
137 let original: String = self.try_convert(name)?;
138 let expr = Expression::parse(original.as_str())
139 .map_err(|err| vec![_partialerror!(*self.span(), ErrorKind::from(err),)])?;
140
141 Ok(License { original, expr })
142 }
143}
144
145#[cfg(test)]
146mod test {
147 use crate::{
148 assert_miette_snapshot,
149 recipe::{jinja::SelectorConfig, Recipe},
150 variant_config::ParseErrors,
151 };
152
153 #[test]
154 fn invalid_url() {
155 let recipe = r#"
156 package:
157 name: test
158 version: 0.0.1
159
160 about:
161 homepage: license_urla.asda:://sdskd
162 "#;
163
164 let err: ParseErrors = Recipe::from_yaml(recipe, SelectorConfig::default())
165 .unwrap_err()
166 .into();
167
168 assert_miette_snapshot!(err);
169 }
170
171 #[test]
172 fn invalid_license() {
173 let recipe = r#"
174 package:
175 name: test
176 version: 0.0.1
177
178 about:
179 license: MIT/X derivate
180 "#;
181
182 let err: ParseErrors = Recipe::from_yaml(recipe, SelectorConfig::default())
183 .unwrap_err()
184 .into();
185
186 assert_miette_snapshot!(err);
187 }
188}