Skip to main content

provenant/models/
datasource_id.rs

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