|
|
|
@ -1,7 +1,57 @@ |
|
|
|
use std::path::Path;
|
|
|
|
|
|
|
|
use anyhow::Context;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use solstice_proto::server::Credentials;
|
|
|
|
use tokio::fs::File;
|
|
|
|
use tokio::io::AsyncReadExt;
|
|
|
|
|
|
|
|
/// Represents a TOML configuration file.
|
|
|
|
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
|
|
|
pub struct TomlConfig {
|
|
|
|
/// The address of the server to connect to.
|
|
|
|
/// If `None`, a default is used.
|
|
|
|
pub server_address: Option<String>,
|
|
|
|
|
|
|
|
/// User name with which to log in to the server.
|
|
|
|
user_name: String,
|
|
|
|
|
|
|
|
/// Password with which to log in to the server.
|
|
|
|
password: String,
|
|
|
|
|
|
|
|
/// The address on which to listen for incoming peer connections.
|
|
|
|
/// If `None`, a default value is used.
|
|
|
|
pub peer_listen_address: Option<String>,
|
|
|
|
|
|
|
|
/// The address on which to listen for incoming control connections.
|
|
|
|
/// If `None`, a default value is used.
|
|
|
|
pub control_listen_address: Option<String>,
|
|
|
|
|
|
|
|
/// The maximum number of peer connections allowed at once.
|
|
|
|
/// If `None`, a default value is used.
|
|
|
|
pub max_peers: Option<usize>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TomlConfig {
|
|
|
|
fn from_str(s: &str) -> Result<Self, toml::de::Error> {
|
|
|
|
toml::from_str(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn from_file_path(path: &Path) -> anyhow::Result<Self> {
|
|
|
|
let mut file =
|
|
|
|
File::open(path).await.context("opening toml config file")?;
|
|
|
|
|
|
|
|
let mut contents = String::new();
|
|
|
|
file
|
|
|
|
.read_to_string(&mut contents)
|
|
|
|
.await
|
|
|
|
.context("reading toml config file")?;
|
|
|
|
|
|
|
|
// TODO: Implement reading this from .toml files.
|
|
|
|
#[derive(Debug)]
|
|
|
|
Self::from_str(&contents).context("parsing toml config file")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
|
|
pub struct Config {
|
|
|
|
/// Credentials to present to the server.
|
|
|
|
pub credentials: Credentials,
|
|
|
|
@ -25,11 +75,20 @@ pub struct Config { |
|
|
|
pub max_peers: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Delete in favor of `new`, or mark test-only.
|
|
|
|
impl Default for Config {
|
|
|
|
fn default() -> Self {
|
|
|
|
let credentials =
|
|
|
|
Credentials::new("solstice".to_string(), "topsekrit".to_string())
|
|
|
|
.expect("building default credentials");
|
|
|
|
|
|
|
|
Self::new(credentials)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Config {
|
|
|
|
/// Builds a new config with the given `credentials` and default values.
|
|
|
|
fn new(credentials: Credentials) -> Self {
|
|
|
|
Self {
|
|
|
|
credentials,
|
|
|
|
server_address: "server.slsknet.org:2242".to_string(),
|
|
|
|
@ -39,4 +98,179 @@ impl Default for Config { |
|
|
|
max_peers: 1000,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Attempts to build a `Config` from the given `TomlConfig`.
|
|
|
|
fn from_toml(toml: TomlConfig) -> anyhow::Result<Self> {
|
|
|
|
let credentials = Credentials::new(toml.user_name, toml.password)
|
|
|
|
.context("validating credentials")?;
|
|
|
|
|
|
|
|
let mut config = Self::new(credentials);
|
|
|
|
|
|
|
|
if let Some(server_address) = toml.server_address {
|
|
|
|
config.server_address = server_address;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(peer_listen_address) = toml.peer_listen_address {
|
|
|
|
config.peer_listen_address = peer_listen_address;
|
|
|
|
}
|
|
|
|
if let Some(control_listen_address) = toml.control_listen_address {
|
|
|
|
config.control_listen_address = control_listen_address;
|
|
|
|
}
|
|
|
|
if let Some(max_peers) = toml.max_peers {
|
|
|
|
config.max_peers = max_peers;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(config)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use std::env;
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use tokio::fs::{DirBuilder, File};
|
|
|
|
use tokio::io::AsyncWriteExt;
|
|
|
|
|
|
|
|
use solstice_proto::server::Credentials;
|
|
|
|
|
|
|
|
use super::{Config, TomlConfig};
|
|
|
|
|
|
|
|
async fn make_test_dir(test_name: &Path) -> PathBuf {
|
|
|
|
let path = env::temp_dir()
|
|
|
|
.join("rust_tests/solstice_client/config")
|
|
|
|
.join(test_name);
|
|
|
|
|
|
|
|
DirBuilder::new()
|
|
|
|
.recursive(true)
|
|
|
|
.create(&path)
|
|
|
|
.await
|
|
|
|
.expect(&format!("creating test dir {:?}", path));
|
|
|
|
|
|
|
|
path
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn toml_config_from_str_missing_field() {
|
|
|
|
TomlConfig::from_str(
|
|
|
|
r#"
|
|
|
|
user_name = "emiliano"
|
|
|
|
"#,
|
|
|
|
)
|
|
|
|
.unwrap_err();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn toml_config_from_str_minimal() {
|
|
|
|
let config = TomlConfig::from_str(
|
|
|
|
r#"
|
|
|
|
user_name = "emiliano"
|
|
|
|
password = "zapata"
|
|
|
|
"#,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
config,
|
|
|
|
TomlConfig {
|
|
|
|
user_name: "emiliano".to_string(),
|
|
|
|
password: "zapata".to_string(),
|
|
|
|
server_address: None,
|
|
|
|
peer_listen_address: None,
|
|
|
|
control_listen_address: None,
|
|
|
|
max_peers: None,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn toml_config_from_str_maximal() {
|
|
|
|
let config = TomlConfig::from_str(
|
|
|
|
r#"
|
|
|
|
user_name = "emiliano"
|
|
|
|
password = "zapata"
|
|
|
|
server_address = "foo.example:1234"
|
|
|
|
peer_listen_address = "localhost:1337"
|
|
|
|
control_listen_address = "[::1]:1338"
|
|
|
|
max_peers = 42
|
|
|
|
"#,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
config,
|
|
|
|
TomlConfig {
|
|
|
|
user_name: "emiliano".to_string(),
|
|
|
|
password: "zapata".to_string(),
|
|
|
|
server_address: Some("foo.example:1234".to_string()),
|
|
|
|
peer_listen_address: Some("localhost:1337".to_string()),
|
|
|
|
control_listen_address: Some("[::1]:1338".to_string()),
|
|
|
|
max_peers: Some(42),
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn toml_config_from_file_path() {
|
|
|
|
let dir = make_test_dir(Path::new("toml_config_from_file_path")).await;
|
|
|
|
let path = dir.join("config.toml");
|
|
|
|
|
|
|
|
let mut file = File::create(&path)
|
|
|
|
.await
|
|
|
|
.expect(&format!("creating config file {:?}", path));
|
|
|
|
|
|
|
|
let contents = r#"
|
|
|
|
user_name = "emiliano"
|
|
|
|
password = "zapata"
|
|
|
|
"#;
|
|
|
|
file
|
|
|
|
.write_all(contents.as_bytes())
|
|
|
|
.await
|
|
|
|
.expect("writing config file contents");
|
|
|
|
|
|
|
|
let config = TomlConfig::from_file_path(&path)
|
|
|
|
.await
|
|
|
|
.expect("reading config file");
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
config,
|
|
|
|
TomlConfig {
|
|
|
|
user_name: "emiliano".to_string(),
|
|
|
|
password: "zapata".to_string(),
|
|
|
|
server_address: None,
|
|
|
|
peer_listen_address: None,
|
|
|
|
control_listen_address: None,
|
|
|
|
max_peers: None,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn config_from_toml_fails_empty_password() {
|
|
|
|
Config::from_toml(TomlConfig {
|
|
|
|
user_name: "emiliano".to_string(),
|
|
|
|
password: "".to_string(),
|
|
|
|
server_address: None,
|
|
|
|
peer_listen_address: None,
|
|
|
|
control_listen_address: None,
|
|
|
|
max_peers: None,
|
|
|
|
})
|
|
|
|
.unwrap_err();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn config_from_toml_success() {
|
|
|
|
let config = Config::from_toml(TomlConfig {
|
|
|
|
user_name: "emiliano".to_string(),
|
|
|
|
password: "zapata".to_string(),
|
|
|
|
server_address: None,
|
|
|
|
peer_listen_address: None,
|
|
|
|
control_listen_address: None,
|
|
|
|
max_peers: None,
|
|
|
|
})
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let credentials =
|
|
|
|
Credentials::new("emiliano".to_string(), "zapata".to_string()).unwrap();
|
|
|
|
assert_eq!(config, Config::new(credentials));
|
|
|
|
}
|
|
|
|
}
|