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: Option<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: Option<String>,
59
60    #[deb822(field = "Origin")]
61    /// Origin of the release
62    pub origin: Option<String>,
63
64    #[deb822(field = "Label")]
65    /// Label of the release
66    pub label: Option<String>,
67
68    #[deb822(field = "Suite")]
69    /// Suite of the release
70    pub suite: Option<String>,
71
72    #[deb822(field = "Version")]
73    /// Version of the release
74    pub version: Option<String>,
75
76    #[deb822(field = "Date")]
77    /// Date the release was published
78    pub date: Option<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: Option<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: Option<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: Option<bool>,
91
92    #[deb822(field = "MD5Sum", deserialize_with = deserialize_checksums::<crate::fields::Md5Checksum>, serialize_with = serialize_checksums::<crate::fields::Md5Checksum>)]
93    /// MD5 checksums of repository index files
94    pub checksums_md5: Option<Vec<crate::fields::Md5Checksum>>,
95
96    #[deb822(field = "SHA1", deserialize_with = deserialize_checksums::<crate::fields::Sha1Checksum>, serialize_with = serialize_checksums::<crate::fields::Sha1Checksum>)]
97    /// SHA-1 checksums of repository index files
98    pub checksums_sha1: Option<Vec<crate::fields::Sha1Checksum>>,
99
100    #[deb822(field = "SHA256", deserialize_with = deserialize_checksums::<crate::fields::Sha256Checksum>, serialize_with = serialize_checksums::<crate::fields::Sha256Checksum>)]
101    /// SHA-256 checksums of repository index files
102    pub checksums_sha256: Option<Vec<crate::fields::Sha256Checksum>>,
103
104    #[deb822(field = "SHA512", deserialize_with = deserialize_checksums::<crate::fields::Sha512Checksum>, serialize_with = serialize_checksums::<crate::fields::Sha512Checksum>)]
105    /// SHA-512 checksums of repository index files
106    pub checksums_sha512: Option<Vec<crate::fields::Sha512Checksum>>,
107}
108
109impl std::str::FromStr for Release {
110    type Err = String;
111
112    fn from_str(s: &str) -> Result<Self, Self::Err> {
113        let para = s
114            .parse::<deb822_fast::Paragraph>()
115            .map_err(|e| e.to_string())?;
116        FromDeb822Paragraph::from_paragraph(&para)
117    }
118}
119
120impl std::fmt::Display for Release {
121    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
122        let para: deb822_fast::Paragraph = self.to_paragraph();
123        write!(f, "{}", para)
124    }
125}
126
127fn deserialize_binaries(value: &str) -> Result<Vec<String>, String> {
128    Ok(value.split(",").map(|s| s.trim().to_string()).collect())
129}
130
131fn deserialize_testsuite_triggers(value: &str) -> Result<Vec<String>, String> {
132    Ok(value.split(",").map(|s| s.trim().to_string()).collect())
133}
134
135fn join_lines(components: &[String]) -> String {
136    components.join("\n")
137}
138
139fn deserialize_package_list(value: &str) -> Result<Vec<String>, String> {
140    Ok(value
141        .split('\n')
142        .map(|s| s.trim().to_string())
143        .filter(|s| !s.is_empty())
144        .collect())
145}
146
147fn deserialize_checksums<T>(value: &str) -> Result<Vec<T>, String>
148where
149    T: std::str::FromStr,
150    T::Err: std::fmt::Display,
151{
152    value
153        .lines()
154        .map(|s| s.trim())
155        .filter(|s| !s.is_empty())
156        .map(|s| s.parse::<T>().map_err(|e| e.to_string()))
157        .collect()
158}
159
160fn serialize_checksums<T: std::fmt::Display>(checksums: &[T]) -> String {
161    checksums
162        .iter()
163        .map(|c| format!(" {}", c))
164        .collect::<Vec<_>>()
165        .join("\n")
166}
167
168#[derive(Debug, Clone, PartialEq, Eq, ToDeb822, FromDeb822)]
169/// A source
170pub struct Source {
171    #[deb822(field = "Directory")]
172    /// The directory of the source
173    pub directory: String,
174
175    #[deb822(field = "Description")]
176    /// Description of the source
177    pub description: Option<String>,
178
179    #[deb822(field = "Version")]
180    /// Version of the source
181    pub version: debversion::Version,
182
183    #[deb822(field = "Package")]
184    /// Package of the source
185    pub package: String,
186
187    #[deb822(field = "Binary", deserialize_with = deserialize_binaries, serialize_with = join_whitespace)]
188    /// Binaries of the source
189    pub binaries: Option<Vec<String>>,
190
191    #[deb822(field = "Maintainer")]
192    /// Maintainer of the source
193    pub maintainer: Option<String>,
194
195    #[deb822(field = "Uploaders")]
196    /// Uploaders of the source
197    pub uploaders: Option<String>,
198
199    #[deb822(field = "Build-Depends")]
200    /// Build dependencies of the source
201    pub build_depends: Option<Relations>,
202
203    #[deb822(field = "Build-Depends-Indep")]
204    /// Build dependencies independent of the architecture of the source
205    pub build_depends_indep: Option<Relations>,
206
207    #[deb822(field = "Build-Depends-Arch")]
208    /// Build dependencies dependent on the architecture of the source
209    pub build_depends_arch: Option<Relations>,
210
211    #[deb822(field = "Build-Conflicts")]
212    /// Build conflicts of the source
213    pub build_conflicts: Option<Relations>,
214
215    #[deb822(field = "Build-Conflicts-Indep")]
216    /// Build conflicts independent of the architecture of the source
217    pub build_conflicts_indep: Option<Relations>,
218
219    #[deb822(field = "Build-Conflicts-Arch")]
220    /// Build conflicts dependent on the architecture of the source
221    pub build_conflicts_arch: Option<Relations>,
222
223    #[deb822(field = "Standards-Version")]
224    /// Standards version of the source
225    pub standards_version: Option<String>,
226
227    #[deb822(field = "Homepage")]
228    /// Homepage of the source
229    pub homepage: Option<String>,
230
231    #[deb822(field = "Autobuild")]
232    /// Whether the source should be autobuilt
233    pub autobuild: Option<bool>,
234
235    #[deb822(field = "Extra-Source-Only", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
236    /// Whether this is a source-only upload
237    pub extra_source_only: Option<bool>,
238
239    #[deb822(field = "Testsuite")]
240    /// Testsuite of the source
241    pub testsuite: Option<String>,
242
243    #[deb822(field = "Testsuite-Triggers", deserialize_with = deserialize_testsuite_triggers, serialize_with = join_whitespace)]
244    /// The packages triggering the testsuite of the source
245    pub testsuite_triggers: Option<Vec<String>>,
246
247    #[deb822(field = "Vcs-Browser")]
248    /// VCS browser of the source
249    pub vcs_browser: Option<String>,
250
251    #[deb822(field = "Vcs-Git")]
252    /// VCS Git of the source
253    pub vcs_git: Option<String>,
254
255    #[deb822(field = "Vcs-Bzr")]
256    /// VCS Bzr of the source
257    pub vcs_bzr: Option<String>,
258
259    #[deb822(field = "Vcs-Hg")]
260    /// VCS Hg of the source
261    pub vcs_hg: Option<String>,
262
263    #[deb822(field = "Vcs-Svn")]
264    /// VCS SVN of the source
265    pub vcs_svn: Option<String>,
266
267    #[deb822(field = "Vcs-Darcs")]
268    /// VCS Darcs of the source
269    pub vcs_darcs: Option<String>,
270
271    #[deb822(field = "Vcs-Cvs")]
272    /// VCS CVS of the source
273    pub vcs_cvs: Option<String>,
274
275    #[deb822(field = "Vcs-Arch")]
276    /// VCS Arch of the source
277    pub vcs_arch: Option<String>,
278
279    #[deb822(field = "Vcs-Mtn")]
280    /// VCS Mtn of the source
281    pub vcs_mtn: Option<String>,
282
283    #[deb822(field = "Dgit")]
284    /// Dgit information (commit, suite, ref, url)
285    pub dgit: Option<crate::fields::DgitInfo>,
286
287    #[deb822(field = "Priority")]
288    /// Priority of the source
289    pub priority: Option<crate::fields::Priority>,
290
291    #[deb822(field = "Section")]
292    /// Section of the source
293    pub section: Option<String>,
294
295    #[deb822(field = "Format")]
296    /// Format of the source
297    pub format: Option<String>,
298
299    #[deb822(field = "Architecture")]
300    /// Architectures the source builds for
301    pub architecture: Option<String>,
302
303    #[deb822(field = "Package-List", deserialize_with = deserialize_package_list, serialize_with = join_lines)]
304    /// Package list of the source
305    pub package_list: Option<Vec<String>>,
306
307    #[deb822(field = "Files", deserialize_with = deserialize_checksums::<crate::fields::Md5Checksum>, serialize_with = serialize_checksums::<crate::fields::Md5Checksum>)]
308    /// MD5 checksums of source files
309    pub files: Option<Vec<crate::fields::Md5Checksum>>,
310
311    #[deb822(field = "Checksums-Sha1", deserialize_with = deserialize_checksums::<crate::fields::Sha1Checksum>, serialize_with = serialize_checksums::<crate::fields::Sha1Checksum>)]
312    /// SHA-1 checksums of source files
313    pub checksums_sha1: Option<Vec<crate::fields::Sha1Checksum>>,
314
315    #[deb822(field = "Checksums-Sha256", deserialize_with = deserialize_checksums::<crate::fields::Sha256Checksum>, serialize_with = serialize_checksums::<crate::fields::Sha256Checksum>)]
316    /// SHA-256 checksums of source files
317    pub checksums_sha256: Option<Vec<crate::fields::Sha256Checksum>>,
318
319    #[deb822(field = "Checksums-Sha512", deserialize_with = deserialize_checksums::<crate::fields::Sha512Checksum>, serialize_with = serialize_checksums::<crate::fields::Sha512Checksum>)]
320    /// SHA-512 checksums of source files
321    pub checksums_sha512: Option<Vec<crate::fields::Sha512Checksum>>,
322}
323
324impl std::str::FromStr for Source {
325    type Err = String;
326
327    fn from_str(s: &str) -> Result<Self, Self::Err> {
328        let para = s
329            .parse::<deb822_fast::Paragraph>()
330            .map_err(|e| e.to_string())?;
331
332        FromDeb822Paragraph::from_paragraph(&para)
333    }
334}
335
336impl std::fmt::Display for Source {
337    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
338        let para: deb822_fast::Paragraph = self.to_paragraph();
339        write!(f, "{}", para)
340    }
341}
342
343/// A package
344#[derive(Debug, Clone, PartialEq, Eq, ToDeb822, FromDeb822)]
345pub struct Package {
346    /// The name of the package
347    #[deb822(field = "Package")]
348    pub name: String,
349
350    /// The version of the package
351    #[deb822(field = "Version")]
352    pub version: debversion::Version,
353
354    /// The name and version of the source package, if different from `name`
355    #[deb822(field = "Source")]
356    pub source: Option<SourceRelation>,
357
358    /// The architecture of the package
359    #[deb822(field = "Architecture")]
360    pub architecture: String,
361
362    /// The maintainer of the package
363    #[deb822(field = "Maintainer")]
364    pub maintainer: Option<String>,
365
366    /// The installed size of the package
367    #[deb822(field = "Installed-Size")]
368    pub installed_size: Option<usize>,
369
370    /// Dependencies
371    #[deb822(field = "Depends")]
372    pub depends: Option<Relations>,
373
374    /// Pre-Depends
375    #[deb822(field = "Pre-Depends")]
376    pub pre_depends: Option<Relations>,
377
378    /// Recommends
379    #[deb822(field = "Recommends")]
380    pub recommends: Option<Relations>,
381
382    /// Suggests
383    #[deb822(field = "Suggests")]
384    pub suggests: Option<Relations>,
385
386    /// Enhances
387    #[deb822(field = "Enhances")]
388    pub enhances: Option<Relations>,
389
390    /// Breaks
391    #[deb822(field = "Breaks")]
392    pub breaks: Option<Relations>,
393
394    /// Conflicts
395    #[deb822(field = "Conflicts")]
396    pub conflicts: Option<Relations>,
397
398    /// Provides
399    #[deb822(field = "Provides")]
400    pub provides: Option<Relations>,
401
402    /// Replaces
403    #[deb822(field = "Replaces")]
404    pub replaces: Option<Relations>,
405
406    /// Built-Using
407    #[deb822(field = "Built-Using")]
408    pub built_using: Option<Relations>,
409
410    /// Static-Built-Using
411    #[deb822(field = "Static-Built-Using")]
412    pub static_built_using: Option<Relations>,
413
414    /// Description
415    #[deb822(field = "Description")]
416    pub description: Option<String>,
417
418    /// Homepage
419    #[deb822(field = "Homepage")]
420    pub homepage: Option<String>,
421
422    /// Origin
423    #[deb822(field = "Origin")]
424    pub origin: Option<String>,
425
426    /// Priority
427    #[deb822(field = "Priority")]
428    pub priority: Option<crate::fields::Priority>,
429
430    /// Section
431    #[deb822(field = "Section")]
432    pub section: Option<String>,
433
434    /// Essential
435    #[deb822(field = "Essential", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
436    pub essential: Option<bool>,
437
438    /// Multi-Arch
439    #[deb822(field = "Multi-Arch")]
440    pub multi_arch: Option<crate::fields::MultiArch>,
441
442    /// Tag
443    #[deb822(field = "Tag")]
444    pub tag: Option<String>,
445
446    /// Task
447    #[deb822(field = "Task")]
448    pub task: Option<String>,
449
450    /// Size
451    #[deb822(field = "Size")]
452    pub size: Option<usize>,
453
454    /// Filename (path within the repository)
455    #[deb822(field = "Filename")]
456    pub filename: Option<String>,
457
458    /// MD5sum
459    #[deb822(field = "MD5sum")]
460    pub md5sum: Option<String>,
461
462    /// SHA1
463    #[deb822(field = "SHA1")]
464    pub sha1: Option<String>,
465
466    /// SHA256
467    #[deb822(field = "SHA256")]
468    pub sha256: Option<String>,
469
470    /// SHA512
471    #[deb822(field = "SHA512")]
472    pub sha512: Option<String>,
473
474    /// Description (MD5)
475    #[deb822(field = "Description-MD5")]
476    pub description_md5: Option<String>,
477}
478
479impl std::str::FromStr for Package {
480    type Err = String;
481
482    fn from_str(s: &str) -> Result<Self, Self::Err> {
483        let para = s
484            .parse::<deb822_fast::Paragraph>()
485            .map_err(|e| e.to_string())?;
486
487        FromDeb822Paragraph::from_paragraph(&para)
488    }
489}
490
491impl std::fmt::Display for Package {
492    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
493        let para: deb822_fast::Paragraph = self.to_paragraph();
494        write!(f, "{}", para)
495    }
496}
497
498#[cfg(test)]
499mod tests {
500    use super::*;
501    use deb822_fast::Paragraph;
502    use deb822_fast::ToDeb822Paragraph;
503
504    #[test]
505    fn test_release() {
506        let release = Release {
507            codename: Some("focal".to_string()),
508            components: vec!["main".to_string(), "restricted".to_string()],
509            architectures: vec!["amd64".to_string(), "arm64".to_string()],
510            description: Some("Ubuntu 20.04 LTS".to_string()),
511            origin: Some("Ubuntu".to_string()),
512            label: Some("Ubuntu".to_string()),
513            suite: Some("focal".to_string()),
514            version: Some("20.04".to_string()),
515            date: Some("Thu, 23 Apr 2020 17:19:19 UTC".to_string()),
516            not_automatic: Some(false),
517            but_automatic_upgrades: Some(true),
518            acquire_by_hash: Some(true),
519            checksums_md5: None,
520            checksums_sha1: None,
521            checksums_sha256: None,
522            checksums_sha512: None,
523        };
524
525        let deb822 = r#"Codename: focal
526Components: main restricted
527Architectures: amd64 arm64
528Description: Ubuntu 20.04 LTS
529Origin: Ubuntu
530Label: Ubuntu
531Suite: focal
532Version: 20.04
533Date: Thu, 23 Apr 2020 17:19:19 UTC
534NotAutomatic: no
535ButAutomaticUpgrades: yes
536Acquire-By-Hash: yes
537"#;
538
539        let para = deb822.parse::<Paragraph>().unwrap();
540
541        let release: deb822_fast::Paragraph = release.to_paragraph();
542
543        assert_eq!(release, para);
544    }
545
546    #[test]
547    fn test_package() {
548        let package = r#"Package: apt
549Version: 2.1.10
550Architecture: amd64
551Maintainer: APT Development Team <apt@lists.debian.org>
552Installed-Size: 3524
553Depends: libc6 (>= 2.14), libgcc1
554Pre-Depends: dpkg (>= 1.15.6)
555Recommends: gnupg
556Suggests: apt-doc, aptitude | synaptic | wajig
557"#;
558
559        let package: Package = package.parse().unwrap();
560
561        assert_eq!(package.name, "apt");
562        assert_eq!(package.version, "2.1.10".parse().unwrap());
563        assert_eq!(package.architecture, "amd64");
564    }
565
566    #[test]
567    fn test_package_essential() {
568        let package = r#"Package: base-files
569Version: 11.1
570Architecture: amd64
571Essential: yes
572"#;
573
574        let package: Package = package.parse().unwrap();
575
576        assert_eq!(package.name, "base-files");
577        assert_eq!(package.essential, Some(true));
578    }
579
580    #[test]
581    fn test_package_essential_no() {
582        let package = r#"Package: apt
583Version: 2.1.10
584Architecture: amd64
585Essential: no
586"#;
587
588        let package: Package = package.parse().unwrap();
589
590        assert_eq!(package.name, "apt");
591        assert_eq!(package.essential, Some(false));
592    }
593
594    #[test]
595    fn test_package_with_different_source() {
596        let package = r#"Package: apt
597Source: not-apt (1.1.5)
598Version: 2.1.10
599Architecture: amd64
600Maintainer: APT Development Team <apt@lists.debian.org>
601Installed-Size: 3524
602Depends: libc6 (>= 2.14), libgcc1
603Pre-Depends: dpkg (>= 1.15.6)
604Recommends: gnupg
605Suggests: apt-doc, aptitude | synaptic | wajig
606"#;
607
608        let package: Package = package.parse().unwrap();
609
610        assert_eq!(package.name, "apt");
611        assert_eq!(package.version, "2.1.10".parse().unwrap());
612        assert_eq!(package.architecture, "amd64");
613        assert_eq!(
614            package.source,
615            Some(SourceRelation {
616                name: "not-apt".to_string(),
617                version: Some("1.1.5".parse().unwrap())
618            })
619        );
620    }
621
622    #[test]
623    fn test_source() {
624        let source = r#"Package: abinit
625Binary: abinit, abinit-doc, abinit-data
626Version: 9.10.4-3
627Maintainer: Debichem Team <debichem-devel@lists.alioth.debian.org>
628Uploaders: Andreas Tille <tille@debian.org>, Michael Banck <mbanck@debian.org>
629Build-Depends: debhelper (>= 11), gfortran, liblapack-dev, python3, graphviz, markdown, ghostscript, help2man, libfftw3-dev, libhdf5-dev, libnetcdff-dev, libssl-dev, libxc-dev, mpi-default-dev, python3-dev, python3-numpy, python3-pandas, python3-yaml, texlive-latex-extra, texlive-fonts-recommended, texlive-extra-utils, texlive-pstricks, texlive-publishers, texlive-luatex
630Architecture: any all
631Standards-Version: 3.9.8
632Format: 3.0 (quilt)
633Files:
634 843550cbd14395c0b9408158a91a239c 2464 abinit_9.10.4-3.dsc
635 a323f11fbd4a7d0f461d99c931903b5c 130747285 abinit_9.10.4.orig.tar.gz
636 27c12d3dac5cd105cebaa2af4247e807 15068 abinit_9.10.4-3.debian.tar.xz
637Vcs-Browser: https://salsa.debian.org/debichem-team/abinit
638Vcs-Git: https://salsa.debian.org/debichem-team/abinit.git
639Checksums-Sha256:
640 c3c217b14bc5705a1d8930a2e7fcef58e64beaa22abc213e2eacc7d5537ef840 2464 abinit_9.10.4-3.dsc
641 6bf3c276c333956f722761f189f2b4324e150c8a50470ecb72ee07cc1c457d48 130747285 abinit_9.10.4.orig.tar.gz
642 80c4fb7575d67f3167d7c34fd59477baf839810d0b863e19f1dd9fea1bc0b3b5 15068 abinit_9.10.4-3.debian.tar.xz
643Homepage: http://www.abinit.org/
644Package-List: 
645 abinit deb science optional arch=any
646 abinit-data deb science optional arch=all
647 abinit-doc deb doc optional arch=all
648Testsuite: autopkgtest
649Testsuite-Triggers: python3, python3-numpy, python3-pandas, python3-yaml
650Directory: pool/main/a/abinit
651Priority: optional
652Section: science
653"#;
654
655        let source: Source = source.parse().unwrap();
656
657        assert_eq!(source.package, "abinit");
658        assert_eq!(source.version, "9.10.4-3".parse().unwrap());
659        assert_eq!(
660            source.binaries,
661            Some(vec![
662                "abinit".to_string(),
663                "abinit-doc".to_string(),
664                "abinit-data".to_string()
665            ])
666        );
667
668        let build_depends = source.build_depends.as_ref();
669        let build_depends: Vec<_> = build_depends.iter().collect();
670        let build_depends = build_depends[0];
671
672        let expected_build_depends = &[
673            "debhelper",
674            "gfortran",
675            "liblapack-dev",
676            "python3",
677            "graphviz",
678            "markdown",
679            "ghostscript",
680            "help2man",
681            "libfftw3-dev",
682            "libhdf5-dev",
683            "libnetcdff-dev",
684            "libssl-dev",
685            "libxc-dev",
686            "mpi-default-dev",
687            "python3-dev",
688            "python3-numpy",
689            "python3-pandas",
690            "python3-yaml",
691            "texlive-latex-extra",
692            "texlive-fonts-recommended",
693            "texlive-extra-utils",
694            "texlive-pstricks",
695            "texlive-publishers",
696            "texlive-luatex",
697        ];
698
699        assert_eq!(build_depends.len(), expected_build_depends.len());
700        assert_eq!(build_depends[0][0].name, expected_build_depends[0]);
701        assert_eq!(
702            build_depends[build_depends.len() - 1][0].name,
703            expected_build_depends[build_depends.len() - 1]
704        );
705
706        assert_eq!(
707            source.testsuite_triggers,
708            Some(
709                ["python3", "python3-numpy", "python3-pandas", "python3-yaml"]
710                    .into_iter()
711                    .map(String::from)
712                    .collect()
713            )
714        );
715
716        // Test Uploaders field
717        assert_eq!(
718            source.uploaders,
719            Some("Andreas Tille <tille@debian.org>, Michael Banck <mbanck@debian.org>".to_string())
720        );
721
722        // Test Files checksum list
723        let files = source.files.as_ref().unwrap();
724        assert_eq!(files.len(), 3);
725        assert_eq!(files[0].md5sum, "843550cbd14395c0b9408158a91a239c");
726        assert_eq!(files[0].size, 2464);
727        assert_eq!(files[0].filename, "abinit_9.10.4-3.dsc");
728
729        // Test Checksums-Sha256 list
730        let checksums_sha256 = source.checksums_sha256.as_ref().unwrap();
731        assert_eq!(checksums_sha256.len(), 3);
732        assert_eq!(
733            checksums_sha256[0].sha256,
734            "c3c217b14bc5705a1d8930a2e7fcef58e64beaa22abc213e2eacc7d5537ef840"
735        );
736        assert_eq!(checksums_sha256[0].size, 2464);
737        assert_eq!(checksums_sha256[0].filename, "abinit_9.10.4-3.dsc");
738
739        // Test Package-List
740        let package_list = source.package_list.as_ref().unwrap();
741        assert_eq!(package_list.len(), 3);
742        assert!(package_list[0].starts_with("abinit "));
743    }
744
745    #[test]
746    fn test_source_with_extra_source_only() {
747        let source = r#"Package: test-pkg
748Version: 1.0-1
749Maintainer: Test Maintainer <test@example.com>
750Extra-Source-Only: yes
751Directory: pool/main/t/test-pkg
752"#;
753
754        let source: Source = source.parse().unwrap();
755        assert_eq!(source.package, "test-pkg");
756        assert_eq!(source.extra_source_only, Some(true));
757    }
758
759    #[test]
760    fn test_source_with_checksums_sha1() {
761        let source = r#"Package: test-pkg
762Version: 1.0-1
763Maintainer: Test Maintainer <test@example.com>
764Checksums-Sha1:
765 da39a3ee5e6b4b0d3255bfef95601890afd80709 0 test_1.0-1.dsc
766Directory: pool/main/t/test-pkg
767"#;
768
769        let source: Source = source.parse().unwrap();
770        let checksums = source.checksums_sha1.as_ref().unwrap();
771        assert_eq!(checksums.len(), 1);
772        assert_eq!(
773            checksums[0].sha1,
774            "da39a3ee5e6b4b0d3255bfef95601890afd80709"
775        );
776        assert_eq!(checksums[0].size, 0);
777        assert_eq!(checksums[0].filename, "test_1.0-1.dsc");
778    }
779
780    #[test]
781    fn test_source_with_checksums_sha512() {
782        let source = r#"Package: test-pkg
783Version: 1.0-1
784Maintainer: Test Maintainer <test@example.com>
785Checksums-Sha512:
786 cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e 0 test_1.0-1.dsc
787Directory: pool/main/t/test-pkg
788"#;
789
790        let source: Source = source.parse().unwrap();
791        let checksums = source.checksums_sha512.as_ref().unwrap();
792        assert_eq!(checksums.len(), 1);
793        assert_eq!(checksums[0].sha512, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e");
794        assert_eq!(checksums[0].size, 0);
795        assert_eq!(checksums[0].filename, "test_1.0-1.dsc");
796    }
797
798    #[test]
799    fn test_package_with_multi_arch() {
800        let package = r#"Package: test-pkg
801Version: 1.0-1
802Architecture: amd64
803Multi-Arch: same
804"#;
805
806        let package: Package = package.parse().unwrap();
807        assert_eq!(package.name, "test-pkg");
808        assert_eq!(package.multi_arch, Some(crate::fields::MultiArch::Same));
809    }
810
811    #[test]
812    fn test_package_with_all_checksums() {
813        let package = r#"Package: test-pkg
814Version: 1.0-1
815Architecture: amd64
816MD5sum: d41d8cd98f00b204e9800998ecf8427e
817SHA1: da39a3ee5e6b4b0d3255bfef95601890afd80709
818SHA256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
819SHA512: cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e
820"#;
821
822        let package: Package = package.parse().unwrap();
823        assert_eq!(
824            package.md5sum,
825            Some("d41d8cd98f00b204e9800998ecf8427e".to_string())
826        );
827        assert_eq!(
828            package.sha1,
829            Some("da39a3ee5e6b4b0d3255bfef95601890afd80709".to_string())
830        );
831        assert_eq!(
832            package.sha256,
833            Some("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".to_string())
834        );
835        assert_eq!(package.sha512, Some("cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e".to_string()));
836    }
837
838    #[test]
839    fn test_package_with_filename() {
840        let package = r#"Package: test-pkg
841Version: 1.0-1
842Architecture: amd64
843Filename: pool/main/t/test-pkg/test-pkg_1.0-1_amd64.deb
844"#;
845
846        let package: Package = package.parse().unwrap();
847        assert_eq!(
848            package.filename,
849            Some("pool/main/t/test-pkg/test-pkg_1.0-1_amd64.deb".to_string())
850        );
851    }
852
853    #[test]
854    fn test_package_with_task() {
855        let package = r#"Package: ubuntu-desktop
856Version: 1.0
857Architecture: amd64
858Task: ubuntu-desktop
859"#;
860
861        let package: Package = package.parse().unwrap();
862        assert_eq!(package.task, Some("ubuntu-desktop".to_string()));
863    }
864
865    #[test]
866    fn test_package_with_origin() {
867        let package = r#"Package: test-pkg
868Version: 1.0-1
869Architecture: amd64
870Origin: Debian
871"#;
872
873        let package: Package = package.parse().unwrap();
874        assert_eq!(package.origin, Some("Debian".to_string()));
875    }
876
877    #[test]
878    fn test_package_full_with_all_new_fields() {
879        let package = r#"Package: test-pkg
880Version: 1.0-1
881Architecture: amd64
882Multi-Arch: foreign
883Origin: Ubuntu
884Task: minimal
885Filename: pool/main/t/test-pkg/test-pkg_1.0-1_amd64.deb
886Size: 1234
887MD5sum: d41d8cd98f00b204e9800998ecf8427e
888SHA1: da39a3ee5e6b4b0d3255bfef95601890afd80709
889SHA256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
890SHA512: cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e
891"#;
892
893        let package: Package = package.parse().unwrap();
894        assert_eq!(package.name, "test-pkg");
895        assert_eq!(package.version, "1.0-1".parse().unwrap());
896        assert_eq!(package.architecture, "amd64");
897        assert_eq!(package.multi_arch, Some(crate::fields::MultiArch::Foreign));
898        assert_eq!(package.origin, Some("Ubuntu".to_string()));
899        assert_eq!(package.task, Some("minimal".to_string()));
900        assert_eq!(
901            package.filename,
902            Some("pool/main/t/test-pkg/test-pkg_1.0-1_amd64.deb".to_string())
903        );
904        assert_eq!(package.size, Some(1234));
905        assert!(package.md5sum.is_some());
906        assert!(package.sha1.is_some());
907        assert!(package.sha256.is_some());
908        assert!(package.sha512.is_some());
909    }
910
911    #[test]
912    fn test_source_with_dgit() {
913        let source = r#"Package: test-pkg
914Version: 1.0-1
915Maintainer: Test Maintainer <test@example.com>
916Dgit: c1370424e2404d3c22bd09c828d4b28d81d897ad debian archive/debian/1.1.0 https://git.dgit.debian.org/test-pkg
917Directory: pool/main/t/test-pkg
918Package-List: 
919 abinit deb science optional arch=any
920 abinit-data deb science optional arch=all
921 abinit-doc deb doc optional arch=all
922"#;
923
924        let source: Source = source.parse().unwrap();
925        assert_eq!(source.package, "test-pkg");
926        let dgit = source.dgit.as_ref().unwrap();
927        assert_eq!(dgit.commit, "c1370424e2404d3c22bd09c828d4b28d81d897ad");
928        assert_eq!(dgit.suite, "debian");
929        assert_eq!(dgit.git_ref, "archive/debian/1.1.0");
930        assert_eq!(dgit.url, "https://git.dgit.debian.org/test-pkg");
931    }
932
933    #[test]
934    fn test_source_checksums() {
935        let source = r#"Package: hello
936Version: 1.0-1
937Directory: pool/main/h/hello
938Package-List: 
939 hello deb misc optional arch=any
940Files:
941 acbd18db4cc2f85cedef654fccc4a4d8 1234 hello_1.0.orig.tar.gz
942Checksums-Sha256:
943 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 1234 hello_1.0.orig.tar.gz
944"#;
945
946        let source: Source = source.parse().unwrap();
947        assert_eq!(source.package, "hello");
948        let files = source.files.as_ref().unwrap();
949        assert_eq!(files.len(), 1);
950        assert_eq!(files[0].md5sum, "acbd18db4cc2f85cedef654fccc4a4d8");
951        assert_eq!(files[0].size, 1234);
952        assert_eq!(files[0].filename, "hello_1.0.orig.tar.gz");
953        let sha256 = source.checksums_sha256.as_ref().unwrap();
954        assert_eq!(sha256.len(), 1);
955        assert_eq!(
956            sha256[0].sha256,
957            "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
958        );
959    }
960
961    #[test]
962    fn test_release_checksums() {
963        let release_str = r#"Suite: stable
964Codename: stable
965Architectures: amd64
966Components: main
967MD5Sum:
968 acbd18db4cc2f85cedef654fccc4a4d8 1234 main/binary-amd64/Packages
969SHA256:
970 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 1234 main/binary-amd64/Packages
971"#;
972
973        let release: Release = release_str.parse().unwrap();
974        assert_eq!(release.suite, Some("stable".to_string()));
975        let md5 = release.checksums_md5.as_ref().unwrap();
976        assert_eq!(md5.len(), 1);
977        assert_eq!(md5[0].filename, "main/binary-amd64/Packages");
978        assert_eq!(md5[0].size, 1234);
979        let sha256 = release.checksums_sha256.as_ref().unwrap();
980        assert_eq!(sha256.len(), 1);
981        assert_eq!(sha256[0].filename, "main/binary-amd64/Packages");
982    }
983}