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