alpm_srcinfo/pkgbuild_bridge/
mod.rs

1//! Convert untyped and unchecked [`BridgeOutput`] into a well-formed [`SourceInfoV1`].
2
3pub mod error;
4mod package;
5mod package_base;
6
7use std::collections::HashMap;
8
9use alpm_pkgbuild::bridge::{BridgeOutput, Keyword, Value};
10use alpm_types::{Architecture, Name};
11use package::handle_packages;
12use package_base::handle_package_base;
13use winnow::{
14    Parser,
15    error::{ContextError, ErrMode, ParseError},
16};
17
18use crate::{SourceInfoV1, pkgbuild_bridge::error::BridgeError};
19
20impl TryFrom<BridgeOutput> for SourceInfoV1 {
21    type Error = BridgeError;
22
23    /// Creates a [`SourceInfoV1`] from a [`BridgeOutput`].
24    ///
25    /// See errors and documentation in [`SourceInfoV1::from_pkgbuild`]
26    fn try_from(mut value: BridgeOutput) -> Result<Self, Self::Error> {
27        let mut name = None;
28        // Check if there's a `pkgbase` section, which hints that this is a split package.
29        let pkgbase_keyword = Keyword::simple("pkgbase");
30        if let Some(value) = value.package_base.remove(&pkgbase_keyword) {
31            name = Some(parse_value(&pkgbase_keyword, &value, Name::parser)?);
32        }
33
34        // Get the list of all packages that are declared.
35        let pkgname_keyword = Keyword::simple("pkgname");
36        let names = ensure_keyword_exists(&pkgname_keyword, &mut value.package_base)?;
37        let names = parse_value_array(&pkgname_keyword, &names, Name::parser)?;
38
39        // Use the `pkgbase` name by default, otherwise fallback to the first `pkgname` entry.
40        let name = match name {
41            Some(name) => name,
42            None => {
43                // The first package name is used as the name for the pkgbase section.
44                names.first().cloned().ok_or(BridgeError::NoName)?
45            }
46        };
47
48        let base = handle_package_base(name.clone(), value.package_base)?;
49
50        // Go through all declared functions and ensure that the package functions are also
51        // declared via `pkgname`. If one of them is not, this is a bug.
52        for name in value.functions {
53            let Some(name) = name.0 else { continue };
54
55            let name =
56                Name::parser
57                    .parse(&name)
58                    .map_err(|err| BridgeError::InvalidPackageName {
59                        name: name.clone(),
60                        error: err.into(),
61                    })?;
62
63            if !names.contains(&name) {
64                return Err(BridgeError::UndeclaredPackageName(name.to_string()));
65            }
66        }
67
68        let packages = handle_packages(name, names, value.packages)?;
69
70        Ok(SourceInfoV1 { base, packages })
71    }
72}
73
74/// Ensures a [`Keyword`] exists in a [`HashMap`], removes it and returns it.
75///
76/// This is a helper function to ensure expected values are set while throwing context-rich
77/// errors if they don't.
78///
79/// # Errors
80///
81/// Returns an error if `keyword` is not a key in `map`.
82fn ensure_keyword_exists(
83    keyword: &Keyword,
84    map: &mut HashMap<Keyword, Value>,
85) -> Result<Value, BridgeError> {
86    match map.remove(keyword) {
87        Some(value) => Ok(value),
88        None => Err(BridgeError::MissingRequiredKeyword {
89            keyword: keyword.clone(),
90        }),
91    }
92}
93
94/// Ensures that a combination of a [`Keyword`] and an optional [`Architecture`] does not use an
95/// [`Architecture`].
96///
97/// # Errors
98///
99/// Returns an error, if `architecture` provides an [`Architecture`].
100fn ensure_no_suffix(
101    keyword: &Keyword,
102    architecture: Option<Architecture>,
103) -> Result<(), BridgeError> {
104    if let Some(arch) = architecture {
105        return Err(BridgeError::UnexpectedArchitecture {
106            keyword: keyword.clone(),
107            suffix: arch,
108        });
109    }
110
111    Ok(())
112}
113
114/// Ensures that a combination of [`Keyword`] and [`Value`] uses a [`Value::Single`] and returns the
115/// value.
116///
117/// # Errors
118///
119/// Returns an error, if `value` is a [`Value::Array`].
120fn ensure_single_value<'a>(keyword: &Keyword, value: &'a Value) -> Result<&'a String, BridgeError> {
121    match value {
122        Value::Single(item) => Ok(item),
123        Value::Array(values) => Err(BridgeError::UnexpectedArray {
124            keyword: keyword.clone(),
125            values: values.clone(),
126        }),
127    }
128}
129
130/// Ensures that a combination of [`Keyword`] and [`Value`] uses a [`Value::Single`] and parses the
131/// value as a specific type.
132///
133/// # Errors
134///
135/// Returns a error for `keyword` if `value` is not [`Value::Single`] or cannot be parsed as the
136/// specific type.
137fn parse_value<'a, O, P: Parser<&'a str, O, ErrMode<ContextError>>>(
138    keyword: &Keyword,
139    value: &'a Value,
140    mut parser: P,
141) -> Result<O, BridgeError> {
142    let input = ensure_single_value(keyword, value)?;
143    Ok(parser.parse(input).map_err(|err| (keyword.clone(), err))?)
144}
145
146/// Ensures a combination of [`Keyword`] and [`Value`] uses a [`Value::Single`] and parses the value
147/// as a specific, but optional type.
148///
149/// Returns [`None`] if `value` is **empty**.
150///
151/// # Errors
152///
153/// Returns a error for `keyword` if `value` is not [`Value::Single`] or cannot be parsed as the
154/// specific type.
155fn parse_optional_value<'a, O, P: Parser<&'a str, O, ErrMode<ContextError>>>(
156    keyword: &Keyword,
157    value: &'a Value,
158    mut parser: P,
159) -> Result<Option<O>, BridgeError> {
160    let input = ensure_single_value(keyword, value)?;
161
162    if input.trim().is_empty() {
163        return Ok(None);
164    }
165
166    Ok(Some(
167        parser.parse(input).map_err(|err| (keyword.clone(), err))?,
168    ))
169}
170
171/// Parses a [`Value`] as a [`Vec`] of specific types.
172///
173/// Does not differentiate between [`Value::Single`] and [`Value::Array`] variants, as a
174/// [`PKGBUILD`] allows either for array values.
175///
176/// # Errors
177///
178/// Returns a error for `keyword` if `value` cannot be parsed.
179///
180/// [`PKGBUILD`]: https://man.archlinux.org/man/PKGBUILD.5
181fn parse_value_array<'a, O, P: Parser<&'a str, O, ErrMode<ContextError>>>(
182    keyword: &Keyword,
183    value: &'a Value,
184    mut parser: P,
185) -> Result<Vec<O>, BridgeError> {
186    let input = value.as_vec();
187    Ok(input
188        .into_iter()
189        .map(|item| parser.parse(item).map_err(|err| (keyword.clone(), err)))
190        .collect::<Result<Vec<O>, (Keyword, ParseError<&'a str, ContextError>)>>()?)
191}
192
193#[cfg(test)]
194mod tests {
195    use testresult::TestResult;
196    use winnow::token::rest;
197
198    use super::*;
199
200    /// Ensure that an empty single value will return `None` when passed into
201    /// [`parse_optional_value`].
202    #[test]
203    pub fn test_empty_optional_value() -> TestResult {
204        let keyword = Keyword::simple("test");
205        let value = Value::Single("".to_string());
206
207        let value = parse_optional_value(&keyword, &value, rest)?;
208
209        assert!(value.is_none(), "Empty string values should return `None`.");
210
211        Ok(())
212    }
213}