1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
|
# frozen_string_literal: true
require 'thread'
require 'set'
module Seahorse
module Client
class HandlerList
include Enumerable
# @api private
def initialize(options = {})
@index = options[:index] || 0
@entries = {}
@mutex = Mutex.new
entries = options[:entries] || []
add_entries(entries) unless entries.empty?
end
# @return [Array<HandlerListEntry>]
def entries
@mutex.synchronize do
@entries.values
end
end
# Registers a handler. Handlers are used to build a handler stack.
# Handlers default to the `:build` step with default priority of 50.
# The step and priority determine where in the stack a handler
# will be.
#
# ## Handler Stack Ordering
#
# A handler stack is built from the inside-out. The stack is
# seeded with the send handler. Handlers are constructed recursively
# in reverse step and priority order so that the highest priority
# handler is on the outside.
#
# By constructing the stack from the inside-out, this ensures
# that the validate handlers will be called first and the sign handlers
# will be called just before the final and only send handler is called.
#
# ## Steps
#
# Handlers are ordered first by step. These steps represent the
# life-cycle of a request. Valid steps are:
#
# * `:initialize`
# * `:validate`
# * `:build`
# * `:sign`
# * `:send`
#
# Many handlers can be added to the same step, except for `:send`.
# There can be only one `:send` handler. Adding an additional
# `:send` handler replaces the previous one.
#
# ## Priorities
#
# Handlers within a single step are executed in priority order. The
# higher the priority, the earlier in the stack the handler will
# be called.
#
# * Handler priority is an integer between 0 and 99, inclusively.
# * Handler priority defaults to 50.
# * When multiple handlers are added to the same step with the same
# priority, the last one added will have the highest priority and
# the first one added will have the lowest priority.
#
# @param [Class<Handler>] handler_class This should be a subclass
# of {Handler}.
#
# @option options [Symbol] :step (:build) The request life-cycle
# step the handler should run in. Defaults to `:build`. The
# list of possible steps, in high-to-low priority order are:
#
# * `:initialize`
# * `:validate`
# * `:build`
# * `:sign`
# * `:send`
#
# There can only be one send handler. Registering an additional
# `:send` handler replaces the previous one.
#
# @option options [Integer] :priority (50) The priority of this
# handler within a step. The priority must be between 0 and 99
# inclusively. It defaults to 50. When two handlers have the
# same `:step` and `:priority`, the handler registered last has
# the highest priority.
#
# @option options [Array<Symbol,String>] :operations A list of
# operations names the handler should be applied to. When
# `:operations` is omitted, the handler is applied to all
# operations for the client.
#
# @raise [InvalidStepError]
# @raise [InvalidPriorityError]
# @note There can be only one `:send` handler. Adding an additional
# send handler replaces the previous.
#
# @return [Class<Handler>] Returns the handler class that was added.
#
def add(handler_class, options = {})
@mutex.synchronize do
add_entry(
HandlerListEntry.new(options.merge(
handler_class: handler_class,
inserted: next_index
))
)
end
handler_class
end
# @param [Class<Handler>] handler_class
def remove(handler_class)
@entries.each do |key, entry|
@entries.delete(key) if entry.handler_class == handler_class
end
end
# Copies handlers from the `source_list` onto the current handler list.
# If a block is given, only the entries that return a `true` value
# from the block will be copied.
# @param [HandlerList] source_list
# @return [void]
def copy_from(source_list, &block)
entries = []
source_list.entries.each do |entry|
if block_given?
entries << entry.copy(inserted: next_index) if yield(entry)
else
entries << entry.copy(inserted: next_index)
end
end
add_entries(entries)
end
# Returns a handler list for the given operation. The returned
# will have the operation specific handlers merged with the common
# handlers.
# @param [String] operation The name of an operation.
# @return [HandlerList]
def for(operation)
HandlerList.new(index: @index, entries: filter(operation.to_s))
end
# Yields the handlers in stack order, which is reverse priority.
def each(&block)
entries.sort.each do |entry|
yield(entry.handler_class) if entry.operations.nil?
end
end
# Constructs the handlers recursively, building a handler stack.
# The `:send` handler will be at the top of the stack and the
# `:validate` handlers will be at the bottom.
# @return [Handler]
def to_stack
inject(nil) { |stack, handler| handler.new(stack) }
end
private
def add_entries(entries)
@mutex.synchronize do
entries.each { |entry| add_entry(entry) }
end
end
def add_entry(entry)
key = entry.step == :send ? :send : entry.object_id
@entries[key] = entry
end
def filter(operation)
entries.inject([]) do |filtered, entry|
if entry.operations.nil?
filtered << entry.copy
elsif entry.operations.include?(operation)
filtered << entry.copy(operations: nil)
end
filtered
end
end
def next_index
@index += 1
end
end
end
end
|