use super::ServiceCheck;
use crate::config::{Host, HostRef, Role, RoleRef, ServiceCheckRef};
use crate::{prelude::*, util::*};
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use std::str::FromStr;
use std::sync::Arc;
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct Hashtag {
pub name: String,
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_string_or_number_to_option_bool",
serialize_with = "serialize_option_bool_as_string",
default
)]
pub all_hosts: Option<bool>,
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_string_or_number_to_option_bool",
serialize_with = "serialize_option_bool_as_string",
default
)]
pub all_servicechecks: Option<bool>,
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_string_or_number_to_option_bool",
serialize_with = "serialize_option_bool_as_string",
default
)]
pub calculate_hard_states: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_string_or_number_to_option_bool",
serialize_with = "serialize_option_bool_as_string",
default
)]
pub enabled: Option<bool>,
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_string_or_number_to_option_bool",
serialize_with = "serialize_option_bool_as_string",
default
)]
pub exclude_handled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hosts: Option<ConfigRefMap<HostRef>>,
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_string_or_number_to_option_bool",
serialize_with = "serialize_option_bool_as_string",
default
)]
pub public: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub roles: Option<ConfigRefMap<RoleRef>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub servicechecks: Option<ConfigRefMap<ServiceCheckRef>>,
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_string_or_number_to_option_bool",
serialize_with = "serialize_option_bool_as_string",
default
)]
pub show_contextual_menus: Option<bool>,
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_hashtag_style",
default
)]
pub style: Option<HashtagStyle>,
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_string_or_number_to_u64",
default
)]
pub id: Option<u64>,
#[serde(
rename = "ref",
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_readonly",
default
)]
pub ref_: Option<String>,
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_string_or_number_to_option_bool",
serialize_with = "serialize_option_bool_as_string",
default
)]
pub uncommitted: Option<bool>,
}
impl Default for Hashtag {
fn default() -> Self {
Hashtag {
name: "".to_string(),
all_hosts: None,
all_servicechecks: None,
calculate_hard_states: None,
description: None,
enabled: Some(true),
exclude_handled: None,
hosts: None,
public: None,
roles: None,
servicechecks: None,
show_contextual_menus: Some(true),
style: None,
id: None,
ref_: None,
uncommitted: None,
}
}
}
impl CreateFromJson for Hashtag {}
impl ConfigObject for Hashtag {
type Builder = HashtagBuilder;
fn builder() -> Self::Builder {
HashtagBuilder::new()
}
fn config_path() -> Option<String> {
Some("/config/keyword".to_string())
}
fn minimal(name: &str) -> Result<Self, OpsviewConfigError> {
Ok(Self {
name: validate_and_trim_hashtag_name(name)?,
..Default::default()
})
}
fn unique_name(&self) -> String {
self.name.clone()
}
}
impl Persistent for Hashtag {
fn id(&self) -> Option<u64> {
self.id
}
fn ref_(&self) -> Option<String> {
if self.ref_.as_ref().is_some_and(|x| !x.is_empty()) {
self.ref_.clone()
} else {
None
}
}
fn name(&self) -> Option<String> {
if self.name.is_empty() {
None
} else {
Some(self.name.clone())
}
}
fn name_regex(&self) -> Option<String> {
Some(HASHTAG_NAME_REGEX_STR.to_string())
}
fn validated_name(&self, name: &str) -> Result<String, OpsviewConfigError> {
validate_and_trim_hashtag_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.id = None;
self.ref_ = None;
self.uncommitted = None;
}
}
impl PersistentMap for ConfigObjectMap<Hashtag> {
fn config_path() -> Option<String> {
Some("/config/keyword".to_string())
}
}
#[derive(Clone, Debug)]
pub struct HashtagBuilder {
name: Option<String>,
all_hosts: Option<bool>,
all_servicechecks: Option<bool>,
calculate_hard_states: Option<bool>,
description: Option<String>,
enabled: Option<bool>,
exclude_handled: Option<bool>,
hosts: Option<ConfigRefMap<HostRef>>,
public: Option<bool>,
roles: Option<ConfigRefMap<RoleRef>>,
servicechecks: Option<ConfigRefMap<ServiceCheckRef>>,
show_contextual_menus: Option<bool>,
style: Option<HashtagStyle>,
}
impl Default for HashtagBuilder {
fn default() -> Self {
HashtagBuilder {
name: None,
all_hosts: None,
all_servicechecks: None,
calculate_hard_states: None,
description: None,
enabled: Some(true),
exclude_handled: None,
hosts: None,
public: None,
roles: None,
servicechecks: None,
show_contextual_menus: Some(true),
style: None,
}
}
}
impl Builder for HashtagBuilder {
type ConfigObject = Hashtag;
fn new() -> Self {
HashtagBuilder::default()
}
fn name(mut self, name: &str) -> Self {
self.name = Some(name.to_string());
self
}
fn build(self) -> Result<Self::ConfigObject, OpsviewConfigError> {
let name = require_field(&self.name, "name")?;
let validated_description =
validate_opt_string(self.description, validate_and_trim_description)?;
Ok(Hashtag {
name: validate_and_trim_hashtag_name(&name)?,
all_hosts: self.all_hosts,
all_servicechecks: self.all_servicechecks,
calculate_hard_states: self.calculate_hard_states,
description: validated_description,
enabled: self.enabled,
exclude_handled: self.exclude_handled,
hosts: self.hosts,
public: self.public,
roles: self.roles,
servicechecks: self.servicechecks,
show_contextual_menus: self.show_contextual_menus,
style: self.style,
id: None,
ref_: None,
uncommitted: None,
})
}
}
impl HashtagBuilder {
pub fn all_hosts(mut self, all_hosts: bool) -> Self {
self.all_hosts = Some(all_hosts);
self
}
pub fn all_servicechecks(mut self, all_servicechecks: bool) -> Self {
self.all_servicechecks = Some(all_servicechecks);
self
}
pub fn calculate_hard_states(mut self, calculate_hard_states: bool) -> Self {
self.calculate_hard_states = Some(calculate_hard_states);
self
}
pub fn clear_all_hosts(mut self) -> Self {
self.all_hosts = None;
self
}
pub fn clear_all_servicechecks(mut self) -> Self {
self.all_servicechecks = None;
self
}
pub fn clear_calculate_hard_states(mut self) -> Self {
self.calculate_hard_states = None;
self
}
pub fn clear_description(mut self) -> Self {
self.description = None;
self
}
pub fn clear_enabled(mut self) -> Self {
self.enabled = None;
self
}
pub fn clear_exclude_handled(mut self) -> Self {
self.exclude_handled = None;
self
}
pub fn clear_hosts(mut self) -> Self {
self.hosts = None;
self
}
pub fn clear_name(mut self) -> Self {
self.name = None;
self
}
pub fn clear_public(mut self) -> Self {
self.public = None;
self
}
pub fn clear_roles(mut self) -> Self {
self.roles = None;
self
}
pub fn clear_servicechecks(mut self) -> Self {
self.servicechecks = None;
self
}
pub fn clear_show_contextual_menus(mut self) -> Self {
self.show_contextual_menus = None;
self
}
pub fn clear_style(mut self) -> Self {
self.style = None;
self
}
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_string());
self
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = Some(enabled);
self
}
pub fn exclude_handled(mut self, exclude_handled: bool) -> Self {
self.exclude_handled = Some(exclude_handled);
self
}
pub fn hosts(mut self, hosts: &ConfigObjectMap<Host>) -> Self {
self.hosts = Some(hosts.into());
self
}
pub fn public(mut self, public: bool) -> Self {
self.public = Some(public);
self
}
pub fn roles(mut self, roles: &ConfigObjectMap<Role>) -> Self {
self.roles = Some(roles.into());
self
}
pub fn servicechecks(mut self, servicechecks: &ConfigObjectMap<ServiceCheck>) -> Self {
self.servicechecks = Some(servicechecks.into());
self
}
pub fn show_contextual_menus(mut self, show_contextual_menus: bool) -> Self {
self.show_contextual_menus = Some(show_contextual_menus);
self
}
pub fn style(mut self, style: HashtagStyle) -> Self {
self.style = Some(style);
self
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
pub struct HashtagRef {
name: String,
#[serde(
rename = "ref",
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_readonly",
default
)]
ref_: Option<String>,
}
impl CreateFromJson for HashtagRef {}
impl ConfigRef for HashtagRef {
type FullObject = Hashtag;
fn ref_(&self) -> Option<String> {
self.ref_.clone()
}
fn name(&self) -> String {
self.name.clone()
}
fn unique_name(&self) -> String {
self.name.clone()
}
}
impl From<Hashtag> for HashtagRef {
fn from(full_object: Hashtag) -> Self {
Self {
name: full_object.name.clone(),
ref_: full_object.ref_.clone(),
}
}
}
impl From<Arc<Hashtag>> for HashtagRef {
fn from(item: Arc<Hashtag>) -> Self {
let hashtag: Hashtag = Arc::try_unwrap(item).unwrap_or_else(|arc| (*arc).clone());
HashtagRef::from(hashtag)
}
}
impl From<&ConfigObjectMap<Hashtag>> for ConfigRefMap<HashtagRef> {
fn from(hashtags: &ConfigObjectMap<Hashtag>) -> Self {
ref_map_from(hashtags)
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
#[allow(missing_docs)]
pub enum HashtagStyle {
GroupByHost,
GroupByService,
HostSummary,
ErrorsAndHostCells,
Performance,
}
impl FromStr for HashtagStyle {
type Err = OpsviewConfigError;
fn from_str(s: &str) -> Result<Self, OpsviewConfigError> {
match s {
"group_by_host" => Ok(HashtagStyle::GroupByHost),
"group_by_service" => Ok(HashtagStyle::GroupByService),
"host_summary" => Ok(HashtagStyle::HostSummary),
"errors_and_host_cells" => Ok(HashtagStyle::ErrorsAndHostCells),
"performance" => Ok(HashtagStyle::Performance),
_ => Err(OpsviewConfigError::InvalidHashtagStyle(s.to_string())),
}
}
}
fn deserialize_hashtag_style<'de, D>(deserializer: D) -> Result<Option<HashtagStyle>, D::Error>
where
D: Deserializer<'de>,
{
let value = Value::deserialize(deserializer)?;
match value {
Value::Null => Ok(None),
Value::String(ref s) if s == "null" => Ok(None),
_ => {
let maybe_style = HashtagStyle::deserialize(value)
.map(Some)
.map_err(serde::de::Error::custom);
maybe_style
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default() {
let hashtag = Hashtag::default();
assert_eq!(hashtag.name, "".to_string());
}
#[test]
fn test_minimal() {
let hashtag = Hashtag::minimal("MyHashtag");
assert_eq!(hashtag.unwrap().name, "MyHashtag".to_string());
let hashtag_2 = Hashtag::minimal("My Hashtag");
assert!(hashtag_2.is_err());
}
#[test]
fn test_build() {
let hashtag = Hashtag::builder()
.name("MyHashtag")
.description("My Hashtag Description")
.build()
.unwrap();
assert_eq!(hashtag.name, "MyHashtag".to_string());
assert_eq!(
hashtag.description.unwrap(),
"My Hashtag Description".to_string()
);
let hashtag_2 = Hashtag::builder()
.name("My Hashtag")
.description("My Hashtag Description")
.build();
assert!(hashtag_2.is_err());
}
}