mirror of
https://github.com/rzuasti/oott.git
synced 2026-07-08 19:21:54 +02:00
Make [notifications.pushover] optional for non-pushover methods
The pushover config section is now only required when notifications.method is "pushover". Validation at startup rejects the missing-section case so a misconfiguration fails fast instead of erroring on every notification. Updates the sample TOML, README and nix module for consistency. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
6c4f8c22bb
commit
be2a8fce58
+18
-9
@@ -185,9 +185,17 @@ fn send_notification(notification: Notification) -> Result<(), Box<dyn Error>> {
|
||||
debug!("About to send notification ({notification}).");
|
||||
|
||||
match get_settings().notifications.method.as_str() {
|
||||
"pushover" => {
|
||||
pushover::send_message(notification.title, notification.body)?;
|
||||
}
|
||||
"pushover" => match &get_settings().notifications.pushover {
|
||||
Some(pushover) => {
|
||||
pushover::send_message(pushover, notification.title, notification.body)?;
|
||||
}
|
||||
None => {
|
||||
error!(
|
||||
"Notification method is 'pushover' but no [notifications.pushover] section is \
|
||||
configured; cannot deliver notification."
|
||||
);
|
||||
}
|
||||
},
|
||||
other => {
|
||||
warn!("Notification method set to '{other}'. Set logs to 'info' to see notifications.");
|
||||
info!("Notification: {}", notification.body);
|
||||
@@ -294,8 +302,12 @@ pub fn trigger_existing_device(
|
||||
let vendor_changed_flag = vendor_changed(&existing_device.vendor, &new_device.vendor);
|
||||
|
||||
if ip_changed || vendor_changed_flag {
|
||||
let (title, body) =
|
||||
render_device_changed(&existing_device, &new_device, ip_changed, vendor_changed_flag);
|
||||
let (title, body) = render_device_changed(
|
||||
&existing_device,
|
||||
&new_device,
|
||||
ip_changed,
|
||||
vendor_changed_flag,
|
||||
);
|
||||
let notification = Notification::new(
|
||||
Utc::now(),
|
||||
NotificationType::DeviceChanged,
|
||||
@@ -411,10 +423,7 @@ mod tests {
|
||||
|
||||
let (title, body) = render_device_back_online(&device, "12d");
|
||||
|
||||
assert_eq!(
|
||||
title,
|
||||
"Device back online after 12d: bobs-iphone.local"
|
||||
);
|
||||
assert_eq!(title, "Device back online after 12d: bobs-iphone.local");
|
||||
assert!(body.contains("Registered to Bob"));
|
||||
assert!(body.contains("Absent for: 12d"));
|
||||
}
|
||||
|
||||
@@ -3,17 +3,13 @@ use pushover::API;
|
||||
use pushover::requests::message::SendMessage;
|
||||
|
||||
use crate::events::error::DeliveryError;
|
||||
use crate::settings::get_settings;
|
||||
use crate::settings::Pushover;
|
||||
|
||||
pub fn send_message(title: String, body: String) -> Result<(), DeliveryError> {
|
||||
pub fn send_message(config: &Pushover, title: String, body: String) -> Result<(), DeliveryError> {
|
||||
debug!("About to send message via pushover ({body})");
|
||||
let api = API::new();
|
||||
|
||||
let mut msg = SendMessage::new(
|
||||
get_settings().notifications.pushover.token.as_str(),
|
||||
get_settings().notifications.pushover.user_key.as_str(),
|
||||
body,
|
||||
);
|
||||
let mut msg = SendMessage::new(config.token.as_str(), config.user_key.as_str(), body);
|
||||
|
||||
msg.set_title(title);
|
||||
|
||||
|
||||
+80
-2
@@ -180,7 +180,8 @@ pub struct Pushover {
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Notifications {
|
||||
pub method: String,
|
||||
pub pushover: Pushover,
|
||||
// Only required when `method` is "pushover"; other methods leave this section out.
|
||||
pub pushover: Option<Pushover>,
|
||||
#[serde(default = "default_notify_when_not_seen_for")]
|
||||
pub notify_when_not_seen_for: DurationString,
|
||||
}
|
||||
@@ -255,7 +256,22 @@ impl Settings {
|
||||
.add_source(File::with_name(config_path.as_str()))
|
||||
.build()?;
|
||||
|
||||
local_settings.try_deserialize()
|
||||
let settings: Settings = local_settings.try_deserialize()?;
|
||||
settings.validate()?;
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
// Cross-field checks that serde can't express on its own. Run once at load time so a
|
||||
// misconfiguration fails fast at startup rather than on every notification attempt.
|
||||
fn validate(&self) -> Result<(), ConfigError> {
|
||||
if self.notifications.method == "pushover" && self.notifications.pushover.is_none() {
|
||||
return Err(ConfigError::Message(
|
||||
"notifications.method is \"pushover\" but the [notifications.pushover] section is \
|
||||
missing; add it with your token and user_key, or change notifications.method."
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,6 +549,68 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_rejects_pushover_method_without_section() {
|
||||
// method = "pushover" but no [notifications.pushover] section: must fail at load time.
|
||||
const PUSHOVER_NO_SECTION: &str = r#"
|
||||
[database]
|
||||
path = "./oott.db"
|
||||
[networking]
|
||||
[log]
|
||||
level = "info"
|
||||
[notifications]
|
||||
method = "pushover"
|
||||
[web_server]
|
||||
api_key = "test"
|
||||
"#;
|
||||
let settings = parse(PUSHOVER_NO_SECTION);
|
||||
assert!(settings.validate().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_accepts_non_pushover_method_without_section() {
|
||||
let settings = parse(NO_PUSHOVER_CONFIG);
|
||||
assert!(settings.validate().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_accepts_pushover_method_with_section() {
|
||||
let settings = parse(BASE_CONFIG);
|
||||
assert!(settings.validate().is_ok());
|
||||
}
|
||||
|
||||
const NO_PUSHOVER_CONFIG: &str = r#"
|
||||
[database]
|
||||
path = "./oott.db"
|
||||
[networking]
|
||||
[log]
|
||||
level = "info"
|
||||
[notifications]
|
||||
method = "none"
|
||||
[web_server]
|
||||
api_key = "test"
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn pushover_section_is_optional_for_non_pushover_methods() {
|
||||
// With a non-pushover method, the `[notifications.pushover]` section may be omitted
|
||||
// entirely; it should deserialize to `None` rather than fail parsing.
|
||||
let settings = parse(NO_PUSHOVER_CONFIG);
|
||||
assert_eq!(settings.notifications.method, "none");
|
||||
assert!(settings.notifications.pushover.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pushover_section_is_parsed_when_present() {
|
||||
let settings = parse(BASE_CONFIG);
|
||||
let pushover = settings
|
||||
.notifications
|
||||
.pushover
|
||||
.expect("pushover section should be parsed when present");
|
||||
assert_eq!(pushover.token, "");
|
||||
assert_eq!(pushover.user_key, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn networking_section_is_optional() {
|
||||
// The `[networking]` section has no mandatory fields, so omitting it entirely
|
||||
|
||||
Reference in New Issue
Block a user