errapi 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ module Errapi
2
+
3
+ class SingleValidator
4
+
5
+ def self.configure *args, &block
6
+
7
+ options = args.last.kind_of?(Hash) ? args.pop : {}
8
+ config = options[:config] || Errapi.config
9
+ config = Errapi.config config if config.kind_of? Symbol
10
+
11
+ @errapi_validator = ObjectValidator.new config, options, &block
12
+ end
13
+
14
+ def self.validate *args, &block
15
+ raise "Validator has not yet been configured. You must call #configure before calling #validate." unless @errapi_validator
16
+ @errapi_validator.validate *args, &block
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ module Errapi::Utils
2
+
3
+ def self.camelize string, uppercase_first_letter = false
4
+ parts = string.split '_'
5
+ return string if parts.length < 2
6
+ parts[0] + parts[1, parts.length - 1].collect(&:capitalize).join
7
+ end
8
+
9
+ def self.underscore string
10
+ string.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
11
+ end
12
+ end
@@ -0,0 +1,49 @@
1
+ require 'ostruct'
2
+
3
+ class Errapi::ValidationContext
4
+ attr_reader :data
5
+ attr_reader :errors
6
+ attr_reader :config
7
+
8
+ def initialize options = {}
9
+ @errors = []
10
+ @data = OpenStruct.new options[:data] || {}
11
+ @config = options[:config]
12
+ end
13
+
14
+ def add_error options = {}, &block
15
+
16
+ error = options.kind_of?(Errapi::ValidationError) ? options : @config.new_error(options)
17
+ yield error if block_given?
18
+ @config.build_error error, self
19
+
20
+ @errors << error
21
+ self
22
+ end
23
+
24
+ def errors? criteria = {}, &block
25
+ return !@errors.empty? if criteria.empty? && !block
26
+ block ? @errors.any?{ |err| err.matches?(criteria) && block.call(err) } : @errors.any?{ |err| err.matches?(criteria) }
27
+ end
28
+
29
+ def valid?
30
+ !errors?
31
+ end
32
+
33
+ def clear
34
+ @errors.clear
35
+ @data = OpenStruct.new
36
+ end
37
+
38
+ # TODO: add custom serialization options
39
+ def serialize
40
+ # TODO: add hook for plugins to serialize context
41
+ { errors: [] }.tap do |h|
42
+ @errors.each do |error|
43
+ serialized = {}
44
+ @config.serialize_error error, serialized
45
+ h[:errors] << serialized
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,43 @@
1
+ require 'ostruct'
2
+
3
+ class Errapi::ValidationError
4
+ attr_accessor :reason
5
+ attr_accessor :check_value
6
+ attr_accessor :checked_value
7
+ attr_accessor :validation
8
+ attr_accessor :constraints
9
+ attr_accessor :location
10
+
11
+ def initialize options = {}
12
+ ATTRIBUTES.each do |attr|
13
+ instance_variable_set "@#{attr}", options[attr] if options.key? attr
14
+ end
15
+ end
16
+
17
+ def matches? criteria = {}
18
+ unknown_criteria = criteria.keys - ATTRIBUTES
19
+ raise "Unknown error attributes: #{unknown_criteria.join(', ')}." if unknown_criteria.any?
20
+ ATTRIBUTES.all?{ |attr| criterion_matches? criteria, attr }
21
+ end
22
+
23
+ private
24
+
25
+ ATTRIBUTES = %i(reason location check_value checked_value validation)
26
+
27
+ def criterion_matches? criteria, attr
28
+ return true unless criteria.key? attr
29
+
30
+ value = send attr
31
+ criterion = criteria[attr]
32
+
33
+ if criterion.kind_of? Regexp
34
+ !!criterion.match(value.to_s)
35
+ elsif criterion.kind_of? String
36
+ criterion == value.to_s
37
+ elsif criterion.respond_to? :===
38
+ criterion === value
39
+ else
40
+ criterion == value
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,45 @@
1
+ module Errapi::Validations
2
+ module Clusivity
3
+ private
4
+
5
+ DELIMITER_METHOD_CHECKS = %i(include? call to_sym).freeze
6
+
7
+ def check_delimiter! option_desc
8
+ unless @delimiter.respond_to?(:include?) || callable_option_value?(@delimiter)
9
+ raise callable_option_type_error option_desc, "an object with the #include? method", @delimiter
10
+ end
11
+ end
12
+
13
+ def members option_desc, options = {}
14
+ enumerable = actual_option_value @delimiter, options
15
+
16
+ unless enumerable.respond_to? :include?
17
+ raise callable_option_value_error option_desc, "an object with the #include? method", @delimiter
18
+ end
19
+
20
+ enumerable
21
+ end
22
+
23
+ def include? members, value
24
+ members.send inclusion_method(members), value
25
+ end
26
+
27
+ # From rails/activemodel/lib/active_model/validations/clusivity.rb:
28
+ # In Ruby 1.9 <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
29
+ # possible values in the range for equality, which is slower but more accurate.
30
+ # <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range
31
+ # endpoints, which is fast but is only accurate on Numeric, Time, or DateTime ranges.
32
+ def inclusion_method enumerable
33
+ if enumerable.is_a? Range
34
+ case enumerable.first
35
+ when Numeric, Time, DateTime
36
+ :cover?
37
+ else
38
+ :include?
39
+ end
40
+ else
41
+ :include?
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,28 @@
1
+ require File.join(File.dirname(__FILE__), 'clusivity.rb')
2
+
3
+ module Errapi::Validations
4
+ class Exclusion < Base
5
+ include Clusivity
6
+
7
+ def initialize options = {}
8
+ unless key = exactly_one_option?(OPTIONS, options)
9
+ raise ArgumentError, "Either :from or :in or :within must be supplied (but only one of them)."
10
+ end
11
+
12
+ @delimiter = options[key]
13
+ check_delimiter! OPTIONS_DESCRIPTION
14
+ end
15
+
16
+ def validate value, context, options = {}
17
+ excluded_values = members OPTIONS_DESCRIPTION, options
18
+ if include? excluded_values, value
19
+ context.add_error reason: :excluded, check_value: excluded_values
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ OPTIONS = %i(from in within)
26
+ OPTIONS_DESCRIPTION = ":from (or :in or :within)"
27
+ end
28
+ end
@@ -0,0 +1,33 @@
1
+ module Errapi::Validations
2
+ class Format < Base
3
+
4
+ def initialize options = {}
5
+ unless key = exactly_one_option?(OPTIONS, options)
6
+ raise ArgumentError, "Either :with or :without must be supplied (but not both)."
7
+ end
8
+
9
+ @format = options[key]
10
+ @should_match = key == :with
11
+
12
+ unless @format.kind_of?(Regexp) or callable_option_value?(@format)
13
+ raise callable_option_type_error ":with (or :without)", "a regular expression", @format
14
+ end
15
+ end
16
+
17
+ def validate value, context, options = {}
18
+
19
+ regexp = actual_option_value @format, options
20
+ unless regexp.kind_of? Regexp
21
+ raise callable_option_value_error ":with (or :without)", "a regular expression", regexp
22
+ end
23
+
24
+ if !regexp.match(value.to_s) == @should_match
25
+ context.add_error reason: :invalid_format, check_value: regexp
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ OPTIONS = %i(with without)
32
+ end
33
+ end
@@ -0,0 +1,28 @@
1
+ require File.join(File.dirname(__FILE__), 'clusivity.rb')
2
+
3
+ module Errapi::Validations
4
+ class Inclusion < Base
5
+ include Clusivity
6
+
7
+ def initialize options = {}
8
+ unless key = exactly_one_option?(OPTIONS, options)
9
+ raise ArgumentError, "Either :in or :within must be supplied (but not both)."
10
+ end
11
+
12
+ @delimiter = options[key]
13
+ check_delimiter! OPTIONS_DESCRIPTION
14
+ end
15
+
16
+ def validate value, context, options = {}
17
+ allowed_values = members OPTIONS_DESCRIPTION, options
18
+ unless include? allowed_values, value
19
+ context.add_error reason: :not_included, check_value: allowed_values
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ OPTIONS = %i(in within)
26
+ OPTIONS_DESCRIPTION = ":in (or :within)"
27
+ end
28
+ end
@@ -0,0 +1,66 @@
1
+ module Errapi::Validations
2
+ class Length < Base
3
+ CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze
4
+ REASONS = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze
5
+
6
+ def initialize options = {}
7
+
8
+ constraints = options.select{ |k,v| OPTIONS.include? k }
9
+ if constraints.empty?
10
+ raise ArgumentError, "The :is, :minimum/:maximum or :within options must be supplied (but only :minimum and :maximum can be used together)."
11
+ elsif options.key?(:is) && constraints.length != 1
12
+ raise ArgumentError, "The :is option cannot be combined with :minimum, :maximum or :within."
13
+ elsif options.key?(:is)
14
+ check_numeric! options[:is]
15
+ elsif options.key?(:within)
16
+ if options.key?(:minimum) || options.key?(:maximum)
17
+ raise ArgumentError, "The :within option cannot be combined with :minimum or :maximum."
18
+ else
19
+ check_range! options[:within]
20
+ end
21
+ else
22
+ check_numeric! options[:minimum] if options.key? :minimum
23
+ check_numeric! options[:maximum] if options.key? :maximum
24
+ end
25
+
26
+ @constraints = actual_constraints constraints
27
+ end
28
+
29
+ def validate value, context, options = {}
30
+ return unless value.respond_to? :length
31
+ actual_length = value.length
32
+
33
+ CHECKS.each_pair do |key,check|
34
+ next unless check_value = @constraints[key]
35
+ next if actual_length.send check, check_value
36
+ context.add_error reason: REASONS[key], check_value: check_value, checked_value: actual_length, constraints: @constraints
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ OPTIONS = %i(is minimum maximum within)
43
+
44
+ def actual_constraints options = {}
45
+ if range = options[:within]
46
+ { minimum: range.min, maximum: range.max }
47
+ else
48
+ options
49
+ end
50
+ end
51
+
52
+ def check_numeric! bound
53
+ unless bound.kind_of? Numeric
54
+ raise ArgumentError, "The :is, :minimum or :maximum option must be a numeric value, but a #{bound.class.name} was given."
55
+ end
56
+ end
57
+
58
+ def check_range! range
59
+ if !range.kind_of?(Range)
60
+ raise ArgumentError, "The :within option must be a numeric range, but a #{range.class.name} was given."
61
+ elsif !(t = range.first).kind_of?(Numeric)
62
+ raise ArgumentError, "The :within option must be a numeric range, but a #{t.class.name} range was given."
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,39 @@
1
+ module Errapi::Validations
2
+ class Presence < Factory
3
+ class Implementation < Base
4
+
5
+ def validate value, context, options = {}
6
+ if reason = check(value, options.fetch(:value_set, true))
7
+ context.add_error reason: reason
8
+ end
9
+ end
10
+
11
+ private
12
+
13
+ BLANK_REGEXP = /\A[[:space:]]*\z/
14
+
15
+ def check value, value_set
16
+ # TODO: allow customization (e.g. values that are not required, booleans, etc)
17
+ if !value_set
18
+ :missing
19
+ elsif value.nil?
20
+ :null
21
+ elsif value.respond_to?(:empty?) && value.empty?
22
+ :empty
23
+ elsif value_blank? value
24
+ :blank
25
+ end
26
+ end
27
+
28
+ def value_blank? value
29
+ if value.respond_to? :blank?
30
+ value.blank?
31
+ elsif value.kind_of? String
32
+ BLANK_REGEXP === value
33
+ else
34
+ false
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,10 @@
1
+ module Errapi::Validations
2
+ class Trim < Base
3
+
4
+ def validate value, context, options = {}
5
+ if value.kind_of?(String) && /(?:\A\s|\s\Z)/.match(value)
6
+ context.add_error reason: :untrimmed
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,54 @@
1
+ module Errapi::Validations
2
+ class Type < Base
3
+
4
+ def initialize options = {}
5
+ unless key = exactly_one_option?(OPTIONS, options)
6
+ raise ArgumentError, "One option among :instance_of, :kind_of, :is_a or :is_an must be supplied (but only one)."
7
+ end
8
+
9
+ if key == :instance_of
10
+ @instance_of = check_types! options[key]
11
+ raise ArgumentError, "Type aliases cannot be used with the :instance_of option. Use :kind_of, :is_a or :is_an." if options[key].kind_of? Symbol
12
+ else
13
+ @kind_of = check_types! options[key]
14
+ end
15
+ end
16
+
17
+ def validate value, context, options = {}
18
+ if @instance_of && @instance_of.none?{ |type| value.instance_of? type }
19
+ context.add_error reason: :wrong_type, check_value: @instance_of, checked_value: value.class
20
+ elsif @kind_of && @kind_of.none?{ |type| value.kind_of? type }
21
+ context.add_error reason: :wrong_type, check_value: @kind_of, checked_value: value.class
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def check_types! types
28
+ if !types.kind_of?(Array)
29
+ types = [ types ]
30
+ elsif types.empty?
31
+ raise ArgumentError, "At least one class or module is required, but an empty array was given."
32
+ end
33
+
34
+ types.each do |type|
35
+ unless TYPE_ALIASES.key?(type) || type.class == Class || type.class == Module
36
+ raise ArgumentError, "A class or module (or an array of classes or modules, or a type alias) is required, but a #{type.class} was given."
37
+ end
38
+ end
39
+
40
+ types.collect{ |type| TYPE_ALIASES[type] || type }.flatten.uniq
41
+ end
42
+
43
+ OPTIONS = %i(instance_of kind_of is_a is_an)
44
+ TYPE_ALIASES = {
45
+ string: [ String ],
46
+ number: [ Numeric ],
47
+ integer: [ Integer ],
48
+ boolean: [ TrueClass, FalseClass ],
49
+ object: [ Hash ],
50
+ array: [ Array ],
51
+ null: [ NilClass ]
52
+ }
53
+ end
54
+ end
@@ -0,0 +1,57 @@
1
+ module Errapi::Validations
2
+
3
+ class Base
4
+
5
+ def initialize options = {}
6
+ end
7
+
8
+ def actual_option_value supplied_value, options
9
+ if supplied_value.respond_to? :call
10
+ supplied_value.call options[:source]
11
+ elsif supplied_value.respond_to? :to_sym
12
+ unless options[:source].respond_to? supplied_value
13
+ raise ArgumentError, "The validation source (#{options[:source].class.name}) does not respond to :#{supplied_value}."
14
+ else
15
+ options[:source].send supplied_value
16
+ end
17
+ else
18
+ supplied_value
19
+ end
20
+ end
21
+
22
+ def callable_option_value? supplied_value
23
+ supplied_value.respond_to?(:call) || supplied_value.respond_to?(:to_sym)
24
+ end
25
+
26
+ def exactly_one_option? keys, options
27
+ found_keys = options.keys.select{ |k| keys.include? k }
28
+ found_keys.length == 1 ? found_keys.first : false
29
+ end
30
+
31
+ def callable_option_type_error key_desc, value_desc, supplied_value
32
+ ArgumentError.new "The #{key_desc} option must be #{value_desc}, a proc, a lambda or a symbol, but a #{supplied_value.class.name} was given."
33
+ end
34
+
35
+ def callable_option_value_error key_desc, type_desc, supplied_value
36
+ ArgumentError.new "The call supplied to #{key_desc} must return #{type_desc}, but a #{supplied_value.class.name} was returned."
37
+ end
38
+ end
39
+
40
+ class Factory
41
+
42
+ def config= config
43
+ raise "A configuration has already been set for this factory." if @config
44
+ @config = config
45
+ end
46
+
47
+ def validation options = {}
48
+ self.class.const_get('Implementation').new options
49
+ end
50
+
51
+ def to_s
52
+ Errapi::Utils.underscore self.class.name.sub(/.*::/, '')
53
+ end
54
+ end
55
+ end
56
+
57
+ Dir[File.join File.dirname(__FILE__), File.basename(__FILE__, '.*'), '*.rb'].each{ |lib| require lib }
@@ -0,0 +1,21 @@
1
+ module Errapi
2
+
3
+ class ValidatorProxy
4
+ instance_methods.each{ |m| undef_method m unless m =~ /(^__|^send$|^object_id$)/ }
5
+
6
+ def initialize object, validator
7
+ @object = object
8
+ @validator = validator
9
+ end
10
+
11
+ def validate context, options = {}
12
+ @validator.validate @object, context, options
13
+ end
14
+
15
+ protected
16
+
17
+ def method_missing name, *args, &block
18
+ @validator.send name, *args, &block
19
+ end
20
+ end
21
+ end
data/lib/errapi.rb CHANGED
@@ -1,3 +1,47 @@
1
1
  module Errapi
