respect 0.1.0
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.
- data/MIT-LICENSE +20 -0
- data/README.md +289 -0
- data/RELATED_WORK.md +40 -0
- data/RELEASE_NOTES.md +23 -0
- data/Rakefile +31 -0
- data/STATUS_MATRIX.html +137 -0
- data/lib/respect.rb +231 -0
- data/lib/respect/any_schema.rb +22 -0
- data/lib/respect/array_def.rb +28 -0
- data/lib/respect/array_schema.rb +203 -0
- data/lib/respect/boolean_schema.rb +32 -0
- data/lib/respect/composite_schema.rb +86 -0
- data/lib/respect/core_statements.rb +206 -0
- data/lib/respect/datetime_schema.rb +27 -0
- data/lib/respect/def_without_name.rb +6 -0
- data/lib/respect/divisible_by_validator.rb +20 -0
- data/lib/respect/doc_helper.rb +24 -0
- data/lib/respect/doc_parser.rb +37 -0
- data/lib/respect/dsl_dumper.rb +181 -0
- data/lib/respect/equal_to_validator.rb +20 -0
- data/lib/respect/fake_name_proxy.rb +116 -0
- data/lib/respect/float_schema.rb +27 -0
- data/lib/respect/format_validator.rb +136 -0
- data/lib/respect/global_def.rb +79 -0
- data/lib/respect/greater_than_or_equal_to_validator.rb +19 -0
- data/lib/respect/greater_than_validator.rb +19 -0
- data/lib/respect/has_constraints.rb +34 -0
- data/lib/respect/hash_def.rb +40 -0
- data/lib/respect/hash_schema.rb +218 -0
- data/lib/respect/in_validator.rb +19 -0
- data/lib/respect/integer_schema.rb +27 -0
- data/lib/respect/ip_addr_schema.rb +23 -0
- data/lib/respect/ipv4_addr_schema.rb +27 -0
- data/lib/respect/ipv6_addr_schema.rb +27 -0
- data/lib/respect/items_def.rb +21 -0
- data/lib/respect/json_schema_html_formatter.rb +143 -0
- data/lib/respect/less_than_or_equal_to_validator.rb +19 -0
- data/lib/respect/less_than_validator.rb +19 -0
- data/lib/respect/match_validator.rb +19 -0
- data/lib/respect/max_length_validator.rb +20 -0
- data/lib/respect/min_length_validator.rb +20 -0
- data/lib/respect/multiple_of_validator.rb +10 -0
- data/lib/respect/null_schema.rb +26 -0
- data/lib/respect/numeric_schema.rb +33 -0
- data/lib/respect/org3_dumper.rb +213 -0
- data/lib/respect/regexp_schema.rb +19 -0
- data/lib/respect/schema.rb +285 -0
- data/lib/respect/schema_def.rb +16 -0
- data/lib/respect/string_schema.rb +21 -0
- data/lib/respect/unit_test_helper.rb +37 -0
- data/lib/respect/uri_schema.rb +23 -0
- data/lib/respect/utc_time_schema.rb +17 -0
- data/lib/respect/validator.rb +51 -0
- data/lib/respect/version.rb +3 -0
- data/test/any_schema_test.rb +79 -0
- data/test/array_def_test.rb +113 -0
- data/test/array_schema_test.rb +487 -0
- data/test/boolean_schema_test.rb +89 -0
- data/test/composite_schema_test.rb +30 -0
- data/test/datetime_schema_test.rb +83 -0
- data/test/doc_helper_test.rb +34 -0
- data/test/doc_parser_test.rb +109 -0
- data/test/dsl_dumper_test.rb +395 -0
- data/test/fake_name_proxy_test.rb +138 -0
- data/test/float_schema_test.rb +146 -0
- data/test/format_validator_test.rb +224 -0
- data/test/hash_def_test.rb +126 -0
- data/test/hash_schema_test.rb +613 -0
- data/test/integer_schema_test.rb +142 -0
- data/test/ip_addr_schema_test.rb +78 -0
- data/test/ipv4_addr_schema_test.rb +71 -0
- data/test/ipv6_addr_schema_test.rb +71 -0
- data/test/json_schema_html_formatter_test.rb +214 -0
- data/test/null_schema_test.rb +46 -0
- data/test/numeric_schema_test.rb +294 -0
- data/test/org3_dumper_test.rb +784 -0
- data/test/regexp_schema_test.rb +54 -0
- data/test/respect_test.rb +108 -0
- data/test/schema_def_test.rb +405 -0
- data/test/schema_test.rb +290 -0
- data/test/string_schema_test.rb +209 -0
- data/test/support/circle.rb +11 -0
- data/test/support/color.rb +24 -0
- data/test/support/point.rb +11 -0
- data/test/support/respect/circle_schema.rb +16 -0
- data/test/support/respect/color_def.rb +19 -0
- data/test/support/respect/color_schema.rb +33 -0
- data/test/support/respect/point_schema.rb +19 -0
- data/test/support/respect/rgba_schema.rb +20 -0
- data/test/support/respect/universal_validator.rb +25 -0
- data/test/support/respect/user_macros.rb +12 -0
- data/test/support/rgba.rb +11 -0
- data/test/test_helper.rb +90 -0
- data/test/uri_schema_test.rb +54 -0
- data/test/utc_time_schema_test.rb +63 -0
- data/test/validator_test.rb +22 -0
- metadata +288 -0
data/lib/respect.rb
ADDED
@@ -0,0 +1,231 @@
|
|
1
|
+
require 'active_support/dependencies/autoload'
|
2
|
+
require 'active_support/core_ext/string/inflections'
|
3
|
+
require 'active_support/core_ext/integer/inflections'
|
4
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
5
|
+
require 'active_support/core_ext/string/strip'
|
6
|
+
|
7
|
+
# Setup inflection rules for our acronyms
|
8
|
+
ActiveSupport::Inflector.inflections do |inflect|
|
9
|
+
inflect.acronym "URI"
|
10
|
+
inflect.acronym "UTC"
|
11
|
+
inflect.acronym "IP"
|
12
|
+
inflect.acronym "JSON"
|
13
|
+
end
|
14
|
+
|
15
|
+
# Provide methods and classes to define, validate, sanitize and dump object schema.
|
16
|
+
#
|
17
|
+
# Classes in this module are split in 5 groups:
|
18
|
+
# * The _schema_ classes are the core of this module since they support the validation
|
19
|
+
# process and are the internal representation of schema specification (see {Schema}).
|
20
|
+
# * The _definition_ classes (aka _def_ classes) are the front-end of this module since
|
21
|
+
# they implement the schema definition DSL (see {GlobalDef}).
|
22
|
+
# * The _validator_ classes implement validation routine you can attach to your schema.
|
23
|
+
# accessible via the schema's options (see {Validator}).
|
24
|
+
# * The _dumper_ classes are the back-end of this module since they implement the
|
25
|
+
# convertion of the internal schema representation to different formats.
|
26
|
+
# * The _miscellaneous_ classes provides various support for the other categories.
|
27
|
+
#
|
28
|
+
# You can extend this library in many ways:
|
29
|
+
#
|
30
|
+
# 1. If you want to add your own schema class, you can sub-class the {CompositeSchema}
|
31
|
+
# class. Sub-classing of the {Schema} class is not well supported yet as it may have
|
32
|
+
# some issues with the current dumpers (see {DslDumper} and {Org3Dumper}). Fortunately,
|
33
|
+
# most of the cases can be handled by {CompositeSchema}.
|
34
|
+
# 1. If you want to simply add new statements to the schema definition DSL, you can just
|
35
|
+
# bundle them in a module and call {Respect.extend_dsl_with} (see {CoreStatements} for
|
36
|
+
# further information).
|
37
|
+
#
|
38
|
+
# Extension of the _validator_ and _dumper_ classes is still experimental. Also, creating
|
39
|
+
# custom _definition_ classes is not recommended yet.
|
40
|
+
module Respect
|
41
|
+
extend ActiveSupport::Autoload
|
42
|
+
|
43
|
+
# Schema classes
|
44
|
+
autoload :Schema
|
45
|
+
autoload :HashSchema
|
46
|
+
autoload :IntegerSchema
|
47
|
+
autoload :FloatSchema
|
48
|
+
autoload :NumericSchema
|
49
|
+
autoload :StringSchema
|
50
|
+
autoload :ArraySchema
|
51
|
+
autoload :AnySchema
|
52
|
+
autoload :BooleanSchema
|
53
|
+
autoload :NullSchema
|
54
|
+
autoload :URISchema
|
55
|
+
autoload :RegexpSchema
|
56
|
+
autoload :DatetimeSchema
|
57
|
+
autoload :IPAddrSchema
|
58
|
+
autoload :Ipv4AddrSchema
|
59
|
+
autoload :Ipv6AddrSchema
|
60
|
+
autoload :UTCTimeSchema
|
61
|
+
autoload :HasConstraints
|
62
|
+
autoload :CompositeSchema
|
63
|
+
# Validator classes
|
64
|
+
autoload :Validator
|
65
|
+
autoload :EqualToValidator
|
66
|
+
autoload :GreaterThanValidator
|
67
|
+
autoload :GreaterThanOrEqualToValidator
|
68
|
+
autoload :LessThanValidator
|
69
|
+
autoload :LessThanOrEqualToValidator
|
70
|
+
autoload :DivisibleByValidator
|
71
|
+
autoload :MultipleOfValidator
|
72
|
+
autoload :InValidator
|
73
|
+
autoload :MatchValidator
|
74
|
+
autoload :MinLengthValidator
|
75
|
+
autoload :MaxLengthValidator
|
76
|
+
autoload :FormatValidator
|
77
|
+
# DSL classes
|
78
|
+
autoload :SchemaDef
|
79
|
+
autoload :ArrayDef
|
80
|
+
autoload :HashDef
|
81
|
+
autoload :GlobalDef
|
82
|
+
autoload :ItemsDef
|
83
|
+
autoload :CoreStatements
|
84
|
+
autoload :DefWithoutName
|
85
|
+
autoload :FakeNameProxy
|
86
|
+
# Dumper classes
|
87
|
+
autoload :DslDumper
|
88
|
+
autoload :Org3Dumper
|
89
|
+
# Miscellaneous classes
|
90
|
+
autoload :DocParser
|
91
|
+
autoload :DocHelper
|
92
|
+
autoload :JSONSchemaHTMLFormatter
|
93
|
+
|
94
|
+
# Base error of all errors raised by this module.
|
95
|
+
class RespectError < StandardError
|
96
|
+
end
|
97
|
+
|
98
|
+
# Raised when the validation process has failed.
|
99
|
+
class ValidationError < RespectError
|
100
|
+
def initialize(message)
|
101
|
+
super
|
102
|
+
@context = [ message ]
|
103
|
+
end
|
104
|
+
|
105
|
+
# An array of error messages to help you track where
|
106
|
+
# the error happened. Use it as a back-trace but in
|
107
|
+
# your validated object instead of your code.
|
108
|
+
attr_reader :context
|
109
|
+
end
|
110
|
+
|
111
|
+
# Raised when you did an illegal operation while defining
|
112
|
+
# a schema. See it as an ArgumentError but more specific.
|
113
|
+
class InvalidSchemaError < RespectError
|
114
|
+
end
|
115
|
+
|
116
|
+
class << self
|
117
|
+
|
118
|
+
# Extend the schema definition DSL with the statements defined in the given
|
119
|
+
# module +mod+. Its methods would be available to each definition class
|
120
|
+
# calling {GlobalDef.include_core_statements}.
|
121
|
+
def extend_dsl_with(mod)
|
122
|
+
raise ArugmentError, "cannot extend DSL with CoreStatements" if mod == CoreStatements
|
123
|
+
CoreStatements.send(:include, mod)
|
124
|
+
# We must "refresh" all the classes include "CoreStatements" by re-including it to
|
125
|
+
# work around the
|
126
|
+
# {dynamic module include problem}[http://eigenclass.org/hiki/The+double+inclusion+problem]
|
127
|
+
GlobalDef.core_contexts.each{|c| c.send(:include, CoreStatements) }
|
128
|
+
end
|
129
|
+
|
130
|
+
STATEMENT_NAME_REGEXP = /^[a-z_][a-z_0-9]*$/
|
131
|
+
|
132
|
+
# Build a schema class name from the given +statement_name+.
|
133
|
+
def schema_name_for(statement_name)
|
134
|
+
unless statement_name =~ STATEMENT_NAME_REGEXP
|
135
|
+
raise ArgumentError, "statement '#{statement_name}' name must match #{STATEMENT_NAME_REGEXP.inspect}"
|
136
|
+
end
|
137
|
+
const_name = statement_name.to_s
|
138
|
+
if const_name == "schema"
|
139
|
+
"#{self.name}::Schema"
|
140
|
+
else
|
141
|
+
"#{self.name}::#{const_name.camelize}Schema"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Return the schema class associated to the given +statement_name+.
|
146
|
+
#
|
147
|
+
# A "valid" schema class must verify the following properties:
|
148
|
+
# * Named like +StatementNameSchema+ in {Respect} module.
|
149
|
+
# * Be a sub-class of {Schema}.
|
150
|
+
# * Be concrete (i.e. have a public method +new+)
|
151
|
+
def schema_for(statement_name)
|
152
|
+
klass = Respect.schema_name_for(statement_name).safe_constantize
|
153
|
+
if klass && klass < Schema && klass.public_methods.include?(:new)
|
154
|
+
klass
|
155
|
+
else
|
156
|
+
nil
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Test whether a schema is defined for the given +statement_name+.
|
161
|
+
def schema_defined_for?(statement_name)
|
162
|
+
!!schema_for(statement_name)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Turn the given string (assuming it is a constraint name) into a
|
166
|
+
# validator class name string.
|
167
|
+
def validator_name_for(constraint_name)
|
168
|
+
"#{self.name}::#{constraint_name.to_s.camelize}Validator"
|
169
|
+
end
|
170
|
+
|
171
|
+
# Turn the given +constraint_name+ into a validator class symbol.
|
172
|
+
# Return nil if the validator class does not exist.
|
173
|
+
def validator_for(constraint_name)
|
174
|
+
validator_name_for(constraint_name).safe_constantize
|
175
|
+
end
|
176
|
+
|
177
|
+
# Test whether a validator is defined for the given +constraint_name+.
|
178
|
+
def validator_defined_for?(constraint_name)
|
179
|
+
!!validator_for(constraint_name)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Sanitize the given +object+ *in-place* according to the given +sanitized_object+.
|
183
|
+
# A sanitized object contains value with more specific data type. Like a URI
|
184
|
+
# object instead of a plain string.
|
185
|
+
#
|
186
|
+
# Non-sanitized value are not touch (i.e. values present in +object+ but not in
|
187
|
+
# +sanitized_object+). However, +object["key"]+ and +object[:key]+ are considered as
|
188
|
+
# referring to the same value, but they original key would be preserved.
|
189
|
+
#
|
190
|
+
# Example:
|
191
|
+
# object = { "int" => "42" }
|
192
|
+
# Respect.sanitize_object!(object, { "int" => 42 }
|
193
|
+
# object #=> { "int" => 42 }
|
194
|
+
# object = { :int => "42" }
|
195
|
+
# Respect.sanitize_object!(object, { "int" => 42 }
|
196
|
+
# object #=> { :int => 42 }
|
197
|
+
#
|
198
|
+
# The sanitized object is accessible via the {Schema#sanitized_object} method after a
|
199
|
+
# successful validation.
|
200
|
+
def sanitize_object!(object, sanitized_object)
|
201
|
+
case object
|
202
|
+
when Hash
|
203
|
+
if sanitized_object.is_a? Hash
|
204
|
+
sanitized_object.each do |name, value|
|
205
|
+
if object.has_key?(name)
|
206
|
+
object[name] = sanitize_object!(object[name], value)
|
207
|
+
else
|
208
|
+
object[name.to_sym] = sanitize_object!(object[name.to_sym], value)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
object
|
212
|
+
else
|
213
|
+
sanitized_object
|
214
|
+
end
|
215
|
+
when Array
|
216
|
+
if sanitized_object.is_a? Array
|
217
|
+
sanitized_object.each_with_index do |value, index|
|
218
|
+
object[index] = sanitize_object!(object[index], value)
|
219
|
+
end
|
220
|
+
object
|
221
|
+
else
|
222
|
+
sanitized_object
|
223
|
+
end
|
224
|
+
else
|
225
|
+
sanitized_object
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Respect
|
2
|
+
class AnySchema < Schema
|
3
|
+
|
4
|
+
public_class_method :new
|
5
|
+
|
6
|
+
def validate(object)
|
7
|
+
case object
|
8
|
+
when Hash, Array, TrueClass, FalseClass, Numeric, NilClass, String
|
9
|
+
self.sanitized_object = object
|
10
|
+
true
|
11
|
+
else
|
12
|
+
raise ValidationError,
|
13
|
+
"object is not of a valid type but a #{object.class}"
|
14
|
+
end
|
15
|
+
rescue ValidationError => e
|
16
|
+
# Reset sanitized object.
|
17
|
+
self.sanitized_object = nil
|
18
|
+
raise e
|
19
|
+
end
|
20
|
+
|
21
|
+
end # class AnySchema
|
22
|
+
end # module Respect
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Respect
|
2
|
+
class ArrayDef < GlobalDef
|
3
|
+
include_core_statements
|
4
|
+
include DefWithoutName
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@array_schema = ArraySchema.new(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def items(&block)
|
11
|
+
@array_schema.items = ItemsDef.eval(&block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def extra_items(&block)
|
15
|
+
@array_schema.extra_items = ItemsDef.eval(&block)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def evaluation_result
|
21
|
+
@array_schema
|
22
|
+
end
|
23
|
+
|
24
|
+
def update_context(name, schema)
|
25
|
+
@array_schema.item = schema
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
module Respect
|
2
|
+
# A schema to specify the structure of an array.
|
3
|
+
#
|
4
|
+
# They are two approaches to specify the structure of an array.
|
5
|
+
#
|
6
|
+
# If the items of your array have all the same structure then you
|
7
|
+
# should use the {#item=} method to set their schema.
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
# # An array where all items are integer greater than 42.
|
11
|
+
# s = ArraySchema.define do |s|
|
12
|
+
# s.item do |s|
|
13
|
+
# s.integer greater_than: 42
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
# s.validate?([]) #=> true
|
17
|
+
# s.validate?([ 43 ]) #=> true
|
18
|
+
# s.validate?([ 43, 44 ]) #=> true
|
19
|
+
# s.validate?([ 43, 44, 30 ]) #=> false
|
20
|
+
#
|
21
|
+
# Otherwise, you should use the {#items=} and {#extra_items=}. This is called
|
22
|
+
# "tuple" typing.
|
23
|
+
#
|
24
|
+
# Example:
|
25
|
+
# # An array where first item is an integer and the second one
|
26
|
+
# # is a string.
|
27
|
+
# ArraySchema.define do |s|
|
28
|
+
# s.items do |s|
|
29
|
+
# s.integer
|
30
|
+
# s.string
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
# s.validate?([]) #=> false
|
34
|
+
# s.validate?([ 43 ]) #=> false
|
35
|
+
# s.validate?([ 43, "foo" ]) #=> true
|
36
|
+
# s.validate?([ 43, 44 ]) #=> false
|
37
|
+
#
|
38
|
+
# You cannot mix tuple typing and single item typing.
|
39
|
+
#
|
40
|
+
# You can pass several options when creating an {ArraySchema}:
|
41
|
+
# uniq:: if +true+, duplicated items are forbidden (+false+ by default).
|
42
|
+
# min_size:: if set the array must have at least the given number of items
|
43
|
+
# (+nil+ by default). This option apply only in non-tuple typing.
|
44
|
+
# max_size:: if set the array must have at most the given number of items
|
45
|
+
# (+nil+ by default). This option apply only in non-tuple typing.
|
46
|
+
class ArraySchema < Schema
|
47
|
+
|
48
|
+
public_class_method :new
|
49
|
+
|
50
|
+
class << self
|
51
|
+
# Overwritten method. See {Schema.default_options}
|
52
|
+
def default_options
|
53
|
+
super().merge({
|
54
|
+
uniq: false,
|
55
|
+
}).freeze
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def initialize(options = {})
|
60
|
+
super(self.class.default_options.merge(options))
|
61
|
+
end
|
62
|
+
|
63
|
+
def initialize_copy(other)
|
64
|
+
super
|
65
|
+
@items = other.items.dup unless other.items.nil?
|
66
|
+
@extra_items = other.extra_items.dup unless other.extra_items.nil?
|
67
|
+
end
|
68
|
+
|
69
|
+
# Set the schema that all items in the array must validate.
|
70
|
+
def item=(item)
|
71
|
+
if @items
|
72
|
+
raise InvalidSchemaError,
|
73
|
+
"cannot mix single item and multiple items validation"
|
74
|
+
end
|
75
|
+
@item = item
|
76
|
+
end
|
77
|
+
|
78
|
+
# Get the schema that all items in the array must validate.
|
79
|
+
attr_reader :item
|
80
|
+
|
81
|
+
# Set the array of schema that the corresponding items must validate.
|
82
|
+
def items=(items)
|
83
|
+
if @item
|
84
|
+
raise InvalidSchemaError,
|
85
|
+
"cannot mix single item and multiple items validation"
|
86
|
+
end
|
87
|
+
@items = items
|
88
|
+
end
|
89
|
+
|
90
|
+
# Get the array of schema that the corresponding items must validate.
|
91
|
+
attr_reader :items
|
92
|
+
|
93
|
+
# Set extra schema items. These are optional. If they are not in the
|
94
|
+
# object the validation pass anyway.
|
95
|
+
def extra_items=(extra_items)
|
96
|
+
return @extra_items unless extra_items
|
97
|
+
if @item
|
98
|
+
raise InvalidSchemaError,
|
99
|
+
"cannot mix single item and extra items validation"
|
100
|
+
end
|
101
|
+
@items = [] if @items.nil?
|
102
|
+
@extra_items = extra_items
|
103
|
+
end
|
104
|
+
|
105
|
+
# Get the extra schema items.
|
106
|
+
attr_reader :extra_items
|
107
|
+
|
108
|
+
# Overwritten method. See {Schema#validate}
|
109
|
+
def validate(object)
|
110
|
+
# Handle nil case.
|
111
|
+
if object.nil?
|
112
|
+
if allow_nil?
|
113
|
+
self.sanitized_object = nil
|
114
|
+
return true
|
115
|
+
else
|
116
|
+
raise ValidationError, "object is nil but this #{self.class} does not allow nil"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
# Validate type.
|
120
|
+
unless object.is_a?(Array)
|
121
|
+
raise ValidationError, "object is not an array but a #{object.class}"
|
122
|
+
end
|
123
|
+
# At this point we are sure @item and (@items or @extra_items) cannot be
|
124
|
+
# defined both. (see the setters).
|
125
|
+
sanitized_object = []
|
126
|
+
# Validate expected item.
|
127
|
+
if @item
|
128
|
+
if options[:min_size] && object.size < options[:min_size]
|
129
|
+
raise ValidationError,
|
130
|
+
"expected at least #{options[:min_size]} item(s) but got #{object.size}"
|
131
|
+
end
|
132
|
+
if options[:max_size] && object.size > options[:max_size]
|
133
|
+
raise ValidationError,
|
134
|
+
"expected at most #{options[:min_size]} item(s) but got #{object.size}"
|
135
|
+
end
|
136
|
+
object.each_with_index do |item, i|
|
137
|
+
validate_item(i, @item, object, sanitized_object)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
# Validate object items count.
|
141
|
+
if @items || @extra_items
|
142
|
+
if @extra_items
|
143
|
+
min_size = @items ? @items.size : 0
|
144
|
+
unless min_size <= object.size
|
145
|
+
raise ValidationError,
|
146
|
+
"array size should be at least #{min_size} but is #{object.size}"
|
147
|
+
end
|
148
|
+
else
|
149
|
+
if @items.size != object.size
|
150
|
+
raise ValidationError,
|
151
|
+
"array size should be #{@items.size} but is #{object.size}"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
# Validate expected multiple items.
|
156
|
+
if @items
|
157
|
+
@items.each_with_index do |schema, i|
|
158
|
+
validate_item(i, schema, object, sanitized_object)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
# Validate extra items.
|
162
|
+
if @extra_items
|
163
|
+
@extra_items.each_with_index do |schema, i|
|
164
|
+
if @items.size + i < object.size
|
165
|
+
validate_item(@items.size + i, schema, object, sanitized_object)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
# Validate all items are unique.
|
170
|
+
if options[:uniq]
|
171
|
+
s = Set.new
|
172
|
+
object.each_with_index do |e, i|
|
173
|
+
if s.add?(e).nil?
|
174
|
+
raise ValidationError,
|
175
|
+
"duplicated item number #{i}"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
self.sanitized_object = sanitized_object
|
180
|
+
true
|
181
|
+
rescue ValidationError => e
|
182
|
+
# Reset sanitized object.
|
183
|
+
self.sanitized_object = nil
|
184
|
+
raise e
|
185
|
+
end
|
186
|
+
|
187
|
+
def ==(other)
|
188
|
+
super && @item == other.item && @items == other.items && @extra_items == other.extra_items
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
def validate_item(index, schema, object, sanitized_object)
|
194
|
+
begin
|
195
|
+
schema.validate(object[index])
|
196
|
+
sanitized_object << schema.sanitized_object
|
197
|
+
rescue ValidationError => e
|
198
|
+
e.context << "in array #{index.ordinalize} item"
|
199
|
+
raise e
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|