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