2
- VERSION = '0.1.0'
2
+ VERSION = '0.1.2'
3
+ end
4
+
5
+ Dir[File.join File.dirname(__FILE__), File.basename(__FILE__, '.*'), '*.rb'].each{ |lib| require lib }
6
+
7
+ module Errapi
8
+
9
+ def self.configure name = nil, &block
10
+
11
+ init_configs
12
+ name ||= :default
13
+
14
+ if @configs[name]
15
+ @configs[name].configure &block
16
+ else
17
+ @configs[name] = Configuration.new &block
18
+ end
19
+ end
20
+
21
+ def self.config name = nil
22
+ init_configs[name || :default]
23
+ end
24
+
25
+ private
26
+
27
+ def self.init_configs
28
+ @configs ? @configs : @configs = { default: default_config }
29
+ end
30
+
31
+ def self.default_config
32
+ Configuration.new.tap do |config|
33
+ config.plugin Errapi::Plugins::I18nMessages
34
+ config.plugin Errapi::Plugins::Reason
35
+ config.plugin Errapi::Plugins::Location
36
+ config.validation_factory Errapi::Validations::Exclusion
37
+ config.validation_factory Errapi::Validations::Format
38
+ config.validation_factory Errapi::Validations::Inclusion
39
+ config.validation_factory Errapi::Validations::Length
40
+ config.validation_factory Errapi::Validations::Presence.new
41
+ config.validation_factory Errapi::Validations::Trim
42
+ config.validation_factory Errapi::Validations::Type
43
+ config.register_condition Errapi::Condition::SimpleCheck
44
+ config.register_condition Errapi::Condition::ErrorCheck
45
+ end
46
+ end
3
47
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: errapi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Oulevay
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-10-09 00:00:00.000000000 Z
11
+ date: 2015-01-26 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: i18n
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.7.0
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.7.0
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rake
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -38,6 +52,20 @@ dependencies:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
54
  version: '3.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-collection_matchers
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.1'
41
69
  - !ruby/object:Gem::Dependency
