errapi 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +4 -2
- data/README.md +6 -2
- data/VERSION +1 -1
- data/lib/errapi/condition.rb +71 -0
- data/lib/errapi/configuration.rb +79 -0
- data/lib/errapi/errors.rb +15 -0
- data/lib/errapi/location_builders.rb +17 -0
- data/lib/errapi/locations/dotted.rb +33 -0
- data/lib/errapi/locations/json.rb +33 -0
- data/lib/errapi/locations/none.rb +28 -0
- data/lib/errapi/locations.rb +4 -0
- data/lib/errapi/model.rb +30 -0
- data/lib/errapi/object_validator.rb +230 -0
- data/lib/errapi/plugins/i18n_messages.rb +20 -0
- data/lib/errapi/plugins/location.rb +29 -0
- data/lib/errapi/plugins/reason.rb +22 -0
- data/lib/errapi/plugins.rb +4 -0
- data/lib/errapi/single_validator.rb +19 -0
- data/lib/errapi/utils.rb +12 -0
- data/lib/errapi/validation_context.rb +49 -0
- data/lib/errapi/validation_error.rb +43 -0
- data/lib/errapi/validations/clusivity.rb +45 -0
- data/lib/errapi/validations/exclusion.rb +28 -0
- data/lib/errapi/validations/format.rb +33 -0
- data/lib/errapi/validations/inclusion.rb +28 -0
- data/lib/errapi/validations/length.rb +66 -0
- data/lib/errapi/validations/presence.rb +39 -0
- data/lib/errapi/validations/trim.rb +10 -0
- data/lib/errapi/validations/type.rb +54 -0
- data/lib/errapi/validations.rb +57 -0
- data/lib/errapi/validator_proxy.rb +21 -0
- data/lib/errapi.rb +45 -1
- metadata +63 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dcae888fc36cd422f1fda237dd5381aa10e776d1
|
4
|
+
data.tar.gz: 757ad4e09cea3666e7279d59d544ff2e99c6bab4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5fe74be1e6ef55493f04accf713edfc0ce497c982dc84d68e92262bc7183f4d4076fd872d53f15b27a782e7f613054dc76fd8f26c9f2ef95842cfc9bb020c5a3
|
7
|
+
data.tar.gz: 3ff0b1fee2094513a11daf6ef7d8eb12155a23cadcb4d318843d1bb57dbae2e6c4cf570dac8e1015343a50acf928ccaa4c91da8ad3800b4a92075369124d9c2b
|
data/Gemfile
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
source "https://rubygems.org"
|
2
2
|
|
3
3
|
group :development do
|
4
|
+
gem 'i18n', '~> 0.7.0'
|
4
5
|
gem 'rake', '~> 10.3'
|
5
6
|
gem 'rspec', '~> 3.1'
|
7
|
+
gem 'rspec-collection_matchers', '~> 1.1'
|
6
8
|
gem 'jeweler', '~> 2.0'
|
7
9
|
gem 'rake-version', '~> 0.4'
|
8
|
-
gem 'simplecov', '~> 0.9'
|
9
|
-
gem 'coveralls', '~> 0.7', require: false
|
10
|
+
gem 'simplecov', '~> 0.9.1'
|
11
|
+
gem 'coveralls', '~> 0.7.3', require: false
|
10
12
|
end
|
data/README.md
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
**An extensible API-oriented validation library.**
|
4
4
|
|
5
|
-
[![Gem Version](https://badge.fury.io/rb/errapi.
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/errapi.svg)](http://badge.fury.io/rb/errapi)
|
6
6
|
[![Dependency Status](https://gemnasium.com/AlphaHydrae/errapi.png)](https://gemnasium.com/AlphaHydrae/errapi)
|
7
7
|
[![Build Status](https://secure.travis-ci.org/AlphaHydrae/errapi.png)](http://travis-ci.org/AlphaHydrae/errapi)
|
8
|
-
[![Coverage Status](https://coveralls.io/repos/AlphaHydrae/errapi/badge.
|
8
|
+
[![Coverage Status](https://coveralls.io/repos/AlphaHydrae/errapi/badge.svg)](https://coveralls.io/r/AlphaHydrae/errapi?branch=master)
|
9
9
|
|
10
10
|
## Installation
|
11
11
|
|
@@ -17,6 +17,10 @@ gem 'errapi', '~> 0.1.0'
|
|
17
17
|
|
18
18
|
Then run `bundle install`.
|
19
19
|
|
20
|
+
### Requirements
|
21
|
+
|
22
|
+
* Ruby 2+
|
23
|
+
|
20
24
|
## Meta
|
21
25
|
|
22
26
|
* **Author:** Simon Oulevay (Alpha Hydrae)
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.2
|
@@ -0,0 +1,71 @@
|
|
1
|
+
class Errapi::Condition
|
2
|
+
ALLOWED_CONDITIONALS = %i(if unless).freeze
|
3
|
+
|
4
|
+
def self.conditionals
|
5
|
+
h = const_get('CONDITIONALS')
|
6
|
+
raise LoadError, "The CONDITIONALS constant in class #{self} is of the wrong type (#{h.class}). Either make it a Hash or override #{self}.conditionals to return a list of symbols." unless h.kind_of? Hash
|
7
|
+
h.keys
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize conditional, predicate, options = {}
|
11
|
+
|
12
|
+
@conditional = resolve_conditional conditional
|
13
|
+
raise ArgumentError, "Conditional must be either :if or :unless" unless ALLOWED_CONDITIONALS.include? @conditional
|
14
|
+
|
15
|
+
@predicate = predicate
|
16
|
+
end
|
17
|
+
|
18
|
+
def fulfilled? *args
|
19
|
+
result = check @predicate, *args
|
20
|
+
result = !result if @conditional == :unless
|
21
|
+
result
|
22
|
+
end
|
23
|
+
|
24
|
+
def resolve_conditional conditional
|
25
|
+
conditional
|
26
|
+
end
|
27
|
+
|
28
|
+
def check predicate, value, context, options = {}
|
29
|
+
raise NotImplementedError, "Subclasses should implement the #check method to check whether the value matches the predicate of the condition"
|
30
|
+
end
|
31
|
+
|
32
|
+
class SimpleCheck < Errapi::Condition
|
33
|
+
|
34
|
+
CONDITIONALS = {
|
35
|
+
if: :if,
|
36
|
+
unless: :unless
|
37
|
+
}.freeze
|
38
|
+
|
39
|
+
def check predicate, value, context, options = {}
|
40
|
+
if @predicate.kind_of?(Symbol) || @predicate.kind_of?(String)
|
41
|
+
value.respond_to?(:[]) ? value[@predicate] : value.send(@predicate)
|
42
|
+
elsif @predicate.respond_to? :call
|
43
|
+
@predicate.call value, context, options
|
44
|
+
else
|
45
|
+
@predicate
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class ErrorCheck < Errapi::Condition
|
51
|
+
|
52
|
+
CONDITIONALS = {
|
53
|
+
if_error: :if,
|
54
|
+
unless_error: :unless
|
55
|
+
}.freeze
|
56
|
+
|
57
|
+
def resolve_conditional conditional
|
58
|
+
CONDITIONALS[conditional]
|
59
|
+
end
|
60
|
+
|
61
|
+
def check predicate, value, context, options = {}
|
62
|
+
if @predicate.respond_to? :call
|
63
|
+
context.errors? &@predicate
|
64
|
+
elsif @predicate.kind_of? Hash
|
65
|
+
context.errors? @predicate
|
66
|
+
else
|
67
|
+
@predicate ? context.errors? : !context.errors?
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'utils.rb')
|
2
|
+
|
3
|
+
module Errapi
|
4
|
+
|
5
|
+
class Configuration
|
6
|
+
attr_reader :options
|
7
|
+
attr_reader :plugins
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@options = OpenStruct.new
|
11
|
+
@plugins = OpenStruct.new
|
12
|
+
@validation_factories = {}
|
13
|
+
@condition_factories = {}
|
14
|
+
@location_factories = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def configure
|
18
|
+
yield self
|
19
|
+
end
|
20
|
+
|
21
|
+
def new_error options = {}
|
22
|
+
Errapi::ValidationError.new options
|
23
|
+
end
|
24
|
+
|
25
|
+
def build_error error, context
|
26
|
+
apply_plugins :build_error, error, context
|
27
|
+
end
|
28
|
+
|
29
|
+
def serialize_error error, serialized
|
30
|
+
apply_plugins :serialize_error, error, serialized
|
31
|
+
end
|
32
|
+
|
33
|
+
def new_context
|
34
|
+
Errapi::ValidationContext.new config: self
|
35
|
+
end
|
36
|
+
|
37
|
+
def plugin impl, options = {}
|
38
|
+
name = options[:name] || Utils.underscore(impl.to_s.sub(/.*::/, '')).to_sym
|
39
|
+
impl.config = self if impl.respond_to? :config=
|
40
|
+
@plugins[name] = impl
|
41
|
+
end
|
42
|
+
|
43
|
+
def validation_factory factory, options = {}
|
44
|
+
name = options[:name] || Utils.underscore(factory.to_s.sub(/.*::/, '')).to_sym
|
45
|
+
factory.config = self if factory.respond_to? :config=
|
46
|
+
@validation_factories[name] = factory
|
47
|
+
end
|
48
|
+
|
49
|
+
def validation name, options = {}
|
50
|
+
raise ArgumentError, "No validation factory registered for name #{name.inspect}" unless @validation_factories.key? name
|
51
|
+
factory = @validation_factories[name]
|
52
|
+
factory.respond_to?(:validation) ? factory.validation(options) : factory.new(options)
|
53
|
+
end
|
54
|
+
|
55
|
+
def register_condition factory
|
56
|
+
factory.conditionals.each do |conditional|
|
57
|
+
raise ArgumentError, "Conditional #{conditional} should start with 'if' or 'unless'." unless conditional.to_s.match /^(if|unless)/
|
58
|
+
@condition_factories[conditional] = factory
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def extract_conditions! source, options = {}
|
63
|
+
[].tap do |conditions|
|
64
|
+
@condition_factories.each_pair do |conditional,factory|
|
65
|
+
next unless source.key? conditional
|
66
|
+
conditions << factory.new(conditional, source.delete(conditional), options)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def apply_plugins operation, *args
|
74
|
+
@plugins.each_pair do |name,plugin|
|
75
|
+
plugin.send operation, *args if plugin.respond_to? operation
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Errapi
|
2
|
+
# TODO: check all "raise" statements and use custom errors
|
3
|
+
class Error < StandardError; end
|
4
|
+
class ValidationErrorInvalid < Error; end
|
5
|
+
class ValidationDefinitionInvalid < Error; end
|
6
|
+
|
7
|
+
class ValidationFailed < Error
|
8
|
+
attr_reader :context
|
9
|
+
|
10
|
+
def initialize context
|
11
|
+
super "#{context.errors.length} errors were found during validation."
|
12
|
+
@context = context
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Errapi
|
2
|
+
|
3
|
+
module LocationBuilders
|
4
|
+
|
5
|
+
def json_location string = nil
|
6
|
+
Locations::Json.new string
|
7
|
+
end
|
8
|
+
|
9
|
+
def dotted_location string = nil
|
10
|
+
Locations::Dotted.new string
|
11
|
+
end
|
12
|
+
|
13
|
+
def no_location
|
14
|
+
Locations::None.instance
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Errapi
|
2
|
+
|
3
|
+
class Locations::Dotted
|
4
|
+
|
5
|
+
def initialize location = nil
|
6
|
+
@location = location.to_s.sub /^\./, '' unless location.nil?
|
7
|
+
end
|
8
|
+
|
9
|
+
def relative parts
|
10
|
+
if @location.nil?
|
11
|
+
self.class.new parts
|
12
|
+
else
|
13
|
+
self.class.new "#{@location}.#{parts.to_s.sub(/^\./, '')}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def location_type
|
18
|
+
:dotted
|
19
|
+
end
|
20
|
+
|
21
|
+
def serialize
|
22
|
+
@location.nil? ? nil : @location
|
23
|
+
end
|
24
|
+
|
25
|
+
def === location
|
26
|
+
@location.to_s == location.to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
@location.to_s
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Errapi
|
2
|
+
|
3
|
+
class Locations::Json
|
4
|
+
|
5
|
+
def initialize location = nil
|
6
|
+
@location = location.nil? ? '' : "/#{location.to_s.sub(/^\//, '').sub(/\/$/, '')}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def relative parts
|
10
|
+
if @location.nil?
|
11
|
+
self.class.new parts
|
12
|
+
else
|
13
|
+
self.class.new "#{@location}/#{parts.to_s.sub(/^\./, '').sub(/\/$/, '')}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def location_type
|
18
|
+
:json
|
19
|
+
end
|
20
|
+
|
21
|
+
def serialize
|
22
|
+
@location
|
23
|
+
end
|
24
|
+
|
25
|
+
def === location
|
26
|
+
@location.to_s == location.to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
@location
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Errapi
|
4
|
+
|
5
|
+
class Locations::None
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
def relative parts
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
def serialize
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def === location
|
17
|
+
location.nil? || self == location
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
LOCATION_STRING
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
LOCATION_STRING = ''.freeze
|
27
|
+
end
|
28
|
+
end
|
data/lib/errapi/model.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Errapi
|
2
|
+
|
3
|
+
module Model
|
4
|
+
|
5
|
+
def errapi name = :default
|
6
|
+
validator = self.class.errapi name
|
7
|
+
ValidatorProxy.new self, validator
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.included mod
|
11
|
+
mod.extend ClassMethods
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
|
16
|
+
def errapi *args, &block
|
17
|
+
|
18
|
+
options = args.last.kind_of?(Hash) ? args.pop : {}
|
19
|
+
config = options[:config] || Errapi.config
|
20
|
+
config = Errapi.config config if config.kind_of? Symbol
|
21
|
+
|
22
|
+
name = args.shift || :default
|
23
|
+
|
24
|
+
@errapi_validators ||= {}
|
25
|
+
@errapi_validators[name] = Errapi::ObjectValidator.new(config, &block) if block
|
26
|
+
@errapi_validators[name]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'location_builders.rb')
|
2
|
+
|
3
|
+
module Errapi
|
4
|
+
|
5
|
+
class ObjectValidator
|
6
|
+
include LocationBuilders
|
7
|
+
|
8
|
+
def initialize config, options = {}, &block
|
9
|
+
# TODO: remove these options or if used, pass them to new validators instantiated in #register_validations
|
10
|
+
@config = config
|
11
|
+
@validations = []
|
12
|
+
instance_eval &block if block
|
13
|
+
end
|
14
|
+
|
15
|
+
def validates *args, &block
|
16
|
+
register_validations *args, &block
|
17
|
+
end
|
18
|
+
|
19
|
+
def validates_each *args, &block
|
20
|
+
|
21
|
+
options = args.last.kind_of?(Hash) ? args.pop : {}
|
22
|
+
options[:each] = args.shift
|
23
|
+
options[:each_options] = options.delete(:each_options) || {}
|
24
|
+
args << options
|
25
|
+
|
26
|
+
validates *args, &block
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate value, context, options = {}
|
30
|
+
# TODO: skip validation by default if previous errors at current location
|
31
|
+
# TODO: add support for previous value and skip validation by default if value is unchanged
|
32
|
+
|
33
|
+
return context.valid? unless @validations
|
34
|
+
|
35
|
+
location = if options[:location]
|
36
|
+
options[:location]
|
37
|
+
elsif options[:location_type]
|
38
|
+
builder = "#{options[:location_type]}_location"
|
39
|
+
raise "Unknown location type #{options[:location_type].inspect}" unless respond_to? builder
|
40
|
+
send builder
|
41
|
+
else
|
42
|
+
no_location
|
43
|
+
end
|
44
|
+
|
45
|
+
source = options[:source]
|
46
|
+
context_proxy = ContextProxy.new context, self, location
|
47
|
+
|
48
|
+
@validations.each do |validation_definition|
|
49
|
+
|
50
|
+
each = validation_definition[:each]
|
51
|
+
|
52
|
+
each_values = nil
|
53
|
+
each_values_set = nil
|
54
|
+
|
55
|
+
if each
|
56
|
+
each_values = extract(value, each[:target], options)[:value]
|
57
|
+
each_values_set = each_values.collect{ |v| true } if each_values.kind_of? Array
|
58
|
+
each_values = [] unless each_values.kind_of? Array
|
59
|
+
each_sources = each_values.collect{ |v| source }
|
60
|
+
else
|
61
|
+
each_values = [ value ]
|
62
|
+
each_values_set = [ options.fetch(:value_set, true) ]
|
63
|
+
each_sources = [ source ]
|
64
|
+
end
|
65
|
+
|
66
|
+
each_location = each ? location.relative(each[:options][:as] || each[:target]) : location
|
67
|
+
|
68
|
+
each_values.each.with_index do |each_value,i|
|
69
|
+
|
70
|
+
each_index_location = each ? each_location.relative(i) : location
|
71
|
+
context_proxy.current_location = each_index_location
|
72
|
+
|
73
|
+
next if validation_definition[:conditions].any?{ |condition| !condition.fulfilled?(each_value, context_proxy) }
|
74
|
+
|
75
|
+
validation_definition[:validations].each do |validation|
|
76
|
+
|
77
|
+
next if validation[:conditions] && validation[:conditions].any?{ |condition| !condition.fulfilled?(each_value, context_proxy) }
|
78
|
+
|
79
|
+
validation_definition[:targets].each do |target|
|
80
|
+
|
81
|
+
target_value_info = extract each_value, target, value_set: each_values_set[i], source: source
|
82
|
+
|
83
|
+
validation_location = target ? each_index_location.relative(validation[:target_alias] || target) : each_index_location
|
84
|
+
context_proxy.current_location = validation_location
|
85
|
+
|
86
|
+
error_options = {
|
87
|
+
value: target_value_info[:value],
|
88
|
+
source: target_value_info[:source],
|
89
|
+
value_set: target_value_info[:value_set],
|
90
|
+
constraints: validation[:validation_options]
|
91
|
+
}
|
92
|
+
|
93
|
+
error_options[:location] = validation_location unless validation_location.kind_of? Errapi::Locations::None
|
94
|
+
|
95
|
+
error_options[:validation] = validation[:validation_name] if validation[:validation_name]
|
96
|
+
|
97
|
+
context_proxy.with_error_options error_options do
|
98
|
+
validation_options = { location: validation_location, value_set: target_value_info[:value_set] }
|
99
|
+
validation[:validation].validate target_value_info[:value], context_proxy, validation_options
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# TODO: add config option to raise error by default
|
107
|
+
raise Errapi::ValidationFailed.new(context) if options[:raise_error] && context.errors?
|
108
|
+
|
109
|
+
context_proxy.valid?
|
110
|
+
end
|
111
|
+
|
112
|
+
def relative_location location
|
113
|
+
RelativeLocation.new location
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
class RelativeLocation
|
119
|
+
attr_reader :location
|
120
|
+
|
121
|
+
def initialize location
|
122
|
+
@location = location
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def extract value, target, options = {}
|
127
|
+
|
128
|
+
source = options[:source]
|
129
|
+
value_set = options.fetch :value_set, true
|
130
|
+
|
131
|
+
if target.nil?
|
132
|
+
{ value: value, value_set: value_set, source: source }
|
133
|
+
elsif target.respond_to? :call
|
134
|
+
{ value: target.call(value), value_set: value_set, source: value }
|
135
|
+
elsif value.kind_of? Hash
|
136
|
+
{ value: value[target], value_set: value.key?(target), source: value }
|
137
|
+
elsif value.respond_to?(target)
|
138
|
+
{ value: value.send(target), value_set: value_set, source: value }
|
139
|
+
else
|
140
|
+
{ value_set: false, source: value }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def register_validations *args, &block
|
145
|
+
# TODO: allow to set custom error options (e.g. reason) when registering validation
|
146
|
+
|
147
|
+
options = args.last.kind_of?(Hash) ? args.pop : {}
|
148
|
+
target_alias = options.delete :as
|
149
|
+
|
150
|
+
validations_definition = {
|
151
|
+
validations: []
|
152
|
+
}
|
153
|
+
|
154
|
+
# FIXME: register all validations (from :with, from block and from hash) in the order they are given
|
155
|
+
if options[:with]
|
156
|
+
validations_definition[:validations] += [*options.delete(:with)].collect{ |with| { validation: with, validation_options: {}, target_alias: target_alias } }
|
157
|
+
end
|
158
|
+
|
159
|
+
if block
|
160
|
+
validations_definition[:validations] << { validation: self.class.new(@config, &block), validation_options: {}, target_alias: target_alias }
|
161
|
+
end
|
162
|
+
|
163
|
+
if options[:each]
|
164
|
+
validations_definition[:each] = {
|
165
|
+
target: options.delete(:each),
|
166
|
+
options: options.delete(:each_options)
|
167
|
+
}
|
168
|
+
end
|
169
|
+
|
170
|
+
validations_definition[:conditions] = @config.extract_conditions! options
|
171
|
+
|
172
|
+
validations = options
|
173
|
+
raise Errapi::ValidationDefinitionInvalid, "No validation was defined. Use registered validations (e.g. `presence: true`), the :with option, or a block to define validations." if validations_definition[:validations].empty? && validations.empty?
|
174
|
+
|
175
|
+
validations.each do |validation_name,options|
|
176
|
+
next unless options
|
177
|
+
validation_options = options.kind_of?(Hash) ? options : {}
|
178
|
+
validation_target_alias = validation_options.delete(:as) || target_alias
|
179
|
+
conditions = @config.extract_conditions! validation_options
|
180
|
+
validation = @config.validation validation_name, validation_options
|
181
|
+
validations_definition[:validations] << { validation: validation, validation_name: validation_name, validation_options: validation_options, target_alias: validation_target_alias, conditions: conditions }
|
182
|
+
end
|
183
|
+
|
184
|
+
validations_definition[:targets] = args.empty? ? [ nil ] : args
|
185
|
+
|
186
|
+
@validations << validations_definition
|
187
|
+
end
|
188
|
+
|
189
|
+
class ContextProxy
|
190
|
+
instance_methods.each{ |m| undef_method m unless m =~ /(^__|^send$|^object_id$)/ }
|
191
|
+
attr_accessor :current_location
|
192
|
+
|
193
|
+
def initialize context, validator, location
|
194
|
+
@context = context
|
195
|
+
@validator = validator
|
196
|
+
@current_location = location
|
197
|
+
@error_options = {}
|
198
|
+
end
|
199
|
+
|
200
|
+
def add_error options = {}, &block
|
201
|
+
@context.add_error @error_options.merge(options), &block
|
202
|
+
end
|
203
|
+
|
204
|
+
def errors? criteria = {}, &block
|
205
|
+
|
206
|
+
if criteria[:location].kind_of? RelativeLocation
|
207
|
+
criteria = criteria.dup
|
208
|
+
criteria[:location] = @current_location.relative criteria[:location].location
|
209
|
+
end
|
210
|
+
|
211
|
+
@context.errors? criteria, &block
|
212
|
+
end
|
213
|
+
|
214
|
+
# TODO: override errors? to support matching relative error locations
|
215
|
+
|
216
|
+
def with_error_options error_options = {}, &block
|
217
|
+
previous_error_options = @error_options
|
218
|
+
@error_options = error_options
|
219
|
+
block.call
|
220
|
+
@error_options = previous_error_options
|
221
|
+
end
|
222
|
+
|
223
|
+
protected
|
224
|
+
|
225
|
+
def method_missing name, *args, &block
|
226
|
+
@context.send name, *args, &block
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'i18n'
|
2
|
+
|
3
|
+
# TODO: support interpolating source and target name (e.g. "Project name cannot be null.")
|
4
|
+
class Errapi::Plugins::I18nMessages
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def serialize_error error, serialized
|
8
|
+
return if serialized.key? :message
|
9
|
+
|
10
|
+
if I18n.exists? translation_key = "errapi.#{error.reason}"
|
11
|
+
interpolation_values = INTERPOLATION_KEYS.inject({}){ |memo,key| memo[key] = error.send(key); memo }.reject{ |k,v| v.nil? }
|
12
|
+
serialized[:message] = I18n.t translation_key, interpolation_values
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
INTERPOLATION_KEYS = %i(check_value checked_value)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Errapi
|
2
|
+
class Plugins::Location
|
3
|
+
class << self
|
4
|
+
attr_writer :config
|
5
|
+
attr_accessor :camelize
|
6
|
+
|
7
|
+
def serialize_error error, serialized
|
8
|
+
if error.location && error.location.respond_to?(:serialize)
|
9
|
+
|
10
|
+
serialized_location = error.location.serialize
|
11
|
+
unless serialized_location.nil?
|
12
|
+
serialized[:location] = serialized_location
|
13
|
+
serialized[location_type_key] = error.location.location_type if error.location.respond_to? :location_type
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def location_type_key
|
21
|
+
camelize? ? :locationType : :location_type
|
22
|
+
end
|
23
|
+
|
24
|
+
def camelize?
|
25
|
+
@camelize.nil? ? @config.options.camelize : @camelize
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Errapi
|
2
|
+
class Plugins::Reason
|
3
|
+
class << self
|
4
|
+
attr_writer :config
|
5
|
+
attr_accessor :camelize
|
6
|
+
|
7
|
+
def serialize_error error, serialized
|
8
|
+
serialized[:reason] = serialized_reason error
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def serialized_reason error
|
14
|
+
camelize? ? Utils.camelize(error.reason.to_s).to_sym : error.reason
|
15
|
+
end
|
16
|
+
|
17
|
+
def camelize?
|
18
|
+
@camelize.nil? ? @config.options.camelize : @camelize
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|