debcontrol_struct_with_oma_decontrol/
lib.rs

1// SPDX-FileCopyrightText: 2022 Agathe Porte <microjoe@microjoe.org>
2//
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5//! Automatic Debian control file parsing for structs.
6//!
7//! # Example
8//!
9//! ```rust
10//! use debcontrol::{Paragraph, Field};
11//! use debcontrol_struct::DebControl;
12//!
13//! #[derive(DebControl)]
14//! struct DerivedStruct {
15//!     first: String,
16//!     multiple_words: String,
17//!     optional: Option<String>,
18//! }
19//!
20//! let input = &debcontrol::parse_str(
21//!     "First: Hello\n\
22//!      Multiple-Words: World\n"
23//! ).unwrap()[0];
24//!
25//! let derived = DerivedStruct::from_paragraph(&input).unwrap();
26//! assert_eq!("Hello", derived.first);
27//! assert_eq!("World", derived.multiple_words);
28//! assert_eq!(None, derived.optional);
29//! ```
30
31use debcontrol::Paragraph;
32
33pub trait DebControl {
34    fn from_paragraph(p: &Paragraph) -> Result<Self, &'static str>
35    where
36        Self: Sized;
37
38    fn to_paragraph(&self) -> Paragraph;
39}
40
41// Re-export #[derive(DebControl)].
42#[cfg(feature = "derive")]
43#[doc(hidden)]
44pub use debcontrol_struct_derive::DebControl;
45
46#[cfg(test)]
47mod manual {
48    use crate::*;
49    use debcontrol::{Field, Paragraph};
50
51    macro_rules! mandatory {
52        ( $x:expr ) => {{
53            $x.ok_or(concat!(
54                "Could not find the mandatory \"",
55                stringify!($x),
56                "\" field in paragraph"
57            ))
58        }};
59    }
60
61    struct StandaloneLicense {
62        license: String,
63        comment: Option<String>,
64    }
65
66    impl DebControl for StandaloneLicense {
67        fn from_paragraph(p: &Paragraph) -> Result<Self, &'static str> {
68            let mut license = None;
69            let mut comment = None;
70
71            for field in &p.fields {
72                match field.name {
73                    "License" => {
74                        license = Some(field.value.clone());
75                    }
76                    "Comment" => {
77                        comment = Some(field.value.clone());
78                    }
79                    _ => {}
80                }
81            }
82
83            let license = mandatory!(license)?;
84            Ok(StandaloneLicense { license, comment })
85        }
86
87        fn to_paragraph(&self) -> Paragraph {
88            let mut p = Paragraph {
89                fields: vec![
90                    Field {
91                        name: "License",
92                        value: self.license.clone(),
93                    }
94                ]
95            };
96
97            if let Some(comment) = &self.comment {
98                p.fields.push(Field {name: "Comment", value: comment.to_string()});
99            }
100
101            p
102        }
103    }
104
105    #[test]
106    fn test_parse_standalone_license() {
107        let input = Paragraph {
108            fields: vec![Field {
109                name: "License",
110                value: "Expat".into(),
111            }],
112        };
113
114        let license = StandaloneLicense::from_paragraph(&input).unwrap();
115
116        assert_eq!("Expat", license.license);
117        assert_eq!(None, license.comment);
118    }
119
120    #[test]
121    fn test_parse_standalone_license_extended() {
122        let input = Paragraph {
123            fields: vec![
124                Field {
125                    name: "License",
126                    value: "Expat".into(),
127                },
128                Field {
129                    name: "Comment",
130                    value: "Curious license to use...".into(),
131                },
132            ],
133        };
134
135        let license = StandaloneLicense::from_paragraph(&input).unwrap();
136
137        assert_eq!("Expat", license.license);
138        assert_eq!("Curious license to use...", license.comment.unwrap());
139    }
140
141    #[test]
142    fn test_parse_standalone_license_bogus() {
143        let input = Paragraph {
144            fields: vec![Field {
145                name: "Lic",
146                value: "Expat".into(),
147            }],
148        };
149
150        assert!(StandaloneLicense::from_paragraph(&input).is_err());
151    }
152
153    #[test]
154    fn test_to_paragraph() {
155        let expected = Paragraph {
156            fields: vec![Field {
157                name: "License",
158                value: "Expat".into(),
159            }],
160        };
161
162        let value = StandaloneLicense {
163            license: "Expat".into(),
164            comment: None,
165        };
166
167        assert_eq!(expected, value.to_paragraph());
168    }
169
170    #[test]
171    fn test_to_paragraph_extended() {
172        let expected = Paragraph {
173            fields: vec![
174            Field {
175                name: "License",
176                value: "Expat".into(),
177            },
178            Field {
179                name: "Comment",
180                value: "Curious license to use...".into(),
181            }],
182        };
183
184        let value = StandaloneLicense {
185            license: "Expat".into(),
186            comment: Some("Curious license to use...".into()),
187        };
188
189        assert_eq!(expected, value.to_paragraph());
190    }
191}
192
193#[cfg(feature = "derive")]
194#[cfg(test)]
195mod derive {
196    use crate::*;
197    use debcontrol::{Field, Paragraph};
198    use debcontrol_struct_derive::DebControl;
199
200    #[derive(DebControl)]
201    struct DerivedStruct {
202        first: String,
203        multiple_words: String,
204        optional: Option<String>,
205    }
206
207    #[test]
208    fn test_parse_derived() {
209        let input = Paragraph {
210            fields: vec![
211                Field {
212                    name: "First",
213                    value: "Hello".into(),
214                },
215                Field {
216                    name: "Multiple-Words",
217                    value: "World".into(),
218                },
219            ],
220        };
221
222        let derived = DerivedStruct::from_paragraph(&input).unwrap();
223
224        assert_eq!("Hello", derived.first);
225        assert_eq!("World", derived.multiple_words);
226        assert_eq!(None, derived.optional);
227    }
228
229    #[test]
230    fn test_parse_derived_extended() {
231        let input = Paragraph {
232            fields: vec![
233                Field {
234                    name: "First",
235                    value: "Hello".into(),
236                },
237                Field {
238                    name: "Multiple-Words",
239                    value: "World".into(),
240                },
241                Field {
242                    name: "Optional",
243                    value: "!".into(),
244                },
245            ],
246        };
247
248        let derived = DerivedStruct::from_paragraph(&input).unwrap();
249
250        assert_eq!("Hello", derived.first);
251        assert_eq!("World", derived.multiple_words);
252        assert_eq!(Some("!".into()), derived.optional);
253    }
254
255    #[test]
256    fn test_parse_derived_bogus() {
257        let input = Paragraph {
258            fields: vec![Field {
259                name: "First",
260                value: "Hello".into(),
261            }],
262        };
263
264        assert!(DerivedStruct::from_paragraph(&input).is_err());
265    }
266
267    #[test]
268    fn test_to_paragraph() {
269        let expected = Paragraph {
270            fields: vec![
271                Field {
272                    name: "First",
273                    value: "Hello".into(),
274                },
275                Field {
276                    name: "Multiple-Words",
277                    value: "World".into(),
278                },
279            ],
280        };
281
282        let value = DerivedStruct {
283            first: "Hello".into(),
284            multiple_words: "World".into(),
285            optional: None,
286        };
287
288        assert_eq!(expected, value.to_paragraph());
289    }
290
291    #[test]
292    fn test_to_paragraph_extended() {
293        let expected = Paragraph {
294            fields: vec![
295                Field {
296                    name: "First",
297                    value: "Hello".into(),
298                },
299                Field {
300                    name: "Multiple-Words",
301                    value: "World".into(),
302                },
303                Field {
304                    name: "Optional",
305                    value: "!".into(),
306                },
307            ],
308        };
309
310        let value = DerivedStruct {
311            first: "Hello".into(),
312            multiple_words: "World".into(),
313            optional: Some("!".into()),
314        };
315
316        assert_eq!(expected, value.to_paragraph());
317    }
318}