Make serde optional

This commit is contained in:
Kaleb Elwert 2020-01-28 17:58:38 -08:00
parent 2339ca5fe6
commit dd09555537
5 changed files with 81 additions and 74 deletions

View file

@ -2,7 +2,7 @@ language: rust
rust: stable
sudo: false
script:
- cargo test --all --features "toml yaml json"
- cargo test --all --features "toml_config yaml_config json_config"
- cargo build
# No idea how to fix this, since we don't depend on futures-preview directly.
# - rustdoc --test README.md --extern irc=target/debug/libirc.rlib -L target/debug/deps --edition 2018

View file

@ -20,11 +20,16 @@ is-it-maintained-open-issues = { repository = "aatxe/irc" }
members = [ "./", "irc-proto" ]
[features]
default = ["ctcp", "toml"]
default = ["ctcp", "toml_config"]
ctcp = []
nochanlists = []
json = ["serde_json"]
yaml = ["serde_yaml"]
json_config = ["serde", "serde_derive", "serde_json"]
toml_config = ["serde", "serde_derive", "toml"]
yaml_config = ["serde", "serde_derive", "serde_yaml"]
# Temporary transitionary features
json = ["json_config"]
yaml = ["yaml_config"]
[dependencies]
thiserror = "1.0.2"
@ -35,8 +40,8 @@ encoding = "0.2"
irc-proto = { version = "*", path = "irc-proto" }
log = "0.4"
native-tls = "0.2"
serde = { version = "1.0", features = ["derive"] }
serde_derive = "1.0"
serde = { version = "1.0", features = ["derive"], optional = true }
serde_derive = { version = "1.0", optional = true }
tokio = { version = "0.2.4", features = ["time", "net", "stream", "macros", "stream"] }
tokio-util = { version = "0.2.0", features = ["codec"] }
tokio-tls = "0.3.0"

View file

