#![deny(
clippy::all,
clippy::cargo,
clippy::nursery,
clippy::pedantic,
deprecated_in_future,
future_incompatible,
missing_docs,
nonstandard_style,
rust_2018_idioms,
rustdoc,
warnings,
unused_results,
unused_qualifications,
unused_lifetimes,
unused_import_braces,
unsafe_code,
unreachable_pub,
trivial_casts,
trivial_numeric_casts,
missing_debug_implementations,
missing_copy_implementations
)]
#![warn(variant_size_differences)]
#![allow(clippy::multiple_crate_versions, missing_doc_code_examples)]
#![doc(html_root_url = "https://docs.rs/automaat-processor-redis-command/0.1.0")]
use automaat_core::{Context, Processor};
use redis::RedisError;
use serde::{Deserialize, Serialize};
use std::{error, fmt, str::from_utf8};
use url::Url;
#[cfg_attr(feature = "juniper", derive(juniper::GraphQLObject))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct RedisCommand {
pub command: String,
pub arguments: Option<Vec<String>>,
#[serde(with = "url_serde")]
pub url: Url,
}
#[cfg(feature = "juniper")]
#[graphql(name = "RedisCommandInput")]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, juniper::GraphQLInputObject)]
pub struct Input {
command: String,
arguments: Option<Vec<String>>,
#[serde(with = "url_serde")]
url: Url,
}
#[cfg(feature = "juniper")]
impl From<Input> for RedisCommand {
fn from(input: Input) -> Self {
Self {
command: input.command,
arguments: input.arguments,
url: input.url,
}
}
}
impl<'a> Processor<'a> for RedisCommand {
const NAME: &'static str = "Redis Command";
type Error = Error;
type Output = String;
fn run(&self, _context: &Context) -> Result<Option<Self::Output>, Self::Error> {
use redis::Value;
let client = redis::Client::open(self.url.as_str())?;
let conn = client.get_connection()?;
let args = self.arguments.clone().unwrap_or_else(Default::default);
redis::cmd(self.command.as_str())
.arg(args)
.query(&conn)
.map_err(Into::into)
.map(|v| match v {
Value::Nil => None,
Value::Status(string) => Some(string),
Value::Data(ref val) => match from_utf8(val) {
Ok(string) => Some(string.to_owned()),
Err(_) => Some(format!("{:?}", val)),
},
other => Some(format!("{:?}", other)),
})
}
}
#[derive(Debug)]
pub enum Error {
Response(RedisError),
AuthenticationFailed(RedisError),
Type(RedisError),
ExecAbort(RedisError),
BusyLoading(RedisError),
NoScript(RedisError),
InvalidClientConfig(RedisError),
Io(RedisError),
Extension(RedisError),
#[doc(hidden)]
__Unknown, }
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Error::Response(ref err)
| Error::AuthenticationFailed(ref err)
| Error::Type(ref err)
| Error::ExecAbort(ref err)
| Error::BusyLoading(ref err)
| Error::NoScript(ref err)
| Error::InvalidClientConfig(ref err)
| Error::Io(ref err)
| Error::Extension(ref err) => write!(f, "Redis error: {}", err),
Error::__Unknown => unreachable!(),
}
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
Error::Response(ref err)
| Error::AuthenticationFailed(ref err)
| Error::Type(ref err)
| Error::ExecAbort(ref err)
| Error::BusyLoading(ref err)
| Error::NoScript(ref err)
| Error::InvalidClientConfig(ref err)
| Error::Io(ref err)
| Error::Extension(ref err) => Some(err),
Error::__Unknown => unreachable!(),
}
}
}
impl From<RedisError> for Error {
fn from(err: RedisError) -> Self {
use redis::ErrorKind;
match err.kind() {
ErrorKind::ResponseError => Error::Response(err),
ErrorKind::AuthenticationFailed => Error::AuthenticationFailed(err),
ErrorKind::TypeError => Error::Type(err),
ErrorKind::ExecAbortError => Error::ExecAbort(err),
ErrorKind::BusyLoadingError => Error::BusyLoading(err),
ErrorKind::NoScriptError => Error::NoScript(err),
ErrorKind::InvalidClientConfig => Error::InvalidClientConfig(err),
ErrorKind::IoError => Error::Io(err),
ErrorKind::ExtensionError => Error::Extension(err),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn processor_stub() -> RedisCommand {
RedisCommand {
command: "PING".to_owned(),
arguments: None,
url: Url::parse("redis://127.0.0.1").unwrap(),
}
}
mod run {
use super::*;
#[test]
fn test_command() {
let mut processor = processor_stub();
processor.command = "PING".to_owned();
let context = Context::new().unwrap();
let output = processor.run(&context).unwrap();
assert_eq!(output, Some("PONG".to_owned()))
}
#[test]
fn test_command_and_arguments() {
let mut processor = processor_stub();
processor.command = "PING".to_owned();
processor.arguments = Some(vec!["hello world".to_owned()]);
let context = Context::new().unwrap();
let output = processor.run(&context).unwrap();
assert_eq!(output, Some("hello world".to_owned()))
}
#[test]
fn test_unknown_command() {
let mut processor = processor_stub();
processor.command = "UNKNOWN".to_owned();
let context = Context::new().unwrap();
let error = processor.run(&context).unwrap_err();
assert!(error.to_string().contains("unknown command `UNKNOWN`"));
}
}
#[test]
fn test_readme_deps() {
version_sync::assert_markdown_deps_updated!("README.md");
}
#[test]
fn test_html_root_url() {
version_sync::assert_html_root_url_updated!("src/lib.rs");
}
}