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
10
    pub fn for_item(label: &str, attributes: &impl AsAttributes) -> Self {
25
        Self {
26
10
            label: label.to_owned(),
27
21
            attributes: Some(attributes.as_attributes()),
28
        }
29
    }
30

            
31
4
    pub fn for_collection(label: &str) -> Self {
32
        Self {
33
4
            label: label.to_owned(),
34
            attributes: None,
35
        }
36
    }
37

            
38
4
    pub fn label(&self) -> &str {
39
4
        &self.label
40
    }
41

            
42
4
    pub fn attributes(&self) -> Option<&HashMap<String, String>> {
43
4
        self.attributes.as_ref()
44
    }
45
}
46

            
47
impl Serialize for Properties {
48
7
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
49
    where
50
        S: serde::Serializer,
51
    {
52
20
        if self.attributes.is_none() {
53
12
            let mut map = serializer.serialize_map(Some(1))?;
54
12
            map.serialize_entry(COLLECTION_PROPERTY_LABEL, &Value::from(&self.label))?;
55
6
            map.end()
56
        } else {
57
14
            let mut map = serializer.serialize_map(Some(2))?;
58
14
            map.serialize_entry(ITEM_PROPERTY_LABEL, &Value::from(&self.label))?;
59
7
            let mut dict = zbus::zvariant::Dict::new(String::SIGNATURE, String::SIGNATURE);
60

            
61
7
            if let Some(attributes) = &self.attributes {
62
14
                for (key, value) in attributes {
63
7
                    dict.add(key, value).expect("Key/Value of correct types");
64
                }
65
            }
66

            
67
12
            map.serialize_entry(ITEM_PROPERTY_ATTRIBUTES, &Value::from(dict))?;
68
6
            map.end()
69
        }
70
    }
71
}
72

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

            
97
#[cfg(test)]
98
mod tests {
99
    use zbus::zvariant::{Endian, Type, serialized::Context, to_bytes};
100

            
101
    use super::*;
102

            
103
    #[test]
104
    fn serialize_label() {
105
        let properties = Properties::for_collection("some_label");
106

            
107
        assert!(properties.attributes().is_none());
108
        assert_eq!(properties.label(), "some_label");
109

            
110
        let ctxt = Context::new_dbus(Endian::Little, 0);
111
        let encoded = to_bytes(ctxt, &properties).unwrap();
112
        let decoded: HashMap<&str, Value<'_>> = encoded.deserialize().unwrap().0;
113

            
114
        assert_eq!(
115
            decoded[COLLECTION_PROPERTY_LABEL],
116
            Value::from("some_label")
117
        );
118
        assert!(!decoded.contains_key(ITEM_PROPERTY_ATTRIBUTES));
119
        assert!(!decoded.contains_key(ITEM_PROPERTY_LABEL));
120
    }
121

            
122
    #[test]
123
    fn serialize_label_with_attributes() {
124
        let mut attributes = HashMap::new();
125
        attributes.insert("some", "attribute");
126
        let properties = Properties::for_item("some_label", &attributes);
127

            
128
        assert!(properties.attributes().is_some());
129
        assert_eq!(properties.label(), "some_label");
130

            
131
        let ctxt = Context::new_dbus(Endian::Little, 0);
132
        let encoded = to_bytes(ctxt, &properties).unwrap();
133
        let decoded: HashMap<&str, Value<'_>> = encoded.deserialize().unwrap().0;
134

            
135
        assert_eq!(decoded[ITEM_PROPERTY_LABEL], Value::from("some_label"));
136
        assert!(!decoded.contains_key(COLLECTION_PROPERTY_LABEL));
137
        assert!(decoded.contains_key(ITEM_PROPERTY_ATTRIBUTES));
138
        assert_eq!(
139
            decoded[ITEM_PROPERTY_ATTRIBUTES],
140
            zvariant::Dict::from(attributes).into()
141
        );
142
    }
143

            
144
    #[test]
145
    fn signature() {
146
        assert_eq!(Properties::SIGNATURE, "a{sv}");
147
    }
148

            
149
    #[test]
150
    fn deserialize_collection_properties() {
151
        // Create serialized data that represents collection properties
152
        let mut map = HashMap::new();
153
        map.insert(COLLECTION_PROPERTY_LABEL, Value::from("test_collection"));
154

            
155
        let ctxt = Context::new_dbus(Endian::Little, 0);
156
        let encoded = to_bytes(ctxt, &map).unwrap();
157

            
158
        // Deserialize through the Properties Deserialize trait
159
        let properties: Properties = encoded.deserialize().unwrap().0;
160

            
161
        assert_eq!(properties.label(), "test_collection");
162
        assert!(properties.attributes().is_none());
163
    }
164

            
165
    #[test]
166
    fn deserialize_item_properties() {
167
        use zvariant::Dict;
168

            
169
        // Create serialized data that represents item properties
170
        let mut attrs_dict = Dict::new(String::SIGNATURE, String::SIGNATURE);
171
        attrs_dict.add("key1", "value1").unwrap();
172
        attrs_dict.add("key2", "value2").unwrap();
173

            
174
        let mut map = HashMap::new();
175
        map.insert(ITEM_PROPERTY_LABEL, Value::from("test_item"));
176
        map.insert(ITEM_PROPERTY_ATTRIBUTES, Value::from(attrs_dict));
177

            
178
        let ctxt = Context::new_dbus(Endian::Little, 0);
179
        let encoded = to_bytes(ctxt, &map).unwrap();
180

            
181
        // Deserialize through the Properties Deserialize trait
182
        let properties: Properties = encoded.deserialize().unwrap().0;
183

            
184
        assert_eq!(properties.label(), "test_item");
185
        let attributes = properties.attributes().unwrap();
186
        assert_eq!(attributes.get("key1"), Some(&"value1".to_string()));
187
        assert_eq!(attributes.get("key2"), Some(&"value2".to_string()));
188
        assert_eq!(attributes.len(), 2);
189
    }
190
}