Skip to main content

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