bitwarden_managed_settings_types/
profile.rs1use std::collections::HashMap;
2
3use bitwarden_error::bitwarden_error;
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6#[cfg(feature = "wasm")]
7use tsify::Tsify;
8
9#[bitwarden_error(flat)]
11#[derive(Debug, Error)]
12pub enum ManagedSettingsError {
13 #[error("Failed to decode managed settings value: {0}")]
15 Decode(String),
16}
17
18#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
27#[serde(rename_all = "camelCase")]
28#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
29#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
30pub struct ManagementProfile {
31 pub version: u32,
33 pub updated_at: i64,
39 pub settings: HashMap<String, String>,
41}
42
43impl ManagementProfile {
44 pub fn empty() -> Self {
46 Self {
47 version: 1,
48 updated_at: 0,
49 settings: HashMap::new(),
50 }
51 }
52
53 pub fn is_managed(&self, key: &str) -> bool {
55 self.settings.contains_key(key)
56 }
57
58 pub fn get(&self, key: &str) -> Option<String> {
60 self.settings.get(key).cloned()
61 }
62
63 pub fn get_as<T: serde::de::DeserializeOwned>(
65 &self,
66 key: &str,
67 ) -> Result<Option<T>, ManagedSettingsError> {
68 match self.settings.get(key) {
69 None => Ok(None),
70 Some(raw) => serde_json::from_str(raw)
71 .map(Some)
72 .map_err(|e| ManagedSettingsError::Decode(e.to_string())),
73 }
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 fn profile_with(entries: &[(&str, &str)]) -> ManagementProfile {
82 ManagementProfile {
83 version: 1,
84 updated_at: 1_750_000_000,
85 settings: entries
86 .iter()
87 .map(|(k, v)| (k.to_string(), v.to_string()))
88 .collect(),
89 }
90 }
91
92 #[test]
93 fn is_managed_reflects_key_presence() {
94 let profile = profile_with(&[("environment.base", "\"https://vault.example.com\"")]);
95
96 assert!(profile.is_managed("environment.base"));
97 assert!(!profile.is_managed("environment.api"));
98 }
99
100 #[test]
101 fn get_returns_the_raw_json_encoded_value() {
102 let profile = profile_with(&[("environment.base", "\"https://vault.example.com\"")]);
103
104 assert_eq!(
105 profile.get("environment.base"),
106 Some("\"https://vault.example.com\"".to_string())
107 );
108 }
109
110 #[test]
111 fn get_returns_none_for_an_absent_key() {
112 let profile = profile_with(&[("environment.base", "\"https://vault.example.com\"")]);
113
114 assert_eq!(profile.get("environment.api"), None);
115 }
116
117 #[test]
118 fn get_as_decodes_a_present_value() {
119 let profile = profile_with(&[("generator.password.length", "14")]);
120
121 assert_eq!(
122 profile.get_as::<u32>("generator.password.length").unwrap(),
123 Some(14)
124 );
125 }
126
127 #[test]
128 fn get_as_returns_none_for_an_absent_key() {
129 let profile = profile_with(&[("generator.password.length", "14")]);
130
131 assert_eq!(
132 profile
133 .get_as::<u32>("generator.password.uppercase")
134 .unwrap(),
135 None
136 );
137 }
138
139 #[test]
140 fn get_as_errors_when_the_value_does_not_match_the_requested_type() {
141 let profile = profile_with(&[("generator.password.length", "\"fourteen\"")]);
142
143 let error = profile
144 .get_as::<u32>("generator.password.length")
145 .expect_err("decoding a string as u32 should fail");
146
147 assert!(matches!(error, ManagedSettingsError::Decode(_)));
148 }
149
150 #[test]
151 fn empty_profile_manages_nothing() {
152 let profile = ManagementProfile::empty();
153
154 assert!(!profile.is_managed("environment.base"));
155 assert_eq!(profile.get("environment.base"), None);
156 assert_eq!(profile.get_as::<u32>("environment.base").unwrap(), None);
157 }
158}