Skip to main content

provenant/models/
datasource_id.rs

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