Merge pull request #195 from belak/optional-serde

Make serde and toml optional
This commit is contained in:
Aaron Weiss 2020-01-30 17:08:12 -05:00 committed by GitHub
commit f488f9562e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 136 additions and 82 deletions

View file

@ -1,15 +1,59 @@
language: rust
rust: stable
sudo: false
script:
- cargo test --all --features "toml yaml json"
- 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
- cargo run --example build-bot
jobs:
allow_failures:
- stage: test
rust: beta
env: FEATURES=default
- stage: test
rust: nightly
env: FEATURES=default
include:
# Default features
- &test
rust: stable
env: FEATURES=default
script:
- cargo test --all --no-default-features --features "$FEATURES"
- cargo build --no-default-features --features "$FEATURES"
- cargo run --no-default-features --features "$FEATURES" --example build-bot
# No features
- <<: *test
env: FEATURES=
# CTCP on without toml
- <<: *test
env: FEATURES=ctcp
# CTCP on w/toml (this is the same as default)
#- <<: *test
# env: FEATURES=ctcp toml_config
# nochanlists on without toml
- <<: *test
env: FEATURES=nochanlists
# nochanlists on w/toml
- <<: *test
env: FEATURES=nochanlists toml_config
# Beta
- <<: *test
rust: beta
env: FEATURES=default
# Nightly
- <<: *test
rust: nightly
env: FEATURES=default
# 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
notifications:
email: false
irc:
on_failure: always
on_success: never
channels:
- "ircs://irc.pdgn.co:6697/#commits"
template:

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

@ -11,10 +11,12 @@ async fn main() -> irc::error::Result<()> {
let branch = env::var("TRAVIS_BRANCH").unwrap();
let commit = env::var("TRAVIS_COMMIT").unwrap();
let commit_message = env::var("TRAVIS_COMMIT_MESSAGE").unwrap();
let features = env::var("FEATURES").unwrap();
let config = Config {
nickname: Some("irc-crate-ci".to_owned()),
server: Some("irc.pdgn.co".to_owned()),
alt_nicks: vec!["[irc-crate-ci]".to_owned()],
use_ssl: true,
..Default::default()
};
@ -31,11 +33,12 @@ async fn main() -> irc::error::Result<()> {
client.send_privmsg(
"#commits",
format!(
"[{}/{}] ({}) {}",
"[{}/{}] ({}) {} [{}]",
repository_slug,
branch,
&commit[..7],
commit_message
commit_message,
features,
),
)?;

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.