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