Skip to main content

debian_control/lossy/
apt.rs

1//! APT related structures
2use crate::lossy::{Relations, SourceRelation};
3use deb822_fast::{FromDeb822, FromDeb822Paragraph, ToDeb822, ToDeb822Paragraph};
4
5fn deserialize_yesno(s: &str) -> Result<bool, String> {
6    match s {
7        "yes" => Ok(true),
8        "no" => Ok(false),
9        _ => Err(format!("invalid value for yesno: {}", s)),
10    }
11}
12
13fn serialize_yesno(b: &bool) -> String {
14    if *b {
15        "yes".to_string()
16    } else {
17        "no".to_string()
18    }
19}
20
21fn deserialize_components(value: &str) -> Result<Vec<String>, String> {
22    Ok(value.split_whitespace().map(|s| s.to_string()).collect())
23}
24
25fn join_whitespace(components: &[String]) -> String {
26    components.join(" ")
27}
28
29fn deserialize_architectures(value: &str) -> Result<Vec<String>, String> {
30    Ok(value.split_whitespace().map(|s| s.to_string()).collect())
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, ToDeb822, FromDeb822)]
34/// A Release file
35pub struct Release {
36    #[deb822(field = "Codename")]
37    /// The codename of the release
38    pub codename: String,
39
40    #[deb822(
41        field = "Components",
42        deserialize_with = deserialize_components,
43        serialize_with = join_whitespace
44    )]
45    /// Components supported by the release
46    pub components: Vec<String>,
47
48    #[deb822(
49        field = "Architectures",
50        deserialize_with = deserialize_architectures,
51        serialize_with = join_whitespace
52    )]
53    /// Architectures supported by the release
54    pub architectures: Vec<String>,
55
56    #[deb822(field = "Description")]
57    /// Description of the release
58    pub description: String,
59
60    #[deb822(field = "Origin")]
61    /// Origin of the release
62    pub origin: String,
63
64    #[deb822(field = "Label")]
65    /// Label of the release
66    pub label: String,
67
68    #[deb822(field = "Suite")]
69    /// Suite of the release
70    pub suite: String,
71
72    #[deb822(field = "Version")]
73    /// Version of the release
74    pub version: String,
75
76    #[deb822(field = "Date")]
77    /// Date the release was published
78    pub date: String,
79
80    #[deb822(field = "NotAutomatic", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
81    /// Whether the release is not automatic
82    pub not_automatic: bool,
83
84    #[deb822(field = "ButAutomaticUpgrades", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
85    /// Indicates if packages retrieved from this release should be automatically upgraded
86    pub but_automatic_upgrades: bool,
87
88    #[deb822(field = "Acquire-By-Hash", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
89    /// Whether packages files can be acquired by hash
90    pub acquire_by_hash: bool,
91}
92
93fn deserialize_binaries(value: &str) -> Result<Vec<String>, String> {
94    Ok(value.split_whitespace().map(|s| s.to_string()).collect())
95}
96
97fn join_lines(components: &[String]) -> String {
98    components.join("\n")
99}
100
101fn deserialize_package_list(value: &str) -> Result<Vec<String>, String> {
102    Ok(value.split('\n').map(|s| s.to_string()).collect())
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, ToDeb822, FromDeb822)]
106/// A source
107pub struct Source {
108    #[deb822(field = "Directory")]
109    /// The directory of the source
110    pub directory: String,
111
112    #[deb822(field = "Description")]
113    /// Description of the source
114    pub description: Option<String>,
115
116    #[deb822(field = "Version")]
117    /// Version of the source
118    pub version: debversion::Version,
119
120    #[deb822(field = "Package")]
121    /// Package of the source
122    pub package: String,
123
124    #[deb822(field = "Binary", deserialize_with = deserialize_binaries, serialize_with = join_whitespace)]
125    /// Binaries of the source
126    pub binaries: Option<Vec<String>>,
127
128    #[deb822(field = "Maintainer")]
129    /// Maintainer of the source
130    pub maintainer: Option<String>,
131
132    #[deb822(field = "Build-Depends")]
133    /// Build dependencies of the source
134    pub build_depends: Option<String>,
135
136    #[deb822(field = "Build-Depends-Indep")]
137    /// Build dependencies independent of the architecture of the source
138    pub build_depends_indep: Option<Relations>,
139
140    #[deb822(field = "Build-Conflicts")]
141    /// Build conflicts of the source
142    pub build_conflicts: Option<Relations>,
143
144    #[deb822(field = "Build-Conflicts-Indep")]
145    /// Build conflicts independent of the architecture of the source
146    pub build_conflicts_indep: Option<Relations>,
147
148    #[deb822(field = "Standards-Version")]
149    /// Standards version of the source
150    pub standards_version: Option<String>,
151
152    #[deb822(field = "Homepage")]
153    /// Homepage of the source
154    pub homepage: Option<String>,
155
156    #[deb822(field = "Autobuild")]
157    /// Whether the source should be autobuilt
158    pub autobuild: Option<bool>,
159
160    #[deb822(field = "Testsuite")]
161    /// Testsuite of the source
162    pub testsuite: Option<String>,
163
164    #[deb822(field = "Vcs-Browser")]
165    /// VCS browser of the source
166    pub vcs_browser: Option<String>,
167
168    #[deb822(field = "Vcs-Git")]
169    /// VCS Git of the source
170    pub vcs_git: Option<String>,
171
172    #[deb822(field = "Vcs-Bzr")]
173    /// VCS Bzr of the source
174    pub vcs_bzr: Option<String>,
175
176    #[deb822(field = "Vcs-Hg")]
177    /// VCS Hg of the source
178    pub vcs_hg: Option<String>,
179
180    #[deb822(field = "Vcs-Svn")]
181    /// VCS SVN of the source
182    pub vcs_svn: Option<String>,
183
184    #[deb822(field = "Vcs-Darcs")]
185    /// VCS Darcs of the source
186    pub vcs_darcs: Option<String>,
187
188    #[deb822(field = "Vcs-Cvs")]
189    /// VCS CVS of the source
190    pub vcs_cvs: Option<String>,
191
192    #[deb822(field = "Vcs-Arch")]
193    /// VCS Arch of the source
194    pub vcs_arch: Option<String>,
195
196    #[deb822(field = "Vcs-Mtn")]
197    /// VCS Mtn of the source
198    pub vcs_mtn: Option<String>,
199
200    #[deb822(field = "Priority")]
201    /// Priority of the source
202    pub priority: Option<crate::fields::Priority>,
203
204    #[deb822(field = "Section")]
205    /// Section of the source
206    pub section: Option<String>,
207
208    #[deb822(field = "Format")]
209    /// Format of the source
210    pub format: Option<String>,
211
212    #[deb822(field = "Package-List", deserialize_with = deserialize_package_list, serialize_with = join_lines)]
213    /// Package list of the source
214    pub package_list: Vec<String>,
215}
216
217impl std::str::FromStr for Source {
218    type Err = String;
219
220    fn from_str(s: &str) -> Result<Self, Self::Err> {
221        let para = s
222            .parse::<deb822_fast::Paragraph>()
223            .map_err(|e| e.to_string())?;
224
225        FromDeb822Paragraph::from_paragraph(&para)
226    }
227}
228
229impl std::fmt::Display for Source {
230    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
231        let para: deb822_fast::Paragraph = self.to_paragraph();
232        write!(f, "{}", para)
233    }
234}
235
236/// A package
237#[derive(Debug, Clone, PartialEq, Eq, ToDeb822, FromDeb822)]
238pub struct Package {
239    /// The name of the package
240    #[deb822(field = "Package")]
241    pub name: String,
242
243    /// The version of the package
244    #[deb822(field = "Version")]
245    pub version: debversion::Version,
246
247    /// The name and version of the source package, if different from `name`
248    #[deb822(field = "Source")]
249    pub source: Option<SourceRelation>,
250
251    /// The architecture of the package
252    #[deb822(field = "Architecture")]
253    pub architecture: String,
254
255    /// The maintainer of the package
256    #[deb822(field = "Maintainer")]
257    pub maintainer: Option<String>,
258
259    /// The installed size of the package
260    #[deb822(field = "Installed-Size")]
261    pub installed_size: Option<usize>,
262
263    /// Dependencies
264    #[deb822(field = "Depends")]
265    pub depends: Option<Relations>,
266
267    /// Pre-Depends
268    #[deb822(field = "Pre-Depends")]
269    pub pre_depends: Option<Relations>,
270
271    /// Recommends
272    #[deb822(field = "Recommends")]
273    pub recommends: Option<Relations>,
274
275    /// Suggests
276    #[deb822(field = "Suggests")]
277    pub suggests: Option<Relations>,
278
279    /// Enhances
280    #[deb822(field = "Enhances")]
281    pub enhances: Option<Relations>,
282
283    /// Breaks
284    #[deb822(field = "Breaks")]
285    pub breaks: Option<Relations>,
286
287    /// Conflicts
288    #[deb822(field = "Conflicts")]
289    pub conflicts: Option<Relations>,
290
291    /// Provides
292    #[deb822(field = "Provides")]
293    pub provides: Option<Relations>,
294
295    /// Replaces
296    #[deb822(field = "Replaces")]
297    pub replaces: Option<Relations>,
298
299    /// Built-Using
300    #[deb822(field = "Built-Using")]
301    pub built_using: Option<Relations>,
302
303    /// Static-Built-Using
304    #[deb822(field = "Static-Built-Using")]
305    pub static_built_using: Option<Relations>,
306
307    /// Description
308    #[deb822(field = "Description")]
309    pub description: Option<String>,
310
311    /// Homepage
312    #[deb822(field = "Homepage")]
313    pub homepage: Option<String>,
314
315    /// Priority
316    #[deb822(field = "Priority")]
317    pub priority: Option<crate::fields::Priority>,
318
319    /// Section
320    #[deb822(field = "Section")]
321    pub section: Option<String>,
322
323    /// Essential
324    #[deb822(field = "Essential", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
325    pub essential: Option<bool>,
326
327    /// Tag
328    #[deb822(field = "Tag")]
329    pub tag: Option<String>,
330
331    /// Size
332    #[deb822(field = "Size")]
333    pub size: Option<usize>,
334
335    /// MD5sum
336    #[deb822(field = "MD5sum")]
337    pub md5sum: Option<String>,
338
339    /// SHA256
340    #[deb822(field = "SHA256")]
341    pub sha256: Option<String>,
342
343    /// Description (MD5)
344    #[deb822(field = "Description-MD5")]
345    pub description_md5: Option<String>,
346}
347
348impl std::str::FromStr for Package {
349    type Err = String;
350
351    fn from_str(s: &str) -> Result<Self, Self::Err> {
352        let para = s
353            .parse::<deb822_fast::Paragraph>()
354            .map_err(|e| e.to_string())?;
355
356        FromDeb822Paragraph::from_paragraph(&para)
357    }
358}
359
360impl std::fmt::Display for Package {
361    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
362        let para: deb822_fast::Paragraph = self.to_paragraph();
363        write!(f, "{}", para)
364    }
365}
366
367#[cfg(test)]
368mod tests {
369    use super::*;
370    use deb822_fast::Paragraph;
371    use deb822_fast::ToDeb822Paragraph;
372
373    #[test]
374    fn test_release() {
375        let release = Release {
376            codename: "focal".to_string(),
377            components: vec!["main".to_string(), "restricted".to_string()],
378            architectures: vec!["amd64".to_string(), "arm64".to_string()],
379            description: "Ubuntu 20.04 LTS".to_string(),
380            origin: "Ubuntu".to_string(),
381            label: "Ubuntu".to_string(),
382            suite: "focal".to_string(),
383            version: "20.04".to_string(),
384            date: "Thu, 23 Apr 2020 17:19:19 UTC".to_string(),
385            not_automatic: false,
386            but_automatic_upgrades: true,
387            acquire_by_hash: true,
388        };
389
390        let deb822 = r#"Codename: focal
391Components: main restricted
392Architectures: amd64 arm64
393Description: Ubuntu 20.04 LTS
394Origin: Ubuntu
395Label: Ubuntu
396Suite: focal
397Version: 20.04
398Date: Thu, 23 Apr 2020 17:19:19 UTC
399NotAutomatic: no
400ButAutomaticUpgrades: yes
401Acquire-By-Hash: yes
402"#;
403
404        let para = deb822.parse::<Paragraph>().unwrap();
405
406        let release: deb822_fast::Paragraph = release.to_paragraph();
407
408        assert_eq!(release, para);
409    }
410
411    #[test]
412    fn test_package() {
413        let package = r#"Package: apt
414Version: 2.1.10
415Architecture: amd64
416Maintainer: APT Development Team <apt@lists.debian.org>
417Installed-Size: 3524
418Depends: libc6 (>= 2.14), libgcc1
419Pre-Depends: dpkg (>= 1.15.6)
420Recommends: gnupg
421Suggests: apt-doc, aptitude | synaptic | wajig
422"#;
423
424        let package: Package = package.parse().unwrap();
425
426        assert_eq!(package.name, "apt");
427        assert_eq!(package.version, "2.1.10".parse().unwrap());
428        assert_eq!(package.architecture, "amd64");
429    }
430
431    #[test]
432    fn test_package_essential() {
433        let package = r#"Package: base-files
434Version: 11.1
435Architecture: amd64
436Essential: yes
437"#;
438
439        let package: Package = package.parse().unwrap();
440
441        assert_eq!(package.name, "base-files");
442        assert_eq!(package.essential, Some(true));
443    }
444
445    #[test]
446    fn test_package_essential_no() {
447        let package = r#"Package: apt
448Version: 2.1.10
449Architecture: amd64
450Essential: no
451"#;
452
453        let package: Package = package.parse().unwrap();
454
455        assert_eq!(package.name, "apt");
456        assert_eq!(package.essential, Some(false));
457    }
458
459    #[test]
460    fn test_package_with_different_source() {
461        let package = r#"Package: apt
462Source: not-apt (1.1.5)
463Version: 2.1.10
464Architecture: amd64
465Maintainer: APT Development Team <apt@lists.debian.org>
466Installed-Size: 3524
467Depends: libc6 (>= 2.14), libgcc1
468Pre-Depends: dpkg (>= 1.15.6)
469Recommends: gnupg
470Suggests: apt-doc, aptitude | synaptic | wajig
471"#;
472
473        let package: Package = package.parse().unwrap();
474
475        assert_eq!(package.name, "apt");
476        assert_eq!(package.version, "2.1.10".parse().unwrap());
477        assert_eq!(package.architecture, "amd64");
478        assert_eq!(
479            package.source,
480            Some(SourceRelation {
481                name: "not-apt".to_string(),
482                version: Some("1.1.5".parse().unwrap())
483            })
484        );
485    }
486}