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 rstest::rstest;
161 use testresult::TestResult;
162
163 use super::*;
164 use crate::{ElfArchitectureFormat, Soname, VersionOrSoname};
165
166 #[rstest]
167 #[case("libexample.so.1", "invalid shared library prefix delimiter")]
168 #[case("lib:libexample.so-abc", "invalid version delimiter")]
169 #[case("lib:libexample.so.10-10", "invalid pkgver character")]
170 #[case("lib:libexample.so.1.0.0-64", "invalid pkgver character")]
171 fn invalid_sonamev2_parser(#[case] input: &str, #[case] error_snippet: &str) {
172 let result = SonameV2::from_str(input);
173 assert!(result.is_err(), "Expected SonameV2 parsing to fail");
174 let err = result.unwrap_err();
175 let pretty_error = err.to_string();
176 assert!(
177 pretty_error.contains(error_snippet),
178 "Error:\n=====\n{pretty_error}\n=====\nshould contain snippet:\n\n{error_snippet}"
179 );
180 }
181
182 #[rstest]
183 #[case(
184 "example",
185 RelationOrSoname::Relation(PackageRelation::new("example".parse().unwrap(), None))
186 )]
187 #[case(
188 "example=1.0.0",
189 RelationOrSoname::Relation(PackageRelation::new("example".parse().unwrap(), "=1.0.0".parse().ok()))
190 )]
191 #[case(
192 "example>=1.0.0",
193 RelationOrSoname::Relation(PackageRelation::new("example".parse().unwrap(), ">=1.0.0".parse().ok()))
194 )]
195 #[case(
196 "lib:example.so.1",
197 RelationOrSoname::SonameV2(
198 SonameV2::new(
199 "lib".parse().unwrap(),
200 Soname::from_str("example.so.1").unwrap(),
201 )
202 )
203 )]
204 #[case(
205 "lib:example.so",
206 RelationOrSoname::SonameV2(
207 SonameV2::new(
208 "lib".parse().unwrap(),
209 Soname::from_str("example.so").unwrap(),
210 )
211 )
212 )]
213 #[case(
214 "example.so",
215 RelationOrSoname::SonameV1(
216 SonameV1::new(
217 "example.so".parse().unwrap(),
218 None,
219 None,
220 ).unwrap()
221 )
222 )]
223 #[case(
224 "example.so=1.0.0-64",
225 RelationOrSoname::SonameV1(
226 SonameV1::new(
227 "example.so".parse().unwrap(),
228 Some(VersionOrSoname::Version("1.0.0".parse().unwrap())),
229 Some(ElfArchitectureFormat::Bit64),
230 ).unwrap()
231 )
232 )]
233 #[case(
234 "libexample.so=otherlibexample.so-64",
235 RelationOrSoname::SonameV1(
236 SonameV1::new(
237 "libexample.so".parse().unwrap(),
238 Some(VersionOrSoname::Soname("otherlibexample.so".parse().unwrap())),
239 Some(ElfArchitectureFormat::Bit64),
240 ).unwrap()
241 )
242 )]
243 fn test_relation_or_soname_parser(
244 #[case] mut input: &str,
245 #[case] expected: RelationOrSoname,
246 ) -> TestResult {
247 let input_str = input.to_string();
248 let result = RelationOrSoname::parser(&mut input)?;
249 assert_eq!(result, expected);
250 assert_eq!(result.to_string(), input_str);
251 Ok(())
252 }
253}