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