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 proptest::prelude::*;
109 use serde_json::json;
110
111 fn zeros_hash() -> Hash {
112 "0000000000000000000000000000000000000000000000000000000000000000".parse().unwrap()
113 }
114
115 fn ones_hash() -> Hash {
116 "1111111111111111111111111111111111111111111111111111111111111111".parse().unwrap()
117 }
118
119 #[test]
120 fn deserialize_known_file() {
121 let bytes = r#"{
122 "version": "1",
123 "subpackages": {
124 "a_0_subpackage": "0000000000000000000000000000000000000000000000000000000000000000",
125 "other-1-subpackage": "1111111111111111111111111111111111111111111111111111111111111111"
126 }
127 }"#.as_bytes();
128 let meta_subpackages = MetaSubpackages::deserialize(bytes).unwrap();
129 let expected_subpackages = HashMap::from([
130 (RelativePackageUrl::parse("a_0_subpackage").unwrap(), zeros_hash()),
131 (RelativePackageUrl::parse("other-1-subpackage").unwrap(), ones_hash()),
132 ]);
133 assert_eq!(meta_subpackages.subpackages(), &expected_subpackages);
134 assert_eq!(meta_subpackages.into_subpackages(), expected_subpackages);
135 }
136
137 proptest! {
138 #![proptest_config(ProptestConfig{
139 failure_persistence: None,
140 ..Default::default()
141 })]
142
143 #[test]
144 fn serialize(
145 ref path0 in random_relative_package_url(),
146 ref hex0 in random_hash(),
147 ref path1 in random_relative_package_url(),
148 ref hex1 in random_hash())
149 {
150 prop_assume!(path0 != path1);
151 let map = HashMap::from([
152 (path0.clone(), *hex0),
153 (path1.clone(), *hex1),
154 ]);
155 let meta_subpackages = MetaSubpackages::from_iter(map);
156
157 prop_assert_eq!(
158 serde_json::to_value(meta_subpackages).unwrap(),
159 json!(
160 {
161 "version": "1",
162 "subpackages": {
163 path0: hex0,
164 path1: hex1,
165 }
166 }
167 )
168 );
169 }
170
171 #[test]
172 fn serialize_deserialize_is_id(
173 subpackages in prop::collection::hash_map(
174 random_relative_package_url(), random_hash(), 0..4)
175 ) {
176 let meta_subpackages = MetaSubpackages::from_iter(subpackages);
177 let deserialized = MetaSubpackages::deserialize(
178 &*serde_json::to_vec(&meta_subpackages).unwrap()
179 )
180 .unwrap();
181 prop_assert_eq!(meta_subpackages, deserialized);
182 }
183 }
184}