1 use std::{collections::HashMap, convert::TryFrom, hash::BuildHasher};
2
3 use serde::ser::{Serialize, SerializeSeq, SerializeStruct, Serializer};
4 use static_assertions::assert_impl_all;
5
6 use crate::{Basic, DynamicType, Error, Signature, Type, Value};
7
8 /// A helper type to wrap dictionaries in a [`Value`].
9 ///
10 /// API is provided to convert from, and to a [`HashMap`].
11 ///
12 /// [`Value`]: enum.Value.html#variant.Dict
13 /// [`HashMap`]: https://doc.rust-lang.org/std/collections/struct.HashMap.html
14 #[derive(Debug, Clone, PartialEq)]
15 pub struct Dict<'k, 'v> {
16 entries: Vec<DictEntry<'k, 'v>>,
17 key_signature: Signature<'k>,
18 value_signature: Signature<'v>,
19 // should use a separate lifetime or everything should use the same but API break.
20 signature: Signature<'k>,
21 }
22
23 assert_impl_all!(Dict<'_, '_>: Send, Sync, Unpin);
24
25 impl<'k, 'v> Dict<'k, 'v> {
26 /// Create a new empty `Dict`, given the signature of the keys and values.
new(key_signature: Signature<'k>, value_signature: Signature<'v>) -> Self27 pub fn new(key_signature: Signature<'k>, value_signature: Signature<'v>) -> Self {
28 let signature = create_signature(&key_signature, &value_signature);
29
30 Self {
31 entries: vec![],
32 key_signature,
33 value_signature,
34 signature,
35 }
36 }
37
38 /// Append `key` and `value` as a new entry.
39 ///
40 /// # Errors
41 ///
42 /// * if [`key.value_signature()`] doesn't match the key signature `self` was created for.
43 /// * if [`value.value_signature()`] doesn't match the value signature `self` was created for.
44 ///
45 /// [`key.value_signature()`]: enum.Value.html#method.value_signature
46 /// [`value.value_signature()`]: enum.Value.html#method.value_signature
append<'kv: 'k, 'vv: 'v>( &mut self, key: Value<'kv>, value: Value<'vv>, ) -> Result<(), Error>47 pub fn append<'kv: 'k, 'vv: 'v>(
48 &mut self,
49 key: Value<'kv>,
50 value: Value<'vv>,
51 ) -> Result<(), Error> {
52 check_child_value_signature!(self.key_signature, key.value_signature(), "key");
53 check_child_value_signature!(self.value_signature, value.value_signature(), "value");
54
55 self.entries.push(DictEntry { key, value });
56
57 Ok(())
58 }
59
60 /// Add a new entry.
add<K, V>(&mut self, key: K, value: V) -> Result<(), Error> where K: Basic + Into<Value<'k>> + std::hash::Hash + std::cmp::Eq, V: Into<Value<'v>> + DynamicType,61 pub fn add<K, V>(&mut self, key: K, value: V) -> Result<(), Error>
62 where
63 K: Basic + Into<Value<'k>> + std::hash::Hash + std::cmp::Eq,
64 V: Into<Value<'v>> + DynamicType,
65 {
66 check_child_value_signature!(self.key_signature, K::signature(), "key");
67 check_child_value_signature!(self.value_signature, value.dynamic_signature(), "value");
68
69 self.entries.push(DictEntry {
70 key: Value::new(key),
71 value: Value::new(value),
72 });
73
74 Ok(())
75 }
76
77 /// Get the value for the given key.
get<'d, K, V>(&'d self, key: &K) -> Result<Option<&'v V>, Error> where 'd: 'k + 'v, K: ?Sized + std::cmp::Eq + 'k, V: ?Sized, &'k K: TryFrom<&'k Value<'k>>, &'v V: TryFrom<&'v Value<'v>>,78 pub fn get<'d, K, V>(&'d self, key: &K) -> Result<Option<&'v V>, Error>
79 where
80 'd: 'k + 'v,
81 K: ?Sized + std::cmp::Eq + 'k,
82 V: ?Sized,
83 &'k K: TryFrom<&'k Value<'k>>,
84 &'v V: TryFrom<&'v Value<'v>>,
85 {
86 for entry in &self.entries {
87 let entry_key = entry.key.downcast_ref::<K>().ok_or(Error::IncorrectType)?;
88 if *entry_key == *key {
89 return entry
90 .value
91 .downcast_ref()
92 .ok_or(Error::IncorrectType)
93 .map(Some);
94 }
95 }
96
97 Ok(None)
98 }
99
100 /// Get the signature of this `Dict`.
101 ///
102 /// NB: This method potentially allocates and copies. Use [`full_signature`] if you'd like to
103 /// avoid that.
104 ///
105 /// [`full_signature`]: #method.full_signature
signature(&self) -> Signature<'static>106 pub fn signature(&self) -> Signature<'static> {
107 self.signature.to_owned()
108 }
109
110 /// Get the signature of this `Dict`.
full_signature(&self) -> &Signature<'_>111 pub fn full_signature(&self) -> &Signature<'_> {
112 &self.signature
113 }
114
to_owned(&self) -> Dict<'static, 'static>115 pub(crate) fn to_owned(&self) -> Dict<'static, 'static> {
116 Dict {
117 key_signature: self.key_signature.to_owned(),
118 value_signature: self.value_signature.to_owned(),
119 signature: self.signature.to_owned(),
120 entries: self.entries.iter().map(|v| v.to_owned()).collect(),
121 }
122 }
123
124 /// Create a new empty `Dict`, given the complete signature.
new_full_signature<'s: 'k + 'v>(signature: Signature<'s>) -> Self125 pub(crate) fn new_full_signature<'s: 'k + 'v>(signature: Signature<'s>) -> Self {
126 let key_signature = signature.slice(2..3);
127 let value_signature = signature.slice(3..signature.len() - 1);
128
129 Self {
130 entries: vec![],
131 key_signature,
132 value_signature,
133 signature,
134 }
135 }
136
137 // TODO: Provide more API like https://docs.rs/toml/0.5.5/toml/map/struct.Map.html
138 }
139
140 impl<'k, 'v> Serialize for Dict<'k, 'v> {
serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer,141 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
142 where
143 S: Serializer,
144 {
145 let mut seq = serializer.serialize_seq(Some(self.entries.len()))?;
146 for entry in &self.entries {
147 seq.serialize_element(entry)?;
148 }
149
150 seq.end()
151 }
152 }
153
154 // Conversion of Dict to HashMap
155 impl<'k, 'v, K, V, H> TryFrom<Dict<'k, 'v>> for HashMap<K, V, H>
156 where
157 K: Basic + TryFrom<Value<'k>> + std::hash::Hash + std::cmp::Eq,
158 V: TryFrom<Value<'v>>,
159 H: BuildHasher + Default,
160 K::Error: Into<crate::Error>,
161 V::Error: Into<crate::Error>,
162 {
163 type Error = Error;
164
try_from(v: Dict<'k, 'v>) -> Result<Self, Self::Error>165 fn try_from(v: Dict<'k, 'v>) -> Result<Self, Self::Error> {
166 let mut map = HashMap::default();
167 for e in v.entries.into_iter() {
168 let key = if let Value::Value(v) = e.key {
169 K::try_from(*v)
170 } else {
171 K::try_from(e.key)
172 }
173 .map_err(Into::into)?;
174
175 let value = if let Value::Value(v) = e.value {
176 V::try_from(*v)
177 } else {
178 V::try_from(e.value)
179 }
180 .map_err(Into::into)?;
181
182 map.insert(key, value);
183 }
184 Ok(map)
185 }
186 }
187
188 // TODO: this could be useful
189 // impl<'d, 'k, 'v, K, V, H> TryFrom<&'d Dict<'k, 'v>> for HashMap<&'k K, &'v V, H>
190
191 // Conversion of Hashmap to Dict
192 impl<'k, 'v, K, V, H> From<HashMap<K, V, H>> for Dict<'k, 'v>
193 where
194 K: Type + Into<Value<'k>> + std::hash::Hash + std::cmp::Eq,
195 V: Type + Into<Value<'v>>,
196 H: BuildHasher + Default,
197 {
from(value: HashMap<K, V, H>) -> Self198 fn from(value: HashMap<K, V, H>) -> Self {
199 let entries = value
200 .into_iter()
201 .map(|(key, value)| DictEntry {
202 key: Value::new(key),
203 value: Value::new(value),
204 })
205 .collect();
206 let key_signature = K::signature();
207 let value_signature = V::signature();
208 let signature = create_signature(&key_signature, &value_signature);
209
210 Self {
211 entries,
212 key_signature,
213 value_signature,
214 signature,
215 }
216 }
217 }
218
219 // TODO: Conversion of Dict from/to BTreeMap
220
221 #[derive(Debug, Clone, PartialEq)]
222 struct DictEntry<'k, 'v> {
223 key: Value<'k>,
224 value: Value<'v>,
225 }
226
227 impl<'k, 'v> DictEntry<'k, 'v> {
to_owned(&self) -> DictEntry<'static, 'static>228 fn to_owned(&self) -> DictEntry<'static, 'static> {
229 DictEntry {
230 key: self.key.to_owned(),
231 value: self.value.to_owned(),
232 }
233 }
234 }
235
236 impl<'k, 'v> Serialize for DictEntry<'k, 'v> {
serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer,237 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
238 where
239 S: Serializer,
240 {
241 let mut entry = serializer.serialize_struct("zvariant::DictEntry", 2)?;
242 self.key
243 .serialize_value_as_struct_field("zvariant::DictEntry::Key", &mut entry)?;
244 self.value
245 .serialize_value_as_struct_field("zvariant::DictEntry::Value", &mut entry)?;
246
247 entry.end()
248 }
249 }
250
create_signature( key_signature: &Signature<'_>, value_signature: &Signature<'_>, ) -> Signature<'static>251 fn create_signature(
252 key_signature: &Signature<'_>,
253 value_signature: &Signature<'_>,
254 ) -> Signature<'static> {
255 Signature::from_string_unchecked(format!("a{{{}{}}}", key_signature, value_signature,))
256 }
257