Skip to main content

provenant/models/
package_type.rs

1// SPDX-FileCopyrightText: Provenant contributors
2// SPDX-License-Identifier: Apache-2.0
3
4//! Package type identifiers for package parsers.
5//!
6//! Each variant uniquely identifies the package ecosystem/registry type.
7//! These are used in Package URL (purl) type fields and in the JSON output
8//! as the `"type"` field of package data.
9
10use serde::{Deserialize, Serialize};
11use std::fmt;
12use std::str::FromStr;
13
14/// Package ecosystem/registry type identifier.
15///
16/// Identifies the package manager or ecosystem a package belongs to
17/// (e.g., npm, PyPI, Maven, Cargo). Used as the `"type"` field in
18/// ScanCode Toolkit-compatible JSON output.
19///
20/// This enum includes both standard purl types and ScanCode-specific types
21/// for file format recognizers (e.g., `Jar`, `War`) and metadata sources
22/// (e.g., `About`, `Readme`). For the official list of standardized purl types, see:
23/// <https://github.com/package-url/purl-spec/blob/main/purl-types-index.json>
24///
25/// # Serialization
26///
27/// Variants serialize to lowercase/kebab-case strings matching the
28/// Python reference values. The JSON output is identical to the
29/// Python ScanCode Toolkit.
30///
31/// For example, `PackageType::Npm` formats as `npm` for both `AsRef<str>` and
32/// `Display`.
33#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
34#[serde(rename_all = "snake_case")]
35pub enum PackageType {
36    About,
37    Alpm,
38    Alpine,
39    Android,
40    AndroidLib,
41    Autotools,
42    Axis2,
43    Bazel,
44    Bitbake,
45    Bower,
46    Buck,
47    Cab,
48    Cargo,
49    Carthage,
50    Chef,
51    Chrome,
52    Cocoapods,
53    Composer,
54    Conan,
55    Conda,
56    Cpan,
57    Cran,
58    Dart,
59    Deb,
60    Deno,
61    Docker,
62    Dmg,
63    Ear,
64    Freebsd,
65    Gem,
66    Generic,
67    Github,
68    Golang,
69    Hackage,
70    Haxe,
71    Helm,
72    Hex,
73    Installshield,
74    Julia,
75    Ios,
76    Iso,
77    Ivy,
78    Jar,
79    #[serde(rename = "jboss-service")]
80    JbossService,
81    #[serde(rename = "linux-distro")]
82    LinuxDistro,
83    Maven,
84    Meson,
85    Meteor,
86    Nix,
87    Mozilla,
88    Npm,
89    Nsis,
90    Nuget,
91    Opam,
92    Osgi,
93    #[serde(rename = "pnpm-lock")]
94    PnpmLock,
95    Pubspec,
96    Pypi,
97    Pixi,
98    Publiccode,
99    Readme,
100    Rpm,
101    Shar,
102    Squashfs,
103    Swift,
104    Vcpkg,
105    War,
106    Winexe,
107    #[serde(rename = "windows-update")]
108    WindowsUpdate,
109}
110
111impl PackageType {
112    /// Returns the string representation of this package type.
113    ///
114    /// This matches the serialized form used in JSON output.
115    pub fn as_str(&self) -> &'static str {
116        match self {
117            Self::About => "about",
118            Self::Alpm => "alpm",
119            Self::Alpine => "alpine",
120            Self::Android => "android",
121            Self::AndroidLib => "android_lib",
122            Self::Autotools => "autotools",
123            Self::Axis2 => "axis2",
124            Self::Bazel => "bazel",
125            Self::Bitbake => "bitbake",
126            Self::Bower => "bower",
127            Self::Buck => "buck",
128            Self::Cab => "cab",
129            Self::Cargo => "cargo",
130            Self::Carthage => "carthage",
131            Self::Chef => "chef",
132            Self::Chrome => "chrome",
133            Self::Cocoapods => "cocoapods",
134            Self::Composer => "composer",
135            Self::Conan => "conan",
136            Self::Conda => "conda",
137            Self::Cpan => "cpan",
138            Self::Cran => "cran",
139            Self::Dart => "dart",
140            Self::Deb => "deb",
141            Self::Deno => "deno",
142            Self::Docker => "docker",
143            Self::Dmg => "dmg",
144            Self::Ear => "ear",
145            Self::Freebsd => "freebsd",
146            Self::Gem => "gem",
147            Self::Generic => "generic",
148            Self::Github => "github",
149            Self::Golang => "golang",
150            Self::Hackage => "hackage",
151            Self::Haxe => "haxe",
152            Self::Helm => "helm",
153            Self::Hex => "hex",
154            Self::Installshield => "installshield",
155            Self::Julia => "julia",
156            Self::Ios => "ios",
157            Self::Iso => "iso",
158            Self::Ivy => "ivy",
159            Self::Jar => "jar",
160            Self::JbossService => "jboss-service",
161            Self::LinuxDistro => "linux-distro",
162            Self::Maven => "maven",
163            Self::Meson => "meson",
164            Self::Meteor => "meteor",
165            Self::Nix => "nix",
166            Self::Mozilla => "mozilla",
167            Self::Npm => "npm",
168            Self::Nsis => "nsis",
169            Self::Nuget => "nuget",
170            Self::Opam => "opam",
171            Self::Osgi => "osgi",
172            Self::PnpmLock => "pnpm-lock",
173            Self::Pubspec => "pubspec",
174            Self::Pypi => "pypi",
175            Self::Pixi => "pixi",
176            Self::Publiccode => "publiccode",
177            Self::Readme => "readme",
178            Self::Rpm => "rpm",
179            Self::Shar => "shar",
180            Self::Squashfs => "squashfs",
181            Self::Swift => "swift",
182            Self::Vcpkg => "vcpkg",
183            Self::War => "war",
184            Self::Winexe => "winexe",
185            Self::WindowsUpdate => "windows-update",
186        }
187    }
188}
189
190impl AsRef<str> for PackageType {
191    fn as_ref(&self) -> &str {
192        self.as_str()
193    }
194}
195
196impl fmt::Display for PackageType {
197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198        f.write_str(self.as_str())
199    }
200}
201
202impl FromStr for PackageType {
203    type Err = String;
204
205    fn from_str(s: &str) -> Result<Self, Self::Err> {
206        let json = format!("\"{}\"", s);
207        serde_json::from_str(&json).map_err(|_| format!("unknown package type: {}", s))
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214
215    #[test]
216    fn test_serialization() {
217        let pt = PackageType::Npm;
218        let json = serde_json::to_string(&pt).unwrap();
219        assert_eq!(json, r#""npm""#);
220    }
221
222    #[test]
223    fn test_deserialization() {
224        let json = r#""npm""#;
225        let pt: PackageType = serde_json::from_str(json).unwrap();
226        assert_eq!(pt, PackageType::Npm);
227    }
228
229    #[test]
230    fn test_as_str() {
231        assert_eq!(PackageType::Npm.as_str(), "npm");
232        assert_eq!(PackageType::Cargo.as_str(), "cargo");
233        assert_eq!(PackageType::Pypi.as_str(), "pypi");
234        assert_eq!(PackageType::Alpm.as_str(), "alpm");
235        assert_eq!(PackageType::Vcpkg.as_str(), "vcpkg");
236        assert_eq!(PackageType::Hackage.as_str(), "hackage");
237        assert_eq!(PackageType::Hex.as_str(), "hex");
238    }
239
240    #[test]
241    fn test_display() {
242        assert_eq!(PackageType::Npm.to_string(), "npm");
243    }
244
245    #[test]
246    fn test_as_ref() {
247        let pt = PackageType::Npm;
248        let s: &str = pt.as_ref();
249        assert_eq!(s, "npm");
250    }
251
252    #[test]
253    fn test_kebab_case_variants() {
254        assert_eq!(PackageType::JbossService.as_str(), "jboss-service");
255        assert_eq!(PackageType::LinuxDistro.as_str(), "linux-distro");
256        assert_eq!(PackageType::PnpmLock.as_str(), "pnpm-lock");
257        assert_eq!(PackageType::Winexe.as_str(), "winexe");
258        assert_eq!(PackageType::WindowsUpdate.as_str(), "windows-update");
259
260        // Verify serialization matches
261        let json = serde_json::to_string(&PackageType::JbossService).unwrap();
262        assert_eq!(json, r#""jboss-service""#);
263
264        let json = serde_json::to_string(&PackageType::LinuxDistro).unwrap();
265        assert_eq!(json, r#""linux-distro""#);
266
267        let json = serde_json::to_string(&PackageType::PnpmLock).unwrap();
268        assert_eq!(json, r#""pnpm-lock""#);
269
270        let json = serde_json::to_string(&PackageType::Winexe).unwrap();
271        assert_eq!(json, r#""winexe""#);
272
273        let json = serde_json::to_string(&PackageType::WindowsUpdate).unwrap();
274        assert_eq!(json, r#""windows-update""#);
275    }
276
277    #[test]
278    fn test_snake_case_variant() {
279        assert_eq!(PackageType::AndroidLib.as_str(), "android_lib");
280
281        let json = serde_json::to_string(&PackageType::AndroidLib).unwrap();
282        assert_eq!(json, r#""android_lib""#);
283    }
284
285    #[test]
286    fn test_deserialization_kebab_case() {
287        let pt: PackageType = serde_json::from_str(r#""jboss-service""#).unwrap();
288        assert_eq!(pt, PackageType::JbossService);
289
290        let pt: PackageType = serde_json::from_str(r#""linux-distro""#).unwrap();
291        assert_eq!(pt, PackageType::LinuxDistro);
292
293        let pt: PackageType = serde_json::from_str(r#""pnpm-lock""#).unwrap();
294        assert_eq!(pt, PackageType::PnpmLock);
295
296        let pt: PackageType = serde_json::from_str(r#""winexe""#).unwrap();
297        assert_eq!(pt, PackageType::Winexe);
298
299        let pt: PackageType = serde_json::from_str(r#""windows-update""#).unwrap();
300        assert_eq!(pt, PackageType::WindowsUpdate);
301    }
302}