use Transformer;
use transformer::{
TransformResult,
TransformError,
};
use transformers::{
Transformers,
TrimTransformer,
NoneTransformer,
};
use std::hash::{
Hash,
Hasher,
};
use std::iter::FromIterator;
use std::cmp::Ordering;
use std::collections::{
BinaryHeap,
HashSet,
};
use std::error;
use std::fmt::{
self,
Formatter,
Display,
};
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
pub enum Applicability {
Global,
Fields {
field_names: HashSet<String>
}
}
impl Hash for Applicability {
fn hash<H>(&self, state: &mut H)
where H: Hasher {
use self::Applicability::*;
match *self {
Global => (self as *const Applicability).hash(state), Fields { ref field_names } => field_names.iter().collect::<Vec<&String>>().hash(state)
}
}
}
fn priority_is_default(priority: &isize) -> bool {
priority == &0
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Clone, Debug)]
pub struct Rule
{
applicability: Applicability,
transformer: Transformers,
#[serde(default, skip_serializing_if="priority_is_default")]
priority: isize
}
impl Rule
{
pub fn for_fields(field_names: &[&str], transformer: Transformers) -> Rule {
Self::for_fields_with_priority(field_names, transformer, Default::default())
}
pub fn for_fields_with_priority(field_names: &[&str], transformer: Transformers, priority: isize) -> Rule {
Rule {
applicability: Applicability::Fields { field_names: field_names.iter().map(|s| s.to_string()).collect() },
transformer: transformer,
priority: priority
}
}
pub fn global(transformer: Transformers) -> Rule {
Self::global_with_priority(transformer, Default::default())
}
pub fn global_with_priority(transformer: Transformers, priority: isize) -> Rule {
Rule {
applicability: Applicability::Global,
transformer: transformer,
priority: priority
}
}
pub fn apply(&self, field_value: &str, field_name: &str, record_n: usize) -> TransformResult {
match self.applicability {
Applicability::Global => self.transformer.transform(field_value, field_name, record_n),
Applicability::Fields { ref field_names } if field_names.contains(&field_name.to_string()) => {
self.transformer.transform(field_value, field_name, record_n)
},
_ => Ok(Some(field_value.to_string()))
}
}
}
impl Ord for Rule
{
fn cmp(&self, other: &Self) -> Ordering {
other.priority.cmp(&self.priority)
}
}
impl PartialOrd for Rule
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Ruleset {
rules: BinaryHeap<Rule>
}
impl Ruleset {
pub fn new() -> Ruleset {
let mut ruleset = Self::without_default_rules();
ruleset.add_rule(Rule::global_with_priority(Transformers::None(NoneTransformer::with_blank_matcher()), -10));
ruleset.add_rule(Rule::global_with_priority(Transformers::Trim(TrimTransformer::new()), -10));
ruleset
}
pub fn without_default_rules() -> Ruleset {
Ruleset {
rules: BinaryHeap::new()
}
}
pub fn add_rule(&mut self, rule: Rule) {
self.rules.push(rule);
}
pub fn validate_rules(&self, headers: &Vec<String>) -> Result<(), Vec<ValidationError>> {
let mut errors = Vec::new();
for rule in self.rules.iter() {
if let Applicability::Fields { ref field_names } = rule.applicability {
let header_set = HashSet::<String>::from_iter(headers.clone());
let field_set = HashSet::<String>::from_iter(field_names.clone());
let diff: HashSet<String> = field_set.difference(&header_set).cloned().collect();
if diff.len() > 0 {
errors.push(
ValidationError {
reason: format!("The following fields were not found in headers: '{:?}'", diff),
}
)
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
pub fn apply_rules(&self, headers: &Vec<String>, fields: &Vec<String>, record_n: usize) -> TransformedRecord {
let expected_n_fields = headers.len();
let mut errors: Vec<TransformError> = Vec::new();
let mut transformed_fields: Vec<Option<String>> = Vec::new();
for (field_n, field_value) in fields.iter().enumerate() {
if field_n < expected_n_fields {
let field_name = &headers[field_n];
let mut transformed_field_value = Some(field_value.clone());
for rule in self.rules.iter() {
let new_value = match transformed_field_value {
Some(ref fv) => {
let transform_result = rule.apply(fv, &field_name, record_n);
match transform_result {
Ok(tfv) => tfv,
Err(e) => {
errors.push(e);
None
}
}
},
None => break
};
transformed_field_value = new_value;
}
transformed_fields.insert(field_n, transformed_field_value);
} else {
errors.push(
TransformError {
field_value: field_value.to_string(),
field_name: field_n.to_string(),
record_n: record_n,
reason: format!("found {} header fields but record had extra field at position {}", expected_n_fields, field_n)
}
);
}
}
TransformedRecord {
field_values: transformed_fields,
errors: errors,
}
}
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash, Debug)]
pub struct ValidationError {
reason: String,
}
impl Display for ValidationError
{
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
write!(formatter, "{}", self.reason)
}
}
impl error::Error for ValidationError
{
fn description(&self) -> &str {
&self.reason
}
}
#[derive(Serialize, Deserialize, Eq, PartialEq, Hash, Debug)]
pub struct TransformedRecord {
pub field_values: Vec<Option<String>>,
pub errors: Vec<TransformError>,
}