Add default configuration and ability to generate default configuration

pull/172/head
Chip Senkbeil 1 year ago
parent 27dc5775f9
commit 55036478a0
No known key found for this signature in database
GPG Key ID: 35EF1F8EC72A4131

1
.gitignore vendored

@ -2,3 +2,4 @@
**/.DS_Store
/distant-core/Cargo.lock
/distant-ssh2/Cargo.lock
**/*.un~

5
Cargo.lock generated

@ -922,6 +922,7 @@ dependencies = [
"schemars",
"serde",
"serde_bytes",
"serde_json",
"sha2 0.10.6",
"strum",
"tempfile",
@ -2855,9 +2856,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.89"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db"
checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
dependencies = [
"itoa",
"ryu",

@ -36,5 +36,6 @@ schemars = { version = "0.8.11", optional = true }
[dev-dependencies]
env_logger = "0.9.3"
serde_json = "1.0.94"
tempfile = "3.3.0"
test-log = "0.2.11"

@ -1,13 +1,14 @@
use derive_more::Display;
use serde::{Deserialize, Serialize};
use serde::{de, Deserialize, Serialize};
use std::{
fmt,
net::{IpAddr, SocketAddr},
ops::RangeInclusive,
str::FromStr,
};
/// Represents some range of ports
#[derive(Copy, Clone, Debug, Display, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, Display, PartialEq, Eq)]
#[display(
fmt = "{}{}",
start,
@ -87,6 +88,140 @@ impl FromStr for PortRange {
}
}
impl Serialize for PortRange {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
String::serialize(&self.to_string(), serializer)
}
}
impl<'de> Deserialize<'de> for PortRange {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
struct PortRangeVisitor;
impl<'de> de::Visitor<'de> for PortRangeVisitor {
type Value = PortRange;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "a port in the form NUMBER or START:END")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
FromStr::from_str(s).map_err(de::Error::custom)
}
fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(PortRange {
start: v as u16,
end: None,
})
}
fn visit_u16<E>(self, v: u16) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(PortRange {
start: v,
end: None,
})
}
fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(PortRange {
start: v.try_into().map_err(de::Error::custom)?,
end: None,
})
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(PortRange {
start: v.try_into().map_err(de::Error::custom)?,
end: None,
})
}
fn visit_u128<E>(self, v: u128) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(PortRange {
start: v.try_into().map_err(de::Error::custom)?,
end: None,
})
}
fn visit_i8<E>(self, v: i8) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(PortRange {
start: v.try_into().map_err(de::Error::custom)?,
end: None,
})
}
fn visit_i16<E>(self, v: i16) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(PortRange {
start: v.try_into().map_err(de::Error::custom)?,
end: None,
})
}
fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(PortRange {
start: v.try_into().map_err(de::Error::custom)?,
end: None,
})
}
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(PortRange {
start: v.try_into().map_err(de::Error::custom)?,
end: None,
})
}
fn visit_i128<E>(self, v: i128) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(PortRange {
start: v.try_into().map_err(de::Error::custom)?,
end: None,
})
}
}
deserializer.deserialize_any(PortRangeVisitor)
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -187,4 +322,58 @@ mod tests {
}
);
}
#[test]
fn serialize_should_leverage_tostring() {
assert_eq!(
serde_json::to_value(PortRange {
start: 123,
end: None,
})
.unwrap(),
serde_json::Value::String("123".to_string())
);
assert_eq!(
serde_json::to_value(PortRange {
start: 123,
end: Some(456),
})
.unwrap(),
serde_json::Value::String("123:456".to_string())
);
}
#[test]
fn deserialize_should_use_single_number_as_start() {
// Supports parsing numbers
assert_eq!(
serde_json::from_str::<PortRange>("123").unwrap(),
PortRange {
start: 123,
end: None
}
);
}
#[test]
fn deserialize_should_leverage_fromstr_for_strings() {
// Supports string number
assert_eq!(
serde_json::from_str::<PortRange>("\"123\"").unwrap(),
PortRange {
start: 123,
end: None
}
);
// Supports string start:end
assert_eq!(
serde_json::from_str::<PortRange>("\"123:456\"").unwrap(),
PortRange {
start: 123,
end: Some(456)
}
);
}
}

@ -29,7 +29,7 @@ impl Default for ServerConfig {
}
/// Rules for how a server will shut itself down automatically
#[derive(Copy, Clone, Debug, Display, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, Display, PartialEq, Eq)]
pub enum Shutdown {
/// Server should shutdown immediately after duration exceeded
#[display(fmt = "after={}", "_0.as_secs_f32()")]
@ -105,3 +105,22 @@ impl FromStr for Shutdown {
}
}
}
impl Serialize for Shutdown {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
String::serialize(&self.to_string(), serializer)
}
}
impl<'de> Deserialize<'de> for Shutdown {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
FromStr::from_str(&s).map_err(serde::de::Error::custom)
}
}

@ -1,4 +1,8 @@
use crate::{cli::Opt, config::GenerateConfig, CliResult};
use crate::{
cli::Opt,
config::{Config, GenerateConfig},
CliResult,
};
use anyhow::Context;
use clap::{CommandFactory, Subcommand};
use clap_complete::{generate as clap_generate, Shell};
@ -10,6 +14,12 @@ use std::{fs, io, path::PathBuf};
#[derive(Debug, Subcommand)]
pub enum GenerateSubcommand {
/// Generate configuration file with base settings
Config {
/// Path to where the configuration file should be created
file: PathBuf,
},
/// Generate JSON schema for server request/response
Schema {
/// If specified, will output to the file at the given path instead of stdout
@ -37,6 +47,10 @@ impl GenerateSubcommand {
async fn async_run(self) -> CliResult {
match self {
Self::Config { file } => tokio::fs::write(file, Config::default_raw_str())
.await
.context("Failed to write default config to {file:?}")?,
Self::Schema { file } => {
let request_schema =
serde_json::to_value(&Request::<DistantMsg<DistantRequestData>>::root_schema())

@ -1,5 +1,6 @@
use crate::paths;
use anyhow::Context;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::{
io,
@ -21,8 +22,10 @@ pub use manager::*;
pub use network::*;
pub use server::*;
const DEFAULT_RAW_STR: &str = include_str!("config.toml");
/// Represents configuration settings for all of distant
#[derive(Debug, Default, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Config {
pub client: ClientConfig,
pub generate: GenerateConfig,
@ -31,6 +34,11 @@ pub struct Config {
}
impl Config {
/// Returns a reference to the default config file as a raw str.
pub const fn default_raw_str() -> &'static str {
DEFAULT_RAW_STR
}
/// Loads the configuration from multiple sources in a blocking fashion
///
/// 1. If `custom` is provided, it is used by itself as the source for configuration
@ -124,3 +132,209 @@ impl Config {
tokio::fs::write(path, text).await
}
}
impl Default for Config {
fn default() -> Self {
static DEFAULT_CONFIG: Lazy<Config> = Lazy::new(|| {
toml_edit::de::from_str(Config::default_raw_str())
.expect("Default config failed to parse")
});
DEFAULT_CONFIG.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
use distant_core::net::common::{Host, Map, PortRange};
use distant_core::net::map;
use distant_core::net::server::Shutdown;
use std::net::Ipv4Addr;
use std::time::Duration;
use test_log::test;
#[test]
fn default_should_parse_config_from_internal_toml() {
let config = Config::default();
assert_eq!(
config,
Config {
client: ClientConfig {
action: ClientActionConfig { timeout: Some(0.) },
common: CommonConfig {
log_level: Some(LogLevel::Info),
log_file: None
},
connect: ClientConnectConfig {
options: Map::new()
},
launch: ClientLaunchConfig {
distant: ClientLaunchDistantConfig {
bin: Some("distant".to_owned()),
bind_server: Some(BindAddress::Ssh),
args: Some("".to_string())
},
options: Map::new(),
},
network: NetworkConfig {
unix_socket: None,
windows_pipe: None
},
repl: ClientReplConfig { timeout: Some(0.) },
},
generate: GenerateConfig {
common: CommonConfig {
log_level: Some(LogLevel::Info),
log_file: None
},
},
manager: ManagerConfig {
access: Some(AccessControl::Owner),
common: CommonConfig {
log_level: Some(LogLevel::Info),
log_file: None
},
network: NetworkConfig {
unix_socket: None,
windows_pipe: None
},
},
server: ServerConfig {
common: CommonConfig {
log_level: Some(LogLevel::Info),
log_file: None
},
listen: ServerListenConfig {
host: Some(BindAddress::Any),
port: Some(0.into()),
use_ipv6: false,
shutdown: Some(Shutdown::Never),
current_dir: None,
},
},
}
);
}
#[test(tokio::test)]
async fn default_should_parse_config_from_specified_file() {
use assert_fs::prelude::*;
let config_file = assert_fs::NamedTempFile::new("config.toml").unwrap();
config_file
.write_str(
r#"
[client]
log_file = "client-log-file"
log_level = "trace"
unix_socket = "client-unix-socket"
windows_pipe = "client-windows-pipe"
[client.action]
timeout = 123
[client.connect]
options = "key=\"value\",key2=\"value2\""
[client.launch]
bin = "some-bin"
bind_server = "any"
args = "a b c"
options = "key3=\"value3\",key4=\"value4\""
[client.repl]
timeout = 456
[generate]
log_file = "generate-log-file"
log_level = "debug"
[manager]
log_file = "manager-log-file"
log_level = "warn"
access = "anyone"
unix_socket = "manager-unix-socket"
windows_pipe = "manager-windows-pipe"
[server]
log_file = "server-log-file"
log_level = "error"
[server.listen]
host = "127.0.0.1"
port = "8080:8089"
use_ipv6 = true
shutdown = "after=123"
current_dir = "server-current-dir"
"#,
)
.unwrap();
let config = Config::load(config_file.path()).await.unwrap();
assert_eq!(
config,
Config {
client: ClientConfig {
action: ClientActionConfig {
timeout: Some(123.)
},
common: CommonConfig {
log_level: Some(LogLevel::Trace),
log_file: Some(PathBuf::from("client-log-file")),
},
connect: ClientConnectConfig {
options: map!("key" -> "value", "key2" -> "value2"),
},
launch: ClientLaunchConfig {
distant: ClientLaunchDistantConfig {
bin: Some("some-bin".to_owned()),
bind_server: Some(BindAddress::Any),
args: Some(String::from("a b c"))
},
options: map!("key3" -> "value3", "key4" -> "value4"),
},
network: NetworkConfig {
unix_socket: Some(PathBuf::from("client-unix-socket")),
windows_pipe: Some(String::from("client-windows-pipe"))
},
repl: ClientReplConfig {
timeout: Some(456.)
},
},
generate: GenerateConfig {
common: CommonConfig {
log_level: Some(LogLevel::Debug),
log_file: Some(PathBuf::from("generate-log-file"))
},
},
manager: ManagerConfig {
access: Some(AccessControl::Anyone),
common: CommonConfig {
log_level: Some(LogLevel::Warn),
log_file: Some(PathBuf::from("manager-log-file"))
},
network: NetworkConfig {
unix_socket: Some(PathBuf::from("manager-unix-socket")),
windows_pipe: Some(String::from("manager-windows-pipe")),
},
},
server: ServerConfig {
common: CommonConfig {
log_level: Some(LogLevel::Error),
log_file: Some(PathBuf::from("server-log-file")),
},
listen: ServerListenConfig {
host: Some(BindAddress::Host(Host::Ipv4(Ipv4Addr::new(127, 0, 0, 1)))),
port: Some(PortRange {
start: 8080,
end: Some(8089)
}),
use_ipv6: true,
shutdown: Some(Shutdown::After(Duration::from_secs(123))),
current_dir: Some(PathBuf::from("server-current-dir")),
},
},
}
);
}
}

@ -0,0 +1,178 @@
###############################################################################
# All configuration specific to the distant client will be found under
# this heading
###############################################################################
[client]
# Specifies an alternative path to use when logging information while the
# client is running
# log_file = "path/to/file"
# Specifies the log level used when logging information while the client
# is running
#
# Choices are off, error, warn, info, debug, trace
# The default setting is info
log_level = "info"
# Alternative unix domain socket to connect to when using a manger (Unix only)
# unix_socket = "path/to/socket"
# Alternative name for a local named Windows pipe to connect to when using a
# manager (Windows only)
# windows_pipe = "some_name"
# Configuration related to the client's action command
[client.action]
# Maximum time (in seconds) to wait for a network request before timing out
# where 0 indicates no timeout will occur
timeout = 0
# Configuration related to the client's connect command
[client.connect]
# Additional options to provide, typically forwarded to the handler within
# the manager facilitating the connection. Options are key-value pairs separated
# by comma.
#
# E.g. `key="value",key2="value2"`
options = ""
# Configuration related to the client's launch command
[client.launch]
# Path to distant program on remote machine to execute via ssh; by default,
# this program needs to be available within PATH as specified when compiling
# ssh (not your login shell).
bin = "distant"
# Control the IP address that the server binds to.
#
# The default is `ssh', in which case the server will reply from the IP address
# that the SSH connection came from (as found in the SSH_CONNECTION environment
# variable). This is useful for multihomed servers.
#
# With --bind-server=any, the server will reply on the default interface and
# will not bind to a particular IP address. This can be useful if the
# connection is made through sslh or another tool that makes the SSH connection
# appear to come from localhost.
#
# With --bind-server=IP, the server will attempt to bind to the specified IP
# address.
bind_server = "ssh"
# Additional arguments to provide to the server when launching it.
args = ""
# Additional options to provide, typically forwarded to the handler within the
# manager facilitating the launch of a distant server. Options are key-value
# pairs separated by comma.
#
# E.g. `key="value",key2="value2"`
options = ""
# Configuration related to the client's repl command
[client.repl]
# Maximum time (in seconds) to wait for a network request before timing out
# where 0 indicates no timeout will occur
timeout = 0
###############################################################################
# All configuration specific to the distant generate option will be found under
# this heading
###############################################################################
[generate]
# Specifies an alternative path to use when logging information related
# to generation
# log_file = "path/to/file"
# Specifies the log level used when logging information related to generation
# Choices are off, error, warn, info, debug, trace
# The default setting is info
log_level = "info"
###############################################################################
# All configuration specific to the distant manager will be found under
# this heading
###############################################################################
[manager]
# Specifies an alternative path to use when logging information while the
# manager is running
# log_file = "path/to/file"
# Specifies the log level used when logging information while the manager
# is running
#
# Choices are off, error, warn, info, debug, trace
# The default setting is info
log_level = "info"
# Level of access control to the unix socket or windows pipe.
#
# * "owner": equates to `0o600` on Unix (read & write for owner).
# * "group": equates to `0o660` on Unix (read & write for owner and group).
# # "anyone": equates to `0o666` on Unix (read & write for owner, group, and other).
access = "owner"
# Alternative unix domain socket to listen on (Unix only)
# unix_socket = "path/to/socket"
# Alternative name for a local named Windows pipe to listen on (Windows only)
# windows_pipe = "some_name"
###############################################################################
# All configuration specific to the distant server will be found under
# this heading
###############################################################################
[server]
# Specifies an alternative path to use when logging information while the
# server is running
# log_file = "path/to/file"
# Specifies the log level used when logging information while the server
# is running
#
# Choices are off, error, warn, info, debug, trace
# The default setting is info
log_level = "info"
# Configuration related to the server's listen command
[server.listen]
# IP address that the server will bind to. This can be one of three things:
#
# 1. "ssh": the server will reply from the IP address that the SSH connection
# came from (as found in the SSH_CONNECTION environment variable).
# This is useful for multihomed servers.
# 2. "any": the server will reply on the default interface and will not bind to
# a particular IP address. This can be useful if the connection is
# made through ssh or another tool that makes the SSH connection
# appear to come from localhost.
# 3. "{IP}": the server will attempt to bind to the specified IP address.
host = "any"
# Set the port(s) that the server will attempt to bind to.
#
# This can be in the form of PORT1 or PORT1:PORTN to provide a range of ports.
# With "0", the server will let the operating system pick an available TCP port.
#
# Please note that this option does not affect the server-side port used by SSH.
port = "0"
# If true, will bind to the ipv6 interface if host is any instead of ipv4
use_ipv6 = false
# Logic to apply to server when determining when to shutdown automatically.
#
# 1. "never" means the server will never automatically shut down
# 2. "after=<N>" means the server will shut down after N seconds
# 3. "lonely=<N>" means the server will shut down after N seconds with no connections
shutdown = "never"
# Changes the current working directory (cwd) to the specified directory.
# current_dir = "path/to/dir"

@ -12,7 +12,7 @@ pub use launch::*;
pub use repl::*;
/// Represents configuration settings for the distant client
#[derive(Debug, Default, Serialize, Deserialize)]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct ClientConfig {
#[serde(flatten)]
pub common: CommonConfig,

@ -1,7 +1,7 @@
use clap::Args;
use serde::{Deserialize, Serialize};
#[derive(Args, Debug, Default, Serialize, Deserialize)]
#[derive(Args, Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct ClientActionConfig {
/// Represents the maximum time (in seconds) to wait for a network request before timing out
pub timeout: Option<f32>,

@ -2,14 +2,13 @@ use clap::Args;
use distant_core::net::common::Map;
use serde::{Deserialize, Serialize};
#[derive(Args, Debug, Default, Serialize, Deserialize)]
#[derive(Args, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ClientConnectConfig {
/// Additional options to provide, typically forwarded to the handler within the manager
/// facilitating the connection. Options are key-value pairs separated by comma.
///
/// E.g. `key="value",key2="value2"`
#[clap(long, default_value_t)]
#[serde(flatten)]
pub options: Map,
}

@ -3,7 +3,7 @@ use clap::Args;
use distant_core::net::common::Map;
use serde::{Deserialize, Serialize};
#[derive(Args, Debug, Default, Serialize, Deserialize)]
#[derive(Args, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ClientLaunchConfig {
#[clap(flatten)]
#[serde(flatten)]
@ -15,7 +15,6 @@ pub struct ClientLaunchConfig {
///
/// E.g. `key="value",key2="value2"`
#[clap(long, default_value_t)]
#[serde(flatten)]
pub options: Map,
}
@ -56,7 +55,7 @@ impl From<ClientLaunchConfig> for Map {
}
}
#[derive(Args, Debug, Default, Serialize, Deserialize)]
#[derive(Args, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ClientLaunchDistantConfig {
/// Path to distant program on remote machine to execute via ssh;
/// by default, this program needs to be available within PATH as

@ -1,7 +1,7 @@
use clap::Args;
use serde::{Deserialize, Serialize};
#[derive(Args, Debug, Default, Serialize, Deserialize)]
#[derive(Args, Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct ClientReplConfig {
/// Represents the maximum time (in seconds) to wait for a network request before timing out
pub timeout: Option<f32>,

@ -34,7 +34,7 @@ impl Default for LogLevel {
}
/// Contains options that are common across subcommands
#[derive(Args, Clone, Debug, Default, Serialize, Deserialize)]
#[derive(Args, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct CommonConfig {
/// Log level to use throughout the application
#[clap(long, global = true, value_enum)]

@ -2,7 +2,7 @@ use super::CommonConfig;
use serde::{Deserialize, Serialize};
/// Represents configuration settings for the distant generate
#[derive(Debug, Default, Serialize, Deserialize)]
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct GenerateConfig {
#[serde(flatten)]
pub common: CommonConfig,

@ -1,11 +1,9 @@
use super::{AccessControl, CommonConfig, NetworkConfig};
use clap::Args;
use distant_core::net::common::Destination;
use serde::{Deserialize, Serialize};
use service_manager::ServiceManagerKind;
/// Represents configuration settings for the distant manager
#[derive(Args, Debug, Default, Serialize, Deserialize)]
#[derive(Args, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ManagerConfig {
/// Type of access to apply to created unix socket or windows pipe
#[clap(long, value_enum)]
@ -15,35 +13,7 @@ pub struct ManagerConfig {
#[serde(flatten)]
pub common: CommonConfig,
#[clap(skip)]
pub connections: Vec<ManagerConnectionConfig>,
#[clap(flatten)]
#[serde(flatten)]
pub network: NetworkConfig,
#[clap(value_enum)]
pub service: Option<ServiceManagerKind>,
}
/// Represents configuration for some managed connection
#[derive(Debug, Serialize, Deserialize)]
pub enum ManagerConnectionConfig {
Distant(ManagerDistantConnectionConfig),
Ssh(ManagerSshConnectionConfig),
}
/// Represents configuration for a distant connection
#[derive(Debug, Serialize, Deserialize)]
pub struct ManagerDistantConnectionConfig {
pub name: String,
pub destination: Destination,
pub key_cmd: Option<String>,
}
/// Represents configuration for an SSH connection
#[derive(Debug, Serialize, Deserialize)]
pub struct ManagerSshConnectionConfig {
pub name: String,
pub destination: Destination,
}

@ -35,7 +35,7 @@ impl Default for AccessControl {
}
/// Represents common networking configuration
#[derive(Args, Clone, Debug, Default, Serialize, Deserialize)]
#[derive(Args, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct NetworkConfig {
/// Override the path to the Unix socket used by the manager (unix-only)
#[clap(long)]

@ -5,7 +5,7 @@ mod listen;
pub use listen::*;
/// Represents configuration settings for the distant server
#[derive(Debug, Default, Serialize, Deserialize)]
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ServerConfig {
#[serde(flatten)]
pub common: CommonConfig,

@ -10,7 +10,7 @@ use std::{
str::FromStr,
};
#[derive(Args, Debug, Default, Serialize, Deserialize)]
#[derive(Args, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ServerListenConfig {
/// Control the IP address that the distant binds to
///
@ -104,7 +104,7 @@ impl From<ServerListenConfig> for Map {
}
/// Represents options for binding a server to an IP address
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BindAddress {
/// Should read address from `SSH_CONNECTION` environment variable, which contains four
/// space-separated values:
@ -148,6 +148,25 @@ impl FromStr for BindAddress {
}
}
impl Serialize for BindAddress {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
String::serialize(&self.to_string(), serializer)
}
}
impl<'de> Deserialize<'de> for BindAddress {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
FromStr::from_str(&s).map_err(serde::de::Error::custom)
}
}
impl BindAddress {
/// Resolves address into valid IP; in the case of "any", will leverage the
/// `use_ipv6` flag to determine if binding should use ipv4 or ipv6

Loading…
Cancel
Save