1
use std::collections::HashMap;
2

            
3
use serde::{
4
    Deserialize,
5
    ser::{Serialize, SerializeMap},
6
};
7
use zbus::zvariant::{Type, Value};
8

            
9
use crate::AsAttributes;
10

            
11
const ITEM_PROPERTY_LABEL: &str = "org.freedesktop.Secret.Item.Label";
12
const ITEM_PROPERTY_ATTRIBUTES: &str = "org.freedesktop.Secret.Item.Attributes";
13

            
14
const COLLECTION_PROPERTY_LABEL: &str = "org.freedesktop.Secret.Collection.Label";
15

            
16
#[derive(Debug, Type)]
17
#[zvariant(signature = "a{sv}")]
18
pub struct Properties {
19
    label: String,
20
    attributes: Option<HashMap<String, String>>,
21
}
22

            
23
impl Properties {
24
17
    pub fn for_item(label: &str, attributes: &impl AsAttributes) -> Self {
25
        Self {
26
18
            label: label.to_owned(),
27
17
            attributes: Some(
28
                attributes
29
                    .as_attributes()
30
                    .iter()
31
                    .map(|(k, v)| (k.to_string(), v.to_string()))
32
                    .collect(),
33
            ),
34
        }
35
    }
36

            
37
4
    pub fn for_collection(label: &str) -> Self {
38
        Self {
39
4
            label: label.to_owned(),
40
            attributes: None,
41
        }
42
    }
43

            
44
4
    pub fn label(&self) -> &str {
45
4
        &self.label
46
    }
47

            
48
4
    pub fn attributes(&self) -> Option<&HashMap<String, String>> {
49
4
        self.attributes.as_ref()
50
    }
51
}
52

            
53
impl Serialize for Properties {
54
10
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
55
    where
56
        S: serde::Serializer,
57
    {
58
26
        if self.attributes.is_none() {
59
12
            let mut map = serializer.serialize_map(Some(1))?;
60
12
            map.serialize_entry(COLLECTION_PROPERTY_LABEL, &Value::from(&self.label))?;
61
6
            map.end()
62
        } else {
63
22
            let mut map = serializer.serialize_map(Some(2))?;
64
22
            map.serialize_entry(ITEM_PROPERTY_LABEL, &Value::from(&self.label))?;
65
11
            let mut dict = zbus::zvariant::Dict::new(String::SIGNATURE, String::SIGNATURE);
66

            
67
11
            if let Some(attributes) = &self.attributes {
68
22
                for (key, value) in attributes {
69
11
                    dict.add(key, value).expect("Key/Value of correct types");
70
                }
71
            }
72

            
73
22
            map.serialize_entry(ITEM_PROPERTY_ATTRIBUTES, &Value::from(dict))?;
74
11
            map.end()
75
        }
76
    }
77
}
78

            
79
impl<'de> Deserialize<'de> for Properties {
80
5
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
81
    where
82
        D: serde::Deserializer<'de>,
83
    {
84
5
        let map: HashMap<&str, Value<'_>> = HashMap::deserialize(deserializer)?;
85
10
        if map.contains_key(COLLECTION_PROPERTY_LABEL) {
86
8
            let label =
87
                zvariant::Str::try_from(map.get(COLLECTION_PROPERTY_LABEL).unwrap()).unwrap();
88
8
            Ok(Self::for_collection(&label))
89
        } else {
90
10
            let label = zvariant::Str::try_from(map.get(ITEM_PROPERTY_LABEL).unwrap()).unwrap();
91
            let attributes = HashMap::<String, String>::try_from(
92
5
                map.get(ITEM_PROPERTY_ATTRIBUTES)
93
5
                    .unwrap()
94
5
                    .try_clone()
95
5
                    .unwrap(),
96
            )
97
            .unwrap();
98
10
            Ok(Self::for_item(&label, &attributes))
99
        }
100
    }
101
}
102

            
103
#[cfg(test)]
104
mod tests {
105
    use zbus::zvariant::{Endian, Type, serialized::Context, to_bytes};
106

            
107
    use super::*;
108

            
109
    #[test]
110
    fn serialize_label() {
111
        let properties = Properties::for_collection("some_label");
112

            
113
        assert!(properties.attributes().is_none());
114
        assert_eq!(properties.label(), "some_label");
115

            
116
        let ctxt = Context::new_dbus(Endian::Little, 0);
117
        let encoded = to_bytes(ctxt, &properties).unwrap();
118
        let decoded: HashMap<&str, Value<'_>> = encoded.deserialize().unwrap().0;
119

            
120
        assert_eq!(
121
            decoded[COLLECTION_PROPERTY_LABEL],
122
            Value::from("some_label")
123
        );
124
        assert!(!decoded.contains_key(ITEM_PROPERTY_ATTRIBUTES));
125
        assert!(!decoded.contains_key(ITEM_PROPERTY_LABEL));
126
    }
127

            
128
    #[test]
129
    fn serialize_label_with_attributes() {
130
        let mut attributes = HashMap::new();
131
        attributes.insert("some", "attribute");
132
        let properties = Properties::for_item("some_label", &attributes);
133

            
134
        assert!(properties.attributes().is_some());
135
        assert_eq!(properties.label(), "some_label");
136

            
137
        let ctxt = Context::new_dbus(Endian::Little, 0);
138
        let encoded = to_bytes(ctxt, &properties).unwrap();
139
        let decoded: HashMap<&str, Value<'_>> = encoded.deserialize().unwrap().0;
140

            
141
        assert_eq!(decoded[ITEM_PROPERTY_LABEL], Value::from("some_label"));
142
        assert!(!decoded.contains_key(COLLECTION_PROPERTY_LABEL));
143
        assert!(decoded.contains_key(ITEM_PROPERTY_ATTRIBUTES));
144
        assert_eq!(
145
            decoded[ITEM_PROPERTY_ATTRIBUTES],
146
            zvariant::Dict::from(attributes).into()
147
        );
148
    }
149

            
150
    #[test]
151
    fn signature() {
152
        assert_eq!(Properties::SIGNATURE, "a{sv}");
153
    }
154

            
155
    #[test]
156
    fn deserialize_collection_properties() {
157
        // Create serialized data that represents collection properties
158
        let mut map = HashMap::new();
159
        map.insert(COLLECTION_PROPERTY_LABEL, Value::from("test_collection"));
160

            
161
        let ctxt = Context::new_dbus(Endian::Little, 0);
162
        let encoded = to_bytes(ctxt, &map).unwrap();
163

            
164
        // Deserialize through the Properties Deserialize trait
165
        let properties: Properties = encoded.deserialize().unwrap().0;
166

            
167
        assert_eq!(properties.label(), "test_collection");
168
        assert!(properties.attributes().is_none());
169
    }
170

            
171
    #[test]
172
    fn deserialize_item_properties() {
173
        use zvariant::Dict;
174

            
175
        // Create serialized data that represents item properties
176
        let mut attrs_dict = Dict::new(String::SIGNATURE, String::SIGNATURE);
177
        attrs_dict.add("key1", "value1").unwrap();
178
        attrs_dict.add("key2", "value2").unwrap();
179

            
180
        let mut map = HashMap::new();
181
        map.insert(ITEM_PROPERTY_LABEL, Value::from("test_item"));
182
        map.insert(ITEM_PROPERTY_ATTRIBUTES, Value::from(attrs_dict));
183

            
184
        let ctxt = Context::new_dbus(Endian::Little, 0);
185
        let encoded = to_bytes(ctxt, &map).unwrap();
186

            
187
        // Deserialize through the Properties Deserialize trait
188
        let properties: Properties = encoded.deserialize().unwrap().0;
189

            
190
        assert_eq!(properties.label(), "test_item");
191
        let attributes = properties.attributes().unwrap();
192
        assert_eq!(attributes.get("key1"), Some(&"value1".to_string()));
193
        assert_eq!(attributes.get("key2"), Some(&"value2".to_string()));
194
        assert_eq!(attributes.len(), 2);
195
    }
196
}