fuchsia_pkg/
meta_package.rs

1// Copyright 2019 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 crate::errors::MetaPackageError;
6use crate::path::PackagePath;
7use fuchsia_url::{PackageName, PackageVariant};
8use serde::{Deserialize, Serialize};
9use std::convert::TryInto as _;
10use std::io;
11
12/// A `MetaPackage` represents the "meta/package" file of a meta.far (which is
13/// a Fuchsia archive file of a Fuchsia package).
14/// It validates that the name and variant (called "version" in json) are valid.
15#[derive(Debug, Eq, PartialEq, Clone)]
16pub struct MetaPackage {
17    name: PackageName,
18    variant: PackageVariant,
19}
20
21impl MetaPackage {
22    pub const PATH: &'static str = "meta/package";
23
24    /// Create a `MetaPackage` with `name` and variant 0.
25    pub fn from_name_and_variant_zero(name: PackageName) -> Self {
26        Self { name, variant: PackageVariant::zero() }
27    }
28
29    /// Returns the package's name.
30    pub fn name(&self) -> &PackageName {
31        &self.name
32    }
33
34    /// Returns the package's variant.
35    pub fn variant(&self) -> &PackageVariant {
36        &self.variant
37    }
38
39    /// Convert into PackagePath.
40    pub fn into_path(self) -> PackagePath {
41        PackagePath::from_name_and_variant(self.name, self.variant)
42    }
43
44    /// Deserializes a `MetaPackage` from json.
45    ///
46    /// # Examples
47    /// ```
48    /// # use fuchsia_pkg::MetaPackage;
49    /// let json = r#"
50    ///     {"name": "package-name",
51    ///      "version": "0"}"#;
52    /// assert_eq!(
53    ///     MetaPackage::deserialize(json.as_bytes()).unwrap(),
54    ///     MetaPackage::from_name(
55    ///         "package-name".parse().unwrap(),
56    ///         "0".parse().unwrap(),
57    ///     )
58    /// );
59    /// ```
60    pub fn deserialize(reader: impl io::BufRead) -> Result<Self, MetaPackageError> {
61        MetaPackage::from_v0(serde_json::from_reader(reader)?)
62    }
63
64    /// Serializes a `MetaPackage` to json.
65    ///
66    /// # Examples
67    /// ```
68    /// # use fuchsia_pkg::MetaPackage;
69    /// let meta_package = MetaPackage::from_name("package-name".parse().unwrap());
70    /// let mut v: Vec<u8> = vec![];
71    /// meta_package.serialize(&mut v).unwrap();
72    /// assert_eq!(std::str::from_utf8(v.as_slice()).unwrap(),
73    ///            r#"{"name":"package-name","version":"0"}"#);
74    /// ```
75    pub fn serialize(&self, writer: impl io::Write) -> Result<(), MetaPackageError> {
76        serde_json::to_writer(
77            writer,
78            &MetaPackageV0Serialize { name: self.name.as_ref(), variant: self.variant.as_ref() },
79        )?;
80        Ok(())
81    }
82
83    fn from_v0(v0: MetaPackageV0Deserialize) -> Result<MetaPackage, MetaPackageError> {
84        Ok(MetaPackage {
85            name: v0.name.try_into().map_err(MetaPackageError::PackageName)?,
86            variant: v0.variant.try_into().map_err(MetaPackageError::PackageVariant)?,
87        })
88    }
89}
90
91#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
92struct MetaPackageV0Deserialize {
93    name: String,
94    #[serde(rename = "version")]
95    variant: String,
96}
97
98#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
99struct MetaPackageV0Serialize<'a> {
100    name: &'a str,
101    #[serde(rename = "version")]
102    variant: &'a str,
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use crate::test::*;
109    use lazy_static::lazy_static;
110    use proptest::prelude::*;
111    use regex::Regex;
112
113    #[test]
114    fn test_accessors() {
115        let meta_package = MetaPackage::from_name_and_variant_zero("foo".parse().unwrap());
116        assert_eq!(meta_package.name(), &"foo".parse::<PackageName>().unwrap());
117        assert_eq!(meta_package.variant(), &"0".parse::<PackageVariant>().unwrap());
118    }
119
120    #[test]
121    fn test_serialize() {
122        let meta_package = MetaPackage::from_name_and_variant_zero("package-name".parse().unwrap());
123        let mut v: Vec<u8> = Vec::new();
124
125        meta_package.serialize(&mut v).unwrap();
126
127        let expected = r#"{"name":"package-name","version":"0"}"#;
128        assert_eq!(v.as_slice(), expected.as_bytes());
129    }
130
131    #[test]
132    fn test_deserialize() {
133        let json_bytes = r#"{"name":"package-name","version":"0"}"#.as_bytes();
134        assert_eq!(
135            MetaPackage::deserialize(json_bytes).unwrap(),
136            MetaPackage { name: "package-name".parse().unwrap(), variant: "0".parse().unwrap() }
137        );
138    }
139
140    proptest! {
141        #![proptest_config(ProptestConfig{
142            failure_persistence: None,
143            ..Default::default()
144        })]
145
146        #[test]
147        fn test_serialize_deserialize_is_identity(
148            meta_package in random_meta_package(),
149        ) {
150            let mut v: Vec<u8> = Vec::new();
151            meta_package.serialize(&mut v).unwrap();
152            let meta_package_round_trip = MetaPackage::deserialize(v.as_slice()).unwrap();
153            assert_eq!(meta_package, meta_package_round_trip);
154        }
155
156        #[test]
157        fn test_serialized_contains_no_whitespace(
158            meta_package in random_meta_package(),
159        ) {
160            lazy_static! {
161                static ref RE: Regex = Regex::new(r"(\p{White_Space})").unwrap();
162            }
163            let mut v: Vec<u8> = Vec::new();
164            meta_package.serialize(&mut v).unwrap();
165            assert!(!RE.is_match(std::str::from_utf8(v.as_slice()).unwrap()));
166        }
167    }
168}