Skip to content

Commit

Permalink
Support multiple configurations.
Browse files Browse the repository at this point in the history
  • Loading branch information
AlphaHydrae committed Jan 23, 2015
1 parent 928af3b commit b74910b
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 67 deletions.
28 changes: 23 additions & 5 deletions lib/errapi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,33 @@ module Errapi

module Errapi

def self.config
@config ||= default_config
def self.configure name = nil, &block

init_configs
name ||= :default

if @configs[name]
@configs[name].configure &block
else
@configs[name] = Configuration.new &block
end
end

def self.config name = nil
init_configs[name || :default]
end

private

def self.init_configs
@configs ? @configs : @configs = { default: default_config }
end

def self.default_config
Configuration.new.tap do |config|
config.plugins << Errapi::Plugins::I18nMessages.new
config.plugins << Errapi::Plugins::Reason.new
config.plugins << Errapi::Plugins::Location.new
config.plugin Errapi::Plugins::I18nMessages
config.plugin Errapi::Plugins::Reason
config.plugin Errapi::Plugins::Location
config.register_validation :length, Errapi::Validations::Length
config.register_validation :presence, Errapi::Validations::Presence
config.register_validation :type, Errapi::Validations::Type
Expand Down
103 changes: 60 additions & 43 deletions lib/errapi/configuration.rb
Original file line number Diff line number Diff line change
@@ -1,59 +1,76 @@
class Errapi::Configuration
attr_reader :plugins

def initialize
@plugins = []
@validation_factories = {}
@condition_factories = {}
@location_factories = {}
end
require File.join(File.dirname(__FILE__), 'utils.rb')

def new_error options = {}
Errapi::ValidationError.new options
end
module Errapi

def build_error error, context
apply_plugins :build_error, error, context
end
class Configuration
attr_reader :options
attr_reader :plugins

def serialize_error error, serialized
apply_plugins :serialize_error, error, serialized
end
def initialize
@options = OpenStruct.new
@plugins = OpenStruct.new
@validation_factories = {}
@condition_factories = {}
@location_factories = {}
end

def new_context
Errapi::ValidationContext.new config: self
end
def configure
yield self
end

def register_validation name, factory
@validation_factories[name] = factory
end
def new_error options = {}
Errapi::ValidationError.new options
end

def validation name, options = {}
raise ArgumentError, "No validation factory registered for name #{name.inspect}" unless @validation_factories.key? name
@validation_factories[name].new options
end
def build_error error, context
apply_plugins :build_error, error, context
end

def register_condition factory
factory.conditionals.each do |conditional|
raise ArgumentError, "Conditional #{conditional} should start with 'if' or 'unless'." unless conditional.to_s.match /^(if|unless)/
@condition_factories[conditional] = factory
def serialize_error error, serialized
apply_plugins :serialize_error, error, serialized
end
end

def extract_conditions! source, options = {}
[].tap do |conditions|
@condition_factories.each_pair do |conditional,factory|
next unless source.key? conditional
conditions << factory.new(conditional, source.delete(conditional), options)
def new_context
Errapi::ValidationContext.new config: self
end

def plugin plugin, options = {}
name = options[:name] || Utils.underscore(plugin.to_s.sub(/.*::/, ''))
plugin.config = self if plugin.respond_to? :config=
@plugins[name] = plugin
end

def register_validation name, factory
@validation_factories[name] = factory
end

def validation name, options = {}
raise ArgumentError, "No validation factory registered for name #{name.inspect}" unless @validation_factories.key? name
@validation_factories[name].new options
end

def register_condition factory
factory.conditionals.each do |conditional|
raise ArgumentError, "Conditional #{conditional} should start with 'if' or 'unless'." unless conditional.to_s.match /^(if|unless)/
@condition_factories[conditional] = factory
end
end

def extract_conditions! source, options = {}
[].tap do |conditions|
@condition_factories.each_pair do |conditional,factory|
next unless source.key? conditional
conditions << factory.new(conditional, source.delete(conditional), options)
end
end
end
end

private
private

def apply_plugins operation, *args
@plugins.each do |plugin|
plugin.send operation, *args if plugin.respond_to? operation
def apply_plugins operation, *args
@plugins.each_pair do |name,plugin|
plugin.send operation, *args if plugin.respond_to? operation
end
end
end
end
3 changes: 3 additions & 0 deletions lib/errapi/object_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ def validates_each *args, &block
end

def validate value, context, options = {}
# TODO: skip validation by default if previous errors at current location
# TODO: add support for previous value and skip validation by default if value is unchanged

return context.valid? unless @validations

Expand Down Expand Up @@ -135,6 +137,7 @@ def extract value, target, options = {}
end

def register_validations *args, &block
# TODO: allow to set custom error options (e.g. reason) when registering validation

options = args.last.kind_of?(Hash) ? args.pop : {}
target_alias = options.delete :as
Expand Down
19 changes: 11 additions & 8 deletions lib/errapi/plugins/i18n_messages.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
require 'i18n'

# TODO: support interpolating source and target name (e.g. "Project name cannot be null.")
class Errapi::Plugins::I18nMessages
class << self

def serialize_error error, serialized
return if serialized.key? :message
def serialize_error error, serialized
return if serialized.key? :message

if I18n.exists? translation_key = "errapi.#{error.reason}"
interpolation_values = INTERPOLATION_KEYS.inject({}){ |memo,key| memo[key] = error.send(key); memo }.reject{ |k,v| v.nil? }
serialized[:message] = I18n.t translation_key, interpolation_values
if I18n.exists? translation_key = "errapi.#{error.reason}"
interpolation_values = INTERPOLATION_KEYS.inject({}){ |memo,key| memo[key] = error.send(key); memo }.reject{ |k,v| v.nil? }
serialized[:message] = I18n.t translation_key, interpolation_values
end
end
end

private
private

INTERPOLATION_KEYS = %i(check_value checked_value)
INTERPOLATION_KEYS = %i(check_value checked_value)
end
end
27 changes: 20 additions & 7 deletions lib/errapi/plugins/location.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
module Errapi

class Plugins::Location
class << self
attr_writer :config
attr_accessor :camelize

def serialize_error error, serialized
if error.location && error.location.respond_to?(:serialize)
def serialize_error error, serialized
if error.location && error.location.respond_to?(:serialize)

serialized_location = error.location.serialize
unless serialized_location.nil?
serialized[:location] = serialized_location
serialized[:location_type] = error.location.location_type if error.location.respond_to? :location_type
serialized_location = error.location.serialize
unless serialized_location.nil?
serialized[:location] = serialized_location
serialized[location_type_key] = error.location.location_type if error.location.respond_to? :location_type
end
end
end

private

def location_type_key
camelize? ? :locationType : :location_type
end

def camelize?
@camelize.nil? ? @config.options.camelize : @camelize
end
end
end
end
19 changes: 16 additions & 3 deletions lib/errapi/plugins/reason.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
module Errapi

class Plugins::Reason
class << self
attr_writer :config
attr_accessor :camelize

def serialize_error error, serialized
serialized[:reason] = serialized_reason error
end

private

def serialized_reason error
camelize? ? Utils.camelize(error.reason.to_s).to_sym : error.reason
end

def serialize_error error, serialized
serialized[:reason] = error.reason
def camelize?
@camelize.nil? ? @config.options.camelize : @camelize
end
end
end
end
28 changes: 28 additions & 0 deletions lib/errapi/single_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module Errapi

class SingleValidator

def self.configure options = {}, &block
raise "Validator has already been configured. You must call this method before #validate or the various #validates methods." if @errapi_validator
@errapi_validator = ObjectValidator.new options, &block
end

def self.validates *args, &block
init_validator.validates *args, &block
end

def self.validates_each *args, &block
init_validator.validates_each *args, &block
end

def self.validate *args, &block
init_validator.validate *args, &block
end

private

def self.init_validator
@errapi_validator ||= ObjectValidator.new
end
end
end
12 changes: 12 additions & 0 deletions lib/errapi/utils.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module Errapi::Utils

def self.camelize string, uppercase_first_letter = false
parts = string.split '_'
return string if parts.length < 2
parts[0] + parts[1, parts.length - 1].collect(&:capitalize).join
end

def self.underscore string
string.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
end
end
3 changes: 2 additions & 1 deletion lib/errapi/validation_error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ def matches? criteria = {}
def criterion_matches? criteria, attr
return true unless criteria.key? attr

criterion, value = criteria[attr], send(attr)
value = send attr
criterion = criteria[attr]

if criterion.kind_of? Regexp
!!criterion.match(value.to_s)
Expand Down

0 comments on commit b74910b

Please sign in to comment.