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