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