@ -86,9 +86,9 @@ programmatic configuration. Runtime loading is done via the function `Config::lo
sufficient for most IRC bots. Programmatic configuration is convenient for writing tests, but can
also be useful when defining your own custom configuration format that can be converted to `Config`.
The primary configuration format is TOML, but if you are so inclined, you can use JSON and/or YAML
via the optional `json` and `yaml` features respectively. At the minimum, a configuration requires
`nickname` and `server` to be defined, and all other fields are optional. You can find detailed
explanations of the various fields on [docs.rs][config-fields].
via the optional `json_config` and `yaml_config` features respectively. At the minimum, a configuration
requires `nickname` and `server` to be defined, and all other fields are optional. You can find
detailed explanations of the various fields on [docs.rs][config-fields].
[config-fields]: https://docs.rs/irc/*/irc/client/data/config/struct.Config.html#fields

View file

@ -1,4 +1,5 @@
//! JSON configuration files using serde
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
@ -8,15 +9,15 @@ use std::{
};
use tokio::net::ToSocketAddrs;
#[cfg(feature = "json")]
#[cfg(feature = "json_config")]
use serde_json;
#[cfg(feature = "yaml")]
#[cfg(feature = "yaml_config")]
use serde_yaml;
#[cfg(feature = "toml")]
#[cfg(feature = "toml_config")]
use toml;
use crate::error::Error::InvalidConfig;
#[cfg(feature = "toml")]
#[cfg(feature = "toml_config")]
use crate::error::TomlError;
use crate::error::{ConfigError, Result};
@ -65,127 +66,129 @@ use crate::error::{ConfigError, Result};
/// let config = Config::load("config.toml").unwrap();
/// # }
/// ```
#[derive(Clone, Deserialize, Serialize, Default, PartialEq, Debug)]
#[derive(Clone, Default, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Config {
/// A list of the owners of the client by nickname (for bots).
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Vec::is_empty"))]
#[cfg_attr(feature = "serde", serde(default))]
pub owners: Vec<String>,
/// The client's nickname.
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub nickname: Option<String>,
/// The client's NICKSERV password.
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub nick_password: Option<String>,
/// Alternative nicknames for the client, if the default is taken.
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Vec::is_empty"))]
#[cfg_attr(feature = "serde", serde(default))]
pub alt_nicks: Vec<String>,
/// The client's username.
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub username: Option<String>,
/// The client's real name.
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub realname: Option<String>,
/// The server to connect to.
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub server: Option<String>,
/// The port to connect on.
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub port: Option<u16>,
/// The password to connect to the server.
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub password: Option<String>,
/// Whether or not to use SSL.
/// Clients will automatically panic if this is enabled without SSL support.
#[serde(skip_serializing_if = "is_false")]
#[serde(default)]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))]
#[cfg_attr(feature = "serde", serde(default))]
pub use_ssl: bool,
/// The path to the SSL certificate for this server in DER format.
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub cert_path: Option<String>,
/// The path to a SSL certificate to use for CertFP client authentication in DER format.
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub client_cert_path: Option<String>,
/// The password for the certificate to use in CertFP authentication.
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub client_cert_pass: Option<String>,
/// The encoding type used for this connection.
/// This is typically UTF-8, but could be something else.
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub encoding: Option<String>,
/// A list of channels to join on connection.
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Vec::is_empty"))]
#[cfg_attr(feature = "serde", serde(default))]
pub channels: Vec<String>,
/// User modes to set on connect. Example: "+RB -x"
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub umodes: Option<String>,
/// The text that'll be sent in response to CTCP USERINFO requests.
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub user_info: Option<String>,
/// The text that'll be sent in response to CTCP VERSION requests.
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub version: Option<String>,
/// The text that'll be sent in response to CTCP SOURCE requests.
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub source: Option<String>,
/// The amount of inactivity in seconds before the client will ping the server.
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub ping_time: Option<u32>,
/// The amount of time in seconds for a client to reconnect due to no ping response.
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub ping_timeout: Option<u32>,
/// The length in seconds of a rolling window for message throttling. If more than
/// `max_messages_in_burst` messages are sent within `burst_window_length` seconds, additional
/// messages will be delayed automatically as appropriate. In particular, in the past
/// `burst_window_length` seconds, there will never be more than `max_messages_in_burst` messages
/// sent.
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub burst_window_length: Option<u32>,
/// The maximum number of messages that can be sent in a burst window before they'll be delayed.
/// Messages are automatically delayed as appropriate.
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub max_messages_in_burst: Option<u32>,
/// Whether the client should use NickServ GHOST to reclaim its primary nickname if it is in
/// use. This has no effect if `nick_password` is not set.
#[serde(skip_serializing_if = "is_false")]
#[serde(default)]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))]
#[cfg_attr(feature = "serde", serde(default))]
pub should_ghost: bool,
/// The command(s) that should be sent to NickServ to recover a nickname. The nickname and
/// password will be appended in that order after the command.
/// E.g. `["RECOVER", "RELEASE"]` means `RECOVER nick pass` and `RELEASE nick pass` will be sent
/// in that order.
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
#[cfg_attr(feature = "serde", serde(default))]
pub ghost_sequence: Option<Vec<String>>,
/// Whether or not to use a fake connection for testing purposes. You probably will never want
/// to enable this, but it is used in unit testing for the `irc` crate.
#[serde(skip_serializing_if = "is_false")]
#[serde(default)]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))]
#[cfg_attr(feature = "serde", serde(default))]
pub use_mock_connection: bool,
/// The initial value used by the fake connection for testing. You probably will never need to
/// set this, but it is used in unit testing for the `irc` crate.
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub mock_initial_value: Option<String>,
/// A mapping of channel names to keys for join-on-connect.
#[serde(skip_serializing_if = "HashMap::is_empty")]
#[serde(default)]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "HashMap::is_empty"))]
#[cfg_attr(feature = "serde", serde(default))]
pub channel_keys: HashMap<String, String>,
/// A map of additional options to be stored in config.
#[serde(skip_serializing_if = "HashMap::is_empty")]
#[serde(default)]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "HashMap::is_empty"))]
#[cfg_attr(feature = "serde", serde(default))]
pub options: HashMap<String, String>,
/// The path that this configuration was loaded from.
///
/// This should not be specified in any configuration. It will automatically be handled by the library.
#[serde(skip_serializing)]
#[cfg_attr(feature = "serde", serde(skip_serializing))]
#[doc(hidden)]
pub path: Option<PathBuf>,
}
#[cfg(feature = "serde")]
fn is_false(v: &bool) -> bool {
!v
}
@ -230,7 +233,7 @@ impl Config {
res.map(|config| config.with_path(path))
}
#[cfg(feature = "json")]
#[cfg(feature = "json_config")]
fn load_json<P: AsRef<Path>>(path: P, data: &str) -> Result<Config> {
serde_json::from_str(data).map_err(|e| InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
@ -238,7 +241,7 @@ impl Config {
})
}
#[cfg(not(feature = "json"))]
#[cfg(not(feature = "json_config"))]
fn load_json<P: AsRef<Path>>(path: P, _: &str) -> Result<Config> {
Err(InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
@ -246,7 +249,7 @@ impl Config {
})
}
#[cfg(feature = "toml")]
#[cfg(feature = "toml_config")]
fn load_toml<P: AsRef<Path>>(path: P, data: &str) -> Result<Config> {
toml::from_str(data).map_err(|e| InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
@ -254,7 +257,7 @@ impl Config {
})
}
#[cfg(not(feature = "toml"))]
#[cfg(not(feature = "toml_config"))]
fn load_toml<P: AsRef<Path>>(path: P, _: &str) -> Result<Config> {
Err(InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
@ -262,7 +265,7 @@ impl Config {
})
}
#[cfg(feature = "yaml")]
#[cfg(feature = "yaml_config")]
fn load_yaml<P: AsRef<Path>>(path: P, data: &str) -> Result<Config> {
serde_yaml::from_str(data).map_err(|e| InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
@ -270,7 +273,7 @@ impl Config {
})
}
#[cfg(not(feature = "yaml"))]
#[cfg(not(feature = "yaml_config"))]
fn load_yaml<P: AsRef<Path>>(path: P, _: &str) -> Result<Config> {
Err(InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
@ -308,7 +311,7 @@ impl Config {
Ok(())
}
#[cfg(feature = "json")]
#[cfg(feature = "json_config")]
fn save_json<P: AsRef<Path>>(&self, path: &P) -> Result<String> {
serde_json::to_string(self).map_err(|e| InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
@ -316,7 +319,7 @@ impl Config {
})
}
#[cfg(not(feature = "json"))]
#[cfg(not(feature = "json_config"))]
fn save_json<P: AsRef<Path>>(&self, path: &P) -> Result<String> {
Err(InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
@ -324,7 +327,7 @@ impl Config {
})
}
#[cfg(feature = "toml")]
#[cfg(feature = "toml_config")]
fn save_toml<P: AsRef<Path>>(&self, path: &P) -> Result<String> {
toml::to_string(self).map_err(|e| InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
@ -332,7 +335,7 @@ impl Config {
})
}
#[cfg(not(feature = "toml"))]
#[cfg(not(feature = "toml_config"))]
fn save_toml<P: AsRef<Path>>(&self, path: &P) -> Result<String> {
Err(InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
@ -340,7 +343,7 @@ impl Config {
})
}
#[cfg(feature = "yaml")]
#[cfg(feature = "yaml_config")]
fn save_yaml<P: AsRef<Path>>(&self, path: &P) -> Result<String> {
serde_yaml::to_string(self).map_err(|e| InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
@ -348,7 +351,7 @@ impl Config {
})
}
#[cfg(not(feature = "yaml"))]
#[cfg(not(feature = "yaml_config"))]
fn save_yaml<P: AsRef<Path>>(&self, path: &P) -> Result<String> {
Err(InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
@ -561,8 +564,7 @@ impl Config {
#[cfg(test)]
mod test {
use super::Config;
use anyhow::Result;
use super::{Config, Result};
use std::collections::HashMap;
#[allow(unused)]
@ -609,7 +611,7 @@ mod test {
}
#[test]
#[cfg(feature = "json")]
#[cfg(feature = "json_config")]
fn load_from_json() -> Result<()> {
const DATA: &str = include_str!("client_config.json");
assert_eq!(
@ -620,7 +622,7 @@ mod test {
}
#[test]
#[cfg(feature = "toml")]
#[cfg(feature = "toml_config")]
fn load_from_toml() -> Result<()> {
const DATA: &str = include_str!("client_config.toml");
assert_eq!(
@ -631,7 +633,7 @@ mod test {
}
#[test]
#[cfg(feature = "yaml")]
#[cfg(feature = "yaml_config")]
fn load_from_yaml() -> Result<()> {
const DATA: &str = include_str!("client_config.yaml");
assert_eq!(

View file

@ -94,17 +94,17 @@ pub enum Error {
#[derive(Debug, Error)]
pub enum ConfigError {
/// Failed to parse as TOML.
#[cfg(feature = "toml")]
#[cfg(feature = "toml_config")]
#[error("invalid toml")]
InvalidToml(#[source] TomlError),
/// Failed to parse as JSON.
#[cfg(feature = "json")]
#[cfg(feature = "json_config")]
#[error("invalid json")]
InvalidJson(#[source] serde_json::Error),
/// Failed to parse as YAML.
#[cfg(feature = "yaml")]
#[cfg(feature = "yaml_config")]
#[error("invalid yaml")]
InvalidYaml(#[source] serde_yaml::Error),
@ -136,7 +136,7 @@ pub enum ConfigError {
}
/// A wrapper that combines toml's serialization and deserialization errors.
#[cfg(feature = "toml")]
#[cfg(feature = "toml_config")]
#[derive(Debug, Error)]
pub enum TomlError {
/// A TOML deserialization error.