Skip to main content

provenant/models/
datasource_id.rs

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