Skip to main content

update_package/
packages.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use fuchsia_url::fuchsia_pkg::PinnedAbsolutePackageUrl;
6use serde::{Deserialize, Serialize};
7
8#[derive(Serialize, Deserialize, Debug)]
9#[serde(tag = "version", content = "content", deny_unknown_fields)]
10enum Packages {
11    #[serde(rename = "1")]
12    V1(Vec<PinnedAbsolutePackageUrl>),
13}
14
15/// ParsePackageError represents any error which might occur while parsing
16/// `packages.json` contents.
17#[derive(Debug, thiserror::Error)]
18#[allow(missing_docs)]
19pub enum ParsePackageError {
20    #[error("could not parse url from line: {0:?}")]
21    URLParseError(String, #[source] fuchsia_url::errors::ParseError),
22
23    #[error("json parsing error while reading `packages.json`")]
24    JsonError(#[source] serde_json::error::Error),
25}
26
27/// SerializePackageError represents any error which might occur while writing
28/// `packages.json` for an update package.
29#[derive(Debug, thiserror::Error)]
30#[allow(missing_docs)]
31pub enum SerializePackageError {
32    #[error("serialization error while constructing `packages.json`")]
33    JsonError(#[source] serde_json::error::Error),
34}
35
36/// Returns structured `packages.json` data based on file contents string.
37pub fn parse_packages_json(
38    contents: &[u8],
39) -> Result<Vec<PinnedAbsolutePackageUrl>, ParsePackageError> {
40    match serde_json::from_slice(contents).map_err(ParsePackageError::JsonError)? {
41        Packages::V1(packages) => Ok(packages),
42    }
43}
44
45/// Returns serialized `packages.json` contents based package URLs.
46pub fn serialize_packages_json(
47    pkg_urls: &[PinnedAbsolutePackageUrl],
48) -> Result<Vec<u8>, SerializePackageError> {
49    serde_json::to_vec(&Packages::V1(pkg_urls.into())).map_err(SerializePackageError::JsonError)
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use assert_matches::assert_matches;
56
57    fn pkg_urls<'a>(v: impl IntoIterator<Item = &'a str>) -> Vec<PinnedAbsolutePackageUrl> {
58        v.into_iter().map(|s| s.parse().unwrap()).collect()
59    }
60
61    #[test]
62    fn smoke_test_parse_packages_json() {
63        let pkg_urls = pkg_urls([
64            "fuchsia-pkg://fuchsia.com/ls/0?hash=71bad1a35b87a073f72f582065f6b6efec7b6a4a129868f37f6131f02107f1ea",
65            "fuchsia-pkg://fuchsia.com/pkg-resolver/0?hash=26d43a3fc32eaa65e6981791874b6ab80fae31fbfca1ce8c31ab64275fd4e8c0",
66        ]);
67        let packages = Packages::V1(pkg_urls.clone());
68        let packages_json = serde_json::to_vec(&packages).unwrap();
69        assert_eq!(parse_packages_json(&packages_json).unwrap(), pkg_urls);
70    }
71
72    #[test]
73    fn smoke_test_serialize_packages_json() {
74        let input = pkg_urls([
75            "fuchsia-pkg://fuchsia.com/ls/0?hash=71bad1a35b87a073f72f582065f6b6efec7b6a4a129868f37f6131f02107f1ea",
76            "fuchsia-pkg://fuchsia.com/pkg-resolver/0?hash=26d43a3fc32eaa65e6981791874b6ab80fae31fbfca1ce8c31ab64275fd4e8c0",
77        ]);
78        let output =
79            parse_packages_json(serialize_packages_json(input.as_slice()).unwrap().as_slice())
80                .unwrap();
81        assert_eq!(input, output);
82    }
83
84    #[test]
85    fn expect_failure_parse_packages_json() {
86        assert_matches!(parse_packages_json(&[]), Err(ParsePackageError::JsonError(_)));
87    }
88
89    #[test]
90    fn reject_unpinned_urls() {
91        assert_matches!(
92            parse_packages_json(serde_json::json!({
93                "version": "1",
94                "content": ["fuchsia-pkg://fuchsia.example/unpinned"]
95            })
96            .to_string().as_bytes()),
97            Err(ParsePackageError::JsonError(e)) if e.to_string().contains("missing hash")
98        )
99    }
100}