fuchsia_pkg/
meta_subpackages.rs1use crate::errors::MetaSubpackagesError;
6use fuchsia_merkle::Hash;
7use fuchsia_url::RelativePackageUrl;
8use serde::ser::Serializer;
9use serde::{Deserialize, Serialize};
10use std::collections::{BTreeMap, HashMap};
11use std::io;
12
13#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
17#[serde(transparent)]
18pub struct MetaSubpackages(VersionedMetaSubpackages);
19
20impl MetaSubpackages {
21 pub const PATH: &'static str = "meta/fuchsia.pkg/subpackages";
22
23 fn from_v1(meta_subpackages_v1: MetaSubpackagesV1) -> Self {
24 Self(VersionedMetaSubpackages::Version1(meta_subpackages_v1))
25 }
26
27 pub fn subpackages(&self) -> &HashMap<RelativePackageUrl, Hash> {
29 match &self.0 {
30 VersionedMetaSubpackages::Version1(meta) => &meta.subpackages,
31 }
32 }
33
34 pub fn into_subpackages(self) -> HashMap<RelativePackageUrl, Hash> {
36 match self.0 {
37 VersionedMetaSubpackages::Version1(meta) => meta.subpackages,
38 }
39 }
40
41 pub fn into_hashes_undeduplicated(self) -> impl Iterator<Item = Hash> {
44 self.into_subpackages().into_values()
45 }
46
47 pub fn deserialize(reader: impl io::BufRead) -> Result<Self, MetaSubpackagesError> {
48 Ok(MetaSubpackages::from_v1(serde_json::from_reader(reader)?))
49 }
50
51 pub fn serialize(&self, writer: impl io::Write) -> Result<(), MetaSubpackagesError> {
52 Ok(serde_json::to_writer(writer, &self)?)
53 }
54}
55
56impl FromIterator<(RelativePackageUrl, Hash)> for MetaSubpackages {
57 fn from_iter<T>(iter: T) -> Self
58 where
59 T: IntoIterator<Item = (RelativePackageUrl, Hash)>,
60 {
61 MetaSubpackages(VersionedMetaSubpackages::Version1(MetaSubpackagesV1 {
62 subpackages: HashMap::from_iter(iter),
63 }))
64 }
65}
66
67#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
68#[serde(tag = "version", deny_unknown_fields)]
69enum VersionedMetaSubpackages {
70 #[serde(rename = "1")]
71 Version1(MetaSubpackagesV1),
72}
73
74impl Default for VersionedMetaSubpackages {
75 fn default() -> Self {
76 VersionedMetaSubpackages::Version1(MetaSubpackagesV1::default())
77 }
78}
79
80#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
81struct MetaSubpackagesV1 {
82 subpackages: HashMap<RelativePackageUrl, Hash>,
83}
84
85impl Serialize for MetaSubpackagesV1 {
86 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
87 where
88 S: Serializer,
89 {
90 let MetaSubpackagesV1 { subpackages } = self;
91
92 #[derive(Serialize)]
95 struct Helper<'a> {
96 subpackages: BTreeMap<&'a RelativePackageUrl, &'a Hash>,
97 }
98
99 Helper { subpackages: subpackages.iter().collect() }.serialize(serializer)
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106 use crate::test::*;
107 use fuchsia_url::test::random_relative_package_url;
108 use maplit::hashmap;
109 use proptest::prelude::*;
110 use serde_json::json;
111
112 fn zeros_hash() -> Hash {
113 "0000000000000000000000000000000000000000000000000000000000000000".parse().unwrap()
114 }
115
116 fn ones_hash() -> Hash {
117 "1111111111111111111111111111111111111111111111111111111111111111".parse().unwrap()
118 }
119
120 #[test]
121 fn deserialize_known_file() {
122 let bytes = r#"{
123 "version": "1",
124 "subpackages": {
125 "a_0_subpackage": "0000000000000000000000000000000000000000000000000000000000000000",
126 "other-1-subpackage": "1111111111111111111111111111111111111111111111111111111111111111"
127 }
128 }"#.as_bytes();
129 let meta_subpackages = MetaSubpackages::deserialize(bytes).unwrap();
130 let expected_subpackages = hashmap! {
131 RelativePackageUrl::parse("a_0_subpackage").unwrap() => zeros_hash(),
132 RelativePackageUrl::parse("other-1-subpackage").unwrap() => ones_hash(),
133 };
134 assert_eq!(meta_subpackages.subpackages(), &expected_subpackages);
135 assert_eq!(meta_subpackages.into_subpackages(), expected_subpackages);
136 }
137
138 proptest! {
139 #![proptest_config(ProptestConfig{
140 failure_persistence: None,
141 ..Default::default()
142 })]
143
144 #[test]
145 fn serialize(
146 ref path0 in random_relative_package_url(),
147 ref hex0 in random_hash(),
148 ref path1 in random_relative_package_url(),
149 ref hex1 in random_hash())
150 {
151 prop_assume!(path0 != path1);
152 let map = hashmap! {
153 path0.clone() => *hex0,
154 path1.clone() => *hex1,
155 };
156 let meta_subpackages = MetaSubpackages::from_iter(map);
157
158 prop_assert_eq!(
159 serde_json::to_value(meta_subpackages).unwrap(),
160 json!(
161 {
162 "version": "1",
163 "subpackages": {
164 path0: hex0,
165 path1: hex1,
166 }
167 }
168 )
169 );
170 }
171
172 #[test]
173 fn serialize_deserialize_is_id(
174 subpackages in prop::collection::hash_map(
175 random_relative_package_url(), random_hash(), 0..4)
176 ) {
177 let meta_subpackages = MetaSubpackages::from_iter(subpackages);
178 let deserialized = MetaSubpackages::deserialize(
179 &*serde_json::to_vec(&meta_subpackages).unwrap()
180 )
181 .unwrap();
182 prop_assert_eq!(meta_subpackages, deserialized);
183 }
184 }
185}