1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
use crate::{prelude::*, util::*};
use serde::{Deserialize, Serialize};
/// Represents a [HostIcon](https://docs.itrsgroup.com/docs/opsview/6.8.9/administration/customization/index.html#Heading-managing-host-icons) in Opsview.
///
/// Host icons are used to differentiate between [`super::Host`]s of different kinds in Opsview. This struct
/// defines the structure for a host icon entity as used in Opsview.
///
/// # Example
/// ```rust
/// use opsview::config::HostIcon;
/// use opsview::prelude::*;
///
/// let host_icon = HostIcon::builder()
/// .name("LOGO - Some Brand")
/// .img_prefix("/images/logos/somebrand")
/// .build()
/// .unwrap();
///
/// assert_eq!(host_icon.name, "LOGO - Some Brand");
/// ```
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
pub struct HostIcon {
// Required fields ---------------------------------------------------------------------------//
/// The name of the `HostIcon`.
pub name: String,
// Semi-optional fields ----------------------------------------------------------------------//
/// The prefix for the `HostIcon` image.
///
/// # Example
/// * `/images/logos/somebrand` for host icons in the folder `/images/logos/somebrand`.
#[serde(skip_serializing_if = "Option::is_none")]
pub img_prefix: Option<String>,
// Read-only fields --------------------------------------------------------------------------//
/// A reference string unique to this `HostIcon`.
#[serde(
rename = "ref",
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_readonly",
default
)]
pub ref_: Option<String>,
}
/// Enables the creation of a [`HostIcon`] instance from a JSON representation.
/// Typically used when parsing JSON data from the Opsview API.
impl CreateFromJson for HostIcon {}
impl ConfigObject for HostIcon {
type Builder = HostIconBuilder;
/// Returns a builder for constructing a [`HostIcon`] object.
///
/// # Returns
/// A [`HostIconBuilder`] object.
fn builder() -> Self::Builder {
HostIconBuilder::new()
}
/// Provides the configuration path for a [`HostIcon`] object within the Opsview system.
///
/// # Returns
/// A string representing the API path where host icons are configured.
fn config_path() -> Option<String> {
Some("/config/hosticons".to_string())
}
/// Returns the unique name of the [`HostIcon`] object.
/// This name is used to identify the `HostIcon` when building the `HashMap` for an
/// [`ConfigObjectMap`].
fn unique_name(&self) -> String {
self.name.clone()
}
fn minimal(name: &str) -> Result<Self, OpsviewConfigError> {
Ok(Self {
name: validate_and_trim_hosticon_name(name)?,
..Default::default()
})
}
}
impl Persistent for HostIcon {
/// Returns the unique identifier. Since `HostIcon` objects lack this field, `None` is returned.
fn id(&self) -> Option<u64> {
None
}
/// Returns the reference string if it's not empty.
fn ref_(&self) -> Option<String> {
if self.ref_.as_ref().is_some_and(|x| !x.is_empty()) {
self.ref_.clone()
} else {
None
}
}
/// Returns the name if it's not empty.
fn name(&self) -> Option<String> {
if self.name.is_empty() {
None
} else {
Some(self.name.clone())
}
}
fn name_regex(&self) -> Option<String> {
Some(INLINE_FREE_TEXT_REGEX_STR.to_string())
}
fn validated_name(&self, name: &str) -> Result<String, OpsviewConfigError> {
validate_and_trim_hosticon_name(name)
}
fn set_name(&mut self, new_name: &str) -> Result<String, OpsviewConfigError> {
self.name = self.validated_name(new_name)?;
Ok(self.name.clone())
}
fn clear_readonly(&mut self) {
self.ref_ = None;
}
}
impl PersistentMap for ConfigObjectMap<HostIcon> {
fn config_path() -> Option<String> {
Some("/config/hosticons".to_string())
}
}
/// Builder for creating instances of [`HostIcon`].
///
/// Provides a fluent interface for constructing a `HostIcon` object with optional fields.
#[derive(Clone, Debug, Default)]
pub struct HostIconBuilder {
img_prefix: Option<String>,
name: Option<String>,
}
impl Builder for HostIconBuilder {
type ConfigObject = HostIcon;
/// Creates a new instance of [`HostIconBuilder`] with default values.
///
/// Initializes a new builder for creating a [`HostIcon`] object with all fields in their default
/// state.
fn new() -> Self {
HostIconBuilder::default()
}
/// Sets the name field.
///
/// # Arguments
/// * `name` - The name of the `HostIcon`.
fn name(mut self, name: &str) -> Self {
self.name = Some(name.to_string());
self
}
/// Builds the [`HostIcon`] object with the specified properties.
///
/// Constructs a new `HostIcon` object based on the current state of the builder.
/// Returns an error if the required fields `name` or `img_prefix` are not set.
///
/// # Returns
/// A `Result` containing the constructed `HostIcon` object or an error if the object
/// could not be built due to the absence of the required fields `name` and `img_prefix`.
///
/// # Errors
/// * `name not set` - The required `name` field was not set.
/// * `img_prefix not set` - The required `img_prefix` field was not set.
fn build(self) -> Result<Self::ConfigObject, OpsviewConfigError> {
let name = require_field(&self.name, "name")?;
let img_prefix = require_field(&self.img_prefix, "img_prefix")?;
Ok(HostIcon {
name: validate_and_trim_hosticon_name(&name)?,
// From what I can tell, the name and the img_prefix share the same regex validation.
img_prefix: Some(validate_and_trim_hosticon_name(&img_prefix)?),
ref_: None,
})
}
}
impl HostIconBuilder {
/// Clears the img_prefix field.
pub fn clear_img_prefix(mut self) -> Self {
self.img_prefix = None;
self
}
/// Clears the name field.
pub fn clear_name(mut self) -> Self {
self.name = None;
self
}
/// Sets the img_prefix field.
///
/// # Arguments
/// * `img_prefix` - The prefix for the `HostIcon` image.
///
/// # Example
/// * `/images/logos/somebrand` for host icons in the folder `/images/logos/somebrand`.
pub fn img_prefix(mut self, img_prefix: &str) -> Self {
self.img_prefix = Some(img_prefix.to_string());
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default() {
let icon = HostIcon::default();
assert!(icon.name.is_empty());
}
#[test]
fn test_minimal() {
let icon = HostIcon::minimal("My `HostIcon`");
assert_eq!(icon.unwrap().name, "My `HostIcon`".to_string());
}
#[test]
fn test_is_valid_host_icon() {
// Test valid host icon names
assert!(validate_and_trim_hosticon_name("valid_icon_name").is_ok());
assert!(validate_and_trim_hosticon_name("icon_123").is_ok());
assert!(validate_and_trim_hosticon_name("foobar#£$%&/()=?^").is_ok());
assert!(validate_and_trim_hosticon_name(&"i".repeat(128)).is_ok()); // Max length
// Test invalid host icon names
assert!(validate_and_trim_hosticon_name("").is_err()); // Empty name
assert!(validate_and_trim_hosticon_name(&"i".repeat(129)).is_err()); // Exceeds max length
// Additional tests for other specific invalid scenarios can be added
}
}