Passage à RuleSetProcessor
This commit is contained in:
parent
61cb650014
commit
518e94bed7
|
@ -2,11 +2,11 @@ class InMailbox < ApplicationMailbox
|
|||
def process
|
||||
email_importer = EmailImporter.new
|
||||
repository = EmailRepository.new
|
||||
rules_evaluator = RulesEvaluator.new
|
||||
|
||||
email = email_importer.import(mail)
|
||||
|
||||
email = rules_evaluator.evaluate(email, RuleSet.enabled)
|
||||
processor = RuleSetProcessor.new(email: email)
|
||||
email = processor.process_all(RuleSet.enabled)
|
||||
|
||||
repository.save(email)
|
||||
end
|
||||
|
|
|
@ -10,6 +10,7 @@ module EmailAction
|
|||
def process(email)
|
||||
fail NotImplementedError
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
137
app/services/rule_set_processor.rb
Normal file
137
app/services/rule_set_processor.rb
Normal file
|
@ -0,0 +1,137 @@
|
|||
class RuleSetProcessor
|
||||
|
||||
class InvalidRule < ::ArgumentError
|
||||
end
|
||||
|
||||
def process_all(rule_sets, email)
|
||||
rule_sets.each { |rule_set|
|
||||
email = process(rule_set, email)
|
||||
}
|
||||
|
||||
email
|
||||
end
|
||||
|
||||
def process(rule_set, email)
|
||||
return email unless rule_set.enabled?
|
||||
|
||||
if evaluate_rules(rule_set, email)
|
||||
email = execute_actions(rule_set.actions, email)
|
||||
end
|
||||
|
||||
email
|
||||
end
|
||||
|
||||
def evaluate_rules(rule_set, email)
|
||||
rule_set_result = true
|
||||
|
||||
rule_set_result = catch(:done) {
|
||||
rule_set.rules.each do |rule|
|
||||
next unless rule.enabled?
|
||||
|
||||
subjects = prepare_subjects(rule, email)
|
||||
rule_result = apply_rule(subjects, rule)
|
||||
rule_result = !rule_result if rule.inverted?
|
||||
rule_set_result = apply_operator(rule_set_result, rule_set.operator, rule_result)
|
||||
rescue InvalidRule => ex
|
||||
Rails.logger.error "Skipped rule##{rule.id} '#{rule.name}' - #{ex.inspect}"
|
||||
next
|
||||
end
|
||||
}
|
||||
|
||||
rule_set_result
|
||||
end
|
||||
|
||||
def execute_actions(actions, email)
|
||||
actions.each do |action|
|
||||
next unless action.enabled?
|
||||
|
||||
klass = action.class_name.constantize
|
||||
email_action = klass.new
|
||||
email = email_action.process(email)
|
||||
rescue NameError => ex
|
||||
Rails.logger.error "Skipped action##{action.id} '#{action.name}' - #{ex.inspect}"
|
||||
raise InvalidRule, ex.message
|
||||
end
|
||||
|
||||
email
|
||||
end
|
||||
|
||||
def prepare_subjects(rule, email)
|
||||
case rule.subject_type.downcase
|
||||
when "header"
|
||||
Array(email.header_values(rule.subject_value))
|
||||
when "body"
|
||||
Array(email.plain_body)
|
||||
else
|
||||
raise InvalidRule, "Unrecognized subject type '#{rule.subject_type}'"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def apply_rule(subjects, rule)
|
||||
case rule.condition_type.downcase
|
||||
when "match", "matches"
|
||||
subjects.any? { |subject|
|
||||
pattern = Regexp.new(rule.condition_value)
|
||||
subject.match?
|
||||
}
|
||||
when "equal", "equals"
|
||||
subjects.any? { |subject|
|
||||
subject == rule.condition_value
|
||||
}
|
||||
when "start", "starts"
|
||||
subjects.any? { |subject|
|
||||
subject.starts_with? rule.condition_value
|
||||
}
|
||||
when "end", "ends"
|
||||
subjects.any? { |subject|
|
||||
subject.ends_with? rule.condition_value
|
||||
}
|
||||
when "contain", "contains"
|
||||
subjects.any? { |subject|
|
||||
subject.include? rule.condition_value
|
||||
}
|
||||
when "exist", "exists"
|
||||
subjects.any? { |subject|
|
||||
subject.exists?
|
||||
}
|
||||
when "empty"
|
||||
subjects.all? { |subject|
|
||||
subject.empty?
|
||||
}
|
||||
when "date_before"
|
||||
# subjects.all? { |subject|
|
||||
# subject.empty?
|
||||
# }
|
||||
when "date_after"
|
||||
# subjects.all? { |subject|
|
||||
# subject.empty?
|
||||
# }
|
||||
else
|
||||
raise InvalidRule, "Unrecognized condition type '#{rule. condition_type}'"
|
||||
end
|
||||
end
|
||||
|
||||
def apply_operator(state, operator, result)
|
||||
case operator.upcase
|
||||
when "AND"
|
||||
if result
|
||||
(state and result)
|
||||
else
|
||||
throw :done, false
|
||||
end
|
||||
when "OR"
|
||||
if result
|
||||
throw :done, true
|
||||
else
|
||||
(state or result)
|
||||
end
|
||||
# when "XOR"
|
||||
# (state or result) and !(rules_state and result)
|
||||
else
|
||||
raise InvalidRule, "Unrecognized operator '#{operator}'"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,110 +0,0 @@
|
|||
class RulesEvaluator
|
||||
|
||||
def evaluate(email, rule_set_or_sets)
|
||||
if rule_set_or_sets.respond_to? :each
|
||||
rule_set_or_sets.each do |rule_set|
|
||||
email = evaluate_rule_set(email, rule_set)
|
||||
end
|
||||
else
|
||||
email = evaluate_rule_set(email, rule_set_or_sets)
|
||||
end
|
||||
|
||||
email
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def evaluate_rule_set(email, rule_set)
|
||||
rules_state = true
|
||||
|
||||
rule_set.rules.enabled.each do |rule|
|
||||
subjects = prepare_subjects(email, rule)
|
||||
result = apply_rule(subjects, rule)
|
||||
result = ! result if rule.inverted?
|
||||
rules_state = apply_operator(rules_state, rule_set.operator, result)
|
||||
|
||||
break unless rules_state
|
||||
end
|
||||
|
||||
if rules_state
|
||||
rule_set.actions.enabled.each do |action|
|
||||
klass = action.class_name.constantize
|
||||
email = klass.new.process(email)
|
||||
# rescue NameError => ex
|
||||
# # TODO: log a warning
|
||||
end
|
||||
end
|
||||
|
||||
email
|
||||
end
|
||||
|
||||
def prepare_subjects(email, rule)
|
||||
case rule.subject_type.downcase
|
||||
when "header"
|
||||
Array(email.header_values(rule.subject_value))
|
||||
when "body"
|
||||
Array(email.plain_body)
|
||||
else
|
||||
raise ArgumentError, "Unrecognized subject type '#{rule.subject_type}'"
|
||||
end
|
||||
end
|
||||
|
||||
def apply_rule(subjects, rule)
|
||||
case rule.condition_type.downcase
|
||||
when "match", "matches"
|
||||
subjects.any? { |subject|
|
||||
pattern = Regexp.new(rule.condition_value)
|
||||
subject.match?
|
||||
}
|
||||
when "equal", "equals"
|
||||
subjects.any? { |subject|
|
||||
subject == rule.condition_value
|
||||
}
|
||||
when "start", "starts"
|
||||
subjects.any? { |subject|
|
||||
subject.starts_with? rule.condition_value
|
||||
}
|
||||
when "end", "ends"
|
||||
subjects.any? { |subject|
|
||||
subject.ends_with? rule.condition_value
|
||||
}
|
||||
when "contain", "contains"
|
||||
subjects.any? { |subject|
|
||||
subject.include? rule.condition_value
|
||||
}
|
||||
when "exist", "exists"
|
||||
subjects.any? { |subject|
|
||||
subject.exists?
|
||||
}
|
||||
when "empty"
|
||||
subjects.all? { |subject|
|
||||
subject.empty?
|
||||
}
|
||||
when "date_before"
|
||||
# subjects.all? { |subject|
|
||||
# subject.empty?
|
||||
# }
|
||||
when "date_after"
|
||||
# subjects.all? { |subject|
|
||||
# subject.empty?
|
||||
# }
|
||||
else
|
||||
raise ArgumentError, "Unrecognized condition type '#{rule. condition_type}'"
|
||||
end
|
||||
|
||||
def apply_operator(state, operator, result)
|
||||
case operator.upcase
|
||||
when "AND"
|
||||
(state and result)
|
||||
when "OR"
|
||||
(state or result)
|
||||
when "XOR"
|
||||
(state or result) and !(rules_state and result)
|
||||
else
|
||||
raise ArgumentError, "Unrecognized operator '#{operator}'"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
6
test/fixtures/rule_sets.yml
vendored
6
test/fixtures/rule_sets.yml
vendored
|
@ -23,19 +23,19 @@ or_rules:
|
|||
|
||||
invalid_subject:
|
||||
name: Rules with an invalid rule subject
|
||||
enabled: false
|
||||
enabled: true
|
||||
operator: "AND"
|
||||
inverted: false
|
||||
|
||||
invalid_condition_type:
|
||||
name: Rules with an invalid rule condition type
|
||||
enabled: false
|
||||
enabled: true
|
||||
operator: "AND"
|
||||
inverted: false
|
||||
|
||||
invalid_operator:
|
||||
name: Rules with an invalid rule operator
|
||||
enabled: false
|
||||
enabled: true
|
||||
operator: "FOO"
|
||||
inverted: false
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'test_helper'
|
||||
|
||||
class RulesEvaluatorTest < ActiveSupport::TestCase
|
||||
class RuleSetProcessorTest < ActiveSupport::TestCase
|
||||
|
||||
test "mark cron from subject" do
|
||||
email = email_from_eml_with_rules("cron_subject.eml")
|
||||
|
@ -57,33 +57,27 @@ class RulesEvaluatorTest < ActiveSupport::TestCase
|
|||
end
|
||||
|
||||
test "invalid subject type" do
|
||||
rules_evaluator = RulesEvaluator.new
|
||||
email = Email.new
|
||||
processor = RuleSetProcessor.new
|
||||
email = processor.process(rule_sets(:invalid_subject), email)
|
||||
|
||||
exception = assert_raise ArgumentError do
|
||||
rules_evaluator.evaluate(email, rule_sets(:invalid_subject))
|
||||
end
|
||||
assert_match(/^Unrecognized subject type/, exception.message)
|
||||
assert_not_predicate email, :changed?
|
||||
end
|
||||
|
||||
test "invalid condition type" do
|
||||
rules_evaluator = RulesEvaluator.new
|
||||
email = Email.new
|
||||
processor = RuleSetProcessor.new
|
||||
email = processor.process(rule_sets(:invalid_condition_type), email)
|
||||
|
||||
exception = assert_raise ArgumentError do
|
||||
rules_evaluator.evaluate(email, rule_sets(:invalid_condition_type))
|
||||
end
|
||||
assert_match(/^Unrecognized condition type/, exception.message)
|
||||
assert_not_predicate email, :changed?
|
||||
end
|
||||
|
||||
test "invalid operator" do
|
||||
rules_evaluator = RulesEvaluator.new
|
||||
email = Email.new
|
||||
processor = RuleSetProcessor.new
|
||||
email = processor.process(rule_sets(:invalid_operator), email)
|
||||
|
||||
exception = assert_raise ArgumentError do
|
||||
rules_evaluator.evaluate(email, rule_sets(:invalid_operator))
|
||||
end
|
||||
assert_match(/^Unrecognized operator/, exception.message)
|
||||
assert_not_predicate email, :changed?
|
||||
end
|
||||
|
||||
end
|
|
@ -4,7 +4,7 @@ require 'rails/test_help'
|
|||
|
||||
class ActiveSupport::TestCase
|
||||
# Run tests in parallel with specified workers
|
||||
parallelize(workers: :number_of_processors)
|
||||
# parallelize(workers: :number_of_processors)
|
||||
|
||||
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
|
||||
fixtures :all
|
||||
|
@ -21,10 +21,8 @@ class ActiveSupport::TestCase
|
|||
end
|
||||
def email_from_eml_with_rules(file_fixture_name)
|
||||
email = email_from_eml(file_fixture_name)
|
||||
rules_evaluator = RulesEvaluator.new
|
||||
email = rules_evaluator.evaluate(email, RuleSet.enabled)
|
||||
|
||||
email
|
||||
processor = RuleSetProcessor.new
|
||||
email = processor.process_all(RuleSet.enabled, email)
|
||||
end
|
||||
|
||||
def assert_no_html(text)
|
||||
|
|
Loading…
Reference in a new issue