#![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-string-regex/0.1.0")]
use automaat_core::{Context, Processor};
use regex::{Error as RegexError, Regex};
use serde::{Deserialize, Serialize};
use std::{error, fmt};
#[cfg_attr(feature = "juniper", derive(juniper::GraphQLObject))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct StringRegex {
pub input: String,
pub regex: String,
pub mismatch_error: Option<String>,
pub replace: Option<String>,
}
#[cfg(feature = "juniper")]
#[graphql(name = "StringRegexInput")]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, juniper::GraphQLInputObject)]
pub struct Input {
input: String,
regex: String,
mismatch_error: Option<String>,
replace: Option<String>,
}
#[cfg(feature = "juniper")]
impl From<Input> for StringRegex {
fn from(input: Input) -> Self {
Self {
input: input.input,
regex: input.regex,
mismatch_error: input.mismatch_error,
replace: input.replace,
}
}
}
impl<'a> Processor<'a> for StringRegex {
const NAME: &'static str = "String Regex";
type Error = Error;
type Output = String;
fn validate(&self) -> Result<(), Self::Error> {
Regex::new(self.regex.as_str())
.map(|_| ())
.map_err(Into::into)
}
fn run(&self, _context: &Context) -> Result<Option<Self::Output>, Self::Error> {
let re = Regex::new(self.regex.as_str()).map_err(Into::<Self::Error>::into)?;
if re.is_match(self.input.as_str()) {
match &self.replace {
None => Ok(None),
Some(replace) => {
let out = re
.replace(self.input.as_str(), replace.as_str())
.into_owned();
if out.is_empty() {
Ok(None)
} else {
Ok(Some(out))
}
}
}
} else if let Some(msg) = &self.mismatch_error {
Err(Error::Match(msg.to_owned()))
} else {
Err(Error::Match(format!(
"Match error: \"{}\" does not match pattern: {}",
self.input, self.regex
)))
}
}
}
#[derive(Debug)]
pub enum Error {
Syntax(RegexError),
CompiledTooBig(RegexError),
Match(String),
#[doc(hidden)]
__Unknown, }
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Error::Syntax(ref err) | Error::CompiledTooBig(ref err) => {
write!(f, "Regex error: {}", err)
}
Error::Match(ref string) => write!(f, "{}", string),
Error::__Unknown => unreachable!(),
}
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
Error::Syntax(ref err) | Error::CompiledTooBig(ref err) => Some(err),
Error::Match(_) => None,
Error::__Unknown => unreachable!(),
}
}
}
impl From<RegexError> for Error {
fn from(err: RegexError) -> Self {
match err {
RegexError::Syntax(_) => Error::Syntax(err),
RegexError::CompiledTooBig(_) => Error::CompiledTooBig(err),
RegexError::__Nonexhaustive => unreachable!(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn processor_stub() -> StringRegex {
StringRegex {
input: "hello world".to_owned(),
regex: r"\Ahello world\z".to_owned(),
mismatch_error: None,
replace: None,
}
}
mod run {
use super::*;
#[test]
fn test_match_pattern() {
let mut processor = processor_stub();
processor.input = "hello world".to_owned();
processor.regex = r"hello \w+".to_owned();
let context = Context::new().unwrap();
let output = processor.run(&context).unwrap();
assert!(output.is_none())
}
#[test]
fn test_mismatch_pattern_default_error() {
let mut processor = processor_stub();
processor.input = "hello world".to_owned();
processor.regex = r"hi \w+".to_owned();
let context = Context::new().unwrap();
let error = processor.run(&context).unwrap_err();
assert_eq!(
error.to_string(),
r#"Match error: "hello world" does not match pattern: hi \w+"#.to_owned()
)
}
#[test]
fn test_mismatch_pattern_custom_error() {
let mut processor = processor_stub();
processor.input = "hello world".to_owned();
processor.regex = r"hi \w+".to_owned();
processor.mismatch_error = Some("invalid!".to_owned());
let context = Context::new().unwrap();
let error = processor.run(&context).unwrap_err();
assert_eq!(error.to_string(), "invalid!".to_owned())
}
#[test]
fn test_replace_pattern() {
let mut processor = processor_stub();
processor.input = "hello world".to_owned();
processor.regex = r"hello (\w+)".to_owned();
processor.replace = Some("hi $1!".to_owned());
let context = Context::new().unwrap();
let output = processor.run(&context).unwrap().expect("Some");
assert_eq!(output, "hi world!".to_owned())
}
}
mod validate {
use super::*;
#[test]
fn test_valid_syntax() {
let mut processor = processor_stub();
processor.regex = r"hello \w+".to_owned();
processor.validate().unwrap()
}
#[test]
#[should_panic]
fn test_invalid_syntax() {
let mut processor = processor_stub();
processor.regex = r"hello \NO".to_owned();
processor.validate().unwrap()
}
}
#[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");
}
}