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