Skip to main content

provenant/models/
datasource_id.rs

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