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