42
70
  name: jeweler
43
71
  requirement: !ruby/object:Gem::Requirement
@@ -72,28 +100,28 @@ dependencies:
72
100
  requirements:
73
101
  - - "~>"
74
102
  - !ruby/object:Gem::Version
75
- version: '0.9'
103
+ version: 0.9.1
76
104
  type: :development
77
105
  prerelease: false
78
106
  version_requirements: !ruby/object:Gem::Requirement
79
107
  requirements:
80
108
  - - "~>"
81
109
  - !ruby/object:Gem::Version
82
- version: '0.9'
110
+ version: 0.9.1
83
111
  - !ruby/object:Gem::Dependency
84
112
  name: coveralls
85
113
  requirement: !ruby/object:Gem::Requirement
86
114
  requirements:
87
115
  - - "~>"
88
116
  - !ruby/object:Gem::Version
89
- version: '0.7'
117
+ version: 0.7.3
90
118
  type: :development
91
119
  prerelease: false
92
120
  version_requirements: !ruby/object:Gem::Requirement
93
121
  requirements:
94
122
  - - "~>"
95
123
  - !ruby/object:Gem::Version
96
- version: '0.7'
124
+ version: 0.7.3
97
125
  description: Utilities to validate data and serialize errors.
98
126
  email: git@alphahydrae.com
