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