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