alpm_types/relation/
composite.rs1use std::{
4 fmt::{Display, Formatter},
5 str::FromStr,
6};
7
8use serde::{Deserialize, Serialize};
9use winnow::{
10 ModalResult,
11 Parser,
12 combinator::{cut_err, fail},
13 error::{StrContext, StrContextValue},
14 stream::Stream,
15 token::rest,
16};
17
18use crate::{Error, PackageRelation, SonameV1, SonameV2};
19
20#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
30#[serde(untagged)]
31pub enum RelationOrSoname {
32 Relation(PackageRelation),
34 SonameV1(SonameV1),
38 SonameV2(SonameV2),
42}
43
44impl PartialEq<PackageRelation> for RelationOrSoname {
45 fn eq(&self, other: &PackageRelation) -> bool {
46 self.to_string() == other.to_string()
47 }
48}
49
50impl PartialEq<SonameV1> for RelationOrSoname {
51 fn eq(&self, other: &SonameV1) -> bool {
52 self.to_string() == other.to_string()
53 }
54}
55
56impl PartialEq<SonameV2> for RelationOrSoname {
57 fn eq(&self, other: &SonameV2) -> bool {
58 self.to_string() == other.to_string()
59 }
60}
61
62impl RelationOrSoname {
63 pub fn parser(input: &mut &str) -> ModalResult<Self> {
69 let checkpoint = input.checkpoint();
72 let sonamev2_result = SonameV2::parser.parse_next(input);
73 if sonamev2_result.is_ok() {
74 let sonamev2 = sonamev2_result?;
75 return Ok(RelationOrSoname::SonameV2(sonamev2));
76 }
77
78 input.reset(&checkpoint);
79 let sonamev1_result = SonameV1::parser.parse_next(input);
80 if sonamev1_result.is_ok() {
81 let sonamev1 = sonamev1_result?;
82 return Ok(RelationOrSoname::SonameV1(sonamev1));
83 }
84
85 input.reset(&checkpoint);
86 let relation_result = rest.and_then(PackageRelation::parser).parse_next(input);
87 if relation_result.is_ok() {
88 let relation = relation_result?;
89 return Ok(RelationOrSoname::Relation(relation));
90 }
91
92 cut_err(fail)
93 .context(StrContext::Expected(StrContextValue::Description(
94 "alpm-sonamev2, alpm-sonamev1 or alpm-package-relation",
95 )))
96 .parse_next(input)
97 }
98}
99
100impl Display for RelationOrSoname {
101 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
102 match self {
103 RelationOrSoname::Relation(version) => write!(f, "{version}"),
104 RelationOrSoname::SonameV1(soname) => write!(f, "{soname}"),
105 RelationOrSoname::SonameV2(soname) => write!(f, "{soname}"),
106 }
107 }
108}
109
110impl FromStr for RelationOrSoname {
111 type Err = Error;
112
113 fn from_str(s: &str) -> Result<Self, Self::Err> {
152 Self::parser
153 .parse(s)
154 .map_err(|error| Error::ParseError(error.to_string()))
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use insta::assert_snapshot;
161 use rstest::rstest;
162 use testresult::TestResult;
163
164 use super::*;
165 use crate::{ElfArchitectureFormat, Soname, VersionOrSoname, configure_insta};
166
167 #[rstest]
168 #[case("libexample.so.1")]
169 #[case("lib:libexample.so-abc")]
170 #[case("lib:libexample.so.10-10")]
171 #[case("lib:libexample.so.1.0.0-64")]
172 fn invalid_sonamev2_parser(#[case] input: &str) {
173 let Err(Error::ParseError(err_msg)) = SonameV2::from_str(input) else {
174 panic!("'{input}' did not fail to parse as expected")
175 };
176
177 let (test_name, _guard) = configure_insta();
178 assert_snapshot!(test_name, err_msg.to_string());
179 }
180
181 #[rstest]
182 #[case(
183 "example",
184 RelationOrSoname::Relation(PackageRelation::new("example".parse().unwrap(), None))
185 )]
186 #[case(
187 "example=1.0.0",
188 RelationOrSoname::Relation(PackageRelation::new("example".parse().unwrap(), "=1.0.0".parse().ok()))
189 )]
190 #[case(
191 "example>=1.0.0",
192 RelationOrSoname::Relation(PackageRelation::new("example".parse().unwrap(), ">=1.0.0".parse().ok()))
193 )]
194 #[case(
195 "lib:example.so.1",
196 RelationOrSoname::SonameV2(
197 SonameV2::new(
198 "lib".parse().unwrap(),
199 Soname::from_str("example.so.1").unwrap(),
200 )
201 )
202 )]
203 #[case(
204 "lib:example.so",
205 RelationOrSoname::SonameV2(
206 SonameV2::new(
207 "lib".parse().unwrap(),
208 Soname::from_str("example.so").unwrap(),
209 )
210 )
211 )]
212 #[case(
213 "example.so",
214 RelationOrSoname::SonameV1(
215 SonameV1::new(
216 "example.so".parse().unwrap(),
217 None,
218 None,
219 ).unwrap()
220 )
221 )]
222 #[case(
223 "example.so=1.0.0-64",
224 RelationOrSoname::SonameV1(
225 SonameV1::new(
226 "example.so".parse().unwrap(),
227 Some(VersionOrSoname::Version("1.0.0".parse().unwrap())),
228 Some(ElfArchitectureFormat::Bit64),
229 ).unwrap()
230 )
231 )]
232 #[case(
233 "libexample.so=otherlibexample.so-64",
234 RelationOrSoname::SonameV1(
235 SonameV1::new(
236 "libexample.so".parse().unwrap(),
237 Some(VersionOrSoname::Soname("otherlibexample.so".parse().unwrap())),
238 Some(ElfArchitectureFormat::Bit64),
239 ).unwrap()
240 )
241 )]
242 fn test_relation_or_soname_parser(
243 #[case] mut input: &str,
244 #[case] expected: RelationOrSoname,
245 ) -> TestResult {
246 let input_str = input.to_string();
247 let result = RelationOrSoname::parser(&mut input)?;
248 assert_eq!(result, expected);
249 assert_eq!(result.to_string(), input_str);
250 Ok(())
251 }
252}