package_parser/pkgs/
swift.rs1use std::{fs::File, path::Path};
2
3use crate::types::{DependentPackage, Package};
4use serde_json::Value;
5
6use crate::{error::SourcePkgError, PackageManifest};
7
8mod v1 {
9 use serde::Deserialize;
10
11 #[derive(Debug, Deserialize)]
12 pub struct LockFile {
13 pub object: Object,
14 }
15
16 #[derive(Debug, Deserialize)]
17 pub struct Object {
18 #[serde(default)]
19 pub pins: Vec<PinnedPackage>,
20 }
21
22 #[derive(Debug, Deserialize)]
23 pub struct PinnedPackage {
24 pub package: String,
25 #[serde(rename = "repositoryURL")]
26 pub repository_url: Option<String>,
27 pub state: PinnedPackageState,
28 }
29
30 #[derive(Debug, Deserialize)]
31 #[serde(rename_all = "camelCase")]
32 pub struct PinnedPackageState {
33 pub version: Option<String>,
34 pub revision: Option<String>,
36 }
37}
38
39mod v2 {
40 use serde::Deserialize;
41
42 #[derive(Debug, Deserialize)]
43 pub struct LockFile {
44 pub pins: Vec<PinnedPackage>,
45 }
46
47 #[derive(Debug, Deserialize)]
48 #[allow(unused)]
49 pub struct PinnedPackage {
50 pub location: String,
51 pub state: PinnedPackageState,
52 }
53
54 #[derive(Debug, Deserialize)]
55 #[allow(unused)]
56 pub struct PinnedPackageState {
57 pub version: Option<String>,
58 pub revision: Option<String>,
59 }
60}
61
62fn repo_url_to_purl(url: &str) -> Option<String> {
63 let url = if let Some(url) = url.split_once("://") {
64 url.1
65 } else {
66 return None;
67 };
68
69 let url = url.strip_suffix(".git").unwrap_or(url);
70
71 Some(format!("pkg:swift/{}", url))
72}
73
74pub struct SwiftPmLock;
75
76impl SwiftPmLock {
77 pub fn new() -> Self {
78 Self
79 }
80
81 fn parse(path: &Path) -> Result<Package, SourcePkgError> {
82 let file = File::open(path)?;
83 let resolved: Value = serde_json::from_reader(file)?;
84
85 let mut deps = vec![];
86
87 let version = resolved.get("version").and_then(|v| v.as_i64());
88 match version {
89 Some(1) => {
90 let resolved = serde_json::from_value::<v1::LockFile>(resolved)?;
91 for pkg in resolved.object.pins {
92 let url = if let Some(url) = pkg.repository_url {
93 url
94 } else {
95 log::warn!("No URL for package {}", pkg.package);
96 continue;
97 };
98
99 let mut purl = if let Some(purl) = repo_url_to_purl(&url) {
100 purl
101 } else {
102 log::warn!("Could not convert URL {} to purl", url);
103 continue;
104 };
105
106 let version = pkg.state.version.or(pkg.state.revision);
107
108 if let Some(v) = &version {
109 purl.push_str(&format!("@{}", v));
110 }
111
112 let dep = DependentPackage {
113 purl,
114 requirement: version.unwrap_or_default(),
115 scope: "".into(),
116 is_runtime: true,
117 is_optional: false,
118 is_resolved: true,
119 ..Default::default()
120 };
121
122 deps.push(dep);
123 }
124 }
125 Some(2) => {
126 let resolved = serde_json::from_value::<v2::LockFile>(resolved)?;
127
128 for pkg in resolved.pins {
129 let url = pkg.location;
130
131 let mut purl = if let Some(purl) = repo_url_to_purl(&url) {
132 purl
133 } else {
134 log::warn!("Could not convert URL {} to purl", url);
135 continue;
136 };
137
138 let version = pkg.state.version.or(pkg.state.revision);
139
140 if let Some(v) = &version {
141 purl.push_str(&format!("@{}", v));
142 }
143
144 let dep = DependentPackage {
145 purl,
146 requirement: version.unwrap_or_default(),
147 scope: "".into(),
148 is_runtime: true,
149 is_optional: false,
150 is_resolved: true,
151 ..Default::default()
152 };
153
154 deps.push(dep);
155 }
156 }
157 v => {
158 return Err(SourcePkgError::GenericsError2(format!(
159 "Unrecognized swift package manager lock file version: {:?}",
160 v
161 )));
162 }
163 }
164
165 Ok(Package {
166 dependencies: deps,
167 ..Default::default()
168 })
169 }
170}
171
172#[async_trait::async_trait]
173impl PackageManifest for SwiftPmLock {
174 fn get_name(&self) -> String {
175 "swift".to_string()
176 }
177
178 async fn recognize(&self, path: &Path) -> Result<Package, SourcePkgError> {
179 Self::parse(path)
180 }
181
182 fn file_name_patterns(&self) -> &'static [&'static str] {
183 &["Package.resolved"]
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn v1() {
193 let filepath = Path::new(concat!(
194 env!("CARGO_MANIFEST_DIR"),
195 "/testdata/swift/v1_erik.resolved"
196 ));
197
198 let p = SwiftPmLock::parse(filepath).unwrap();
199 println!("{:?}", p);
200 }
201
202 #[test]
203 fn v2() {
204 let filepath = Path::new(concat!(
205 env!("CARGO_MANIFEST_DIR"),
206 "/testdata/swift/v2_firefox.resolved"
207 ));
208
209 let p = SwiftPmLock::parse(filepath).unwrap();
210 println!("{:?}", p);
211 }
212}