summaryrefslogtreecommitdiffstats
path: root/src/config/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/config/mod.rs')
-rw-r--r--src/config/mod.rs131
1 files changed, 78 insertions, 53 deletions
diff --git a/src/config/mod.rs b/src/config/mod.rs
index 538689e..0aaccd6 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -16,6 +16,7 @@ mod import;
use std::collections::BTreeMap;
use std::net::SocketAddr;
use std::path::{Path, PathBuf};
+use std::time::Duration;
use anyhow::{Context, Result, anyhow, bail};
use clap::parser::ValueSource;
@@ -33,6 +34,10 @@ pub(crate) enum Kind {
Flag,
Text,
Integer,
+ /// Seconds, or with units: `500ms`, `30s`, `1h30m` (`units::parse_duration`).
+ Duration,
+ /// Bytes, or with a unit: `64KiB` (`units::parse_size`).
+ Size,
/// Comma- or line-separated; repeatable on the command line.
List,
}
@@ -138,20 +143,20 @@ pub(crate) const OPTIONS: &[OptionSpec] = &[
Kind::Text,
"PATH",
None,
- "This node's document on disk. Read at start: the node resumes with it, ready at once. Kept up to date while ready: changes are appended every --document-append-secs, and the file is rewritten after a switch, a rejoin or a restart, and when the appended changes outgrew it.",
+ "This node's document on disk. Read at start: the node resumes with it, ready at once. Kept up to date while ready: changes are appended every --document-append, and the file is rewritten after a switch, a rejoin or a restart, and when the appended changes outgrew it.",
),
option(
- "document-append-secs",
- Kind::Integer,
- "SECONDS",
- Some("5"),
+ "document-append",
+ Kind::Duration,
+ "DURATION",
+ Some("5s"),
"How often changes are appended to the document file.",
),
option(
- "document-rewrite-secs",
- Kind::Integer,
- "SECONDS",
- Some("3600"),
+ "document-rewrite",
+ Kind::Duration,
+ "DURATION",
+ Some("1h"),
"Shortest time between rewrites of the document file because its appended changes outgrew its full save (a new epoch or a restart rewrites it at once).",
),
option(
@@ -176,10 +181,10 @@ pub(crate) const OPTIONS: &[OptionSpec] = &[
"A name for this node, shown next to its public key (/peers, metrics). Published in the swarm's member list; not unique, never an identity.",
),
option(
- "keepalive-secs",
- Kind::Integer,
- "SECONDS",
- Some("60"),
+ "keepalive",
+ Kind::Duration,
+ "DURATION",
+ Some("1m"),
"Keepalive interval; dormancy, resync and probe timings derive from it.",
),
option(
@@ -190,18 +195,18 @@ pub(crate) const OPTIONS: &[OptionSpec] = &[
"Neighbours each node keeps (at least 2). Swarms of up to this many + 1 nodes are a full mesh; larger ones sync over several hops.",
),
option(
- "electorate-window-days",
- Kind::Integer,
- "DAYS",
- Some("7"),
- "Members that renewed their alive stamp within this many days vote on remakes (a majority of them must agree). Matters on the node that coordinates a remake.",
+ "electorate-window",
+ Kind::Duration,
+ "DURATION",
+ Some("7d"),
+ "Members that renewed their alive stamp within this time vote on remakes (a majority of them must agree). Matters on the node that coordinates a remake.",
),
option(
- "blob-chunk-kib",
- Kind::Integer,
- "KIB",
- Some("64"),
- "Chunk size when serving large transfers to other nodes (e.g. an epoch's snapshot to a rejoining node), in KiB (1 to 4096).",
+ "blob-chunk",
+ Kind::Size,
+ "SIZE",
+ Some("64KiB"),
+ "Chunk size when serving large transfers to other nodes (e.g. an epoch's snapshot to a rejoining node), 1KiB to 4MiB.",
),
option(
"mycelium-rest-url",
@@ -276,24 +281,34 @@ pub(crate) struct Settings {
pub(crate) genesis: bool,
pub(crate) state_file: Option<PathBuf>,
pub(crate) document_file: Option<PathBuf>,
- #[serde(default = "default_document_append_secs")]
- pub(crate) document_append_secs: u64,
- #[serde(default = "default_document_rewrite_secs")]
- pub(crate) document_rewrite_secs: u64,
+ #[serde(
+ default = "default_document_append",
+ with = "crate::units::duration_option"
+ )]
+ pub(crate) document_append: Duration,
+ #[serde(
+ default = "default_document_rewrite",
+ with = "crate::units::duration_option"
+ )]
+ pub(crate) document_rewrite: Duration,
pub(crate) recover_from: Option<PathBuf>,
pub(crate) remake: Option<String>,
#[serde(default)]
pub(crate) force: Vec<String>,
pub(crate) swarm_key_file: Option<PathBuf>,
pub(crate) alias: Option<String>,
- #[serde(default = "default_keepalive_secs")]
- pub(crate) keepalive_secs: u64,
+ #[serde(default = "default_keepalive", with = "crate::units::duration_option")]
+ pub(crate) keepalive: Duration,
#[serde(default = "default_neighbours")]
pub(crate) neighbours: usize,
- #[serde(default = "default_electorate_window_days")]
- pub(crate) electorate_window_days: u64,
- #[serde(default = "default_blob_chunk_kib")]
- pub(crate) blob_chunk_kib: u32,
+ #[serde(
+ default = "default_electorate_window",
+ with = "crate::units::duration_option"
+ )]
+ pub(crate) electorate_window: Duration,
+ /// In bytes.
+ #[serde(default = "default_blob_chunk", with = "crate::units::size_option")]
+ pub(crate) blob_chunk: u64,
pub(crate) mycelium_rest_url: Option<String>,
pub(crate) mycelium_key_file: Option<PathBuf>,
pub(crate) mycelium_network_key_file: Option<PathBuf>,
@@ -307,23 +322,23 @@ pub(crate) struct Settings {
fn default_bind_addr() -> SocketAddr {
SocketAddr::from(([127, 0, 0, 1], 3000))
}
-fn default_document_append_secs() -> u64 {
- 5
+fn default_document_append() -> Duration {
+ Duration::from_secs(5)
}
-fn default_document_rewrite_secs() -> u64 {
- 3600
+fn default_document_rewrite() -> Duration {
+ Duration::from_secs(3600)
}
-fn default_keepalive_secs() -> u64 {
- 60
+fn default_keepalive() -> Duration {
+ Duration::from_secs(60)
}
fn default_neighbours() -> usize {
12
}
-fn default_electorate_window_days() -> u64 {
- 7
+fn default_electorate_window() -> Duration {
+ Duration::from_secs(7 * 24 * 3600)
}
-fn default_blob_chunk_kib() -> u32 {
- 64
+fn default_blob_chunk() -> u64 {
+ 64 * 1024
}
fn default_mycelium_peers() -> Vec<String> {
vec!["tls://127.0.0.1:9651".to_owned()]
@@ -383,17 +398,17 @@ impl Settings {
if self.neighbours < 2 {
bail!("neighbours must be at least 2");
}
- if self.document_append_secs == 0 || self.document_rewrite_secs == 0 {
- bail!("document-append-secs and document-rewrite-secs must be at least 1");
+ if self.document_append.is_zero() || self.document_rewrite.is_zero() {
+ bail!("document-append and document-rewrite must be longer than 0");
}
- if self.keepalive_secs == 0 {
- bail!("keepalive-secs must be at least 1");
+ if self.keepalive < Duration::from_millis(100) {
+ bail!("keepalive must be at least 100ms");
}
- if !(1..=4096).contains(&self.blob_chunk_kib) {
- bail!("blob-chunk-kib must be between 1 and 4096");
+ if !(1024..=4 << 20).contains(&self.blob_chunk) {
+ bail!("blob-chunk must be between 1KiB and 4MiB");
}
- if self.electorate_window_days == 0 {
- bail!("electorate-window-days must be at least 1");
+ if self.electorate_window.is_zero() {
+ bail!("electorate-window must be longer than 0");
}
if let Some(alias) = &self.alias {
check_alias(alias)?;
@@ -598,6 +613,14 @@ fn from_text(kind: Kind, text: &str) -> Result<JsonValue> {
.map_err(|_| anyhow!("{text:?} is not a non-negative integer"))?;
JsonValue::from(number)
}
+ Kind::Duration => {
+ crate::units::parse_duration(text).map_err(|reason| anyhow!(reason))?;
+ JsonValue::String(text.trim().to_owned())
+ }
+ Kind::Size => {
+ crate::units::parse_size(text).map_err(|reason| anyhow!(reason))?;
+ JsonValue::String(text.trim().to_owned())
+ }
Kind::Flag => match text.trim() {
"true" | "yes" | "1" => JsonValue::Bool(true),
"false" | "no" | "0" => JsonValue::Bool(false),
@@ -719,7 +742,9 @@ fn command() -> Command {
command = command.arg(match spec.kind {
Kind::Flag => arg.action(ArgAction::SetTrue),
Kind::List => arg.value_name(spec.value_name).action(ArgAction::Append),
- Kind::Text | Kind::Integer => arg.value_name(spec.value_name).action(ArgAction::Set),
+ Kind::Text | Kind::Integer | Kind::Duration | Kind::Size => {
+ arg.value_name(spec.value_name).action(ArgAction::Set)
+ }
});
}
command
@@ -1089,7 +1114,7 @@ mod tests {
assert!(resolve(layer(json!({ "htpasswd": "u", "alias": " " })), Vec::new()).is_err());
assert!(
resolve(
- layer(json!({ "htpasswd": "u", "keepalive-secs": "x" })),
+ layer(json!({ "htpasswd": "u", "keepalive": "x" })),
Vec::new()
)
.is_err()