Skip to main content

provenant/models/
datasource_id.rs

1// SPDX-FileCopyrightText: nexB Inc. and others
2// SPDX-FileCopyrightText: Provenant contributors
3// SPDX-License-Identifier: Apache-2.0
4// Derived from ScanCode Toolkit (Apache-2.0); modified. See NOTICE.
5
6//! Datasource identifiers for package parsers.
7//!
8//! Each variant uniquely identifies the type of package data source (file format)
9//! that was parsed. These IDs enable the assembly system to intelligently merge
10//! related package files.
11
12use serde::{Deserialize, Serialize};
13use std::fmt;
14use std::str::FromStr;
15use strum::{EnumCount, EnumIter};
16
17/// Unique identifier for the type of package data source (file format).
18///
19/// Datasource IDs distinguish between different file types within the same ecosystem
20/// (e.g., `NpmPackageJson` vs `NpmPackageLockJson`). The assembly system uses these
21/// IDs to match packages from related files for merging into a single logical package.
22///
23/// # Serialization
24///
25/// Variants serialize as PascalCase in the cache/spill format (e.g., `NpmPackageJson`).
26/// For JSON output, use `as_str()` / `Display` which returns snake_case strings
27/// matching the Python ScanCode Toolkit values (e.g., `npm_package_json`).
28///
29/// # Ordering
30///
31/// `PartialOrd`/`Ord` are derived and therefore follow **declaration order**. The assembly
32/// stage iterates active assembler configs via a `BTreeSet<DatasourceId>`, so adding or
33/// reordering variants changes the per-directory assembler execution sequence in polyglot
34/// directories.
35#[derive(
36    Clone,
37    Copy,
38    Debug,
39    PartialEq,
40    Eq,
41    PartialOrd,
42    Ord,
43    Hash,
44    Serialize,
45    Deserialize,
46    EnumCount,
47    EnumIter,
48)]
49pub enum DatasourceId {
50    // ── About/README/OS ──
51    AboutFile,
52    Readme,
53    EtcOsRelease,
54
55    // ── Alpine ──
56    AlpineApkArchive,
57    AlpineApkbuild,
58    AlpineInstalledDb,
59
60    // ── Arch Linux ──
61    ArchAurinfo,
62    ArchPkginfo,
63    ArchSrcinfo,
64
65    // ── Android ──
66    AndroidAab,
67    AndroidAarLibrary,
68    AndroidApk,
69    AndroidManifestXml,
70    AndroidSoongMetadata,
71
72    // ── Apache Axis2 ──
73    Axis2Mar,
74    Axis2ModuleXml,
75
76    // ── Autotools ──
77    AutotoolsConfigure,
78
79    // ── Bazel ──
80    BazelBuild,
81    BazelModule,
82
83    // ── Bitbake ──
84    BitbakeRecipe,
85    BitbakeRecipeAppend,
86
87    // ── Bower ──
88    BowerJson,
89
90    // ── Buck ──
91    BuckFile,
92    BuckMetadata,
93
94    // ── Bun ──
95    BunLock,
96    BunLockb,
97
98    // ── Carthage ──
99    CarthageCartfile,
100    CarthageCartfileResolved,
101
102    // ── Cargo/Rust ──
103    CargoLock,
104    CargoToml,
105    RustBinary,
106    WindowsExecutable,
107
108    // ── Chef ──
109    /// Matches Python reference value.
110    ChefCookbookMetadataJson,
111    /// Matches Python reference value.
112    ChefCookbookMetadataRb,
113
114    CitationCff,
115
116    // ── CocoaPods ──
117    CocoapodsPodfile,
118    CocoapodsPodfileLock,
119    CocoapodsPodspec,
120    CocoapodsPodspecJson,
121
122    // ── Conan ──
123    ConanConanDataYml,
124    ConanConanFilePy,
125    ConanConanFileTxt,
126    ConanLock,
127
128    // ── Conda ──
129    /// Matches Python reference value.
130    CondaYaml,
131    CondaMetaJson,
132    CondaMetaYaml,
133
134    // ── Clojure ──
135    ClojureDepsEdn,
136    ClojureProjectClj,
137
138    // ── CPAN/Perl ──
139    CpanDistIni,
140    /// Matches Python reference value.
141    CpanMakefile,
142    CpanManifest,
143    CpanMetaJson,
144    CpanMetaYml,
145
146    // ── CRAN/R ──
147    CranDescription,
148
149    // ── Dart/Flutter ──
150    PubspecLock,
151    PubspecYaml,
152
153    // ── Debian ──
154    DebianControlExtractedDeb,
155    DebianControlInSource,
156    DebianCopyright,
157    DebianCopyrightInSource,
158    DebianCopyrightInPackage,
159    DebianCopyrightStandalone,
160    DebianDeb,
161    /// Matches Python reference value.
162    DebianSourceMetadataTarball,
163    DebianDistrolessInstalledDb,
164    /// Matches Python reference value.
165    DebianInstalledFilesList,
166    DebianInstalledMd5Sums,
167    DebianInstalledStatusDb,
168    DebianMd5SumsInExtractedDeb,
169    /// Matches Python reference value.
170    DebianOriginalSourceTarball,
171    DebianSourceControlDsc,
172
173    // ── Deno ──
174    DenoJson,
175    DenoLock,
176
177    // ── Docker ──
178    Dockerfile,
179
180    // ── OCI image ──
181    OciImageIndex,
182    OciImageManifest,
183
184    // ── Erlang / OTP ──
185    ErlangOtpAppSrc,
186    RebarConfig,
187    RebarLock,
188
189    // ── FreeBSD ──
190    FreebsdCompactManifest,
191
192    // ── Go ──
193    Godeps,
194    GoBinary,
195    GoMod,
196    GoModGraph,
197    GoSum,
198    GoWork,
199
200    // ── Haskell / Hackage ──
201    HackageCabal,
202    HackageCabalProject,
203    HackageStackYaml,
204
205    // ── Gradle ──
206    BuildGradle,
207    GradleLockfile,
208    GradleModule,
209
210    // ── Haxe ──
211    HaxelibJson,
212
213    // ── Helm ──
214    HelmChartLock,
215    HelmChartYaml,
216
217    // ── Hex/Elixir ──
218    HexMixLock,
219
220    // ── Hugging Face ──
221    HuggingfaceModelCard,
222    HuggingfaceConfigJson,
223    HuggingfaceModelIndexJson,
224
225    // ── Julia ──
226    JuliaProjectToml,
227    JuliaManifestToml,
228
229    // ── Java ──
230    AntIvyXml,
231    JavaEarApplicationXml,
232    JavaEarArchive,
233    JavaJar,
234    JavaJarManifest,
235    JavaOsgiManifest,
236    JavaWarArchive,
237    JavaWarWebXml,
238    JbossSar,
239    JbossServiceXml,
240
241    // ── Maven ──
242    MavenPom,
243    MavenPomProperties,
244    MesonBuild,
245
246    SbtBuildSbt,
247
248    // ── Microsoft ──
249    MicrosoftCabinet,
250    MicrosoftUpdateManifestMum,
251
252    // ── Mobile/Browser ──
253    AppleDmg,
254    ChromeCrx,
255    IosIpa,
256    MozillaXpi,
257
258    // ── Meteor ──
259    MeteorPackage,
260
261    NixDefaultNix,
262    NixFlakeLock,
263    NixFlakeNix,
264
265    // ── npm ──
266    NpmPackageJson,
267    NpmPackageLockJson,
268
269    // ── NuGet ──
270    NugetCsproj,
271    NugetDepsJson,
272    NugetDirectoryBuildProps,
273    NugetDirectoryPackagesProps,
274    NugetNupkg,
275    NugetProjectJson,
276    NugetProjectLockJson,
277    NugetPackagesConfig,
278    NugetPackagesLock,
279    NugetNuspec,
280    NugetVbproj,
281    NugetFsproj,
282
283    // ── OCaml/opam ──
284    OpamFile,
285
286    // ── PHP/Composer ──
287    PhpComposerJson,
288    PhpComposerLock,
289
290    // ── pnpm ──
291    PnpmLockYaml,
292    PnpmWorkspaceYaml,
293
294    // ── Python/PyPI ──
295    Pipfile,
296    PipfileLock,
297    PipRequirements,
298    PixiLock,
299    PixiToml,
300    PypiPipOriginJson,
301    PypiEgg,
302    PypiEggPkginfo,
303    PypiEditableEggPkginfo,
304    PypiInspectDeplock,
305    PypiJson,
306    PypiPoetryLock,
307    PypiPoetryPyprojectToml,
308    PypiSdist,
309    PypiPylockToml,
310    PypiPyprojectToml,
311    PypiSdistPkginfo,
312    PypiSetupCfg,
313    PypiSetupPy,
314    PypiUvLock,
315    PypiWheel,
316    PypiWheelMetadata,
317
318    // ── RPM ──
319    RpmArchive,
320    RpmInstalledDatabaseBdb,
321    RpmInstalledDatabaseNdb,
322    RpmInstalledDatabaseSqlite,
323    RpmMarinerManifest,
324    RpmPackageLicenses,
325    RpmSpecfile,
326    RpmYumdb,
327
328    // ── Ruby/RubyGems ──
329    Gemfile,
330    GemfileExtracted,
331    GemfileLock,
332    GemfileLockExtracted,
333    GemArchive,
334    /// Matches Python reference value.
335    GemArchiveExtracted,
336    Gemspec,
337    GemspecExtracted,
338    GemGemspecInstalledSpecifications,
339
340    // ── Disk Images/Installers ──
341    InstallshieldInstaller,
342    IsoDiskImage,
343    NsisInstaller,
344    SharShellArchive,
345    SquashfsDiskImage,
346
347    // ── Swift ──
348    SwiftPackageManifestJson,
349    SwiftPackageResolved,
350    SwiftPackageShowDependencies,
351
352    PubliccodeYaml,
353
354    // ── vcpkg ──
355    VcpkgJson,
356
357    // ── Yarn ──
358    YarnLock,
359    YarnLockV1,
360    YarnLockV2,
361    YarnPnpCjs,
362
363    // ── Git ──
364    Gitmodules,
365}
366
367impl DatasourceId {
368    /// Returns the string representation of this datasource ID.
369    ///
370    /// This matches the serialized form used in JSON output.
371    pub fn as_str(&self) -> &'static str {
372        match self {
373            // About/README/OS
374            Self::AboutFile => "about_file",
375            Self::Readme => "readme",
376            Self::EtcOsRelease => "etc_os_release",
377
378            // Alpine
379            Self::AlpineApkArchive => "alpine_apk_archive",
380            Self::AlpineApkbuild => "alpine_apkbuild",
381            Self::AlpineInstalledDb => "alpine_installed_db",
382
383            // Arch Linux
384            Self::ArchAurinfo => "arch_aurinfo",
385            Self::ArchPkginfo => "arch_pkginfo",
386            Self::ArchSrcinfo => "arch_srcinfo",
387
388            // Android
389            Self::AndroidAab => "android_aab",
390            Self::AndroidAarLibrary => "android_aar_library",
391            Self::AndroidApk => "android_apk",
392            Self::AndroidManifestXml => "android_manifest_xml",
393            Self::AndroidSoongMetadata => "android_soong_metadata",
394
395            // Apache Axis2
396            Self::Axis2Mar => "axis2_mar",
397            Self::Axis2ModuleXml => "axis2_module_xml",
398
399            // Autotools
400            Self::AutotoolsConfigure => "autotools_configure",
401
402            // Bazel
403            Self::BazelBuild => "bazel_build",
404
405            // Bitbake
406            Self::BitbakeRecipe => "bitbake_recipe",
407            Self::BitbakeRecipeAppend => "bitbake_recipe_append",
408
409            // Bower
410            Self::BowerJson => "bower_json",
411
412            // Buck
413            Self::BuckFile => "buck_file",
414            Self::BuckMetadata => "buck_metadata",
415
416            // Carthage
417            Self::CarthageCartfile => "carthage_cartfile",
418            Self::CarthageCartfileResolved => "carthage_cartfile_resolved",
419
420            // Cargo/Rust
421            Self::CargoLock => "cargo_lock",
422            Self::CargoToml => "cargo_toml",
423            Self::RustBinary => "rust_binary",
424            Self::WindowsExecutable => "windows_executable",
425
426            // Chef
427            Self::ChefCookbookMetadataJson => "chef_cookbook_metadata_json",
428            Self::ChefCookbookMetadataRb => "chef_cookbook_metadata_rb",
429
430            Self::CitationCff => "citation_cff",
431
432            // CocoaPods
433            Self::CocoapodsPodfile => "cocoapods_podfile",
434            Self::CocoapodsPodfileLock => "cocoapods_podfile_lock",
435            Self::CocoapodsPodspec => "cocoapods_podspec",
436            Self::CocoapodsPodspecJson => "cocoapods_podspec_json",
437
438            // Conan
439            Self::ConanConanDataYml => "conan_conandata_yml",
440            Self::ConanConanFilePy => "conan_conanfile_py",
441            Self::ConanConanFileTxt => "conan_conanfile_txt",
442            Self::ConanLock => "conan_lock",
443
444            // Conda
445            Self::CondaYaml => "conda_yaml",
446            Self::CondaMetaJson => "conda_meta_json",
447            Self::CondaMetaYaml => "conda_meta_yaml",
448
449            // Clojure
450            Self::ClojureDepsEdn => "clojure_deps_edn",
451            Self::ClojureProjectClj => "clojure_project_clj",
452
453            // CPAN/Perl
454            Self::CpanDistIni => "cpan_dist_ini",
455            Self::CpanMakefile => "cpan_makefile",
456            Self::CpanManifest => "cpan_manifest",
457            Self::CpanMetaJson => "cpan_meta_json",
458            Self::CpanMetaYml => "cpan_meta_yml",
459
460            // CRAN/R
461            Self::CranDescription => "cran_description",
462
463            // Dart/Flutter
464            Self::PubspecLock => "pubspec_lock",
465            Self::PubspecYaml => "pubspec_yaml",
466
467            // Debian
468            Self::DebianControlExtractedDeb => "debian_control_extracted_deb",
469            Self::DebianControlInSource => "debian_control_in_source",
470            Self::DebianCopyright => "debian_copyright",
471            Self::DebianCopyrightInSource => "debian_copyright_in_source",
472            Self::DebianCopyrightInPackage => "debian_copyright_in_package",
473            Self::DebianCopyrightStandalone => "debian_copyright_standalone",
474            Self::DebianDeb => "debian_deb",
475            Self::DebianSourceMetadataTarball => "debian_source_metadata_tarball",
476            Self::DebianDistrolessInstalledDb => "debian_distroless_installed_db",
477            Self::DebianInstalledFilesList => "debian_installed_files_list",
478            Self::DebianInstalledMd5Sums => "debian_installed_md5sums",
479            Self::DebianInstalledStatusDb => "debian_installed_status_db",
480            Self::DebianMd5SumsInExtractedDeb => "debian_md5sums_in_extracted_deb",
481            Self::DebianOriginalSourceTarball => "debian_original_source_tarball",
482            Self::DebianSourceControlDsc => "debian_source_control_dsc",
483            Self::DenoJson => "deno_json",
484            Self::DenoLock => "deno_lock",
485            Self::Dockerfile => "dockerfile",
486            Self::OciImageIndex => "oci_image_index",
487            Self::OciImageManifest => "oci_image_manifest",
488            Self::ErlangOtpAppSrc => "erlang_otp_app_src",
489            Self::RebarConfig => "rebar_config",
490            Self::RebarLock => "rebar_lock",
491            Self::BazelModule => "bazel_module",
492
493            // FreeBSD
494            Self::FreebsdCompactManifest => "freebsd_compact_manifest",
495
496            // Go
497            Self::Godeps => "godeps",
498            Self::GoBinary => "go_binary",
499            Self::GoMod => "go_mod",
500            Self::GoModGraph => "go_mod_graph",
501            Self::GoSum => "go_sum",
502            Self::GoWork => "go_work",
503
504            // Haskell / Hackage
505            Self::HackageCabal => "hackage_cabal",
506            Self::HackageCabalProject => "hackage_cabal_project",
507            Self::HackageStackYaml => "hackage_stack_yaml",
508
509            // Gradle
510            Self::BuildGradle => "build_gradle",
511            Self::GradleLockfile => "gradle_lockfile",
512            Self::GradleModule => "gradle_module",
513
514            // Haxe
515            Self::HaxelibJson => "haxelib_json",
516
517            // Helm
518            Self::HelmChartLock => "helm_chart_lock",
519            Self::HelmChartYaml => "helm_chart_yaml",
520
521            // Hex/Elixir
522            Self::HexMixLock => "hex_mix_lock",
523
524            // Hugging Face
525            Self::HuggingfaceModelCard => "huggingface_model_card",
526            Self::HuggingfaceConfigJson => "huggingface_config_json",
527            Self::HuggingfaceModelIndexJson => "huggingface_model_index_json",
528
529            // Julia
530            Self::JuliaProjectToml => "julia_project_toml",
531            Self::JuliaManifestToml => "julia_manifest_toml",
532
533            // Java
534            Self::AntIvyXml => "ant_ivy_xml",
535            Self::JavaEarApplicationXml => "java_ear_application_xml",
536            Self::JavaEarArchive => "java_ear_archive",
537            Self::JavaJar => "java_jar",
538            Self::JavaJarManifest => "java_jar_manifest",
539            Self::JavaOsgiManifest => "java_osgi_manifest",
540            Self::JavaWarArchive => "java_war_archive",
541            Self::JavaWarWebXml => "java_war_web_xml",
542            Self::JbossSar => "jboss_sar",
543            Self::JbossServiceXml => "jboss_service_xml",
544
545            // Maven
546            Self::MavenPom => "maven_pom",
547            Self::MavenPomProperties => "maven_pom_properties",
548            Self::MesonBuild => "meson_build",
549            Self::SbtBuildSbt => "sbt_build_sbt",
550
551            // Microsoft
552            Self::MicrosoftCabinet => "microsoft_cabinet",
553            Self::MicrosoftUpdateManifestMum => "microsoft_update_manifest_mum",
554
555            // Mobile/Browser
556            Self::AppleDmg => "apple_dmg",
557            Self::ChromeCrx => "chrome_crx",
558            Self::IosIpa => "ios_ipa",
559            Self::MozillaXpi => "mozilla_xpi",
560
561            // Meteor
562            Self::MeteorPackage => "meteor_package",
563
564            Self::NixDefaultNix => "nix_default_nix",
565            Self::NixFlakeLock => "nix_flake_lock",
566            Self::NixFlakeNix => "nix_flake_nix",
567
568            // npm
569            Self::BunLock => "bun_lock",
570            Self::BunLockb => "bun_lockb",
571            Self::NpmPackageJson => "npm_package_json",
572            Self::NpmPackageLockJson => "npm_package_lock_json",
573
574            // NuGet
575            Self::NugetCsproj => "nuget_csproj",
576            Self::NugetDepsJson => "nuget_deps_json",
577            Self::NugetDirectoryBuildProps => "nuget_directory_build_props",
578            Self::NugetDirectoryPackagesProps => "nuget_directory_packages_props",
579            Self::NugetNupkg => "nuget_nupkg",
580            Self::NugetProjectJson => "nuget_project_json",
581            Self::NugetProjectLockJson => "nuget_project_lock_json",
582            Self::NugetPackagesConfig => "nuget_packages_config",
583            Self::NugetPackagesLock => "nuget_packages_lock",
584            Self::NugetNuspec => "nuget_nuspec",
585            Self::NugetVbproj => "nuget_vbproj",
586            Self::NugetFsproj => "nuget_fsproj",
587
588            // OCaml/opam
589            Self::OpamFile => "opam_file",
590
591            // PHP/Composer
592            Self::PhpComposerJson => "php_composer_json",
593            Self::PhpComposerLock => "php_composer_lock",
594
595            // pnpm
596            Self::PnpmLockYaml => "pnpm_lock_yaml",
597            Self::PnpmWorkspaceYaml => "pnpm_workspace_yaml",
598
599            // Python/PyPI
600            Self::Pipfile => "pipfile",
601            Self::PipfileLock => "pipfile_lock",
602            Self::PipRequirements => "pip_requirements",
603            Self::PixiLock => "pixi_lock",
604            Self::PixiToml => "pixi_toml",
605            Self::PypiPipOriginJson => "pypi_pip_origin_json",
606            Self::PypiEgg => "pypi_egg",
607            Self::PypiEggPkginfo => "pypi_egg_pkginfo",
608            Self::PypiEditableEggPkginfo => "pypi_editable_egg_pkginfo",
609            Self::PypiInspectDeplock => "pypi_inspect_deplock",
610            Self::PypiJson => "pypi_json",
611            Self::PypiPoetryLock => "pypi_poetry_lock",
612            Self::PypiPoetryPyprojectToml => "pypi_poetry_pyproject_toml",
613            Self::PypiSdist => "pypi_sdist",
614            Self::PypiPylockToml => "pypi_pylock_toml",
615            Self::PypiPyprojectToml => "pypi_pyproject_toml",
616            Self::PypiSdistPkginfo => "pypi_sdist_pkginfo",
617            Self::PypiSetupCfg => "pypi_setup_cfg",
618            Self::PypiSetupPy => "pypi_setup_py",
619            Self::PypiUvLock => "pypi_uv_lock",
620            Self::PypiWheel => "pypi_wheel",
621            Self::PypiWheelMetadata => "pypi_wheel_metadata",
622
623            // RPM
624            Self::RpmArchive => "rpm_archive",
625            Self::RpmInstalledDatabaseBdb => "rpm_installed_database_bdb",
626            Self::RpmInstalledDatabaseNdb => "rpm_installed_database_ndb",
627            Self::RpmInstalledDatabaseSqlite => "rpm_installed_database_sqlite",
628            Self::RpmMarinerManifest => "rpm_mariner_manifest",
629            Self::RpmPackageLicenses => "rpm_package_licenses",
630            Self::RpmSpecfile => "rpm_specfile",
631            Self::RpmYumdb => "rpm_yumdb",
632
633            // Ruby/RubyGems
634            Self::Gemfile => "gemfile",
635            Self::GemfileExtracted => "gemfile_extracted",
636            Self::GemfileLock => "gemfile_lock",
637            Self::GemfileLockExtracted => "gemfile_lock_extracted",
638            Self::GemArchive => "gem_archive",
639            Self::GemArchiveExtracted => "gem_archive_extracted",
640            Self::Gemspec => "gemspec",
641            Self::GemspecExtracted => "gemspec_extracted",
642            Self::GemGemspecInstalledSpecifications => "gem_gemspec_installed_specifications",
643
644            // Disk Images/Installers
645            Self::InstallshieldInstaller => "installshield_installer",
646            Self::IsoDiskImage => "iso_disk_image",
647            Self::NsisInstaller => "nsis_installer",
648            Self::SharShellArchive => "shar_shell_archive",
649            Self::SquashfsDiskImage => "squashfs_disk_image",
650
651            // Swift
652            Self::SwiftPackageManifestJson => "swift_package_manifest_json",
653            Self::SwiftPackageResolved => "swift_package_resolved",
654            Self::SwiftPackageShowDependencies => "swift_package_show_dependencies",
655
656            Self::PubliccodeYaml => "publiccode_yaml",
657
658            // vcpkg
659            Self::VcpkgJson => "vcpkg_json",
660
661            // Yarn
662            Self::YarnLock => "yarn_lock",
663            Self::YarnLockV1 => "yarn_lock_v1",
664            Self::YarnLockV2 => "yarn_lock_v2",
665            Self::YarnPnpCjs => "yarn_pnp_cjs",
666
667            // Git
668            Self::Gitmodules => "gitmodules",
669        }
670    }
671}
672
673impl AsRef<str> for DatasourceId {
674    fn as_ref(&self) -> &str {
675        self.as_str()
676    }
677}
678
679impl fmt::Display for DatasourceId {
680    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
681        f.write_str(self.as_str())
682    }
683}
684
685impl FromStr for DatasourceId {
686    type Err = String;
687
688    fn from_str(s: &str) -> Result<Self, Self::Err> {
689        use strum::IntoEnumIterator;
690        Self::iter()
691            .find(|variant| variant.as_str() == s)
692            .ok_or_else(|| format!("unknown datasource id: {}", s))
693    }
694}
695
696#[cfg(test)]
697mod tests {
698    use super::*;
699
700    #[test]
701    fn test_serialization() {
702        let id = DatasourceId::NpmPackageJson;
703        let json = serde_json::to_string(&id).unwrap();
704        assert_eq!(json, r#""NpmPackageJson""#);
705    }
706
707    #[test]
708    fn test_deserialization() {
709        let json = r#""NpmPackageJson""#;
710        let id: DatasourceId = serde_json::from_str(json).unwrap();
711        assert_eq!(id, DatasourceId::NpmPackageJson);
712    }
713
714    #[test]
715    fn test_as_str() {
716        assert_eq!(DatasourceId::NpmPackageJson.as_str(), "npm_package_json");
717        assert_eq!(DatasourceId::CargoLock.as_str(), "cargo_lock");
718        assert_eq!(
719            DatasourceId::PypiPyprojectToml.as_str(),
720            "pypi_pyproject_toml"
721        );
722        assert_eq!(DatasourceId::HackageCabal.as_str(), "hackage_cabal");
723        assert_eq!(DatasourceId::CitationCff.as_str(), "citation_cff");
724        assert_eq!(DatasourceId::PubliccodeYaml.as_str(), "publiccode_yaml");
725        assert_eq!(DatasourceId::YarnPnpCjs.as_str(), "yarn_pnp_cjs");
726    }
727
728    #[test]
729    fn test_display() {
730        assert_eq!(DatasourceId::NpmPackageJson.to_string(), "npm_package_json");
731    }
732
733    #[test]
734    fn test_as_ref() {
735        let id = DatasourceId::NpmPackageJson;
736        let s: &str = id.as_ref();
737        assert_eq!(s, "npm_package_json");
738    }
739
740    #[test]
741    fn test_python_rename_mappings() {
742        // Test the ~12 IDs that changed from our old values to match Python
743        assert_eq!(DatasourceId::BuckFile.as_str(), "buck_file");
744        assert_eq!(DatasourceId::BuckMetadata.as_str(), "buck_metadata");
745        assert_eq!(
746            DatasourceId::ChefCookbookMetadataJson.as_str(),
747            "chef_cookbook_metadata_json"
748        );
749        assert_eq!(
750            DatasourceId::ChefCookbookMetadataRb.as_str(),
751            "chef_cookbook_metadata_rb"
752        );
753        assert_eq!(DatasourceId::CondaYaml.as_str(), "conda_yaml");
754        assert_eq!(DatasourceId::CpanMakefile.as_str(), "cpan_makefile");
755        assert_eq!(
756            DatasourceId::DebianInstalledFilesList.as_str(),
757            "debian_installed_files_list"
758        );
759        assert_eq!(
760            DatasourceId::DebianOriginalSourceTarball.as_str(),
761            "debian_original_source_tarball"
762        );
763        assert_eq!(
764            DatasourceId::DebianSourceMetadataTarball.as_str(),
765            "debian_source_metadata_tarball"
766        );
767        assert_eq!(
768            DatasourceId::GemArchiveExtracted.as_str(),
769            "gem_archive_extracted"
770        );
771        // Corrected from typos in Python reference
772        assert_eq!(DatasourceId::NugetNuspec.as_str(), "nuget_nuspec");
773        assert_eq!(DatasourceId::RpmSpecfile.as_str(), "rpm_specfile");
774    }
775
776    #[test]
777    fn test_canonical_serialization() {
778        let nuget_json = serde_json::to_string(&DatasourceId::NugetNuspec).unwrap();
779        assert_eq!(nuget_json, r#""NugetNuspec""#);
780
781        let rpm_json = serde_json::to_string(&DatasourceId::RpmSpecfile).unwrap();
782        assert_eq!(rpm_json, r#""RpmSpecfile""#);
783    }
784
785    #[test]
786    fn test_from_str_uses_canonical_ids_and_rejects_legacy_typos() {
787        assert_eq!(
788            DatasourceId::from_str("nuget_nuspec").unwrap(),
789            DatasourceId::NugetNuspec
790        );
791        assert_eq!(
792            DatasourceId::from_str("rpm_specfile").unwrap(),
793            DatasourceId::RpmSpecfile
794        );
795        // ScanCode's upstream `nuget_nupsec` / `rpm_spefile` typos are
796        // intentionally not accepted: Provenant emits and parses only the
797        // corrected ids, so the legacy aliases were dead no-ops.
798        assert!(DatasourceId::from_str("nuget_nupsec").is_err());
799        assert!(DatasourceId::from_str("rpm_spefile").is_err());
800    }
801}