lib/airbrake-ruby/filter_chain.rb
module Airbrake
# FilterChain represents an ordered array of filters.
#
# A filter is an object that responds to <b>#call</b> (typically a Proc or a
# class that implements the call method). The <b>#call</b> method must accept
# exactly one argument: an object to be filtered.
#
# When you add a new filter to the chain, it gets inserted according to its
# <b>weight</b>. Smaller weight means the filter will be somewhere in the
# beginning of the array. Larger - in the end. If a filter doesn't implement
# weight, the chain assumes it's equal to 0.
#
# @example
# class MyFilter
# attr_reader :weight
#
# def initialize
# @weight = 1
# end
#
# def call(obj)
# puts 'Filtering...'
# obj[:data] = '[Filtered]'
# end
# end
#
# filter_chain = FilterChain.new
# filter_chain.add_filter(MyFilter)
#
# filter_chain.refine(obj)
# #=> Filtering...
#
# @see Airbrake.add_filter
# @api private
# @since v1.0.0
class FilterChain
# @return [Integer]
DEFAULT_WEIGHT = 0
def initialize
@filters = []
end
# Adds a filter to the filter chain. Sorts filters by weight.
#
# @param [#call] filter The filter object (proc, class, module, etc)
# @return [void]
def add_filter(filter)
@filters = (@filters << filter).sort_by do |f|
f.respond_to?(:weight) ? f.weight : DEFAULT_WEIGHT
end.reverse!
end
# Deletes a filter from the the filter chain.
#
# @param [Class] filter_class The class of the filter you want to delete
# @return [void]
# @since v3.1.0
def delete_filter(filter_class)
# rubocop:disable Style/ClassEqualityComparison
index = @filters.index { |f| f.class.name == filter_class.name }
# rubocop:enable Style/ClassEqualityComparison
@filters.delete_at(index) if index
end
# Applies all the filters in the filter chain to the given notice. Does not
# filter ignored notices.
#
# @param [Airbrake::Notice] notice The notice to be filtered
# @return [void]
# @todo Make it work with anything, not only notices
def refine(notice)
@filters.each do |filter|
break if notice.ignored?
filter.call(notice)
end
end
# @return [String] customized inspect to lessen the amount of clutter
def inspect
filter_classes.to_s
end
# @return [String] {#inspect} for PrettyPrint
def pretty_print(q)
q.text('[')
# Make nesting of the first element consistent on JRuby and MRI.
q.nest(2) { q.breakable } if @filters.any?
q.nest(2) do
q.seplist(@filters) { |f| q.pp(f.class) }
end
q.text(']')
end
# @param [Class] filter_class
# @return [Boolean] true if the current chain has an instance of the given
# class, false otherwise
# @since v4.14.0
def includes?(filter_class)
filter_classes.include?(filter_class)
end
private
def filter_classes
@filters.map(&:class)
end
end
end