Skip to main content

alpm_parsers/custom_ini/
de.rs

1//! A custom INI parser and deserializer.
2//!
3//! This module provides functionality for parsing and deserializing INI-style configuration files,
4//! where each line is expected to follow the format `key=value`.
5//!
6//! It supports keys with single values as well as keys that appear multiple times, which are
7//! represented as sequences of values.
8//!
9//! # Example
10//!
11//! ```
12//! use alpm_parsers::custom_ini;
13//! use serde::Deserialize;
14//!
15//! #[derive(Debug, Deserialize)]
16//! struct Data {
17//!     num: u64,
18//!     text: String,
19//!     list: Vec<String>,
20//! }
21//!
22//! fn main() -> custom_ini::Result<()> {
23//!     let content = "
24//!         num = 42
25//!         text = foo
26//!         list = bar
27//!         list = baz
28//!         list = qux
29//!     ";
30//!
31//!     let data = custom_ini::from_str::<Data>(content)?;
32//!
33//!     assert_eq!(data.num, 42);
34//!     assert_eq!(data.text, "foo");
35//!     assert_eq!(data.list, vec!["bar", "baz", "qux"]);
36//!
37//!     Ok(())
38//! }
39//! ```
40use std::{
41    collections::BTreeMap,
42    error,
43    fmt::{self, Display},
44    marker::PhantomData,
45    num::{self, ParseIntError},
46    str::{self, FromStr, ParseBoolError},
47};
48
49use serde::{
50    Deserialize,
51    de::{self, DeserializeOwned, Error as _, IntoDeserializer, Visitor, value::SeqDeserializer},
52    forward_to_deserialize_any,
53};
54use winnow::Parser;
55
56use super::parser::{Item, ini_file};
57
58/// An error that may occur when parsing.
59#[derive(Clone, Debug)]
60pub enum Error {
61    /// Parsing error
62    ///
63    /// Encountering this is probably due to a syntax error in the input.
64    Parse(String),
65
66    /// Deserialization error
67    ///
68    /// Passed through error message from the type being deserialized.
69    Custom(String),
70
71    /// Internal consistency error
72    ///
73    /// Encountering this is probably misuse of the deserialization API or a bug in serde-ini.
74    UnexpectedEof,
75
76    /// Internal consistency error
77    ///
78    /// Encountering this is probably misuse of the deserialization API or a bug in serde-ini.
79    InvalidState,
80}
81
82impl From<num::ParseIntError> for Error {
83    fn from(e: num::ParseIntError) -> Self {
84        Error::Custom(e.to_string())
85    }
86}
87
88impl From<num::ParseFloatError> for Error {
89    fn from(e: num::ParseFloatError) -> Self {
90        Error::Custom(e.to_string())
91    }
92}
93
94impl From<ParseBoolError> for Error {
95    fn from(e: ParseBoolError) -> Self {
96        Error::Custom(e.to_string())
97    }
98}
99
100impl Display for Error {
101    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102        match self {
103            Error::Custom(msg) => write!(f, "{msg}"),
104            Error::Parse(msg) => write!(f, "{msg}"),
105            Error::UnexpectedEof => write!(f, "internal consistency error: unexpected EOF"),
106            Error::InvalidState => write!(f, "internal consistency error"),
107        }
108    }
109}
110
111impl error::Error for Error {
112    fn description(&self) -> &str {
113        "deserialization error"
114    }
115}
116
117impl de::Error for Error {
118    fn custom<T: Display>(msg: T) -> Self {
119        Error::Custom(msg.to_string())
120    }
121}
122
123/// A custom, generic [`Result`] type which returns an [`Error`].
124pub type Result<T> = std::result::Result<T, Error>;
125
126impl IntoDeserializer<'_, Error> for Item {
127    type Deserializer = ItemDeserializer<Error>;
128
129    fn into_deserializer(self) -> Self::Deserializer {
130        ItemDeserializer::new(self)
131    }
132}
133
134/// A deserializer for parsing a list of `Item` objects.
135struct Deserializer {
136    input: BTreeMap<String, Item>,
137}
138
139// Create a new deserializer from a string.
140//
141/// Parses a string of key-value pairs into a list of `Item` values.
142///
143/// Each line should be in the format `key=value`.
144/// If a key appears multiple times, its values are collected into a `List`.
145impl<'a> TryFrom<&'a str> for Deserializer {
146    type Error = Error;
147
148    fn try_from(contents: &'a str) -> Result<Self> {
149        let input = ini_file
150            .parse(contents)
151            .map_err(|err| Error::Custom(format!("{err}")))?;
152
153        Ok(Deserializer { input })
154    }
155}
156
157impl<'de> de::Deserializer<'de> for &mut Deserializer {
158    type Error = Error;
159
160    fn is_human_readable(&self) -> bool {
161        true
162    }
163
164    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
165    where
166        V: serde::de::Visitor<'de>,
167    {
168        visitor.visit_map(self.input.clone().into_deserializer())
169    }
170
171    forward_to_deserialize_any! {
172        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string bytes
173        byte_buf unit unit_struct newtype_struct tuple_struct
174        struct identifier ignored_any enum option map tuple seq
175    }
176}
177
178/// A deserializer for an [`Item`].
179#[derive(Clone, Debug)]
180pub struct ItemDeserializer<E> {
181    item: Item,
182    marker: PhantomData<E>,
183}
184
185impl<E> ItemDeserializer<E> {
186    /// Creates a new [`ItemDeserializer`] from an [`Item`].
187    pub fn new(item: Item) -> Self {
188        ItemDeserializer {
189            item,
190            marker: PhantomData,
191        }
192    }
193}
194
195impl<'de> de::Deserializer<'de> for ItemDeserializer<Error> {
196    type Error = Error;
197
198    fn is_human_readable(&self) -> bool {
199        true
200    }
201
202    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
203    where
204        V: serde::de::Visitor<'de>,
205    {
206        match &self.item {
207            Item::Value(value) => visitor.visit_str(value),
208            Item::List(vec) => visitor.visit_seq(vec.clone().into_deserializer()),
209        }
210    }
211
212    fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value>
213    where
214        V: serde::de::Visitor<'de>,
215    {
216        // There are 2 important cases here:
217        let de = match self.item {
218            // 1. A single value is deserialized as a list of 1 element.
219            Item::Value(value) => {
220                SeqDeserializer::new(vec![SeqItemDeserializer(value.clone())].into_iter())
221            }
222            // 2. List of values is deserialized as a sequence of multiple elements.
223            Item::List(values) => {
224                let mut items = Vec::new();
225                for value in values.clone() {
226                    items.push(SeqItemDeserializer(value.clone()));
227                }
228                SeqDeserializer::new(items.into_iter())
229            }
230        };
231        visitor
232            .visit_seq(de)
233            .map_err(|e| Error::Custom(e.to_string()))
234    }
235
236    fn deserialize_bool<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
237        visitor.visit_bool(FromStr::from_str(self.item.value_or_error()?)?)
238    }
239
240    fn deserialize_i8<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
241        visitor.visit_i8(FromStr::from_str(self.item.value_or_error()?)?)
242    }
243
244    fn deserialize_i16<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
245        visitor.visit_i16(FromStr::from_str(self.item.value_or_error()?)?)
246    }
247
248    fn deserialize_i32<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
249        visitor.visit_i32(FromStr::from_str(self.item.value_or_error()?)?)
250    }
251
252    fn deserialize_i64<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
253        visitor.visit_i64(FromStr::from_str(self.item.value_or_error()?)?)
254    }
255
256    fn deserialize_i128<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
257        visitor.visit_i128(FromStr::from_str(self.item.value_or_error()?)?)
258    }
259
260    fn deserialize_u8<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
261        visitor.visit_u8(FromStr::from_str(self.item.value_or_error()?)?)
262    }
263
264    fn deserialize_u16<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
265        visitor.visit_u16(FromStr::from_str(self.item.value_or_error()?)?)
266    }
267
268    fn deserialize_u32<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
269        visitor.visit_u32(FromStr::from_str(self.item.value_or_error()?)?)
270    }
271
272    fn deserialize_u64<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
273        visitor.visit_u64(FromStr::from_str(self.item.value_or_error()?)?)
274    }
275
276    fn deserialize_u128<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
277        visitor.visit_u128(FromStr::from_str(self.item.value_or_error()?)?)
278    }
279
280    fn deserialize_f32<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
281        visitor.visit_f32(FromStr::from_str(self.item.value_or_error()?)?)
282    }
283
284    fn deserialize_f64<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
285        visitor.visit_f64(FromStr::from_str(self.item.value_or_error()?)?)
286    }
287
288    forward_to_deserialize_any! {
289        char str string bytes
290        byte_buf unit unit_struct newtype_struct tuple tuple_struct
291        struct identifier ignored_any enum option map
292    }
293}
294
295/// A deserializer for individual sequence values.
296struct SeqItemDeserializer(String);
297
298impl<'de> de::Deserializer<'de> for SeqItemDeserializer {
299    type Error = serde::de::value::Error;
300
301    fn deserialize_any<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
302    where
303        V: Visitor<'de>,
304    {
305        visitor.visit_str(&self.0)
306    }
307
308    fn deserialize_u64<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
309    where
310        V: Visitor<'de>,
311    {
312        visitor.visit_u64(
313            self.0
314                .parse()
315                .map_err(|source: ParseIntError| Self::Error::custom(source.to_string()))?,
316        )
317    }
318
319    forward_to_deserialize_any! {
320        bool i8 i16 i32 i64 u8 u16 u32 f32 f64 char str string bytes
321        byte_buf unit unit_struct newtype_struct tuple tuple_struct
322        map struct identifier ignored_any enum option seq
323    }
324}
325
326impl IntoDeserializer<'_> for SeqItemDeserializer {
327    type Deserializer = SeqItemDeserializer;
328    fn into_deserializer(self) -> Self::Deserializer {
329        SeqItemDeserializer(self.0)
330    }
331}
332
333/// Generically creates a deserializer from string slice.
334///
335/// # Errors
336///
337/// Returns an error if
338///
339/// - a deserializer cannot be created from `s`,
340/// - or the deserializer cannot be deserialized using [`Deserialize::deserialize`].
341pub fn from_str<T: DeserializeOwned>(s: &str) -> Result<T> {
342    let mut de = Deserializer::try_from(s)?;
343    let value = Deserialize::deserialize(&mut de)?;
344    Ok(value)
345}
346
347#[cfg(test)]
348mod tests {
349    use serde::Deserialize;
350    use testresult::TestResult;
351
352    use super::*;
353
354    #[derive(Clone, Debug, Default, Deserialize, PartialEq)]
355    struct TestModel {
356        builddate: i64,
357        builddir: String,
358        buildenv: Vec<String>,
359        format: String,
360        installed: Vec<String>,
361        options: Vec<String>,
362        packager: String,
363        pkgarch: String,
364        pkgbase: String,
365        pkgbuild_sha256sum: String,
366        pkgname: String,
367        pkgver: String,
368    }
369
370    const TEST_INPUT: &str = "
371        format = 1
372        builddate = 1
373        builddir = /build
374        buildenv = ccache
375        buildenv = color
376        installed = bar-1.2.3-1-any
377        installed = beh-2.2.3-4-any
378        options = lto
379        options = !strip
380        packager = Foobar McFooface <foobar@mcfooface.org>
381        pkgarch = any
382        pkgbase = foo
383        pkgbuild_sha256sum = b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
384        pkgname = foo
385        pkgver = 1:1.0.0-1";
386
387    fn expected() -> TestModel {
388        TestModel {
389            builddate: 1,
390            builddir: "/build".into(),
391            buildenv: vec!["ccache".into(), "color".into()],
392            format: "1".into(),
393            installed: vec!["bar-1.2.3-1-any".into(), "beh-2.2.3-4-any".into()],
394            options: vec!["lto".into(), "!strip".into()],
395            packager: "Foobar McFooface <foobar@mcfooface.org>".into(),
396            pkgarch: "any".into(),
397            pkgbase: "foo".into(),
398            pkgbuild_sha256sum: "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c"
399                .into(),
400            pkgname: "foo".into(),
401            pkgver: "1:1.0.0-1".into(),
402        }
403    }
404
405    #[test]
406    fn deserialize() -> TestResult {
407        let v = from_str::<TestModel>(TEST_INPUT)?;
408        assert_eq!(expected(), v);
409
410        Ok(())
411    }
412
413    #[derive(Clone, Debug, Default, Deserialize, PartialEq)]
414    struct TypeTestModel {
415        i64: i64,
416        i32: i32,
417        u64: u64,
418        u32: u32,
419        list: Vec<String>,
420        u64_list: Vec<u64>,
421        bool: bool,
422    }
423
424    const TYPE_TEST_INPUT: &str = "
425        i64 = -64
426        i32 = -32
427        u64 = 64
428        u32 = 32
429        list = a
430        list = b
431        list = c
432        u64_list = 1
433        u64_list = 2
434        u64_list = 3
435        bool = true";
436    #[test]
437    fn deserialize_types() -> TestResult {
438        let value = from_str::<TypeTestModel>(TYPE_TEST_INPUT)?;
439        assert_eq!(
440            TypeTestModel {
441                i64: -64,
442                i32: -32,
443                u64: 64,
444                u32: 32,
445                list: vec!["a".to_string(), "b".to_string(), "c".to_string()],
446                u64_list: vec![1, 2, 3],
447                bool: true
448            },
449            value
450        );
451
452        Ok(())
453    }
454
455    #[derive(Clone, Debug, Default, Deserialize, PartialEq)]
456    struct FlattenTestModelInner {
457        u64_list: Vec<u64>,
458        u64: u64,
459    }
460
461    #[derive(Clone, Debug, Default, Deserialize, PartialEq)]
462    struct FlattenTestModel {
463        #[serde(flatten)]
464        flattened: FlattenTestModelInner,
465    }
466
467    const FLATTEN_TEST_INPUT: &str = "
468        u64 = 42
469        u64_list = 1";
470
471    // Flattened structs are not expected to work due to the limitations of serde.
472    //
473    // See these issues for more information:
474    //
475    // - https://gitlab.archlinux.org/archlinux/alpm/alpm/-/work_items/78
476    // - https://github.com/serde-rs/serde/issues/1881
477    // - https://github.com/serde-rs/serde/issues/1183
478    //
479    // This test asserts that the deserialization fails. If the behavior changes in the future,
480    // this test should be updated to assert that the deserialization succeeds.
481    #[test]
482    fn deserialize_with_flatten() -> TestResult {
483        let expected = FlattenTestModelInner {
484            u64: 42,
485            u64_list: vec![1],
486        };
487
488        let value = from_str::<FlattenTestModelInner>(FLATTEN_TEST_INPUT)?;
489        assert_eq!(expected, value);
490
491        let value = from_str::<FlattenTestModel>(FLATTEN_TEST_INPUT);
492        assert!(value.is_err());
493
494        Ok(())
495    }
496}