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    GradleSettings,
211
212    // ── Haxe ──
213    HaxelibJson,
214
215    // ── Helm ──
216    HelmChartLock,
217    HelmChartYaml,
218
219    // ── Hex/Elixir ──
220    HexMixExs,
221    HexMixLock,
222
223    // ── Hugging Face ──
224    HuggingfaceModelCard,
225    HuggingfaceConfigJson,
226    HuggingfaceModelIndexJson,
227
228    // ── Julia ──
229    JuliaProjectToml,
230    JuliaManifestToml,
231
232    // ── Java ──
233    AntIvyDependenciesProperties,
234    AntIvyXml,
235    JavaEarApplicationXml,
236    JavaEarArchive,
237    JavaJar,
238    JavaJarManifest,
239    JavaOsgiManifest,
240    JavaWarArchive,
241    JavaWarWebXml,
242    JbossSar,
243    JbossServiceXml,
244
245    // ── Maven ──
246    MavenPom,
247    MavenPomProperties,
248    MesonBuild,
249
250    SbtBuildSbt,
251
252    // ── Microsoft ──
253    MicrosoftCabinet,
254    MicrosoftUpdateManifestMum,
255
256    // ── Mobile/Browser ──
257    AppleDmg,
258    ChromeCrx,
259    IosIpa,
260    MozillaXpi,
261
262    // ── Meteor ──
263    MeteorPackage,
264
265    NixDefaultNix,
266    NixFlakeLock,
267    NixFlakeNix,
268
269    // ── npm ──
270    NpmPackageJson,
271    NpmPackageLockJson,
272
273    // ── NuGet ──
274    NugetCsproj,
275    NugetDepsJson,
276    NugetDirectoryBuildProps,
277    NugetDirectoryPackagesProps,
278    NugetNupkg,
279    NugetProjectJson,
280    NugetProjectLockJson,
281    NugetPackagesConfig,
282    NugetPackagesLock,
283    NugetNuspec,
284    NugetVbproj,
285    NugetFsproj,
286
287    // ── OCaml/opam ──
288    OpamFile,
289
290    // ── PHP/Composer ──
291    PhpComposerJson,
292    PhpComposerLock,
293
294    // ── pnpm ──
295    PnpmLockYaml,
296    PnpmWorkspaceYaml,
297
298    // ── Python/PyPI ──
299    Pipfile,
300    PipfileLock,
301    PipRequirements,
302    PixiLock,
303    PixiToml,
304    PypiPipOriginJson,
305    PypiEgg,
306    PypiEggPkginfo,
307    PypiEditableEggPkginfo,
308    PypiInspectDeplock,
309    PypiJson,
310    PypiPoetryLock,
311    PypiPoetryPyprojectToml,
312    PypiSdist,
313    PypiPylockToml,
314    PypiPyprojectToml,
315    PypiSdistPkginfo,
316    PypiSetupCfg,
317    PypiSetupPy,
318    PypiUvLock,
319    PypiWheel,
320    PypiWheelMetadata,
321
322    // ── RPM ──
323    RpmArchive,
324    RpmInstalledDatabaseBdb,
325    RpmInstalledDatabaseNdb,
326    RpmInstalledDatabaseSqlite,
327    RpmMarinerManifest,
328    RpmPackageLicenses,
329    RpmSpecfile,
330    RpmYumdb,
331
332    // ── Ruby/RubyGems ──
333    Gemfile,
334    GemfileExtracted,
335    GemfileLock,
336    GemfileLockExtracted,
337    GemArchive,
338    /// Matches Python reference value.
339    GemArchiveExtracted,
340    Gemspec,
341    GemspecExtracted,
342    GemGemspecInstalledSpecifications,
343
344    // ── Disk Images/Installers ──
345    InstallshieldInstaller,
346    IsoDiskImage,
347    NsisInstaller,
348    SharShellArchive,
349    SquashfsDiskImage,
350
351    // ── Swift ──
352    SwiftPackageManifestJson,
353    SwiftPackageResolved,
354    SwiftPackageShowDependencies,
355
356    // ── VS Code ──
357    VscodeExtensionVsixManifest,
358
359    PubliccodeYaml,
360
361    // ── vcpkg ──
362    VcpkgJson,
363    VcpkgConfigurationJson,
364    VcpkgLockJson,
365    VcpkgControl,
366
367    // ── Yarn ──
368    YarnLock,
369    YarnLockV1,
370    YarnLockV2,
371    YarnPnpCjs,
372
373    // ── Git ──
374    Gitmodules,
375}
376
377impl DatasourceId {
378    /// Returns the string representation of this datasource ID.
379    ///
380    /// This matches the serialized form used in JSON output.
381    pub fn as_str(&self) -> &'static str {
382        match self {
383            // About/README/OS
384            Self::AboutFile => "about_file",
385            Self::Readme => "readme",
386            Self::EtcOsRelease => "etc_os_release",
387
388            // Alpine
389            Self::AlpineApkArchive => "alpine_apk_archive",
390            Self::AlpineApkbuild => "alpine_apkbuild",
391            Self::AlpineInstalledDb => "alpine_installed_db",
392
393            // Arch Linux
394            Self::ArchAurinfo => "arch_aurinfo",
395            Self::ArchPkginfo => "arch_pkginfo",
396            Self::ArchSrcinfo => "arch_srcinfo",
397
398            // Android
399            Self::AndroidAab => "android_aab",
400            Self::AndroidAarLibrary => "android_aar_library",
401            Self::AndroidApk => "android_apk",
402            Self::AndroidManifestXml => "android_manifest_xml",
403            Self::AndroidSoongMetadata => "android_soong_metadata",
404
405            // Apache Axis2
406            Self::Axis2Mar => "axis2_mar",
407            Self::Axis2ModuleXml => "axis2_module_xml",
408
409            // Autotools
410            Self::AutotoolsConfigure => "autotools_configure",
411
412            // Bazel
413            Self::BazelBuild => "bazel_build",
414
415            // Bitbake
416            Self::BitbakeRecipe => "bitbake_recipe",
417            Self::BitbakeRecipeAppend => "bitbake_recipe_append",
418
419            // Bower
420            Self::BowerJson => "bower_json",
421
422            // Buck
423            Self::BuckFile => "buck_file",
424            Self::BuckMetadata => "buck_metadata",
425
426            // Carthage
427            Self::CarthageCartfile => "carthage_cartfile",
428            Self::CarthageCartfileResolved => "carthage_cartfile_resolved",
429
430            // Cargo/Rust
431            Self::CargoLock => "cargo_lock",
432            Self::CargoToml => "cargo_toml",
433            Self::RustBinary => "rust_binary",
434            Self::WindowsExecutable => "windows_executable",
435
436            // Chef
437            Self::ChefCookbookMetadataJson => "chef_cookbook_metadata_json",
438            Self::ChefCookbookMetadataRb => "chef_cookbook_metadata_rb",
439
440            Self::CitationCff => "citation_cff",
441
442            // CocoaPods
443            Self::CocoapodsPodfile => "cocoapods_podfile",
444            Self::CocoapodsPodfileLock => "cocoapods_podfile_lock",
445            Self::CocoapodsPodspec => "cocoapods_podspec",
446            Self::CocoapodsPodspecJson => "cocoapods_podspec_json",
447
448            // Conan
449            Self::ConanConanDataYml => "conan_conandata_yml",
450            Self::ConanConanFilePy => "conan_conanfile_py",
451            Self::ConanConanFileTxt => "conan_conanfile_txt",
452            Self::ConanLock => "conan_lock",
453
454            // Conda
455            Self::CondaYaml => "conda_yaml",
456            Self::CondaMetaJson => "conda_meta_json",
457            Self::CondaMetaYaml => "conda_meta_yaml",
458
459            // Clojure
460            Self::ClojureDepsEdn => "clojure_deps_edn",
461            Self::ClojureProjectClj => "clojure_project_clj",
462
463            // CPAN/Perl
464            Self::CpanDistIni => "cpan_dist_ini",
465            Self::CpanMakefile => "cpan_makefile",
466            Self::CpanManifest => "cpan_manifest",
467            Self::CpanMetaJson => "cpan_meta_json",
468            Self::CpanMetaYml => "cpan_meta_yml",
469
470            // CRAN/R
471            Self::CranDescription => "cran_description",
472
473            // Dart/Flutter
474            Self::PubspecLock => "pubspec_lock",
475            Self::PubspecYaml => "pubspec_yaml",
476
477            // Debian
478            Self::DebianControlExtractedDeb => "debian_control_extracted_deb",
479            Self::DebianControlInSource => "debian_control_in_source",
480            Self::DebianCopyright => "debian_copyright",
481            Self::DebianCopyrightInSource => "debian_copyright_in_source",
482            Self::DebianCopyrightInPackage => "debian_copyright_in_package",
483            Self::DebianCopyrightStandalone => "debian_copyright_standalone",
484            Self::DebianDeb => "debian_deb",
485            Self::DebianSourceMetadataTarball => "debian_source_metadata_tarball",
486            Self::DebianDistrolessInstalledDb => "debian_distroless_installed_db",
487            Self::DebianInstalledFilesList => "debian_installed_files_list",
488            Self::DebianInstalledMd5Sums => "debian_installed_md5sums",
489            Self::DebianInstalledStatusDb => "debian_installed_status_db",
490            Self::DebianMd5SumsInExtractedDeb => "debian_md5sums_in_extracted_deb",
491            Self::DebianOriginalSourceTarball => "debian_original_source_tarball",
492            Self::DebianSourceControlDsc => "debian_source_control_dsc",
493            Self::DenoJson => "deno_json",
494            Self::DenoLock => "deno_lock",
495            Self::Dockerfile => "dockerfile",
496            Self::OciImageIndex => "oci_image_index",
497            Self::OciImageManifest => "oci_image_manifest",
498            Self::ErlangOtpAppSrc => "erlang_otp_app_src",
499            Self::RebarConfig => "rebar_config",
500            Self::RebarLock => "rebar_lock",
501            Self::BazelModule => "bazel_module",
502
503            // FreeBSD
504            Self::FreebsdCompactManifest => "freebsd_compact_manifest",
505
506            // Go
507            Self::Godeps => "godeps",
508            Self::GoBinary => "go_binary",
509            Self::GoMod => "go_mod",
510            Self::GoModGraph => "go_mod_graph",
511            Self::GoSum => "go_sum",
512            Self::GoWork => "go_work",
513
514            // Haskell / Hackage
515            Self::HackageCabal => "hackage_cabal",
516            Self::HackageCabalProject => "hackage_cabal_project",
517            Self::HackageStackYaml => "hackage_stack_yaml",
518
519            // Gradle
520            Self::BuildGradle => "build_gradle",
521            Self::GradleLockfile => "gradle_lockfile",
522            Self::GradleModule => "gradle_module",
523            Self::GradleSettings => "gradle_settings",
524
525            // Haxe
526            Self::HaxelibJson => "haxelib_json",
527
528            // Helm
529            Self::HelmChartLock => "helm_chart_lock",
530            Self::HelmChartYaml => "helm_chart_yaml",
531
532            // Hex/Elixir
533            Self::HexMixExs => "hex_mix_exs",
534            Self::HexMixLock => "hex_mix_lock",
535
536            // Hugging Face
537            Self::HuggingfaceModelCard => "huggingface_model_card",
538            Self::HuggingfaceConfigJson => "huggingface_config_json",
539            Self::HuggingfaceModelIndexJson => "huggingface_model_index_json",
540
541            // Julia
542            Self::JuliaProjectToml => "julia_project_toml",
543            Self::JuliaManifestToml => "julia_manifest_toml",
544
545            // Java
546            Self::AntIvyDependenciesProperties => "ant_ivy_dependencies_properties",
547            Self::AntIvyXml => "ant_ivy_xml",
548            Self::JavaEarApplicationXml => "java_ear_application_xml",
549            Self::JavaEarArchive => "java_ear_archive",
550            Self::JavaJar => "java_jar",
551            Self::JavaJarManifest => "java_jar_manifest",
552            Self::JavaOsgiManifest => "java_osgi_manifest",
553            Self::JavaWarArchive => "java_war_archive",
554            Self::JavaWarWebXml => "java_war_web_xml",
555            Self::JbossSar => "jboss_sar",
556            Self::JbossServiceXml => "jboss_service_xml",
557
558            // Maven
559            Self::MavenPom => "maven_pom",
560            Self::MavenPomProperties => "maven_pom_properties",
561            Self::MesonBuild => "meson_build",
562            Self::SbtBuildSbt => "sbt_build_sbt",
563
564            // Microsoft
565            Self::MicrosoftCabinet => "microsoft_cabinet",
566            Self::MicrosoftUpdateManifestMum => "microsoft_update_manifest_mum",
567
568            // Mobile/Browser
569            Self::AppleDmg => "apple_dmg",
570            Self::ChromeCrx => "chrome_crx",
571            Self::IosIpa => "ios_ipa",
572            Self::MozillaXpi => "mozilla_xpi",
573
574            // Meteor
575            Self::MeteorPackage => "meteor_package",
576
577            Self::NixDefaultNix => "nix_default_nix",
578            Self::NixFlakeLock => "nix_flake_lock",
579            Self::NixFlakeNix => "nix_flake_nix",
580
581            // npm
582            Self::BunLock => "bun_lock",
583            Self::BunLockb => "bun_lockb",
584            Self::NpmPackageJson => "npm_package_json",
585            Self::NpmPackageLockJson => "npm_package_lock_json",
586
587            // NuGet
588            Self::NugetCsproj => "nuget_csproj",
589            Self::NugetDepsJson => "nuget_deps_json",
590            Self::NugetDirectoryBuildProps => "nuget_directory_build_props",
591            Self::NugetDirectoryPackagesProps => "nuget_directory_packages_props",
592            Self::NugetNupkg => "nuget_nupkg",
593            Self::NugetProjectJson => "nuget_project_json",
594            Self::NugetProjectLockJson => "nuget_project_lock_json",
595            Self::NugetPackagesConfig => "nuget_packages_config",
596            Self::NugetPackagesLock => "nuget_packages_lock",
597            Self::NugetNuspec => "nuget_nuspec",
598            Self::NugetVbproj => "nuget_vbproj",
599            Self::NugetFsproj => "nuget_fsproj",
600
601            // OCaml/opam
602            Self::OpamFile => "opam_file",
603
604            // PHP/Composer
605            Self::PhpComposerJson => "php_composer_json",
606            Self::PhpComposerLock => "php_composer_lock",
607
608            // pnpm
609            Self::PnpmLockYaml => "pnpm_lock_yaml",
610            Self::PnpmWorkspaceYaml => "pnpm_workspace_yaml",
611
612            // Python/PyPI
613            Self::Pipfile => "pipfile",
614            Self::PipfileLock => "pipfile_lock",
615            Self::PipRequirements => "pip_requirements",
616            Self::PixiLock => "pixi_lock",
617            Self::PixiToml => "pixi_toml",
618            Self::PypiPipOriginJson => "pypi_pip_origin_json",
619            Self::PypiEgg => "pypi_egg",
620            Self::PypiEggPkginfo => "pypi_egg_pkginfo",
621            Self::PypiEditableEggPkginfo => "pypi_editable_egg_pkginfo",
622            Self::PypiInspectDeplock => "pypi_inspect_deplock",
623            Self::PypiJson => "pypi_json",
624            Self::PypiPoetryLock => "pypi_poetry_lock",
625            Self::PypiPoetryPyprojectToml => "pypi_poetry_pyproject_toml",
626            Self::PypiSdist => "pypi_sdist",
627            Self::PypiPylockToml => "pypi_pylock_toml",
628            Self::PypiPyprojectToml => "pypi_pyproject_toml",
629            Self::PypiSdistPkginfo => "pypi_sdist_pkginfo",
630            Self::PypiSetupCfg => "pypi_setup_cfg",
631            Self::PypiSetupPy => "pypi_setup_py",
632            Self::PypiUvLock => "pypi_uv_lock",
633            Self::PypiWheel => "pypi_wheel",
634            Self::PypiWheelMetadata => "pypi_wheel_metadata",
635
636            // RPM
637            Self::RpmArchive => "rpm_archive",
638            Self::RpmInstalledDatabaseBdb => "rpm_installed_database_bdb",
639            Self::RpmInstalledDatabaseNdb => "rpm_installed_database_ndb",
640            Self::RpmInstalledDatabaseSqlite => "rpm_installed_database_sqlite",
641            Self::RpmMarinerManifest => "rpm_mariner_manifest",
642            Self::RpmPackageLicenses => "rpm_package_licenses",
643            Self::RpmSpecfile => "rpm_specfile",
644            Self::RpmYumdb => "rpm_yumdb",
645
646            // Ruby/RubyGems
647            Self::Gemfile => "gemfile",
648            Self::GemfileExtracted => "gemfile_extracted",
649            Self::GemfileLock => "gemfile_lock",
650            Self::GemfileLockExtracted => "gemfile_lock_extracted",
651            Self::GemArchive => "gem_archive",
652            Self::GemArchiveExtracted => "gem_archive_extracted",
653            Self::Gemspec => "gemspec",
654            Self::GemspecExtracted => "gemspec_extracted",
655            Self::GemGemspecInstalledSpecifications => "gem_gemspec_installed_specifications",
656
657            // Disk Images/Installers
658            Self::InstallshieldInstaller => "installshield_installer",
659            Self::IsoDiskImage => "iso_disk_image",
660            Self::NsisInstaller => "nsis_installer",
661            Self::SharShellArchive => "shar_shell_archive",
662            Self::SquashfsDiskImage => "squashfs_disk_image",
663
664            // Swift
665            Self::SwiftPackageManifestJson => "swift_package_manifest_json",
666            Self::SwiftPackageResolved => "swift_package_resolved",
667            Self::SwiftPackageShowDependencies => "swift_package_show_dependencies",
668
669            // VS Code
670            Self::VscodeExtensionVsixManifest => "vscode_extension_vsixmanifest",
671
672            Self::PubliccodeYaml => "publiccode_yaml",
673
674            // vcpkg
675            Self::VcpkgJson => "vcpkg_json",
676            Self::VcpkgConfigurationJson => "vcpkg_configuration_json",
677            Self::VcpkgLockJson => "vcpkg_lock_json",
678            Self::VcpkgControl => "vcpkg_control",
679
680            // Yarn
681            Self::YarnLock => "yarn_lock",
682            Self::YarnLockV1 => "yarn_lock_v1",
683            Self::YarnLockV2 => "yarn_lock_v2",
684            Self::YarnPnpCjs => "yarn_pnp_cjs",
685
686            // Git
687            Self::Gitmodules => "gitmodules",
688        }
689    }
690}
691
692impl AsRef<str> for DatasourceId {
693    fn as_ref(&self) -> &str {
694        self.as_str()
695    }
696}
697
698impl fmt::Display for DatasourceId {
699    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
700        f.write_str(self.as_str())
701    }
702}
703
704impl FromStr for DatasourceId {
705    type Err = String;
706
707    fn from_str(s: &str) -> Result<Self, Self::Err> {
708        use strum::IntoEnumIterator;
709        Self::iter()
710            .find(|variant| variant.as_str() == s)
711            .ok_or_else(|| format!("unknown datasource id: {}", s))
712    }
713}
714
715#[cfg(test)]
716mod tests {
717    use super::*;
718
719    #[test]
720    fn test_serialization() {
721        let id = DatasourceId::NpmPackageJson;
722        let json = serde_json::to_string(&id).unwrap();
723        assert_eq!(json, r#""NpmPackageJson""#);
724    }
725
726    #[test]
727    fn test_deserialization() {
728        let json = r#""NpmPackageJson""#;
729        let id: DatasourceId = serde_json::from_str(json).unwrap();
730        assert_eq!(id, DatasourceId::NpmPackageJson);
731    }
732
733    #[test]
734    fn test_as_str() {
735        assert_eq!(DatasourceId::NpmPackageJson.as_str(), "npm_package_json");
736        assert_eq!(DatasourceId::CargoLock.as_str(), "cargo_lock");
737        assert_eq!(
738            DatasourceId::PypiPyprojectToml.as_str(),
739            "pypi_pyproject_toml"
740        );
741        assert_eq!(DatasourceId::HackageCabal.as_str(), "hackage_cabal");
742        assert_eq!(DatasourceId::CitationCff.as_str(), "citation_cff");
743        assert_eq!(DatasourceId::PubliccodeYaml.as_str(), "publiccode_yaml");
744        assert_eq!(DatasourceId::YarnPnpCjs.as_str(), "yarn_pnp_cjs");
745        assert_eq!(
746            DatasourceId::VscodeExtensionVsixManifest.as_str(),
747            "vscode_extension_vsixmanifest"
748        );
749    }
750
751    #[test]
752    fn test_display() {
753        assert_eq!(DatasourceId::NpmPackageJson.to_string(), "npm_package_json");
754    }
755
756    #[test]
757    fn test_as_ref() {
758        let id = DatasourceId::NpmPackageJson;
759        let s: &str = id.as_ref();
760        assert_eq!(s, "npm_package_json");
761    }
762
763    #[test]
764    fn test_python_rename_mappings() {
765        // Test the ~12 IDs that changed from our old values to match Python
766        assert_eq!(DatasourceId::BuckFile.as_str(), "buck_file");
767        assert_eq!(DatasourceId::BuckMetadata.as_str(), "buck_metadata");
768        assert_eq!(
769            DatasourceId::ChefCookbookMetadataJson.as_str(),
770            "chef_cookbook_metadata_json"
771        );
772        assert_eq!(
773            DatasourceId::ChefCookbookMetadataRb.as_str(),
774            "chef_cookbook_metadata_rb"
775        );
776        assert_eq!(DatasourceId::CondaYaml.as_str(), "conda_yaml");
777        assert_eq!(DatasourceId::CpanMakefile.as_str(), "cpan_makefile");
778        assert_eq!(
779            DatasourceId::DebianInstalledFilesList.as_str(),
780            "debian_installed_files_list"
781        );
782        assert_eq!(
783            DatasourceId::DebianOriginalSourceTarball.as_str(),
784            "debian_original_source_tarball"
785        );
786        assert_eq!(
787            DatasourceId::DebianSourceMetadataTarball.as_str(),
788            "debian_source_metadata_tarball"
789        );
790        assert_eq!(
791            DatasourceId::GemArchiveExtracted.as_str(),
792            "gem_archive_extracted"
793        );
794        // Corrected from typos in Python reference
795        assert_eq!(DatasourceId::NugetNuspec.as_str(), "nuget_nuspec");
796        assert_eq!(DatasourceId::RpmSpecfile.as_str(), "rpm_specfile");
797    }
798
799    #[test]
800    fn test_canonical_serialization() {
801        let nuget_json = serde_json::to_string(&DatasourceId::NugetNuspec).unwrap();
802        assert_eq!(nuget_json, r#""NugetNuspec""#);
803
804        let rpm_json = serde_json::to_string(&DatasourceId::RpmSpecfile).unwrap();
805        assert_eq!(rpm_json, r#""RpmSpecfile""#);
806    }
807
808    #[test]
809    fn test_from_str_uses_canonical_ids_and_rejects_legacy_typos() {
810        assert_eq!(
811            DatasourceId::from_str("nuget_nuspec").unwrap(),
812            DatasourceId::NugetNuspec
813        );
814        assert_eq!(
815            DatasourceId::from_str("rpm_specfile").unwrap(),
816            DatasourceId::RpmSpecfile
817        );
818        // ScanCode's upstream `nuget_nupsec` / `rpm_spefile` typos are
819        // intentionally not accepted: Provenant emits and parses only the
820        // corrected ids, so the legacy aliases were dead no-ops.
821        assert!(DatasourceId::from_str("nuget_nupsec").is_err());
822        assert!(DatasourceId::from_str("rpm_spefile").is_err());
823    }
824}