update_package/
packages.rs
1use fidl_fuchsia_io as fio;
6use fuchsia_url::PinnedAbsolutePackageUrl;
7use serde::{Deserialize, Serialize};
8
9#[derive(Serialize, Deserialize, Debug)]
10#[serde(tag = "version", content = "content", deny_unknown_fields)]
11enum Packages {
12 #[serde(rename = "1")]
13 V1(Vec<PinnedAbsolutePackageUrl>),
14}
15
16#[derive(Debug, thiserror::Error)]
19#[allow(missing_docs)]
20pub enum ParsePackageError {
21 #[error("could not open `packages.json`")]
22 FailedToOpen(#[source] fuchsia_fs::node::OpenError),
23
24 #[error("could not parse url from line: {0:?}")]
25 URLParseError(String, #[source] fuchsia_url::errors::ParseError),
26
27 #[error("error reading file `packages.json`")]
28 ReadError(#[source] fuchsia_fs::file::ReadError),
29
30 #[error("json parsing error while reading `packages.json`")]
31 JsonError(#[source] serde_json::error::Error),
32}
33
34#[derive(Debug, thiserror::Error)]
37#[allow(missing_docs)]
38pub enum SerializePackageError {
39 #[error("serialization error while constructing `packages.json`")]
40 JsonError(#[source] serde_json::error::Error),
41}
42
43pub fn parse_packages_json(
45 contents: &[u8],
46) -> Result<Vec<PinnedAbsolutePackageUrl>, ParsePackageError> {
47 match serde_json::from_slice(contents).map_err(ParsePackageError::JsonError)? {
48 Packages::V1(packages) => Ok(packages),
49 }
50}
51
52pub fn serialize_packages_json(
54 pkg_urls: &[PinnedAbsolutePackageUrl],
55) -> Result<Vec<u8>, SerializePackageError> {
56 serde_json::to_vec(&Packages::V1(pkg_urls.into())).map_err(SerializePackageError::JsonError)
57}
58
59pub(crate) async fn packages(
61 proxy: &fio::DirectoryProxy,
62) -> Result<Vec<PinnedAbsolutePackageUrl>, ParsePackageError> {
63 let file = fuchsia_fs::directory::open_file(proxy, "packages.json", fio::PERM_READABLE)
64 .await
65 .map_err(ParsePackageError::FailedToOpen)?;
66
67 let contents = fuchsia_fs::file::read(&file).await.map_err(ParsePackageError::ReadError)?;
68 parse_packages_json(&contents)
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use crate::TestUpdatePackage;
75 use assert_matches::assert_matches;
76 use serde_json::json;
77
78 fn pkg_urls<'a>(v: impl IntoIterator<Item = &'a str>) -> Vec<PinnedAbsolutePackageUrl> {
79 v.into_iter().map(|s| s.parse().unwrap()).collect()
80 }
81
82 #[test]
83 fn smoke_test_parse_packages_json() {
84 let pkg_urls = pkg_urls([
85 "fuchsia-pkg://fuchsia.com/ls/0?hash=71bad1a35b87a073f72f582065f6b6efec7b6a4a129868f37f6131f02107f1ea",
86 "fuchsia-pkg://fuchsia.com/pkg-resolver/0?hash=26d43a3fc32eaa65e6981791874b6ab80fae31fbfca1ce8c31ab64275fd4e8c0",
87 ]);
88 let packages = Packages::V1(pkg_urls.clone());
89 let packages_json = serde_json::to_vec(&packages).unwrap();
90 assert_eq!(parse_packages_json(&packages_json).unwrap(), pkg_urls);
91 }
92
93 #[test]
94 fn smoke_test_serialize_packages_json() {
95 let input = pkg_urls([
96 "fuchsia-pkg://fuchsia.com/ls/0?hash=71bad1a35b87a073f72f582065f6b6efec7b6a4a129868f37f6131f02107f1ea",
97 "fuchsia-pkg://fuchsia.com/pkg-resolver/0?hash=26d43a3fc32eaa65e6981791874b6ab80fae31fbfca1ce8c31ab64275fd4e8c0",
98 ]);
99 let output =
100 parse_packages_json(serialize_packages_json(input.as_slice()).unwrap().as_slice())
101 .unwrap();
102 assert_eq!(input, output);
103 }
104
105 #[test]
106 fn expect_failure_parse_packages_json() {
107 assert_matches!(parse_packages_json(&[]), Err(ParsePackageError::JsonError(_)));
108 }
109
110 #[fuchsia_async::run_singlethreaded(test)]
111 async fn smoke_test_packages_json_version_string() {
112 let pkg_list = [
113 "fuchsia-pkg://fuchsia.com/ls/0?hash=71bad1a35b87a073f72f582065f6b6efec7b6a4a129868f37f6131f02107f1ea",
114 "fuchsia-pkg://fuchsia.com/pkg-resolver/0?hash=26d43a3fc32eaa65e6981791874b6ab80fae31fbfca1ce8c31ab64275fd4e8c0",
115 ];
116 let packages = json!({
117 "version": "1",
118 "content": pkg_list,
119 })
120 .to_string();
121 let update_pkg = TestUpdatePackage::new().add_file("packages.json", packages).await;
122 assert_eq!(update_pkg.packages().await.unwrap(), pkg_urls(pkg_list));
123 }
124
125 #[fuchsia_async::run_singlethreaded(test)]
126 async fn expect_failure_json() {
127 let update_pkg = TestUpdatePackage::new();
128 let packages = "{}";
129 let update_pkg = update_pkg.add_file("packages.json", packages).await;
130 assert_matches!(update_pkg.packages().await, Err(ParsePackageError::JsonError(_)))
131 }
132
133 #[fuchsia_async::run_singlethreaded(test)]
134 async fn expect_failure_version_not_supported() {
135 let pkg_list = vec![
136 "fuchsia-pkg://fuchsia.com/ls/0?hash=71bad1a35b87a073f72f582065f6b6efec7b6a4a129868f37f6131f02107f1ea",
137 ];
138 let packages = json!({
139 "version": "2",
140 "content": pkg_list,
141 })
142 .to_string();
143 let update_pkg = TestUpdatePackage::new().add_file("packages.json", packages).await;
144 assert_matches!(
145 update_pkg.packages().await,
146 Err(ParsePackageError::JsonError(e))
147 if e.to_string().contains("unknown variant `2`, expected `1`")
148 );
149 }
150
151 #[fuchsia_async::run_singlethreaded(test)]
152 async fn expect_failure_no_files() {
153 let update_pkg = TestUpdatePackage::new();
154 assert_matches!(update_pkg.packages().await, Err(ParsePackageError::FailedToOpen(_)))
155 }
156
157 #[test]
158 fn reject_unpinned_urls() {
159 assert_matches!(
160 parse_packages_json(serde_json::json!({
161 "version": "1",
162 "content": ["fuchsia-pkg://fuchsia.example/unpinned"]
163 })
164 .to_string().as_bytes()),
165 Err(ParsePackageError::JsonError(e)) if e.to_string().contains("missing hash")
166 )
167 }
168}