99
127
  executables: []
@@ -107,6 +135,34 @@ files:
107
135
  - README.md
108
136
  - VERSION
109
137
  - lib/errapi.rb
138
+ - lib/errapi/condition.rb
139
+ - lib/errapi/configuration.rb
140
+ - lib/errapi/errors.rb
141
+ - lib/errapi/location_builders.rb
142
+ - lib/errapi/locations.rb
143
+ - lib/errapi/locations/dotted.rb
144
+ - lib/errapi/locations/json.rb
145
+ - lib/errapi/locations/none.rb
146
+ - lib/errapi/model.rb
147
+ - lib/errapi/object_validator.rb
148
+ - lib/errapi/plugins.rb
149
+ - lib/errapi/plugins/i18n_messages.rb
150
+ - lib/errapi/plugins/location.rb
151
+ - lib/errapi/plugins/reason.rb
152
+ - lib/errapi/single_validator.rb
153
+ - lib/errapi/utils.rb
154
+ - lib/errapi/validation_context.rb
155
+ - lib/errapi/validation_error.rb
156
+ - lib/errapi/validations.rb
157
+ - lib/errapi/validations/clusivity.rb
158
+ - lib/errapi/validations/exclusion.rb
159
+ - lib/errapi/validations/format.rb
160
+ - lib/errapi/validations/inclusion.rb
161
+ - lib/errapi/validations/length.rb
162
+ - lib/errapi/validations/presence.rb
163
+ - lib/errapi/validations/trim.rb
164
+ - lib/errapi/validations/type.rb
165
+ - lib/errapi/validator_proxy.rb
110
166
  homepage: http://github.com/AlphaHydrae/errapi
111
167
  licenses:
112
168
  - MIT
@@ -127,7 +183,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
183
  version: '0'
128
184
  requirements: []
129
185
  rubyforge_project:
130
- rubygems_version: 2.2.2
186
+ rubygems_version: 2.4.3
131
187
  signing_key:
132
188
  specification_version: 4
133
189
  summary: An extensible API-oriented validation library.