Skip to main content

provenant/models/
package_type.rs

1//! Package type identifiers for package parsers.
2//!
3//! Each variant uniquely identifies the package ecosystem/registry type.
4//! These are used in Package URL (purl) type fields and in the JSON output
5//! as the `"type"` field of package data.
6
7use serde::{Deserialize, Serialize};
8use std::fmt;
9use std::str::FromStr;
10
11/// Package ecosystem/registry type identifier.
12///
13/// Identifies the package manager or ecosystem a package belongs to
14/// (e.g., npm, PyPI, Maven, Cargo). Used as the `"type"` field in
15/// ScanCode Toolkit-compatible JSON output.
16///
17/// This enum includes both standard purl types and ScanCode-specific types
18/// for file format recognizers (e.g., `Jar`, `War`) and metadata sources
19/// (e.g., `About`, `Readme`). For the official list of standardized purl types, see:
20/// <https://github.com/package-url/purl-spec/blob/main/purl-types-index.json>
21///
22/// # Serialization
23///
24/// Variants serialize to lowercase/kebab-case strings matching the
25/// Python reference values. The JSON output is identical to the
26/// Python ScanCode Toolkit.
27///
28/// # Examples
29///
30/// ```ignore
31/// use provenant::models::PackageType;
32///
33/// let pt = PackageType::Npm;
34/// assert_eq!(pt.as_ref(), "npm");
35/// assert_eq!(pt.to_string(), "npm");
36/// ```
37#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
38#[serde(rename_all = "snake_case")]
39pub enum PackageType {
40    About,
41    Alpm,
42    Alpine,
43    Android,
44    AndroidLib,
45    Autotools,
46    Axis2,
47    Bazel,
48    Bower,
49    Buck,
50    Cab,
51    Cargo,
52    Chef,
53    Chrome,
54    Cocoapods,
55    Composer,
56    Conan,
57    Conda,
58    Cpan,
59    Cran,
60    Dart,
61    Deb,
62    Deno,
63    Docker,
64    Dmg,
65    Ear,
66    Freebsd,
67    Gem,
68    Generic,
69    Github,
70    Golang,
71    Hackage,
72    Haxe,
73    Helm,
74    Hex,
75    Installshield,
76    Julia,
77    Ios,
78    Iso,
79    Ivy,
80    Jar,
81    #[serde(rename = "jboss-service")]
82    JbossService,
83    #[serde(rename = "linux-distro")]
84    LinuxDistro,
85    Maven,
86    Meson,
87    Meteor,
88    Nix,
89    Mozilla,
90    Npm,
91    Nsis,
92    Nuget,
93    Opam,
94    Osgi,
95    #[serde(rename = "pnpm-lock")]
96    PnpmLock,
97    Pubspec,
98    Pypi,
99    Pixi,
100    Publiccode,
101    Readme,
102    Rpm,
103    Shar,
104    Squashfs,
105    Swift,
106    Vcpkg,
107    War,
108    Winexe,
109    #[serde(rename = "windows-update")]
110    WindowsUpdate,
111}
112
113impl PackageType {
114    /// Returns the string representation of this package type.
115    ///
116    /// This matches the serialized form used in JSON output.
117    pub fn as_str(&self) -> &'static str {
118        match self {
119            Self::About => "about",
120            Self::Alpm => "alpm",
121            Self::Alpine => "alpine",
122            Self::Android => "android",
123            Self::AndroidLib => "android_lib",
124            Self::Autotools => "autotools",
125            Self::Axis2 => "axis2",
126            Self::Bazel => "bazel",
127            Self::Bower => "bower",
128            Self::Buck => "buck",
129            Self::Cab => "cab",
130            Self::Cargo => "cargo",
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}