1
0
mirror of https://github.com/meineerde/rackstash.git synced 2026-01-31 17:27:13 +00:00

Allow to define conditionals on built filters

Now you can define optional filters which only run if some condition is
true or false. This can be used to e.g. update fields depending on some
tags being present in the event.
This commit is contained in:
Holger Just 2017-12-20 23:23:42 +01:00
parent a475a5c6fc
commit de6b60925a
2 changed files with 109 additions and 2 deletions

View File

@ -58,21 +58,51 @@ module Rackstash
# we then create a filter object as before. When giving an object which
# responds to `call` already (e.g. a `Proc`, we return it unchanged,
# ignoring any additional passed `args`.
# @param only_if [#call, nil] An optional condition defining whether the
# filter should be applied, usually given as a `Proc` object. Before
# evaluating the newly created filter object, we first call the given
# proc with the event as its argument. The filter is applied only if the
# proc returns a truethy value.
# @param not_if [#call, nil] An optional condition defining whether the
# filter should not be applied, usually given as a `Proc` object. Before
# evaluating the newly created filter object, we first call the given
# proc with the event as its argument. The filter is not applied if the
# proc returns a truethy value.
# @param args [Array] an optional list of arguments which is passed to the
# initializer for the new filter object.
# @param kwargs [Hash] an optional list of keyword arguments which are
# passed to the initializer for the new filter object.
# @raise [TypeError] if we can not create a new filter object from the
# given `filter_spec`, usually because it is an unsupported type
# @raise [KeyError] if we could not find a filter class in the registry
# for the specified class name
# @return [Object] a new filter object
def build(filter_spec, *args, &block)
def build(filter_spec, *args, only_if: nil, not_if: nil, **kwargs, &block)
case filter_spec
when ->(filter) { filter.respond_to?(:call) }
filter_spec
else
registry[filter_spec].new(*args, &block)
args << kwargs unless kwargs.empty?
filter = registry[filter_spec].new(*args, &block)
conditional_filter(filter, only_if: only_if, not_if: not_if)
end
end
private
def conditional_filter(filter, only_if: nil, not_if: nil)
return filter if only_if.nil? && not_if.nil?
conditional = Module.new do
define_method(:call) do |event|
return event if only_if && !only_if.call(event)
return event if not_if && not_if.call(event)
super(event)
end
end
filter.extend(conditional)
end
end
end
end

View File

@ -56,6 +56,83 @@ describe Rackstash::Filter do
expect(described_class.build(filter, :ignored, 42)).to equal filter
end
context 'with conditionals' do
let(:event) { Object.new }
it 'applies the only_if conditional for new filters' do
only_if = -> {}
filter = described_class.build(filter_name, only_if: only_if)
expect(only_if).to receive(:call).and_return false
expect { filter.call({}) }.not_to raise_error
end
it 'applies the not_if conditional for new filters' do
not_if = -> {}
filter = described_class.build(filter_name, not_if: not_if)
expect(not_if).to receive(:call).and_return true
expect(filter.call(event)).to equal event
end
it 'applies both conditionals for new filters' do
only_if = -> {}
not_if = -> {}
filter = described_class.build(filter_name, only_if: only_if, not_if: not_if)
expect(only_if).to receive(:call).and_return true
expect(not_if).to receive(:call).and_return false
expect(filter.call(event)).to eql 'filtered'
end
it 'keeps the class hierarchy unchanged' do
filter = described_class.build(filter_name, only_if: ->(event){ false })
expect(filter).to be_instance_of(filter_class)
end
it 'ignores the conditional for existing filters' do
filter = filter_class.new
only_if = -> {}
expect(described_class.build(filter, only_if: only_if))
.to equal filter
expect(only_if).not_to receive(:call)
expect(described_class.build(filter, only_if: only_if).call(event))
.to eql 'filtered'
end
it 'passes keyword arguments to the initializer' do
filter_class.class_eval do
def initialize(mandatory:)
@mandatory = mandatory
end
attr_reader :mandatory
end
filter = described_class.build(filter_name, only_if: ->{}, mandatory: 'foo')
expect(filter.mandatory).to eql 'foo'
end
end
context 'without conditionals' do
it 'passes keyword arguments to the initializer' do
filter_class.class_eval do
def initialize(mandatory:)
@mandatory = mandatory
end
attr_reader :mandatory
end
filter = described_class.build(filter_name, mandatory: 'foo')
expect(filter.mandatory).to eql 'foo'
end
end
it 'raises a TypeError with invalid spec types' do
expect { described_class.build(123) }
.to raise_error(TypeError, '123 can not be used to describe filters')