alpm_srcinfo/
relation.rs

1//! Custom package relationship types.
2use std::{
3    fmt::{Display, Formatter},
4    str::FromStr,
5};
6
7use alpm_types::{PackageRelation, SharedObjectName, SonameV1};
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;
19#[cfg(doc)]
20use crate::SourceInfoV1;
21
22/// Provides either a [`PackageRelation`] or a [`SonameV1::Basic`].
23///
24/// This enum is used for [alpm-package-relations] of type _run-time dependency_ and _provision_ in
25/// [`SourceInfoV1`].
26///
27/// [alpm-package-relations]: https://alpm.archlinux.page/specifications/alpm-package-relation.7.html
28#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
29#[serde(untagged)]
30pub enum RelationOrSoname {
31    /// A shared object name (as [`SonameV1::Basic`]).
32    BasicSonameV1(SonameV1),
33    /// A package relation (as [`PackageRelation`]).
34    Relation(PackageRelation),
35}
36
37impl PartialEq<PackageRelation> for RelationOrSoname {
38    fn eq(&self, other: &PackageRelation) -> bool {
39        self.to_string() == other.to_string()
40    }
41}
42
43impl PartialEq<SonameV1> for RelationOrSoname {
44    fn eq(&self, other: &SonameV1) -> bool {
45        self.to_string() == other.to_string()
46    }
47}
48
49impl RelationOrSoname {
50    /// Recognizes a [`SonameV1::Basic`] or a [`PackageRelation`] in a string slice.
51    ///
52    /// First attempts to recognize a [`SonameV1::Basic`] and if that fails, falls back to
53    /// recognizing a [`PackageRelation`].
54    /// Depending on recognized type, a [`RelationOrSoname`] is created accordingly.
55    pub fn parser(input: &mut &str) -> ModalResult<Self> {
56        // Implement a custom `winnow::combinator::alt`, as all type parsers are built in
57        // such a way that they return errors on unexpected input instead of backtracking.
58        let checkpoint = input.checkpoint();
59        let shared_object_name_result = SharedObjectName::parser.parse_next(input);
60        if shared_object_name_result.is_ok() {
61            let shared_object_name = shared_object_name_result?;
62            return Ok(RelationOrSoname::BasicSonameV1(SonameV1::Basic(
63                shared_object_name,
64            )));
65        }
66
67        input.reset(&checkpoint);
68        let relation_result = rest.and_then(PackageRelation::parser).parse_next(input);
69        if relation_result.is_ok() {
70            let relation = relation_result?;
71            return Ok(RelationOrSoname::Relation(relation));
72        }
73
74        cut_err(fail)
75            .context(StrContext::Expected(StrContextValue::Description(
76                "alpm-sonamev1 or alpm-package-relation",
77            )))
78            .parse_next(input)
79    }
80}
81
82impl Display for RelationOrSoname {
83    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
84        match self {
85            RelationOrSoname::Relation(version) => write!(f, "{version}"),
86            RelationOrSoname::BasicSonameV1(soname) => write!(f, "{soname}"),
87        }
88    }
89}
90
91impl FromStr for RelationOrSoname {
92    type Err = Error;
93
94    /// Creates a [`RelationOrSoname`] from a string slice.
95    ///
96    /// Relies on [`RelationOrSoname::parser`] to recognize types in `input` and create a
97    /// [`RelationOrSoname`] accordingly.
98    ///
99    /// # Errors
100    ///
101    /// Returns an error if no [`RelationOrSoname`] can be created from `input`.
102    ///
103    /// # Examples
104    ///
105    /// ```
106    /// use alpm_srcinfo::RelationOrSoname;
107    /// use alpm_types::{PackageRelation, SonameV1};
108    ///
109    /// # fn main() -> Result<(), alpm_srcinfo::Error> {
110    /// let relation: RelationOrSoname = "example=1.0.0".parse()?;
111    /// assert_eq!(
112    ///     relation,
113    ///     RelationOrSoname::Relation(PackageRelation::new(
114    ///         "example".parse()?,
115    ///         Some("=1.0.0".parse()?)
116    ///     ))
117    /// );
118    ///
119    /// let soname: RelationOrSoname = "example.so".parse()?;
120    /// assert_eq!(
121    ///     soname,
122    ///     RelationOrSoname::BasicSonameV1(SonameV1::new("example.so".parse()?, None, None)?)
123    /// );
124    /// # Ok(())
125    /// # }
126    /// ```
127    fn from_str(s: &str) -> Result<Self, Self::Err> {
128        Self::parser
129            .parse(s)
130            .map_err(|error| Error::ParseError(error.to_string()))
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use rstest::rstest;
137    use testresult::TestResult;
138
139    use super::*;
140
141    #[rstest]
142    #[case(
143        "example",
144        RelationOrSoname::Relation(PackageRelation::new("example".parse().unwrap(), None))
145    )]
146    #[case(
147        "example=1.0.0",
148        RelationOrSoname::Relation(PackageRelation::new("example".parse().unwrap(), "=1.0.0".parse().ok())) 
149    )]
150    #[case(
151        "example>=1.0.0",
152        RelationOrSoname::Relation(PackageRelation::new("example".parse().unwrap(), ">=1.0.0".parse().ok()))
153    )]
154    #[case(
155        "example.so",
156        RelationOrSoname::BasicSonameV1(
157            SonameV1::new(
158                "example.so".parse().unwrap(),
159                None,
160                None,
161            ).unwrap()
162        )
163    )]
164    fn test_relation_or_soname_parser(
165        #[case] mut input: &str,
166        #[case] expected: RelationOrSoname,
167    ) -> TestResult {
168        let input_str = input.to_string();
169        let result = RelationOrSoname::parser(&mut input)?;
170        assert_eq!(result, expected);
171        assert_eq!(result.to_string(), input_str);
172        Ok(())
173    }
174}