mirror of
https://github.com/YGGverse/aquatic.git
synced 2026-03-31 17:55:36 +00:00
Include documentation in printed config files (#41)
* Start work on printing toml config with comments * WIP: toml_config: extract default values for fields * WIP: toml_config: handle single-level nested structs * WIP: toml_config: improve comment handling, std type trait impls * WIP: toml_config: add Private trait, improve comment handling, clean up * toml_config: fix default value bug; improve tests * Use toml_config in all applicable crates; add toml_config enum support * toml_config: improve comments * toml_config_derive: support enum comments * Improve config comments for udp, cli_helpers, common * Improve config comments * Add tests for Config struct TomlConfig implementations * Improve Config comments * Improve Config comments * ws, http: add config comments for tls cert and private key lines * small fixes to toml_config and toml_config_derive * Run cargo fmt * Fix typo in several config comments * Update README * Update README
This commit is contained in:
parent
d694785244
commit
a208775104
28 changed files with 581 additions and 92 deletions
29
Cargo.lock
generated
29
Cargo.lock
generated
|
|
@ -62,6 +62,7 @@ dependencies = [
|
|||
"serde",
|
||||
"simplelog",
|
||||
"toml",
|
||||
"toml_config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -80,6 +81,7 @@ dependencies = [
|
|||
"privdrop",
|
||||
"rand",
|
||||
"serde",
|
||||
"toml_config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -109,6 +111,7 @@ dependencies = [
|
|||
"signal-hook",
|
||||
"slab",
|
||||
"smartstring",
|
||||
"toml_config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -130,6 +133,7 @@ dependencies = [
|
|||
"rand_distr",
|
||||
"rustls",
|
||||
"serde",
|
||||
"toml_config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -179,6 +183,7 @@ dependencies = [
|
|||
"slab",
|
||||
"socket2 0.4.2",
|
||||
"tinytemplate",
|
||||
"toml_config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -196,6 +201,7 @@ dependencies = [
|
|||
"rand",
|
||||
"rand_distr",
|
||||
"serde",
|
||||
"toml_config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -215,6 +221,7 @@ dependencies = [
|
|||
"rand_distr",
|
||||
"serde",
|
||||
"socket2 0.4.2",
|
||||
"toml_config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -259,6 +266,7 @@ dependencies = [
|
|||
"signal-hook",
|
||||
"slab",
|
||||
"socket2 0.4.2",
|
||||
"toml_config",
|
||||
"tungstenite",
|
||||
]
|
||||
|
||||
|
|
@ -284,6 +292,7 @@ dependencies = [
|
|||
"rustls",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"toml_config",
|
||||
"tungstenite",
|
||||
]
|
||||
|
||||
|
|
@ -2066,6 +2075,26 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_config"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"quickcheck",
|
||||
"quickcheck_macros",
|
||||
"serde",
|
||||
"toml",
|
||||
"toml_config_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_config_derive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.29"
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ members = [
|
|||
"aquatic_ws",
|
||||
"aquatic_ws_load_test",
|
||||
"aquatic_ws_protocol",
|
||||
"toml_config",
|
||||
"toml_config_derive",
|
||||
]
|
||||
|
||||
[patch.crates-io]
|
||||
|
|
@ -30,6 +32,8 @@ aquatic_udp_protocol = { path = "aquatic_udp_protocol" }
|
|||
aquatic_ws = { path = "aquatic_ws" }
|
||||
aquatic_ws_load_test = { path = "aquatic_ws_load_test" }
|
||||
aquatic_ws_protocol = { path = "aquatic_ws_protocol" }
|
||||
toml_config = { path = "toml_config" }
|
||||
toml_config_derive = { path = "toml_config_derive" }
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
|
|
|||
39
README.md
39
README.md
|
|
@ -66,8 +66,9 @@ Begin by generating configuration files. They differ between protocols.
|
|||
Make adjustments to the files. You will likely want to adjust `address`
|
||||
(listening address) under the `network` section.
|
||||
|
||||
`aquatic_http` and `aquatic_ws` both require configuring a TLS certificate file as well as a
|
||||
private key file to run. More information is available below.
|
||||
Note that both `aquatic_http` and `aquatic_ws` require configuring TLS
|
||||
certificate and private key files. More details are available in the
|
||||
respective configuration files.
|
||||
|
||||
Once done, run the tracker:
|
||||
|
||||
|
|
@ -79,25 +80,12 @@ Once done, run the tracker:
|
|||
|
||||
### Configuration values
|
||||
|
||||
Starting more socket workers than request workers is recommended. All
|
||||
Starting more `socket_workers` than `request_workers` is recommended. All
|
||||
implementations are quite IO-bound and spend a lot of their time reading from
|
||||
and writing to sockets. This is handled by the `socket_workers`, which
|
||||
and writing to sockets. This is handled by the socket workers, which
|
||||
also do parsing, serialisation and access control. They pass announce and
|
||||
scrape requests to the `request_workers`, which update internal tracker state
|
||||
and pass back responses.
|
||||
|
||||
#### TLS
|
||||
|
||||
`aquatic_ws` and `aquatic_http` both require access to a TLS certificate file
|
||||
(DER-encoded X.509) and a corresponding private key file (DER-encoded ASN.1 in
|
||||
either PKCS#8 or PKCS#1 format) to run. Set their paths in the configuration file, e.g.:
|
||||
|
||||
```toml
|
||||
[network]
|
||||
address = '0.0.0.0:3000'
|
||||
tls_certificate_path = './cert.pem'
|
||||
tls_private_key_path = './key.pem'
|
||||
```
|
||||
scrape requests to the request workers, which update internal tracker state
|
||||
and pass back responses for sending.
|
||||
|
||||
#### Access control
|
||||
|
||||
|
|
@ -106,8 +94,10 @@ of configuration is:
|
|||
|
||||
```toml
|
||||
[access_list]
|
||||
mode = 'off' # Change to 'black' (blacklist) or 'white' (whitelist)
|
||||
path = '' # Path to text file with newline-delimited hex-encoded info hashes
|
||||
# Access list mode. Available modes are white, black and off.
|
||||
mode = "off"
|
||||
# Path to access list file consisting of newline-separated hex-encoded info hashes.
|
||||
path = ""
|
||||
```
|
||||
|
||||
The file is read on start and when the program receives `SIGUSR1`. If initial
|
||||
|
|
@ -115,12 +105,6 @@ parsing fails, the program exits. Later failures result in in emitting of
|
|||
an error-level log message, while successful updates of the access list result
|
||||
in emitting of an info-level log message.
|
||||
|
||||
#### More information
|
||||
|
||||
More documentation of the various configuration options might be available
|
||||
under `src/config.rs` in directories `aquatic_udp`, `aquatic_http` and
|
||||
`aquatic_ws`.
|
||||
|
||||
## Details on implementations
|
||||
|
||||
### aquatic_udp: UDP BitTorrent tracker
|
||||
|
|
@ -146,6 +130,7 @@ More details are available [here](./documents/aquatic-udp-load-test-2021-11-28.p
|
|||
* Using glommio
|
||||
* Using io-uring
|
||||
* Using zerocopy + vectored sends for responses
|
||||
* Using sendmmsg
|
||||
|
||||
### aquatic_http: HTTP BitTorrent tracker
|
||||
|
||||
|
|
|
|||
|
|
@ -12,3 +12,4 @@ anyhow = "1"
|
|||
serde = { version = "1", features = ["derive"] }
|
||||
simplelog = "0.11"
|
||||
toml = "0.5"
|
||||
toml_config = "0.1.0"
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@ use std::io::Read;
|
|||
use anyhow::Context;
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use simplelog::{ColorChoice, ConfigBuilder, LevelFilter, TermLogger, TerminalMode};
|
||||
use toml_config::TomlConfig;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
/// Log level. Available values are off, error, warn, info, debug and trace.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, TomlConfig, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum LogLevel {
|
||||
Off,
|
||||
|
|
@ -22,7 +24,7 @@ impl Default for LogLevel {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait Config: Default + Serialize + DeserializeOwned {
|
||||
pub trait Config: Default + TomlConfig + DeserializeOwned {
|
||||
fn get_log_level(&self) -> Option<LogLevel> {
|
||||
None
|
||||
}
|
||||
|
|
@ -169,9 +171,9 @@ where
|
|||
|
||||
fn default_config_as_toml<T>() -> String
|
||||
where
|
||||
T: Default + Serialize,
|
||||
T: Default + TomlConfig,
|
||||
{
|
||||
toml::to_string_pretty(&T::default()).expect("Could not serialize default config to toml")
|
||||
<T as TomlConfig>::default_to_string()
|
||||
}
|
||||
|
||||
fn start_logger(log_level: LogLevel) -> ::anyhow::Result<()> {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ log = "0.4"
|
|||
privdrop = "0.5"
|
||||
rand = { version = "0.8", features = ["small_rng"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
toml_config = "0.1.0"
|
||||
|
||||
# cpu-pinning
|
||||
hwloc = { version = "0.5", optional = true }
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ use anyhow::Context;
|
|||
use arc_swap::{ArcSwap, Cache};
|
||||
use hashbrown::HashSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use toml_config::TomlConfig;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
/// Access list mode. Available modes are white, black and off.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, TomlConfig, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum AccessListMode {
|
||||
/// Only serve torrents with info hash present in file
|
||||
|
|
@ -25,7 +27,7 @@ impl AccessListMode {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
pub struct AccessListConfig {
|
||||
pub mode: AccessListMode,
|
||||
/// Path to access list file consisting of newline-separated hex-encoded info hashes.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
use hwloc::{CpuSet, ObjectType, Topology, CPUBIND_THREAD};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use toml_config::TomlConfig;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum CpuPinningMode {
|
||||
Ascending,
|
||||
|
|
@ -14,7 +15,7 @@ impl Default for CpuPinningMode {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
pub struct CpuPinningConfig {
|
||||
pub active: bool,
|
||||
pub mode: CpuPinningMode,
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@ use std::{
|
|||
};
|
||||
|
||||
use privdrop::PrivDrop;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Deserialize;
|
||||
use toml_config::TomlConfig;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct PrivilegeConfig {
|
||||
/// Chroot and switch user after binding to sockets
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ serde = { version = "1", features = ["derive"] }
|
|||
signal-hook = { version = "0.3" }
|
||||
slab = "0.4"
|
||||
smartstring = "0.2"
|
||||
toml_config = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "1"
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
use std::{net::SocketAddr, path::PathBuf};
|
||||
|
||||
use aquatic_common::{access_list::AccessListConfig, privileges::PrivilegeConfig};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Deserialize;
|
||||
use toml_config::TomlConfig;
|
||||
|
||||
use aquatic_cli_helpers::LogLevel;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
/// aquatic_http configuration
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Config {
|
||||
/// Socket workers receive requests from the socket, parse them and send
|
||||
/// them on to the request handler. They then recieve responses from the
|
||||
/// request handler, encode them and send them back over the socket.
|
||||
/// them on to the request workers. They then receive responses from the
|
||||
/// request workers, encode them and send them back over the socket.
|
||||
pub socket_workers: usize,
|
||||
/// Request workers receive a number of requests from socket workers,
|
||||
/// generate responses and send them back to the socket workers.
|
||||
|
|
@ -31,18 +33,22 @@ impl aquatic_cli_helpers::Config for Config {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct NetworkConfig {
|
||||
/// Bind to this address
|
||||
pub address: SocketAddr,
|
||||
pub tls_certificate_path: PathBuf,
|
||||
pub tls_private_key_path: PathBuf,
|
||||
/// Only allow access over IPv6
|
||||
pub ipv6_only: bool,
|
||||
/// Path to TLS certificate (DER-encoded X.509)
|
||||
pub tls_certificate_path: PathBuf,
|
||||
/// Path to TLS private key (DER-encoded ASN.1 in PKCS#8 or PKCS#1 format)
|
||||
pub tls_private_key_path: PathBuf,
|
||||
/// Keep connections alive
|
||||
pub keep_alive: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct ProtocolConfig {
|
||||
/// Maximum number of torrents to accept in scrape request
|
||||
|
|
@ -53,12 +59,12 @@ pub struct ProtocolConfig {
|
|||
pub peer_announce_interval: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct CleaningConfig {
|
||||
/// Clean peers this often (seconds)
|
||||
pub torrent_cleaning_interval: u64,
|
||||
/// Remove peers that haven't announced for this long (seconds)
|
||||
/// Remove peers that have not announced for this long (seconds)
|
||||
pub max_peer_age: u64,
|
||||
}
|
||||
|
||||
|
|
@ -109,3 +115,10 @@ impl Default for CleaningConfig {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Config;
|
||||
|
||||
::toml_config::gen_serialize_deserialize_test!(Config);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ rand = { version = "0.8", features = ["small_rng"] }
|
|||
rand_distr = "0.4"
|
||||
rustls = { version = "0.20", features = ["dangerous_configuration"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
toml_config = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "1"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
use std::net::SocketAddr;
|
||||
|
||||
use aquatic_cli_helpers::LogLevel;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Deserialize;
|
||||
use toml_config::TomlConfig;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
/// aquatic_http_load_test configuration
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Config {
|
||||
pub server_address: SocketAddr,
|
||||
|
|
@ -14,7 +16,7 @@ pub struct Config {
|
|||
/// How often to check if num_connections connections are open, and
|
||||
/// open a new one otherwise. A value of 0 means that connections are
|
||||
/// opened as quickly as possible, which is useful when the tracker
|
||||
/// doesn't keep connections alive.
|
||||
/// does not keep connections alive.
|
||||
pub connection_creation_interval_ms: u64,
|
||||
pub duration: usize,
|
||||
pub torrents: TorrentConfig,
|
||||
|
|
@ -28,7 +30,7 @@ impl aquatic_cli_helpers::Config for Config {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct TorrentConfig {
|
||||
pub number_of_torrents: usize,
|
||||
|
|
@ -73,3 +75,10 @@ impl Default for TorrentConfig {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Config;
|
||||
|
||||
::toml_config::gen_serialize_deserialize_test!(Config);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ slab = "0.4"
|
|||
signal-hook = { version = "0.3" }
|
||||
socket2 = { version = "0.4", features = ["all"] }
|
||||
tinytemplate = "1"
|
||||
toml_config = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "1"
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
use std::{net::SocketAddr, path::PathBuf};
|
||||
|
||||
use aquatic_common::{access_list::AccessListConfig, privileges::PrivilegeConfig};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Deserialize;
|
||||
|
||||
use aquatic_cli_helpers::LogLevel;
|
||||
use toml_config::TomlConfig;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
/// aquatic_udp configuration
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Config {
|
||||
/// Socket workers receive requests from the socket, parse them and send
|
||||
/// them on to the request workers. They then recieve responses from the
|
||||
/// them on to the request workers. They then receive responses from the
|
||||
/// request workers, encode them and send them back over the socket.
|
||||
pub socket_workers: usize,
|
||||
/// Request workers receive a number of requests from socket workers,
|
||||
|
|
@ -62,22 +64,21 @@ impl aquatic_cli_helpers::Config for Config {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct NetworkConfig {
|
||||
/// Bind to this address
|
||||
pub address: SocketAddr,
|
||||
/// Only allow access over IPv6
|
||||
pub only_ipv6: bool,
|
||||
/// Size of socket recv buffer. Use 0 for OS default.
|
||||
///
|
||||
/// This setting can have a big impact on dropped packages. It might
|
||||
/// require changing system defaults. Some examples of commands to set
|
||||
/// recommended values for different operating systems:
|
||||
/// values for different operating systems:
|
||||
///
|
||||
/// macOS:
|
||||
/// $ sudo sysctl net.inet.udp.recvspace=6000000
|
||||
/// $ sudo sysctl net.inet.udp.maxdgram=500000 # Not necessary, but recommended
|
||||
/// $ sudo sysctl kern.ipc.maxsockbuf=8388608 # Not necessary, but recommended
|
||||
///
|
||||
/// Linux:
|
||||
/// $ sudo sysctl -w net.core.rmem_max=104857600
|
||||
|
|
@ -108,7 +109,7 @@ impl Default for NetworkConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct ProtocolConfig {
|
||||
/// Maximum number of torrents to accept in scrape request
|
||||
|
|
@ -129,7 +130,7 @@ impl Default for ProtocolConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct StatisticsConfig {
|
||||
/// Collect and print/write statistics this often (seconds)
|
||||
|
|
@ -138,7 +139,7 @@ pub struct StatisticsConfig {
|
|||
pub print_to_stdout: bool,
|
||||
/// Save statistics as HTML to a file
|
||||
pub write_html_to_file: bool,
|
||||
/// Path to save HTML file
|
||||
/// Path to save HTML file to
|
||||
pub html_file_path: PathBuf,
|
||||
}
|
||||
|
||||
|
|
@ -159,7 +160,7 @@ impl Default for StatisticsConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct CleaningConfig {
|
||||
/// Clean connections this often (seconds)
|
||||
|
|
@ -174,9 +175,9 @@ pub struct CleaningConfig {
|
|||
pub pending_scrape_cleaning_interval: u64,
|
||||
/// Remove connections that are older than this (seconds)
|
||||
pub max_connection_age: u64,
|
||||
/// Remove peers that haven't announced for this long (seconds)
|
||||
/// Remove peers who have not announced for this long (seconds)
|
||||
pub max_peer_age: u64,
|
||||
/// Remove pending scrape responses that haven't been returned from request
|
||||
/// Remove pending scrape responses that have not been returned from request
|
||||
/// workers for this long (seconds)
|
||||
pub max_pending_scrape_age: u64,
|
||||
}
|
||||
|
|
@ -193,3 +194,10 @@ impl Default for CleaningConfig {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Config;
|
||||
|
||||
::toml_config::gen_serialize_deserialize_test!(Config);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,3 +21,4 @@ num-format = "0.4"
|
|||
rand = { version = "0.8", features = ["small_rng"] }
|
||||
rand_distr = "0.4"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
toml_config = "0.1.0"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use serde::Deserialize;
|
||||
use toml_config::TomlConfig;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
pub struct BenchConfig {
|
||||
pub num_rounds: usize,
|
||||
pub num_threads: usize,
|
||||
|
|
@ -24,3 +25,10 @@ impl Default for BenchConfig {
|
|||
}
|
||||
|
||||
impl aquatic_cli_helpers::Config for BenchConfig {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::BenchConfig;
|
||||
|
||||
::toml_config::gen_serialize_deserialize_test!(BenchConfig);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ rand = { version = "0.8", features = ["small_rng"] }
|
|||
rand_distr = "0.4"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
socket2 = { version = "0.4", features = ["all"] }
|
||||
toml_config = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "1"
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
use std::net::SocketAddr;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Deserialize;
|
||||
|
||||
use aquatic_cli_helpers::LogLevel;
|
||||
#[cfg(feature = "cpu-pinning")]
|
||||
use aquatic_common::cpu_pinning::CpuPinningConfig;
|
||||
use toml_config::TomlConfig;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
/// aquatic_udp_load_test configuration
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Config {
|
||||
/// Server address
|
||||
|
|
@ -15,6 +17,7 @@ pub struct Config {
|
|||
/// address here.
|
||||
pub server_address: SocketAddr,
|
||||
pub log_level: LogLevel,
|
||||
/// Number of workers sending requests
|
||||
pub workers: u8,
|
||||
/// Run duration (quit and generate report after this many seconds)
|
||||
pub duration: usize,
|
||||
|
|
@ -39,7 +42,7 @@ impl Default for Config {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct NetworkConfig {
|
||||
/// True means bind to one localhost IP per socket.
|
||||
|
|
@ -59,12 +62,10 @@ pub struct NetworkConfig {
|
|||
///
|
||||
/// This setting can have a big impact on dropped packages. It might
|
||||
/// require changing system defaults. Some examples of commands to set
|
||||
/// recommended values for different operating systems:
|
||||
/// values for different operating systems:
|
||||
///
|
||||
/// macOS:
|
||||
/// $ sudo sysctl net.inet.udp.recvspace=6000000
|
||||
/// $ sudo sysctl net.inet.udp.maxdgram=500000 # Not necessary, but recommended
|
||||
/// $ sudo sysctl kern.ipc.maxsockbuf=8388608 # Not necessary, but recommended
|
||||
///
|
||||
/// Linux:
|
||||
/// $ sudo sysctl -w net.core.rmem_max=104857600
|
||||
|
|
@ -84,7 +85,7 @@ impl Default for NetworkConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct RequestConfig {
|
||||
/// Number of torrents to simulate
|
||||
|
|
@ -125,3 +126,10 @@ impl Default for RequestConfig {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Config;
|
||||
|
||||
::toml_config::gen_serialize_deserialize_test!(Config);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ rustls-pemfile = "0.2"
|
|||
serde = { version = "1", features = ["derive"] }
|
||||
signal-hook = { version = "0.3" }
|
||||
slab = "0.4"
|
||||
toml_config = "0.1.0"
|
||||
tungstenite = "0.16"
|
||||
|
||||
# mio
|
||||
|
|
|
|||
|
|
@ -4,16 +4,18 @@ use std::path::PathBuf;
|
|||
#[cfg(feature = "cpu-pinning")]
|
||||
use aquatic_common::cpu_pinning::CpuPinningConfig;
|
||||
use aquatic_common::{access_list::AccessListConfig, privileges::PrivilegeConfig};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Deserialize;
|
||||
|
||||
use aquatic_cli_helpers::LogLevel;
|
||||
use toml_config::TomlConfig;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
/// aquatic_ws configuration
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Config {
|
||||
/// Socket workers receive requests from the socket, parse them and send
|
||||
/// them on to the request handler. They then recieve responses from the
|
||||
/// request handler, encode them and send them back over the socket.
|
||||
/// them on to the request workers. They then receive responses from the
|
||||
/// request workers, encode them and send them back over the socket.
|
||||
pub socket_workers: usize,
|
||||
/// Request workers receive a number of requests from socket workers,
|
||||
/// generate responses and send them back to the socket workers.
|
||||
|
|
@ -38,25 +40,29 @@ impl aquatic_cli_helpers::Config for Config {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct NetworkConfig {
|
||||
/// Bind to this address
|
||||
pub address: SocketAddr,
|
||||
/// Only allow access over IPv6
|
||||
pub ipv6_only: bool,
|
||||
|
||||
/// Path to TLS certificate (DER-encoded X.509)
|
||||
pub tls_certificate_path: PathBuf,
|
||||
/// Path to TLS private key (DER-encoded ASN.1 in PKCS#8 or PKCS#1 format)
|
||||
pub tls_private_key_path: PathBuf,
|
||||
|
||||
pub websocket_max_message_size: usize,
|
||||
pub websocket_max_frame_size: usize,
|
||||
|
||||
pub tls_certificate_path: PathBuf,
|
||||
pub tls_private_key_path: PathBuf,
|
||||
|
||||
#[cfg(feature = "with-mio")]
|
||||
pub poll_event_capacity: usize,
|
||||
#[cfg(feature = "with-mio")]
|
||||
pub poll_timeout_microseconds: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct ProtocolConfig {
|
||||
/// Maximum number of torrents to accept in scrape request
|
||||
|
|
@ -68,7 +74,7 @@ pub struct ProtocolConfig {
|
|||
}
|
||||
|
||||
#[cfg(feature = "with-mio")]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct HandlerConfig {
|
||||
/// Maximum number of requests to receive from channel before locking
|
||||
|
|
@ -77,12 +83,12 @@ pub struct HandlerConfig {
|
|||
pub channel_recv_timeout_microseconds: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct CleaningConfig {
|
||||
/// Clean peers this often (seconds)
|
||||
pub torrent_cleaning_interval: u64,
|
||||
/// Remove peers that haven't announced for this long (seconds)
|
||||
/// Remove peers that have not announced for this long (seconds)
|
||||
pub max_peer_age: u64,
|
||||
|
||||
// Clean connections this often (seconds)
|
||||
|
|
@ -98,10 +104,10 @@ pub struct CleaningConfig {
|
|||
}
|
||||
|
||||
#[cfg(feature = "with-mio")]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct StatisticsConfig {
|
||||
/// Print statistics this often (seconds). Don't print when set to zero.
|
||||
/// Print statistics this often (seconds). Do not print when set to zero.
|
||||
pub interval: u64,
|
||||
}
|
||||
|
||||
|
|
@ -131,12 +137,13 @@ impl Default for NetworkConfig {
|
|||
Self {
|
||||
address: SocketAddr::from(([0, 0, 0, 0], 3000)),
|
||||
ipv6_only: false,
|
||||
websocket_max_message_size: 64 * 1024,
|
||||
websocket_max_frame_size: 16 * 1024,
|
||||
|
||||
tls_certificate_path: "".into(),
|
||||
tls_private_key_path: "".into(),
|
||||
|
||||
websocket_max_message_size: 64 * 1024,
|
||||
websocket_max_frame_size: 16 * 1024,
|
||||
|
||||
#[cfg(feature = "with-mio")]
|
||||
poll_event_capacity: 4096,
|
||||
#[cfg(feature = "with-mio")]
|
||||
|
|
@ -187,3 +194,10 @@ impl Default for StatisticsConfig {
|
|||
Self { interval: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Config;
|
||||
|
||||
::toml_config::gen_serialize_deserialize_test!(Config);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ rand_distr = "0.4"
|
|||
rustls = { version = "0.20", features = ["dangerous_configuration"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
toml_config = "0.1.0"
|
||||
tungstenite = "0.16"
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ use std::net::SocketAddr;
|
|||
use aquatic_cli_helpers::LogLevel;
|
||||
#[cfg(feature = "cpu-pinning")]
|
||||
use aquatic_common::cpu_pinning::CpuPinningConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Deserialize;
|
||||
use toml_config::TomlConfig;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
/// aquatic_ws_load_test configuration
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Config {
|
||||
pub server_address: SocketAddr,
|
||||
|
|
@ -41,7 +43,7 @@ impl Default for Config {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct TorrentConfig {
|
||||
pub offers_per_request: usize,
|
||||
|
|
@ -72,3 +74,10 @@ impl Default for TorrentConfig {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Config;
|
||||
|
||||
::toml_config::gen_serialize_deserialize_test!(Config);
|
||||
}
|
||||
|
|
|
|||
21
toml_config/Cargo.toml
Normal file
21
toml_config/Cargo.toml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "toml_config"
|
||||
version = "0.1.0"
|
||||
authors = ["Joakim Frostegård <joakim.frostegard@gmail.com>"]
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
description = "WebTorrent tracker protocol"
|
||||
repository = "https://github.com/greatest-ape/aquatic"
|
||||
exclude = ["target"]
|
||||
|
||||
[lib]
|
||||
name = "toml_config"
|
||||
|
||||
[dependencies]
|
||||
toml = "0.5"
|
||||
toml_config_derive = { path = "../toml_config_derive" }
|
||||
|
||||
[dev-dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
quickcheck = "1"
|
||||
quickcheck_macros = "1"
|
||||
126
toml_config/src/lib.rs
Normal file
126
toml_config/src/lib.rs
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
pub use toml;
|
||||
pub use toml_config_derive::TomlConfig;
|
||||
|
||||
/// Run this on your struct implementing TomlConfig to generate a
|
||||
/// serialization/deserialization test for it.
|
||||
#[macro_export]
|
||||
macro_rules! gen_serialize_deserialize_test {
|
||||
($ident:ident) => {
|
||||
#[test]
|
||||
fn test_cargo_toml_serialize_deserialize() {
|
||||
use ::toml_config::TomlConfig;
|
||||
let serialized = $ident::default_to_string();
|
||||
let deserialized = ::toml_config::toml::de::from_str(&serialized).unwrap();
|
||||
|
||||
assert_eq!($ident::default(), deserialized);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Export structs to toml, converting Rust doc strings to comments.
|
||||
///
|
||||
/// Supports one level of nesting. Fields containing structs must come
|
||||
/// after regular fields.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```
|
||||
/// use toml_config::TomlConfig;
|
||||
///
|
||||
/// #[derive(TomlConfig)]
|
||||
/// struct SubConfig {
|
||||
/// /// A
|
||||
/// a: usize,
|
||||
/// /// B
|
||||
/// b: String,
|
||||
/// }
|
||||
///
|
||||
/// impl Default for SubConfig {
|
||||
/// fn default() -> Self {
|
||||
/// Self {
|
||||
/// a: 200,
|
||||
/// b: "subconfig hello".into(),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[derive(TomlConfig)]
|
||||
/// struct Config {
|
||||
/// /// A
|
||||
/// a: usize,
|
||||
/// /// B
|
||||
/// b: String,
|
||||
/// /// C
|
||||
/// c: SubConfig,
|
||||
/// }
|
||||
///
|
||||
/// impl Default for Config {
|
||||
/// fn default() -> Self {
|
||||
/// Self {
|
||||
/// a: 100,
|
||||
/// b: "hello".into(),
|
||||
/// c: Default::default(),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let expected = "# A\na = 100\n# B\nb = \"hello\"\n\n# C\n[c]\n# A\na = 200\n# B\nb = \"subconfig hello\"\n";
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// Config::default_to_string(),
|
||||
/// expected,
|
||||
/// );
|
||||
/// ```
|
||||
pub trait TomlConfig: Default {
|
||||
fn default_to_string() -> String;
|
||||
}
|
||||
|
||||
pub mod __private {
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub trait Private {
|
||||
fn __to_string(&self, comment: Option<String>, field_name: String) -> String;
|
||||
}
|
||||
|
||||
macro_rules! impl_trait {
|
||||
($ident:ident) => {
|
||||
impl Private for $ident {
|
||||
fn __to_string(&self, comment: Option<String>, field_name: String) -> String {
|
||||
let mut output = String::new();
|
||||
|
||||
if let Some(comment) = comment {
|
||||
output.push_str(&comment);
|
||||
}
|
||||
|
||||
let value = crate::toml::ser::to_string(self).unwrap();
|
||||
|
||||
output.push_str(&format!("{} = {}\n", field_name, value));
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_trait!(isize);
|
||||
impl_trait!(i8);
|
||||
impl_trait!(i16);
|
||||
impl_trait!(i32);
|
||||
impl_trait!(i64);
|
||||
|
||||
impl_trait!(usize);
|
||||
impl_trait!(u8);
|
||||
impl_trait!(u16);
|
||||
impl_trait!(u32);
|
||||
impl_trait!(u64);
|
||||
|
||||
impl_trait!(f32);
|
||||
impl_trait!(f64);
|
||||
|
||||
impl_trait!(bool);
|
||||
|
||||
impl_trait!(String);
|
||||
|
||||
impl_trait!(PathBuf);
|
||||
impl_trait!(SocketAddr);
|
||||
}
|
||||
46
toml_config/tests/test.rs
Normal file
46
toml_config/tests/test.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
use toml_config::{gen_serialize_deserialize_test, TomlConfig};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, TomlConfig, Deserialize)]
|
||||
struct TestConfigInnerA {
|
||||
/// Comment for a
|
||||
a: String,
|
||||
/// Comment for b
|
||||
b: usize,
|
||||
}
|
||||
|
||||
impl Default for TestConfigInnerA {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
a: "Inner hello world".into(),
|
||||
b: 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Comment for TestConfig
|
||||
#[derive(Clone, Debug, PartialEq, Eq, TomlConfig, Deserialize)]
|
||||
struct TestConfig {
|
||||
/// Comment for a that stretches over
|
||||
/// multiple lines
|
||||
a: String,
|
||||
/// Comment for b
|
||||
b: usize,
|
||||
c: bool,
|
||||
/// Comment for TestConfigInnerA
|
||||
inner_a: TestConfigInnerA,
|
||||
}
|
||||
|
||||
impl Default for TestConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
a: "Hello, world!".into(),
|
||||
b: 100,
|
||||
c: true,
|
||||
inner_a: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gen_serialize_deserialize_test!(TestConfig);
|
||||
17
toml_config_derive/Cargo.toml
Normal file
17
toml_config_derive/Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "toml_config_derive"
|
||||
version = "0.1.0"
|
||||
authors = ["Joakim Frostegård <joakim.frostegard@gmail.com>"]
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
description = "WebTorrent tracker protocol"
|
||||
repository = "https://github.com/greatest-ape/aquatic"
|
||||
exclude = ["target"]
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
syn = "1"
|
||||
177
toml_config_derive/src/lib.rs
Normal file
177
toml_config_derive/src/lib.rs
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
use proc_macro2::{TokenStream, TokenTree};
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, Attribute, Data, DataStruct, DeriveInput, Fields, Ident, Type};
|
||||
|
||||
#[proc_macro_derive(TomlConfig)]
|
||||
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
let comment = extract_comment_string(input.attrs);
|
||||
let ident = input.ident;
|
||||
|
||||
match input.data {
|
||||
Data::Struct(struct_data) => {
|
||||
let mut output_stream = quote! {
|
||||
let mut output = String::new();
|
||||
};
|
||||
|
||||
extract_from_struct(ident.clone(), struct_data, &mut output_stream);
|
||||
|
||||
proc_macro::TokenStream::from(quote! {
|
||||
impl ::toml_config::TomlConfig for #ident {
|
||||
fn default_to_string() -> String {
|
||||
let mut output = String::new();
|
||||
|
||||
let comment: Option<String> = #comment;
|
||||
|
||||
if let Some(comment) = comment {
|
||||
output.push_str(&comment);
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
let body = {
|
||||
#output_stream
|
||||
|
||||
output
|
||||
};
|
||||
|
||||
output.push_str(&body);
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
impl ::toml_config::__private::Private for #ident {
|
||||
fn __to_string(&self, comment: Option<String>, field_name: String) -> String {
|
||||
let mut output = String::new();
|
||||
|
||||
output.push('\n');
|
||||
|
||||
if let Some(comment) = comment {
|
||||
output.push_str(&comment);
|
||||
}
|
||||
output.push_str(&format!("[{}]\n", field_name));
|
||||
|
||||
let body = {
|
||||
#output_stream
|
||||
|
||||
output
|
||||
};
|
||||
|
||||
output.push_str(&body);
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Data::Enum(_) => proc_macro::TokenStream::from(quote! {
|
||||
impl ::toml_config::__private::Private for #ident {
|
||||
fn __to_string(&self, comment: Option<String>, field_name: String) -> String {
|
||||
let mut output = String::new();
|
||||
let wrapping_comment: Option<String> = #comment;
|
||||
|
||||
if let Some(comment) = wrapping_comment {
|
||||
output.push_str(&comment);
|
||||
}
|
||||
|
||||
if let Some(comment) = comment {
|
||||
output.push_str(&comment);
|
||||
}
|
||||
|
||||
let value = match ::toml_config::toml::ser::to_string(self) {
|
||||
Ok(value) => value,
|
||||
Err(err) => panic!("Couldn't serialize enum to toml: {:#}", err),
|
||||
};
|
||||
|
||||
output.push_str(&format!("{} = {}\n", field_name, value));
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
}),
|
||||
Data::Union(_) => panic!("Unions are not supported"),
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_from_struct(
|
||||
struct_ty_ident: Ident,
|
||||
struct_data: DataStruct,
|
||||
output_stream: &mut TokenStream,
|
||||
) {
|
||||
let fields = if let Fields::Named(fields) = struct_data.fields {
|
||||
fields
|
||||
} else {
|
||||
panic!("Fields are not named");
|
||||
};
|
||||
|
||||
output_stream.extend(::std::iter::once(quote! {
|
||||
let struct_default = #struct_ty_ident::default();
|
||||
}));
|
||||
|
||||
for field in fields.named.into_iter() {
|
||||
let ident = field.ident.expect("Encountered unnamed field");
|
||||
let ident_string = format!("{}", ident);
|
||||
let comment = extract_comment_string(field.attrs);
|
||||
|
||||
if let Type::Path(path) = field.ty {
|
||||
output_stream.extend(::std::iter::once(quote! {
|
||||
{
|
||||
let comment: Option<String> = #comment;
|
||||
let field_default: #path = struct_default.#ident;
|
||||
|
||||
let s: String = ::toml_config::__private::Private::__to_string(
|
||||
&field_default,
|
||||
comment,
|
||||
#ident_string.to_string()
|
||||
);
|
||||
output.push_str(&s);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_comment_string(attrs: Vec<Attribute>) -> TokenStream {
|
||||
let mut output = String::new();
|
||||
|
||||
for attr in attrs.into_iter() {
|
||||
let path_ident = if let Some(path_ident) = attr.path.get_ident() {
|
||||
path_ident
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if format!("{}", path_ident) != "doc" {
|
||||
continue;
|
||||
}
|
||||
|
||||
for token_tree in attr.tokens {
|
||||
match token_tree {
|
||||
TokenTree::Literal(literal) => {
|
||||
let mut comment = format!("{}", literal);
|
||||
|
||||
// Strip leading and trailing quotation marks
|
||||
comment.remove(comment.len() - 1);
|
||||
comment.remove(0);
|
||||
|
||||
// Add toml comment indicator
|
||||
comment.insert(0, '#');
|
||||
|
||||
output.push_str(&comment);
|
||||
output.push('\n');
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if output.is_empty() {
|
||||
quote! {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
Some(#output.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue