1use crate::RelativeTo;
6use anyhow::Result;
7use camino::{Utf8Path, Utf8PathBuf};
8use serde::{Deserialize, Serialize};
9use std::fs::{create_dir_all, File};
10use std::{slice, vec};
11use utf8_path::{path_relative_from_file, resolve_path_from_file};
12
13#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
17#[serde(transparent)]
18pub struct PackageManifestList(VersionedPackageManifestList);
19
20#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
21#[serde(tag = "version", content = "content", deny_unknown_fields)]
22enum VersionedPackageManifestList {
23 #[serde(rename = "1")]
24 Version1(PackageManifestListV1),
25}
26
27#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
28struct PackageManifestListV1 {
29 #[serde(default, skip_serializing_if = "RelativeTo::is_default")]
33 paths_relative: RelativeTo,
34 #[serde(default, skip_serializing_if = "Vec::is_empty")]
35 manifests: Vec<Utf8PathBuf>,
36}
37
38impl PackageManifestList {
39 #[allow(clippy::new_without_default)]
41 pub fn new() -> Self {
42 PackageManifestList(VersionedPackageManifestList::Version1(PackageManifestListV1 {
43 manifests: vec![],
44 paths_relative: RelativeTo::default(),
45 }))
46 }
47
48 pub fn push(&mut self, package_manifest_path: Utf8PathBuf) {
50 match &mut self.0 {
51 VersionedPackageManifestList::Version1(ref mut package_manifest_list_v1) => {
52 package_manifest_list_v1.manifests.push(package_manifest_path)
53 }
54 }
55 }
56
57 pub fn iter(&self) -> Iter<'_> {
59 match &self.0 {
60 VersionedPackageManifestList::Version1(package_manifest_list_v1) => {
61 Iter(package_manifest_list_v1.manifests.iter())
62 }
63 }
64 }
65
66 pub fn from_reader(
67 manifest_list_path: &Utf8Path,
68 mut reader: impl std::io::Read,
69 ) -> anyhow::Result<Self> {
70 let mut bytes = Vec::new();
71 reader.read_to_end(&mut bytes)?;
72
73 let versioned_list = match serde_json::from_slice(bytes.as_slice())? {
74 VersionedPackageManifestList::Version1(manifest_list) => {
75 VersionedPackageManifestList::Version1(
76 manifest_list.resolve_source_paths(manifest_list_path)?,
77 )
78 }
79 };
80
81 Ok(Self(versioned_list))
82 }
83
84 pub fn write_with_relative_paths(self, path: &Utf8Path) -> anyhow::Result<Self> {
85 let versioned = match &self.0 {
86 VersionedPackageManifestList::Version1(package_manifest_list) => {
87 VersionedPackageManifestList::Version1(
88 package_manifest_list.clone().write_with_relative_paths(path)?,
89 )
90 }
91 };
92
93 Ok(PackageManifestList(versioned))
94 }
95
96 pub fn to_writer(&self, writer: impl std::io::Write) -> Result<(), std::io::Error> {
98 serde_json::to_writer(writer, &self.0).unwrap();
99 Ok(())
100 }
101}
102
103impl PackageManifestListV1 {
104 pub fn write_with_relative_paths(self, manifest_list_path: &Utf8Path) -> anyhow::Result<Self> {
105 let manifest_list = if let RelativeTo::WorkingDir = &self.paths_relative {
107 let manifests = self
108 .manifests
109 .into_iter()
110 .map(|manifest_path| {
111 path_relative_from_file(manifest_path, manifest_list_path).unwrap()
112 })
113 .collect::<Vec<_>>();
114 Self { manifests, paths_relative: RelativeTo::File }
115 } else {
116 self
117 };
118
119 let versioned_manifest = VersionedPackageManifestList::Version1(manifest_list.clone());
120
121 create_dir_all(manifest_list_path.parent().unwrap())?;
122 let file = File::create(manifest_list_path)?;
123 serde_json::to_writer(file, &versioned_manifest)?;
124
125 Ok(manifest_list)
126 }
127
128 pub fn resolve_source_paths(self, manifest_list_path: &Utf8Path) -> anyhow::Result<Self> {
129 if let RelativeTo::File = self.paths_relative {
130 let manifests = self
131 .manifests
132 .into_iter()
133 .map(|manifest_path| {
134 resolve_path_from_file(manifest_path, manifest_list_path).unwrap()
135 })
136 .collect::<Vec<_>>();
137 Ok(Self { manifests, ..self })
138 } else {
139 Ok(self)
140 }
141 }
142}
143
144impl IntoIterator for PackageManifestList {
145 type Item = Utf8PathBuf;
146 type IntoIter = IntoIter;
147
148 fn into_iter(self) -> Self::IntoIter {
149 match self.0 {
150 VersionedPackageManifestList::Version1(package_manifest_list_v1) => {
151 IntoIter(package_manifest_list_v1.manifests.into_iter())
152 }
153 }
154 }
155}
156
157impl From<Vec<Utf8PathBuf>> for PackageManifestList {
158 fn from(package_manifest_list: Vec<Utf8PathBuf>) -> Self {
159 Self(VersionedPackageManifestList::Version1(PackageManifestListV1 {
160 manifests: package_manifest_list,
161 paths_relative: RelativeTo::default(),
162 }))
163 }
164}
165
166impl From<PackageManifestList> for Vec<Utf8PathBuf> {
167 fn from(package_manifest_list: PackageManifestList) -> Self {
168 match package_manifest_list.0 {
169 VersionedPackageManifestList::Version1(package_manifest_list_v1) => {
170 package_manifest_list_v1.manifests
171 }
172 }
173 }
174}
175
176impl FromIterator<Utf8PathBuf> for PackageManifestList {
177 fn from_iter<T>(iter: T) -> Self
178 where
179 T: IntoIterator<Item = Utf8PathBuf>,
180 {
181 Self(VersionedPackageManifestList::Version1(PackageManifestListV1 {
182 manifests: iter.into_iter().collect(),
183 paths_relative: RelativeTo::default(),
184 }))
185 }
186}
187
188pub struct Iter<'a>(slice::Iter<'a, Utf8PathBuf>);
190
191impl<'a> Iterator for Iter<'a> {
192 type Item = &'a Utf8PathBuf;
193
194 fn next(&mut self) -> Option<Self::Item> {
195 self.0.next()
196 }
197}
198
199pub struct IntoIter(vec::IntoIter<Utf8PathBuf>);
201
202impl Iterator for IntoIter {
203 type Item = Utf8PathBuf;
204
205 fn next(&mut self) -> Option<Self::Item> {
206 self.0.next()
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213 use serde_json::json;
214 use tempfile::TempDir;
215
216 #[test]
217 fn test_serialize() {
218 let package_manifest_list = PackageManifestList::from(vec![
219 "obj/build/images/config-data/package_manifest.json".into(),
220 "obj/build/images/shell-commands/package_manifest.json".into(),
221 "obj/src/sys/component_index/component_index/package_manifest.json".into(),
222 ]);
223
224 assert_eq!(
225 serde_json::to_value(package_manifest_list).unwrap(),
226 json!(
227 {
228 "content": {
229 "manifests": [
230 "obj/build/images/config-data/package_manifest.json",
231 "obj/build/images/shell-commands/package_manifest.json",
232 "obj/src/sys/component_index/component_index/package_manifest.json",
233 ]
234 },
235 "version": "1"
236 }
237 ),
238 );
239 }
240
241 #[test]
242 fn test_iter_from_reader_json_v1() {
243 let temp = TempDir::new().unwrap();
244 let temp_dir = Utf8Path::from_path(temp.path()).unwrap();
245
246 let manifest_dir = temp_dir.join("manifest_dir");
247 let manifest_path = manifest_dir.join("package_manifest_list.json");
248 std::fs::create_dir_all(&manifest_dir).unwrap();
249
250 let raw_package_manifest_list_json_v1 = r#"{
252 "version": "1",
253 "content": {
254 "paths_relative": "working_dir",
255 "manifests": [
256 "obj/build/images/config-data/package_manifest.json",
257 "obj/build/images/shell-commands/package_manifest.json",
258 "obj/src/sys/component_index/component_index/package_manifest.json"
259 ]
260 }
261 }"#;
262 std::fs::write(&manifest_path, raw_package_manifest_list_json_v1).unwrap();
263
264 let package_manifest_list =
265 PackageManifestList::from_reader(&manifest_path, File::open(&manifest_path).unwrap())
266 .unwrap();
267 assert_eq!(
268 PackageManifestList(VersionedPackageManifestList::Version1(PackageManifestListV1 {
269 paths_relative: RelativeTo::WorkingDir,
270 manifests: vec![
271 "obj/build/images/config-data/package_manifest.json".into(),
272 "obj/build/images/shell-commands/package_manifest.json".into(),
273 "obj/src/sys/component_index/component_index/package_manifest.json".into(),
274 ]
275 })),
276 package_manifest_list
277 );
278
279 assert_eq!(
280 package_manifest_list.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
281 vec![
282 "obj/build/images/config-data/package_manifest.json",
283 "obj/build/images/shell-commands/package_manifest.json",
284 "obj/src/sys/component_index/component_index/package_manifest.json",
285 ]
286 );
287
288 let raw_package_manifest_list_json_v1 = r#"{
290 "version": "1",
291 "content": {
292 "paths_relative": "file",
293 "manifests": [
294 "obj/build/images/config-data/package_manifest.json",
295 "obj/build/images/shell-commands/package_manifest.json",
296 "obj/src/sys/component_index/component_index/package_manifest.json"
297 ]
298 }
299 }"#;
300 std::fs::write(&manifest_path, raw_package_manifest_list_json_v1).unwrap();
301
302 let package_manifest_list =
303 PackageManifestList::from_reader(&manifest_path, File::open(&manifest_path).unwrap())
304 .unwrap();
305 assert_eq!(
306 PackageManifestList(VersionedPackageManifestList::Version1(PackageManifestListV1 {
307 paths_relative: RelativeTo::File,
308 manifests: vec![
309 manifest_dir.join("obj/build/images/config-data/package_manifest.json"),
310 manifest_dir.join("obj/build/images/shell-commands/package_manifest.json"),
311 manifest_dir
312 .join("obj/src/sys/component_index/component_index/package_manifest.json"),
313 ]
314 })),
315 package_manifest_list
316 );
317
318 assert_eq!(
319 package_manifest_list.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
320 vec![
321 manifest_dir.join("obj/build/images/config-data/package_manifest.json"),
322 manifest_dir.join("obj/build/images/shell-commands/package_manifest.json"),
323 manifest_dir
324 .join("obj/src/sys/component_index/component_index/package_manifest.json"),
325 ]
326 );
327 }
328
329 #[test]
330 fn test_to_writer() {
331 let package_manifest_list = PackageManifestList::from(vec![
332 "obj/build/images/config-data/package_manifest.json".into(),
333 "obj/build/images/shell-commands/package_manifest.json".into(),
334 "obj/src/sys/component_index/component_index/package_manifest.json".into(),
335 ]);
336
337 let mut out = vec![];
338 package_manifest_list.to_writer(&mut out).unwrap();
339
340 assert_eq!(
341 String::from_utf8(out).unwrap(),
342 "{\
343 \"version\":\"1\",\
344 \"content\":{\
345 \"manifests\":[\
346 \"obj/build/images/config-data/package_manifest.json\",\
347 \"obj/build/images/shell-commands/package_manifest.json\",\
348 \"obj/src/sys/component_index/component_index/package_manifest.json\"\
349 ]\
350 }\
351 }"
352 );
353 }
354
355 #[test]
356 fn test_write_package_manifest_list_making_paths_relative() {
357 let temp = TempDir::new().unwrap();
358 let temp_dir = Utf8Path::from_path(temp.path()).unwrap();
359
360 let other_dir = temp_dir.join("other_dir");
361 let manifest_dir = temp_dir.join("manifest_dir");
362 let manifest_path = manifest_dir.join("package_manifest_list.json");
363
364 std::fs::create_dir_all(&manifest_dir).unwrap();
365 std::fs::create_dir_all(&other_dir).unwrap();
366
367 let package_manifest_list =
368 PackageManifestList(VersionedPackageManifestList::Version1(PackageManifestListV1 {
369 paths_relative: RelativeTo::WorkingDir,
370 manifests: vec![
371 other_dir.join("foo.package_manifest.json"),
372 other_dir.join("bar.package_manifest.json"),
373 ],
374 }));
375
376 let result_manifest_list =
378 package_manifest_list.write_with_relative_paths(&manifest_path).unwrap();
379
380 let path_relative_package_manifest_list =
382 PackageManifestList(VersionedPackageManifestList::Version1(PackageManifestListV1 {
383 paths_relative: RelativeTo::File,
384 manifests: vec![
385 "../other_dir/foo.package_manifest.json".into(),
386 "../other_dir/bar.package_manifest.json".into(),
387 ],
388 }));
389
390 assert_eq!(result_manifest_list, path_relative_package_manifest_list);
391 }
392
393 #[test]
394 fn test_write_package_manifest_list_already_relative() {
395 let temp = TempDir::new().unwrap();
396 let temp_dir = Utf8Path::from_path(temp.path()).unwrap();
397
398 let manifest_dir = temp_dir.join("manifest_dir");
399 let manifest_path = manifest_dir.join("package_manifest_list.json");
400
401 std::fs::create_dir_all(&manifest_dir).unwrap();
402
403 let package_manifest_list =
404 PackageManifestList(VersionedPackageManifestList::Version1(PackageManifestListV1 {
405 paths_relative: RelativeTo::File,
406 manifests: vec![
407 "../other_dir/foo.package_manifest.json".into(),
408 "../other_dir/bar.package_manifest.json".into(),
409 ],
410 }));
411
412 let result_manifest_list =
414 package_manifest_list.clone().write_with_relative_paths(&manifest_path).unwrap();
415
416 assert_eq!(result_manifest_list, package_manifest_list);
418 }
419
420 #[test]
421 fn test_iter() {
422 let package_manifest_list = PackageManifestList::from(vec![
423 "obj/build/images/config-data/package_manifest.json".into(),
424 "obj/build/images/shell-commands/package_manifest.json".into(),
425 "obj/src/sys/component_index/component_index/package_manifest.json".into(),
426 ]);
427
428 assert_eq!(
429 package_manifest_list.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
430 vec![
431 "obj/build/images/config-data/package_manifest.json",
432 "obj/build/images/shell-commands/package_manifest.json",
433 "obj/src/sys/component_index/component_index/package_manifest.json",
434 ]
435 );
436 }
437
438 #[test]
439 fn test_into_iter() {
440 let entries = vec![
441 "obj/build/images/config-data/package_manifest.json".into(),
442 "obj/build/images/shell-commands/package_manifest.json".into(),
443 "obj/src/sys/component_index/component_index/package_manifest.json".into(),
444 ];
445 let package_manifest_list = PackageManifestList::from(entries.clone());
446
447 assert_eq!(package_manifest_list.into_iter().collect::<Vec<_>>(), entries,);
448 }
449}