opsview/config/
tenancy.rs1use super::{Role, RoleRef};
2use crate::{prelude::*, util::*};
3use serde::{Deserialize, Serialize};
4use std::sync::Arc;
5
6#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
8pub struct Tenancy {
9 pub name: String,
12
13 pub primary_role: Option<RoleRef>,
16
17 #[serde(skip_serializing_if = "Option::is_none")]
20 pub description: Option<String>,
21
22 #[serde(
25 skip_serializing_if = "Option::is_none",
26 deserialize_with = "deserialize_string_or_number_to_u64",
27 default
28 )]
29 pub id: Option<u64>,
30
31 #[serde(
33 rename = "ref",
34 skip_serializing_if = "Option::is_none",
35 deserialize_with = "deserialize_readonly",
36 default
37 )]
38 pub ref_: Option<String>,
39
40 #[serde(
42 skip_serializing_if = "Option::is_none",
43 deserialize_with = "deserialize_string_or_number_to_u64",
44 default
45 )]
46 pub priority: Option<u64>,
47
48 #[serde(
50 skip_serializing_if = "Option::is_none",
51 deserialize_with = "deserialize_string_or_number_to_option_bool",
52 serialize_with = "serialize_option_bool_as_string",
53 default
54 )]
55 pub uncommitted: Option<bool>,
56}
57
58impl CreateFromJson for Tenancy {}
61
62impl ConfigObject for Tenancy {
63 type Builder = TenancyBuilder;
64
65 fn builder() -> Self::Builder {
70 TenancyBuilder::new()
71 }
72
73 fn config_path() -> Option<String> {
78 Some("/config/tenancy".to_string())
79 }
80
81 fn unique_name(&self) -> String {
89 self.name.clone()
90 }
91
92 fn minimal(name: &str) -> Result<Self, OpsviewConfigError> {
93 Ok(Self {
94 name: validate_and_trim_tenancy_name(name)?,
95 ..Default::default()
96 })
97 }
98}
99
100impl Persistent for Tenancy {
101 fn id(&self) -> Option<u64> {
103 self.id
104 }
105
106 fn ref_(&self) -> Option<String> {
108 if self.ref_.as_ref().is_some_and(|x| !x.is_empty()) {
109 self.ref_.clone()
110 } else {
111 None
112 }
113 }
114
115 fn name(&self) -> Option<String> {
117 if self.name.is_empty() {
118 None
119 } else {
120 Some(self.name.clone())
121 }
122 }
123
124 fn name_regex(&self) -> Option<String> {
125 Some(TENANCY_NAME_REGEX_STR.to_string())
126 }
127
128 fn validated_name(&self, name: &str) -> Result<String, OpsviewConfigError> {
129 validate_and_trim_tenancy_name(name)
130 }
131
132 fn set_name(&mut self, new_name: &str) -> Result<String, OpsviewConfigError> {
133 self.name = self.validated_name(new_name)?;
134 Ok(self.name.clone())
135 }
136
137 fn clear_readonly(&mut self) {
138 self.id = None;
139 self.priority = None;
140 self.ref_ = None;
141 self.uncommitted = None;
142 }
143}
144
145impl PersistentMap for ConfigObjectMap<Tenancy> {
146 fn config_path() -> Option<String> {
147 Some("/config/tenancy".to_string())
148 }
149}
150
151#[derive(Clone, Debug, Default)]
169pub struct TenancyBuilder {
170 name: Option<String>,
172 primary_role: Option<RoleRef>,
173 description: Option<String>,
175}
176
177impl Builder for TenancyBuilder {
178 type ConfigObject = Tenancy;
179
180 fn new() -> Self {
185 TenancyBuilder::default()
186 }
187
188 fn name(mut self, name: &str) -> Self {
193 self.name = Some(name.to_string());
194 self
195 }
196
197 fn build(self) -> Result<Self::ConfigObject, OpsviewConfigError> {
206 let name = require_field(&self.name, "name")?;
207 let primary_role = require_field(&self.primary_role, "primary_role")?;
208 let validated_description =
209 validate_opt_string(self.description, validate_and_trim_description)?;
210
211 Ok(Tenancy {
212 name: validate_and_trim_tenancy_name(&name)?,
213 primary_role: Some(primary_role),
214 description: validated_description,
215 id: None,
216 priority: None,
217 ref_: None,
218 uncommitted: None,
219 })
220 }
221}
222
223impl TenancyBuilder {
224 pub fn clear_name(mut self) -> Self {
226 self.name = None;
227 self
228 }
229
230 pub fn clear_description(mut self) -> Self {
232 self.description = None;
233 self
234 }
235
236 pub fn clear_primary_role(mut self) -> Self {
238 self.primary_role = None;
239 self
240 }
241
242 pub fn description(mut self, description: &str) -> Self {
247 self.description = Some(description.to_string());
248 self
249 }
250
251 pub fn primary_role(mut self, primary_role: Role) -> Self {
256 self.primary_role = Some(RoleRef::from(primary_role));
257 self
258 }
259}
260
261#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
264pub struct TenancyRef {
265 name: String,
266 #[serde(
267 rename = "ref",
268 skip_serializing_if = "Option::is_none",
269 deserialize_with = "deserialize_readonly",
270 default
271 )]
272 ref_: Option<String>,
273}
274
275impl CreateFromJson for TenancyRef {}
278
279impl ConfigRef for TenancyRef {
280 type FullObject = Tenancy;
281
282 fn ref_(&self) -> Option<String> {
284 self.ref_.clone()
285 }
286
287 fn name(&self) -> String {
289 self.name.clone()
290 }
291
292 fn unique_name(&self) -> String {
297 self.name.clone()
298 }
299}
300
301impl From<Tenancy> for TenancyRef {
302 fn from(tenancy: Tenancy) -> Self {
310 Self {
311 name: tenancy.name.clone(),
312 ref_: tenancy.ref_.clone(),
313 }
314 }
315}
316
317impl From<Arc<Tenancy>> for TenancyRef {
318 fn from(item: Arc<Tenancy>) -> Self {
319 let cmd: Tenancy = Arc::try_unwrap(item).unwrap_or_else(|arc| (*arc).clone());
320 TenancyRef::from(cmd)
321 }
322}
323
324impl From<&ConfigObjectMap<Tenancy>> for ConfigRefMap<TenancyRef> {
325 fn from(tenancies: &ConfigObjectMap<Tenancy>) -> Self {
326 ref_map_from(tenancies)
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333
334 #[test]
335 fn test_tenancy_default() {
336 let tenancy = Tenancy::default();
337
338 assert_eq!(tenancy.name, "".to_string());
339 assert_eq!(tenancy.description, None);
340 assert_eq!(tenancy.id, None);
341 assert_eq!(tenancy.priority, None);
342 assert_eq!(tenancy.ref_, None);
343 assert_eq!(tenancy.primary_role, None);
344 }
345
346 #[test]
347 fn test_tenancy_minimal() {
348 let tenancy = Tenancy::minimal("My Tenancy");
349
350 assert_eq!(tenancy.unwrap().name, "My Tenancy".to_string());
351 }
352
353 #[test]
354 fn test_tenancy_unique_name() {
355 let tenancy = Tenancy::builder()
356 .name("My Tenancy")
357 .primary_role(Role::default())
358 .build()
359 .unwrap();
360
361 assert_eq!(tenancy.unique_name(), "My Tenancy".to_string());
362 }
363
364 #[test]
365 fn test_tenancy_builder() {
366 let tenancy = Tenancy::builder()
367 .name("My Tenancy")
368 .description("My Tenancy description")
369 .primary_role(Role::minimal("My Role").unwrap())
370 .build()
371 .unwrap();
372
373 assert_eq!(tenancy.name, "My Tenancy".to_string());
374 assert_eq!(
375 tenancy.description.unwrap(),
376 "My Tenancy description".to_string()
377 );
378 assert_eq!(tenancy.id, None);
379 assert_eq!(tenancy.priority, None);
380 assert_eq!(tenancy.ref_, None);
381 assert_eq!(tenancy.primary_role.unwrap().name(), "My Role".to_string());
382 }
383
384 #[test]
385 fn test_is_valid_tenancy_name() {
386 assert!(validate_and_trim_tenancy_name("ValidName123").is_ok());
388 assert!(validate_and_trim_tenancy_name("Valid_Name-With.Symbols!").is_ok());
389 assert!(validate_and_trim_tenancy_name("A").is_ok());
390 assert!(validate_and_trim_tenancy_name("1").is_ok());
391 assert!(validate_and_trim_tenancy_name("A name with spaces and symbols *&^%$#@!").is_ok());
392 assert!(validate_and_trim_tenancy_name(&"a".repeat(191)).is_ok()); assert!(validate_and_trim_tenancy_name("").is_err()); assert!(validate_and_trim_tenancy_name(" ").is_err()); assert!(validate_and_trim_tenancy_name(&"a".repeat(192)).is_err()); assert!(validate_and_trim_tenancy_name("Invalid\nName").is_err()); assert!(validate_and_trim_tenancy_name("Invalid\tName").is_err()); assert!(validate_and_trim_tenancy_name("Invalid\rName").is_err()); }
402}