Skip to main content

provenant/assembly/
assemblers.rs

1use crate::models::{DatasourceId, FileInfo, Package, TopLevelDependency};
2use strum::EnumIter;
3
4use super::{
5    AssemblerConfig, AssemblyMode, DirectoryMergeOutput, cargo_resource_assign,
6    cargo_workspace_merge, composer_resource_assign, conda_rootfs_merge, file_ref_resolve,
7    hackage_merge, npm_resource_assign, npm_workspace_merge, nuget_cpm_resolve,
8    ruby_resource_assign, swift_merge,
9};
10
11#[derive(Clone, Copy)]
12pub(super) enum SpecialDirectoryMergerKind {
13    Skip,
14    Hackage,
15}
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, EnumIter)]
18pub(super) enum PostAssemblyPassKind {
19    SwiftMerge,
20    CondaRootfsMerge,
21    NpmResourceAssign,
22    FileReferenceResolve,
23    RpmYumdbMerge,
24    NpmWorkspaceMerge,
25    CargoWorkspaceMerge,
26    NugetCpmResolve,
27    CargoResourceAssign,
28    ComposerResourceAssign,
29    RubyResourceAssign,
30}
31
32pub(super) fn special_directory_merger_for(
33    config_key: DatasourceId,
34) -> Option<SpecialDirectoryMergerKind> {
35    match config_key {
36        DatasourceId::HackageCabal => Some(SpecialDirectoryMergerKind::Hackage),
37        DatasourceId::SwiftPackageManifestJson => Some(SpecialDirectoryMergerKind::Skip),
38        _ => None,
39    }
40}
41
42pub(super) static POST_ASSEMBLY_PASSES: &[PostAssemblyPassKind] = &[
43    PostAssemblyPassKind::SwiftMerge,
44    PostAssemblyPassKind::CondaRootfsMerge,
45    PostAssemblyPassKind::NpmResourceAssign,
46    PostAssemblyPassKind::FileReferenceResolve,
47    PostAssemblyPassKind::RpmYumdbMerge,
48    PostAssemblyPassKind::NpmWorkspaceMerge,
49    PostAssemblyPassKind::CargoWorkspaceMerge,
50    PostAssemblyPassKind::NugetCpmResolve,
51    PostAssemblyPassKind::CargoResourceAssign,
52    PostAssemblyPassKind::ComposerResourceAssign,
53    PostAssemblyPassKind::RubyResourceAssign,
54];
55
56pub(super) fn run_post_assembly_passes(
57    files: &mut [FileInfo],
58    packages: &mut Vec<Package>,
59    dependencies: &mut Vec<TopLevelDependency>,
60) {
61    for pass in POST_ASSEMBLY_PASSES {
62        pass.run(files, packages, dependencies);
63    }
64}
65
66impl SpecialDirectoryMergerKind {
67    pub(super) fn run(
68        self,
69        files: &[FileInfo],
70        file_indices: &[usize],
71    ) -> Vec<DirectoryMergeOutput> {
72        match self {
73            Self::Skip => Vec::new(),
74            Self::Hackage => hackage_merge::assemble_hackage_packages(files, file_indices),
75        }
76    }
77}
78
79impl PostAssemblyPassKind {
80    fn run(
81        self,
82        files: &mut [FileInfo],
83        packages: &mut Vec<Package>,
84        dependencies: &mut Vec<TopLevelDependency>,
85    ) {
86        match self {
87            Self::SwiftMerge => swift_merge::assemble_swift_packages(files, packages, dependencies),
88            Self::CondaRootfsMerge => {
89                conda_rootfs_merge::merge_conda_rootfs_metadata(files, packages, dependencies)
90            }
91            Self::NpmResourceAssign => {
92                npm_resource_assign::assign_npm_package_resources(files, packages)
93            }
94            Self::FileReferenceResolve => {
95                file_ref_resolve::resolve_file_references(files, packages, dependencies)
96            }
97            Self::RpmYumdbMerge => file_ref_resolve::merge_rpm_yumdb_metadata(files, packages),
98            Self::NpmWorkspaceMerge => {
99                npm_workspace_merge::assemble_npm_workspaces(files, packages, dependencies)
100            }
101            Self::CargoWorkspaceMerge => {
102                cargo_workspace_merge::assemble_cargo_workspaces(files, packages, dependencies)
103            }
104            Self::NugetCpmResolve => {
105                nuget_cpm_resolve::resolve_nuget_cpm_versions(files, dependencies)
106            }
107            Self::CargoResourceAssign => {
108                cargo_resource_assign::assign_cargo_package_resources(files, packages)
109            }
110            Self::ComposerResourceAssign => {
111                composer_resource_assign::assign_composer_package_resources(files, packages)
112            }
113            Self::RubyResourceAssign => {
114                ruby_resource_assign::assign_ruby_package_resources(files, packages)
115            }
116        }
117    }
118}
119
120pub static ASSEMBLERS: &[AssemblerConfig] = &[
121    // ── Sibling-merge assemblers ──
122    //
123    // npm ecosystem: package.json + lockfiles in same directory.
124    // NOTE: npm-shrinkwrap.json emits "npm_package_lock_json" as its datasource_id,
125    // so "npm_shrinkwrap_json" is NOT a real datasource_id.
126    AssemblerConfig {
127        datasource_ids: &[
128            DatasourceId::BunLock,
129            DatasourceId::BunLockb,
130            DatasourceId::NpmPackageJson,
131            DatasourceId::NpmPackageLockJson,
132            DatasourceId::YarnLock,
133            DatasourceId::PnpmLockYaml,
134            DatasourceId::PnpmWorkspaceYaml,
135        ],
136        sibling_file_patterns: &[
137            "package.json",
138            "bun.lock",
139            "bun.lockb",
140            "package-lock.json",
141            "npm-shrinkwrap.json",
142            "yarn.lock",
143            "pnpm-lock.yaml",
144            "pnpm-workspace.yaml",
145        ],
146        mode: AssemblyMode::SiblingMerge,
147    },
148    // Rust/Cargo ecosystem
149    AssemblerConfig {
150        datasource_ids: &[DatasourceId::CargoToml, DatasourceId::CargoLock],
151        sibling_file_patterns: &["Cargo.toml", "Cargo.lock"],
152        mode: AssemblyMode::SiblingMerge,
153    },
154    // CocoaPods ecosystem
155    AssemblerConfig {
156        datasource_ids: &[
157            DatasourceId::CocoapodsPodspec,
158            DatasourceId::CocoapodsPodspecJson,
159            DatasourceId::CocoapodsPodfile,
160            DatasourceId::CocoapodsPodfileLock,
161        ],
162        sibling_file_patterns: &["*.podspec", "*.podspec.json", "Podfile", "Podfile.lock"],
163        mode: AssemblyMode::SiblingMerge,
164    },
165    // PHP Composer ecosystem
166    AssemblerConfig {
167        datasource_ids: &[DatasourceId::PhpComposerJson, DatasourceId::PhpComposerLock],
168        sibling_file_patterns: &[
169            "*composer.json",
170            "composer.*.json",
171            "*composer.lock",
172            "composer.*.lock",
173        ],
174        mode: AssemblyMode::SiblingMerge,
175    },
176    // Go ecosystem (includes legacy Godeps)
177    AssemblerConfig {
178        datasource_ids: &[
179            DatasourceId::GoMod,
180            DatasourceId::GoModGraph,
181            DatasourceId::GoSum,
182            DatasourceId::GoWork,
183            DatasourceId::Godeps,
184        ],
185        sibling_file_patterns: &[
186            "go.mod",
187            "go.work",
188            "go.mod.graph",
189            "go.modgraph",
190            "go.sum",
191            "Godeps.json",
192        ],
193        mode: AssemblyMode::SiblingMerge,
194    },
195    // Dart/Flutter ecosystem
196    AssemblerConfig {
197        datasource_ids: &[DatasourceId::PubspecYaml, DatasourceId::PubspecLock],
198        sibling_file_patterns: &["pubspec.yaml", "pubspec.lock"],
199        mode: AssemblyMode::SiblingMerge,
200    },
201    // Pixi ecosystem
202    AssemblerConfig {
203        datasource_ids: &[DatasourceId::PixiToml, DatasourceId::PixiLock],
204        sibling_file_patterns: &["pixi.toml", "pixi.lock"],
205        mode: AssemblyMode::SiblingMerge,
206    },
207    AssemblerConfig {
208        datasource_ids: &[DatasourceId::NixFlakeNix, DatasourceId::NixFlakeLock],
209        sibling_file_patterns: &["flake.nix", "flake.lock"],
210        mode: AssemblyMode::SiblingMerge,
211    },
212    AssemblerConfig {
213        datasource_ids: &[DatasourceId::NixDefaultNix],
214        sibling_file_patterns: &["default.nix"],
215        mode: AssemblyMode::OnePerPackageData,
216    },
217    // Helm chart ecosystem
218    AssemblerConfig {
219        datasource_ids: &[DatasourceId::HelmChartYaml, DatasourceId::HelmChartLock],
220        sibling_file_patterns: &["Chart.yaml", "Chart.lock"],
221        mode: AssemblyMode::SiblingMerge,
222    },
223    AssemblerConfig {
224        datasource_ids: &[
225            DatasourceId::HackageCabal,
226            DatasourceId::HackageCabalProject,
227            DatasourceId::HackageStackYaml,
228        ],
229        sibling_file_patterns: &["*.cabal", "cabal.project", "stack.yaml"],
230        mode: AssemblyMode::SiblingMerge,
231    },
232    // Chef ecosystem
233    AssemblerConfig {
234        datasource_ids: &[
235            DatasourceId::ChefCookbookMetadataJson,
236            DatasourceId::ChefCookbookMetadataRb,
237        ],
238        sibling_file_patterns: &["metadata.json", "metadata.rb"],
239        mode: AssemblyMode::SiblingMerge,
240    },
241    // Conan (C/C++) ecosystem
242    AssemblerConfig {
243        datasource_ids: &[
244            DatasourceId::ConanConanFilePy,
245            DatasourceId::ConanConanFileTxt,
246            DatasourceId::ConanLock,
247            DatasourceId::ConanConanDataYml,
248        ],
249        sibling_file_patterns: &[
250            "conanfile.py",
251            "conanfile.txt",
252            "conan.lock",
253            "conandata.yml",
254        ],
255        mode: AssemblyMode::SiblingMerge,
256    },
257    // Maven/Java ecosystem (nested merge via META-INF)
258    AssemblerConfig {
259        datasource_ids: &[
260            DatasourceId::MavenPom,
261            DatasourceId::MavenPomProperties,
262            DatasourceId::JavaJarManifest,
263            DatasourceId::JavaOsgiManifest,
264        ],
265        sibling_file_patterns: &[
266            "pom.xml",
267            "*.pom",
268            "pom.properties",
269            "**/META-INF/MANIFEST.MF",
270        ],
271        mode: AssemblyMode::SiblingMerge,
272    },
273    AssemblerConfig {
274        datasource_ids: &[DatasourceId::PypiWheel, DatasourceId::PypiPipOriginJson],
275        sibling_file_patterns: &["*.whl", "origin.json"],
276        mode: AssemblyMode::SiblingMerge,
277    },
278    // Python/PyPI ecosystem
279    AssemblerConfig {
280        datasource_ids: &[
281            DatasourceId::PypiPyprojectToml,
282            DatasourceId::PypiSetupPy,
283            DatasourceId::PypiSetupCfg,
284            DatasourceId::PypiWheel,
285            DatasourceId::PypiWheelMetadata,
286            DatasourceId::PypiEgg,
287            DatasourceId::PypiJson,
288            DatasourceId::PypiSdistPkginfo,
289            DatasourceId::PypiInspectDeplock,
290            DatasourceId::PipRequirements,
291            DatasourceId::PypiPoetryLock,
292            DatasourceId::PypiPylockToml,
293            DatasourceId::PypiUvLock,
294            DatasourceId::Pipfile,
295            DatasourceId::PipfileLock,
296        ],
297        sibling_file_patterns: &[
298            "pyproject.toml",
299            "setup.py",
300            "setup.cfg",
301            "PKG-INFO",
302            "METADATA",
303            "pypi.json",
304            "requirements*.txt",
305            "Pipfile",
306            "Pipfile.lock",
307            "poetry.lock",
308            "pylock.toml",
309            "pylock.*.toml",
310            "uv.lock",
311        ],
312        mode: AssemblyMode::SiblingMerge,
313    },
314    AssemblerConfig {
315        datasource_ids: &[DatasourceId::DenoJson, DatasourceId::DenoLock],
316        sibling_file_patterns: &["deno.json", "deno.jsonc", "deno.lock"],
317        mode: AssemblyMode::SiblingMerge,
318    },
319    // Ruby/RubyGems ecosystem
320    AssemblerConfig {
321        datasource_ids: &[
322            DatasourceId::GemArchiveExtracted,
323            DatasourceId::Gemspec,
324            DatasourceId::Gemfile,
325            DatasourceId::GemfileLock,
326            DatasourceId::GemArchive,
327        ],
328        sibling_file_patterns: &[
329            "metadata.gz-extract",
330            "**/data.gz-extract/*.gemspec",
331            "**/data.gz-extract/Gemfile",
332            "**/data.gz-extract/Gemfile.lock",
333            "*.gemspec",
334            "Gemfile",
335            "Gemfile.lock",
336        ],
337        mode: AssemblyMode::SiblingMerge,
338    },
339    // Conda ecosystem
340    AssemblerConfig {
341        datasource_ids: &[
342            DatasourceId::CondaMetaYaml,
343            DatasourceId::CondaYaml,
344            DatasourceId::CondaMetaJson,
345        ],
346        sibling_file_patterns: &[
347            "meta.yaml",
348            "meta.yml",
349            "environment.yml",
350            "environment.yaml",
351            "conda.yaml",
352            "env.yaml",
353            "*.json",
354        ],
355        mode: AssemblyMode::SiblingMerge,
356    },
357    // RPM specfile (source packages)
358    AssemblerConfig {
359        datasource_ids: &[DatasourceId::RpmSpecfile],
360        sibling_file_patterns: &["*.spec"],
361        mode: AssemblyMode::SiblingMerge,
362    },
363    // Debian source packages (nested merge via debian/ directory)
364    AssemblerConfig {
365        datasource_ids: &[
366            DatasourceId::DebianControlInSource,
367            DatasourceId::DebianCopyright,
368        ],
369        sibling_file_patterns: &["**/debian/control", "**/debian/copyright"],
370        mode: AssemblyMode::SiblingMerge,
371    },
372    // Gradle/Android ecosystem
373    AssemblerConfig {
374        datasource_ids: &[DatasourceId::BuildGradle, DatasourceId::GradleLockfile],
375        sibling_file_patterns: &["build.gradle", "build.gradle.kts", "gradle.lockfile"],
376        mode: AssemblyMode::SiblingMerge,
377    },
378    AssemblerConfig {
379        datasource_ids: &[DatasourceId::GradleModule],
380        sibling_file_patterns: &["*.module"],
381        mode: AssemblyMode::OnePerPackageData,
382    },
383    // CPAN/Perl ecosystem
384    AssemblerConfig {
385        datasource_ids: &[
386            DatasourceId::CpanMetaJson,
387            DatasourceId::CpanMetaYml,
388            DatasourceId::CpanManifest,
389            DatasourceId::CpanDistIni,
390            DatasourceId::CpanMakefile,
391        ],
392        sibling_file_patterns: &[
393            "META.json",
394            "META.yml",
395            "MANIFEST",
396            "dist.ini",
397            "Makefile.PL",
398        ],
399        mode: AssemblyMode::SiblingMerge,
400    },
401    // NuGet/.NET ecosystem
402    AssemblerConfig {
403        datasource_ids: &[
404            DatasourceId::NugetCsproj,
405            DatasourceId::NugetFsproj,
406            DatasourceId::NugetNuspec,
407            DatasourceId::NugetNupkg,
408            DatasourceId::NugetProjectJson,
409            DatasourceId::NugetProjectLockJson,
410            DatasourceId::NugetPackagesConfig,
411            DatasourceId::NugetPackagesLock,
412            DatasourceId::NugetVbproj,
413        ],
414        sibling_file_patterns: &[
415            "*.csproj",
416            "*.fsproj",
417            "*.nuspec",
418            "*.nupkg",
419            "project.json",
420            "project.lock.json",
421            "packages.config",
422            "packages.lock.json",
423            "*.vbproj",
424        ],
425        mode: AssemblyMode::SiblingMerge,
426    },
427    AssemblerConfig {
428        datasource_ids: &[DatasourceId::NugetDepsJson],
429        sibling_file_patterns: &["*.deps.json"],
430        mode: AssemblyMode::OnePerPackageData,
431    },
432    // Swift/SPM ecosystem
433    AssemblerConfig {
434        datasource_ids: &[
435            DatasourceId::SwiftPackageManifestJson,
436            DatasourceId::SwiftPackageResolved,
437            DatasourceId::SwiftPackageShowDependencies,
438        ],
439        sibling_file_patterns: &["Package.swift", "Package.resolved"],
440        mode: AssemblyMode::SiblingMerge,
441    },
442    // ── Standalone assemblers (single file → single package) ──
443    //
444    // These ecosystems have only one manifest file type with no sibling merging.
445    // They still need configs so their datasource_ids are recognized by the assembler.
446    //
447    // Bower (JavaScript)
448    AssemblerConfig {
449        datasource_ids: &[DatasourceId::BowerJson],
450        sibling_file_patterns: &["bower.json"],
451        mode: AssemblyMode::SiblingMerge,
452    },
453    // CRAN (R language)
454    AssemblerConfig {
455        datasource_ids: &[DatasourceId::CranDescription],
456        sibling_file_patterns: &["DESCRIPTION"],
457        mode: AssemblyMode::SiblingMerge,
458    },
459    // FreeBSD packages
460    AssemblerConfig {
461        datasource_ids: &[DatasourceId::FreebsdCompactManifest],
462        sibling_file_patterns: &["+COMPACT_MANIFEST"],
463        mode: AssemblyMode::SiblingMerge,
464    },
465    // Haxe ecosystem
466    AssemblerConfig {
467        datasource_ids: &[DatasourceId::HaxelibJson],
468        sibling_file_patterns: &["haxelib.json"],
469        mode: AssemblyMode::SiblingMerge,
470    },
471    AssemblerConfig {
472        datasource_ids: &[DatasourceId::Gitmodules],
473        sibling_file_patterns: &[".gitmodules"],
474        mode: AssemblyMode::SiblingMerge,
475    },
476    // OCaml/opam ecosystem
477    AssemblerConfig {
478        datasource_ids: &[DatasourceId::OpamFile],
479        sibling_file_patterns: &["opam"],
480        mode: AssemblyMode::SiblingMerge,
481    },
482    // RPM Mariner manifest
483    AssemblerConfig {
484        datasource_ids: &[DatasourceId::RpmMarinerManifest],
485        sibling_file_patterns: &["*.rpm.manifest"],
486        mode: AssemblyMode::SiblingMerge,
487    },
488    AssemblerConfig {
489        datasource_ids: &[DatasourceId::RpmYumdb],
490        sibling_file_patterns: &["**/var/lib/yum/yumdb/*/*/from_repo"],
491        mode: AssemblyMode::OnePerPackageData,
492    },
493    // Microsoft Update Manifest
494    AssemblerConfig {
495        datasource_ids: &[DatasourceId::MicrosoftUpdateManifestMum],
496        sibling_file_patterns: &["*.mum"],
497        mode: AssemblyMode::SiblingMerge,
498    },
499    // Autotools (C/C++ build system)
500    AssemblerConfig {
501        datasource_ids: &[DatasourceId::AutotoolsConfigure],
502        sibling_file_patterns: &["configure", "configure.ac"],
503        mode: AssemblyMode::SiblingMerge,
504    },
505    // Bazel (build system)
506    AssemblerConfig {
507        datasource_ids: &[DatasourceId::BazelBuild],
508        sibling_file_patterns: &["BUILD"],
509        mode: AssemblyMode::SiblingMerge,
510    },
511    AssemblerConfig {
512        datasource_ids: &[DatasourceId::BazelModule],
513        sibling_file_patterns: &["MODULE.bazel"],
514        mode: AssemblyMode::OnePerPackageData,
515    },
516    // Buck (build system)
517    AssemblerConfig {
518        datasource_ids: &[DatasourceId::BuckFile, DatasourceId::BuckMetadata],
519        sibling_file_patterns: &["BUCK", ".buckconfig"],
520        mode: AssemblyMode::SiblingMerge,
521    },
522    // Ant/Ivy (Java dependency management)
523    AssemblerConfig {
524        datasource_ids: &[DatasourceId::AntIvyXml],
525        sibling_file_patterns: &["ivy.xml"],
526        mode: AssemblyMode::SiblingMerge,
527    },
528    // Meteor (JavaScript platform)
529    AssemblerConfig {
530        datasource_ids: &[DatasourceId::MeteorPackage],
531        sibling_file_patterns: &["package.js"],
532        mode: AssemblyMode::SiblingMerge,
533    },
534    // ── One-per-PackageData assemblers (database files with many packages) ──
535    //
536    // Alpine installed package database
537    AssemblerConfig {
538        datasource_ids: &[DatasourceId::AlpineInstalledDb],
539        sibling_file_patterns: &["installed"],
540        mode: AssemblyMode::OnePerPackageData,
541    },
542    AssemblerConfig {
543        datasource_ids: &[DatasourceId::AlpineApkbuild],
544        sibling_file_patterns: &["APKBUILD"],
545        mode: AssemblyMode::SiblingMerge,
546    },
547    // RPM installed package databases (BDB, NDB, SQLite)
548    AssemblerConfig {
549        datasource_ids: &[
550            DatasourceId::RpmInstalledDatabaseBdb,
551            DatasourceId::RpmInstalledDatabaseNdb,
552            DatasourceId::RpmInstalledDatabaseSqlite,
553        ],
554        sibling_file_patterns: &["Packages", "Packages.db", "rpmdb.sqlite"],
555        mode: AssemblyMode::OnePerPackageData,
556    },
557    // Debian installed package databases
558    AssemblerConfig {
559        datasource_ids: &[
560            DatasourceId::DebianInstalledStatusDb,
561            DatasourceId::DebianDistrolessInstalledDb,
562        ],
563        sibling_file_patterns: &["status"],
564        mode: AssemblyMode::OnePerPackageData,
565    },
566    AssemblerConfig {
567        datasource_ids: &[DatasourceId::AboutFile],
568        sibling_file_patterns: &["*.ABOUT"],
569        mode: AssemblyMode::OnePerPackageData,
570    },
571];
572
573// Datasource IDs intentionally excluded from package assembly.
574//
575// This list is runtime-significant: files with these datasource IDs may remain
576// unowned by any Package, while their dependencies are still eligible for
577// top-level hoisting. Tests also use it to enforce explicit assembly accounting.
578pub static UNASSEMBLED_DATASOURCE_IDS: &[DatasourceId] = &[
579    // Non-package metadata
580    DatasourceId::Readme,
581    DatasourceId::EtcOsRelease,
582    // Binary archives (require external extraction via ExtractCode before scanning)
583    DatasourceId::AlpineApkArchive,
584    DatasourceId::AndroidAarLibrary,
585    DatasourceId::AndroidApk,
586    DatasourceId::AppleDmg,
587    DatasourceId::Axis2Mar,
588    DatasourceId::ChromeCrx,
589    DatasourceId::DebianDeb,
590    DatasourceId::DebianOriginalSourceTarball,
591    DatasourceId::DebianSourceMetadataTarball,
592    DatasourceId::InstallshieldInstaller,
593    DatasourceId::IosIpa,
594    DatasourceId::IsoDiskImage,
595    DatasourceId::JavaEarArchive,
596    DatasourceId::JavaJar,
597    DatasourceId::JavaWarArchive,
598    DatasourceId::JbossSar,
599    DatasourceId::MicrosoftCabinet,
600    DatasourceId::MozillaXpi,
601    DatasourceId::NsisInstaller,
602    DatasourceId::RpmArchive,
603    DatasourceId::SharShellArchive,
604    DatasourceId::SquashfsDiskImage,
605    // Supplementary metadata (not primary package definitions)
606    DatasourceId::ArchAurinfo,
607    DatasourceId::ArchPkginfo,
608    DatasourceId::ArchSrcinfo,
609    DatasourceId::Axis2ModuleXml,
610    DatasourceId::ClojureDepsEdn,
611    DatasourceId::ClojureProjectClj,
612    DatasourceId::DebianControlExtractedDeb,
613    DatasourceId::DebianInstalledFilesList,
614    DatasourceId::DebianInstalledMd5Sums,
615    DatasourceId::DebianMd5SumsInExtractedDeb,
616    DatasourceId::DebianSourceControlDsc,
617    DatasourceId::Dockerfile,
618    DatasourceId::HexMixLock,
619    DatasourceId::JavaEarApplicationXml,
620    DatasourceId::JavaWarWebXml,
621    DatasourceId::JbossServiceXml,
622    DatasourceId::MesonBuild,
623    DatasourceId::NugetDirectoryBuildProps,
624    DatasourceId::NugetDirectoryPackagesProps,
625    DatasourceId::RpmPackageLicenses,
626    DatasourceId::SbtBuildSbt,
627    DatasourceId::VcpkgJson,
628];
629
630#[cfg(test)]
631mod tests {
632    use super::*;
633    use std::collections::HashSet;
634    use strum::IntoEnumIterator;
635
636    #[test]
637    fn test_every_datasource_id_is_accounted_for() {
638        let mut assembled: HashSet<DatasourceId> = HashSet::new();
639        for config in ASSEMBLERS {
640            for &dsid in config.datasource_ids {
641                assembled.insert(dsid);
642            }
643        }
644
645        let unassembled: HashSet<DatasourceId> =
646            UNASSEMBLED_DATASOURCE_IDS.iter().copied().collect();
647
648        let overlap: Vec<_> = assembled.intersection(&unassembled).collect();
649        assert!(
650            overlap.is_empty(),
651            "Datasource IDs in BOTH ASSEMBLERS and UNASSEMBLED: {overlap:?}"
652        );
653
654        let missing: Vec<_> = DatasourceId::iter()
655            .filter(|dsid| !assembled.contains(dsid) && !unassembled.contains(dsid))
656            .collect();
657
658        assert!(
659            missing.is_empty(),
660            "Datasource IDs in NEITHER ASSEMBLERS nor UNASSEMBLED: {missing:?}\n\
661             Add each to an AssemblerConfig in ASSEMBLERS, or to UNASSEMBLED_DATASOURCE_IDS."
662        );
663    }
664
665    #[test]
666    fn test_post_assembly_passes_are_unique() {
667        let unique: HashSet<PostAssemblyPassKind> = POST_ASSEMBLY_PASSES.iter().copied().collect();
668
669        assert_eq!(
670            unique.len(),
671            POST_ASSEMBLY_PASSES.len(),
672            "POST_ASSEMBLY_PASSES contains duplicate entries"
673        );
674    }
675
676    #[test]
677    fn test_every_post_assembly_pass_kind_is_registered_once() {
678        let registered: HashSet<PostAssemblyPassKind> =
679            POST_ASSEMBLY_PASSES.iter().copied().collect();
680
681        let missing: Vec<_> = PostAssemblyPassKind::iter()
682            .filter(|pass| !registered.contains(pass))
683            .collect();
684
685        assert!(
686            missing.is_empty(),
687            "Post-assembly pass variants not registered in POST_ASSEMBLY_PASSES: {missing:?}"
688        );
689
690        for pass in PostAssemblyPassKind::iter() {
691            let count = POST_ASSEMBLY_PASSES
692                .iter()
693                .filter(|registered| **registered == pass)
694                .count();
695            assert_eq!(
696                count, 1,
697                "Post-assembly pass {pass:?} should be registered exactly once"
698            );
699        }
700    }
701}