1use std::collections::HashMap;
6use std::path::Path;
7
8use serde::Deserialize;
9
10use super::ConfigError;
11
12#[derive(Debug, Default, Deserialize)]
17#[serde(deny_unknown_fields)]
18pub struct TomlConfig {
19 #[serde(default)]
21 pub webhook: WebhookSection,
22
23 #[serde(default)]
25 pub filter: FilterSection,
26
27 #[serde(default)]
29 pub monitor: MonitorSection,
30
31 #[serde(default)]
33 pub retry: RetrySection,
34}
35
36#[derive(Debug, Default, Deserialize)]
38#[serde(deny_unknown_fields)]
39pub struct WebhookSection {
40 pub url: Option<String>,
42
43 pub ip_version: Option<String>,
45
46 pub method: Option<String>,
48
49 #[serde(default)]
51 pub headers: HashMap<String, String>,
52
53 pub bearer: Option<String>,
55
56 pub body_template: Option<String>,
58}
59
60#[derive(Debug, Default, Deserialize)]
62#[serde(deny_unknown_fields)]
63pub struct FilterSection {
64 #[serde(default)]
66 pub include: Vec<String>,
67
68 #[serde(default)]
70 pub exclude: Vec<String>,
71
72 #[serde(default)]
74 pub include_kinds: Vec<String>,
75
76 #[serde(default)]
78 pub exclude_kinds: Vec<String>,
79}
80
81#[derive(Debug, Default, Deserialize)]
83#[serde(deny_unknown_fields)]
84pub struct MonitorSection {
85 pub poll_interval: Option<u64>,
87
88 #[serde(default)]
90 pub poll_only: bool,
91
92 pub state_file: Option<String>,
94
95 pub change_kind: Option<String>,
97}
98
99#[derive(Debug, Default, Deserialize)]
101#[serde(deny_unknown_fields)]
102pub struct RetrySection {
103 pub max_attempts: Option<u32>,
105
106 pub initial_delay: Option<u64>,
108
109 pub max_delay: Option<u64>,
111
112 pub multiplier: Option<f64>,
114}
115
116impl TomlConfig {
117 pub fn load(path: &Path) -> Result<Self, ConfigError> {
123 let content = std::fs::read_to_string(path).map_err(|e| ConfigError::FileRead {
124 path: path.to_path_buf(),
125 source: e,
126 })?;
127
128 Self::parse(&content)
129 }
130
131 pub fn parse(content: &str) -> Result<Self, ConfigError> {
137 toml::from_str(content).map_err(ConfigError::from)
138 }
139}
140
141#[must_use]
143pub fn default_config_template() -> String {
144 r#"# DDNS-A Configuration File
145# Documentation: https://github.com/doraemonkeys/ddns-a
146
147[webhook]
148# Webhook URL (required)
149# url = "https://api.example.com/ddns"
150
151# IP version to monitor (required)
152# Accepted values: "ipv4"/"v4"/"4", "ipv6"/"v6"/"6", or "both"/"all"/"dual"
153# ip_version = "both"
154
155# HTTP method (default: POST, can be overridden by --method CLI flag)
156# method = "POST"
157
158# HTTP headers
159# [webhook.headers]
160# X-Custom-Header = "value"
161
162# Bearer token for Authorization header
163# bearer = "your-token-here"
164
165# Handlebars body template
166# Available variables: {{adapter}}, {{address}}, {{timestamp}}, {{kind}}
167# body_template = '{"ip": "{{address}}", "adapter": "{{adapter}}"}'
168
169[filter]
170# Adapter kinds to include (empty = all kinds)
171# Valid values: ethernet, wireless, virtual, loopback
172# Note: CLI --include-kind REPLACES these entirely (not merged)
173# include_kinds = ["ethernet", "wireless"]
174
175# Adapter kinds to exclude
176# Note: Loopback is excluded by default unless explicitly included
177# Note: CLI --exclude-kind REPLACES these entirely (not merged)
178# exclude_kinds = ["virtual"]
179
180# Regex patterns for adapters to include by name (empty = all names)
181# Note: CLI --include-adapter REPLACES these entirely (not merged)
182# include = ["^eth", "^Ethernet"]
183
184# Regex patterns for adapters to exclude by name
185# Note: CLI --exclude-adapter REPLACES these entirely (not merged)
186# exclude = ["^Docker", "^vEthernet"]
187
188[monitor]
189# Polling interval in seconds (default: 60)
190poll_interval = 60
191
192# Disable API event listening, use polling only
193# poll_only = false
194
195# Path to state file for detecting changes across restarts
196# If set, the program will compare current IP addresses with the saved state
197# and trigger webhooks for any changes detected during the program restart
198# state_file = "ddns-a-state.json"
199
200# Filter changes by type (default: "both")
201# Accepted values: "added", "removed", "both"
202# change_kind = "both"
203
204[retry]
205# Maximum number of retry attempts (default: 3)
206# max_attempts = 3
207
208# Initial retry delay in seconds (default: 5)
209# initial_delay = 5
210
211# Maximum retry delay in seconds (default: 60)
212# max_delay = 60
213
214# Backoff multiplier (default: 2.0)
215# multiplier = 2.0
216"#
217 .to_string()
218}