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