duck-hunt 0.0.3
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/LICENSE +20 -0
- data/README.md +526 -0
- data/Rakefile +15 -0
- data/lib/duck-hunt.rb +17 -0
- data/lib/duck-hunt/hash_helpers.rb +28 -0
- data/lib/duck-hunt/properties.rb +13 -0
- data/lib/duck-hunt/properties/array.rb +81 -0
- data/lib/duck-hunt/properties/boolean.rb +10 -0
- data/lib/duck-hunt/properties/float.rb +10 -0
- data/lib/duck-hunt/properties/integer.rb +9 -0
- data/lib/duck-hunt/properties/nested_hash.rb +61 -0
- data/lib/duck-hunt/properties/nil.rb +15 -0
- data/lib/duck-hunt/properties/property.rb +85 -0
- data/lib/duck-hunt/properties/string.rb +10 -0
- data/lib/duck-hunt/properties/validator_lookup.rb +27 -0
- data/lib/duck-hunt/schemas.rb +8 -0
- data/lib/duck-hunt/schemas/array_schema.rb +254 -0
- data/lib/duck-hunt/schemas/hash_schema.rb +135 -0
- data/lib/duck-hunt/schemas/property_lookup.rb +32 -0
- data/lib/duck-hunt/schemas/schema_definition.rb +25 -0
- data/lib/duck-hunt/string_helpers.rb +25 -0
- data/lib/duck-hunt/validators.rb +16 -0
- data/lib/duck-hunt/validators/accepted_values.rb +19 -0
- data/lib/duck-hunt/validators/divisible_by.rb +19 -0
- data/lib/duck-hunt/validators/equal_to.rb +19 -0
- data/lib/duck-hunt/validators/greater_than.rb +19 -0
- data/lib/duck-hunt/validators/greater_than_or_equal_to.rb +19 -0
- data/lib/duck-hunt/validators/less_than.rb +19 -0
- data/lib/duck-hunt/validators/less_than_or_equal_to.rb +19 -0
- data/lib/duck-hunt/validators/matches.rb +18 -0
- data/lib/duck-hunt/validators/not_divisible_by.rb +19 -0
- data/lib/duck-hunt/validators/not_equal_to.rb +19 -0
- data/lib/duck-hunt/validators/rejected_values.rb +19 -0
- data/lib/duck-hunt/validators/validator.rb +16 -0
- data/lib/duck-hunt/version.rb +3 -0
- data/test/properties/array_test.rb +837 -0
- data/test/properties/boolean_test.rb +37 -0
- data/test/properties/float_test.rb +49 -0
- data/test/properties/integer_test.rb +48 -0
- data/test/properties/nested_hash_test.rb +465 -0
- data/test/properties/nil_test.rb +30 -0
- data/test/properties/property_test.rb +193 -0
- data/test/properties/string_test.rb +24 -0
- data/test/properties/validator_lookup_test.rb +25 -0
- data/test/schemas/array_schema_test.rb +797 -0
- data/test/schemas/hash_schema_test.rb +264 -0
- data/test/schemas/property_lookup_test.rb +41 -0
- data/test/schemas/schema_definition_test.rb +51 -0
- data/test/test_helper.rb +29 -0
- data/test/test_helper/test_classes.rb +74 -0
- data/test/validators/accepted_values_test.rb +46 -0
- data/test/validators/divisible_by_test.rb +38 -0
- data/test/validators/equal_to_test.rb +38 -0
- data/test/validators/greater_than_or_equal_to_test.rb +39 -0
- data/test/validators/greater_than_test.rb +39 -0
- data/test/validators/less_than_or_equal_to_test.rb +40 -0
- data/test/validators/less_than_test.rb +39 -0
- data/test/validators/matches_test.rb +43 -0
- data/test/validators/not_divisible_by_test.rb +38 -0
- data/test/validators/not_equal_to_test.rb +38 -0
- data/test/validators/rejected_values_test.rb +46 -0
- data/test/validators/validator_test.rb +23 -0
- metadata +196 -0
data/Rakefile
ADDED
data/lib/duck-hunt.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module DuckHunt
|
2
|
+
class MethodNotDefined < StandardError; end
|
3
|
+
class AbstractClass < StandardError; end
|
4
|
+
class PropertyAlreadyDefined < StandardError; end
|
5
|
+
class ValidatorAlreadyDefined < StandardError; end
|
6
|
+
class InvalidSchema < StandardError; end
|
7
|
+
|
8
|
+
TYPE_MISMATCH_MESSAGE = "wrong type"
|
9
|
+
REQUIRED_MESSAGE = "required"
|
10
|
+
NIL_OBJECT_NOT_ALLOWED_MESSAGE = "nil object not allowed"
|
11
|
+
|
12
|
+
autoload :StringHelpers, File.dirname(__FILE__) + '/duck-hunt/string_helpers.rb'
|
13
|
+
autoload :HashHelpers, File.dirname(__FILE__) + '/duck-hunt/hash_helpers.rb'
|
14
|
+
autoload :Schemas, File.dirname(__FILE__) + '/duck-hunt/schemas.rb'
|
15
|
+
autoload :Properties, File.dirname(__FILE__) + '/duck-hunt/properties.rb'
|
16
|
+
autoload :Validators, File.dirname(__FILE__) + '/duck-hunt/validators.rb'
|
17
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Support `stringify_keys` and `symbolize_keys`
|
2
|
+
# keep these methods in their own modules so they don't conflict with other libraries that might
|
3
|
+
# be loaded (e.g: activesupport)
|
4
|
+
module DuckHunt
|
5
|
+
module HashHelpers
|
6
|
+
def self.stringify_keys!(hash)
|
7
|
+
hash.keys.each do |key|
|
8
|
+
hash[key.to_s] = hash.delete(key)
|
9
|
+
end
|
10
|
+
return hash
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.stringify_keys(hash)
|
14
|
+
return stringify_keys!(hash.dup)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.symbolize_keys!(hash)
|
18
|
+
hash.keys.each do |key|
|
19
|
+
hash[(key.to_sym rescue key) || key] = hash.delete(key)
|
20
|
+
end
|
21
|
+
return hash
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.symbolize_keys(hash)
|
25
|
+
return symbolize_keys!(hash.dup)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module DuckHunt
|
2
|
+
module Properties
|
3
|
+
autoload :ValidatorLookup, File.dirname(__FILE__) + "/properties/validator_lookup.rb"
|
4
|
+
autoload :Property, File.dirname(__FILE__) + "/properties/property.rb"
|
5
|
+
autoload :Integer, File.dirname(__FILE__) + "/properties/integer.rb"
|
6
|
+
autoload :Float, File.dirname(__FILE__) + "/properties/float.rb"
|
7
|
+
autoload :NestedHash, File.dirname(__FILE__) + "/properties/nested_hash.rb"
|
8
|
+
autoload :Array, File.dirname(__FILE__) + "/properties/array.rb"
|
9
|
+
autoload :String, File.dirname(__FILE__) + "/properties/string.rb"
|
10
|
+
autoload :Boolean, File.dirname(__FILE__) + "/properties/boolean.rb"
|
11
|
+
autoload :Nil, File.dirname(__FILE__) + "/properties/nil.rb"
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module DuckHunt
|
2
|
+
module Properties
|
3
|
+
class Array
|
4
|
+
# Used when an array is a property of a schema.
|
5
|
+
# Allows us to "nest" schemas (eg: an array in a array, an array of arrays)
|
6
|
+
attr_reader :required
|
7
|
+
|
8
|
+
def initialize(options= {}, &block)
|
9
|
+
raise ArgumentError, "a block must be given to define the array" unless block_given?
|
10
|
+
DuckHunt::HashHelpers.stringify_keys!(options)
|
11
|
+
options = {"required" => true}.merge(options)
|
12
|
+
@required = options["required"]
|
13
|
+
@required_but_not_present = false
|
14
|
+
@schema = DuckHunt::Schemas::ArraySchema.define options, &block
|
15
|
+
end
|
16
|
+
|
17
|
+
def required?
|
18
|
+
return self.required
|
19
|
+
end
|
20
|
+
|
21
|
+
def single_type_property
|
22
|
+
return @schema.single_type_property
|
23
|
+
end
|
24
|
+
|
25
|
+
def tuple_properties
|
26
|
+
return @schema.tuple_properties
|
27
|
+
end
|
28
|
+
|
29
|
+
def optional_tuple_properties
|
30
|
+
return @schema.optional_tuple_properties
|
31
|
+
end
|
32
|
+
|
33
|
+
def validates_uniqueness
|
34
|
+
return @schema.validates_uniqueness
|
35
|
+
end
|
36
|
+
|
37
|
+
def validates_uniqueness?
|
38
|
+
return @schema.validates_uniqueness?
|
39
|
+
end
|
40
|
+
|
41
|
+
def allow_nil
|
42
|
+
return @schema.allow_nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def allow_nil?
|
46
|
+
return @schema.allow_nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
def min_size
|
50
|
+
return @schema.min_size
|
51
|
+
end
|
52
|
+
|
53
|
+
def max_size
|
54
|
+
return @schema.max_size
|
55
|
+
end
|
56
|
+
|
57
|
+
def errors
|
58
|
+
return @schema.errors
|
59
|
+
end
|
60
|
+
|
61
|
+
def valid?(value)
|
62
|
+
@required_but_not_present = false
|
63
|
+
return @schema.validate?(value)
|
64
|
+
end
|
65
|
+
|
66
|
+
def add_required_error
|
67
|
+
@required_but_not_present = true
|
68
|
+
end
|
69
|
+
|
70
|
+
def errors
|
71
|
+
errors = @schema.errors
|
72
|
+
if @required_but_not_present
|
73
|
+
errors[:base] = [] if errors[:base].nil?
|
74
|
+
errors[:base] << DuckHunt::REQUIRED_MESSAGE
|
75
|
+
end
|
76
|
+
|
77
|
+
return errors
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module DuckHunt
|
2
|
+
module Properties
|
3
|
+
class NestedHash
|
4
|
+
# Used when a hash is a property of a schema.
|
5
|
+
# Allows us to "nest" schemas (eg: an array in a hash, an array of hashes)
|
6
|
+
attr_reader :required
|
7
|
+
|
8
|
+
def initialize(options= {}, &block)
|
9
|
+
raise ArgumentError, "a block must be given to define the hash" unless block_given?
|
10
|
+
DuckHunt::HashHelpers.stringify_keys!(options)
|
11
|
+
options = {"required" => true}.merge(options)
|
12
|
+
@required = options["required"]
|
13
|
+
@required_but_not_present = false
|
14
|
+
@schema = DuckHunt::Schemas::HashSchema.define options, &block
|
15
|
+
end
|
16
|
+
|
17
|
+
def required?
|
18
|
+
return self.required
|
19
|
+
end
|
20
|
+
|
21
|
+
def properties
|
22
|
+
return @schema.properties
|
23
|
+
end
|
24
|
+
|
25
|
+
def strict_mode
|
26
|
+
return @schema.strict_mode
|
27
|
+
end
|
28
|
+
|
29
|
+
def strict_mode?
|
30
|
+
return strict_mode
|
31
|
+
end
|
32
|
+
|
33
|
+
def allow_nil
|
34
|
+
return @schema.allow_nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def allow_nil?
|
38
|
+
return @schema.allow_nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def valid?(value)
|
42
|
+
@required_but_not_present = false
|
43
|
+
return @schema.validate?(value)
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_required_error
|
47
|
+
@required_but_not_present = true
|
48
|
+
end
|
49
|
+
|
50
|
+
def errors
|
51
|
+
errors = @schema.errors
|
52
|
+
if @required_but_not_present
|
53
|
+
errors[:base] = [] if errors[:base].nil?
|
54
|
+
errors[:base] << DuckHunt::REQUIRED_MESSAGE
|
55
|
+
end
|
56
|
+
|
57
|
+
return errors
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module DuckHunt
|
2
|
+
module Properties
|
3
|
+
class Property
|
4
|
+
# the base class for the individual properties that make up a schema
|
5
|
+
# a property is valid if:
|
6
|
+
# 1. The value is required and has not been provided
|
7
|
+
# 2. The value provided `matches_type?`
|
8
|
+
# 3. Each of the attached validators returns `true` when `valid?` is called
|
9
|
+
include DuckHunt::Properties::ValidatorLookup
|
10
|
+
attr_reader :required, :allow_nil
|
11
|
+
|
12
|
+
def initialize(options= {})
|
13
|
+
DuckHunt::HashHelpers.stringify_keys!(options)
|
14
|
+
options = {"required" => true, "allow_nil" => false}.merge(options)
|
15
|
+
@required = options["required"]
|
16
|
+
@allow_nil = options["allow_nil"]
|
17
|
+
@validators = {}
|
18
|
+
@errors = []
|
19
|
+
|
20
|
+
options.delete("required")
|
21
|
+
options.delete("allow_nil")
|
22
|
+
options.each{ |key, value| find_and_create_validator(key, value) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def required?
|
26
|
+
return self.required
|
27
|
+
end
|
28
|
+
|
29
|
+
def allow_nil?
|
30
|
+
return @allow_nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def valid?(value)
|
34
|
+
@errors.clear
|
35
|
+
if value.nil?
|
36
|
+
return true if allow_nil?
|
37
|
+
add_error(NIL_OBJECT_NOT_ALLOWED_MESSAGE)
|
38
|
+
return false
|
39
|
+
end
|
40
|
+
|
41
|
+
if matches_type?(value)
|
42
|
+
check_validators(value)
|
43
|
+
else
|
44
|
+
add_type_mismatch_error(value)
|
45
|
+
end
|
46
|
+
return @errors.size == 0
|
47
|
+
end
|
48
|
+
|
49
|
+
def add_required_error
|
50
|
+
add_error(DuckHunt::REQUIRED_MESSAGE)
|
51
|
+
end
|
52
|
+
|
53
|
+
def errors
|
54
|
+
@errors.dup
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
def add_type_mismatch_error(value)
|
60
|
+
add_error(DuckHunt::TYPE_MISMATCH_MESSAGE)
|
61
|
+
end
|
62
|
+
|
63
|
+
def check_validators(value)
|
64
|
+
@validators.values.each do |validator|
|
65
|
+
unless validator.valid?(value)
|
66
|
+
add_error(validator.error_message)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def add_error(error_message)
|
72
|
+
@errors << error_message unless @errors.include?(error_message)
|
73
|
+
end
|
74
|
+
|
75
|
+
def add_validator(validator_symbol, validator_constant, *args)
|
76
|
+
raise ValidatorAlreadyDefined, "`#{validator_symbol}` has already been defined in this schema" if @validators.has_key?(validator_symbol)
|
77
|
+
@validators[validator_symbol] = validator_constant.new(*args)
|
78
|
+
end
|
79
|
+
|
80
|
+
def matches_type?(value)
|
81
|
+
raise NotImplementedError
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module DuckHunt
|
2
|
+
module Properties
|
3
|
+
module ValidatorLookup
|
4
|
+
protected
|
5
|
+
def find_and_create_validator(validator_name, *args)
|
6
|
+
#convert the validator name into camel case and turn it into a symbol.
|
7
|
+
camelized_string = DuckHunt::StringHelpers.camelize(validator_name.to_s)
|
8
|
+
validator_symbol = camelized_string.to_sym
|
9
|
+
|
10
|
+
validator_constant = get_validator_constant(validator_symbol)
|
11
|
+
add_validator(validator_symbol, validator_constant, *args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def validator_definition_exists?(validator)
|
15
|
+
DuckHunt::Validators.const_defined?(validator)
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_validator_constant(validator)
|
19
|
+
DuckHunt::Validators.const_get(validator)
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_validator(validator_symbol, validator_constant, *args)
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
module DuckHunt
|
2
|
+
module Schemas
|
3
|
+
autoload :SchemaDefinition, File.dirname(__FILE__) + "/schemas/schema_definition.rb"
|
4
|
+
autoload :PropertyLookup, File.dirname(__FILE__) + "/schemas/property_lookup.rb"
|
5
|
+
autoload :HashSchema, File.dirname(__FILE__) + "/schemas/hash_schema.rb"
|
6
|
+
autoload :ArraySchema, File.dirname(__FILE__) + "/schemas/array_schema.rb"
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,254 @@
|
|
1
|
+
module DuckHunt
|
2
|
+
module Schemas
|
3
|
+
class ArraySchema
|
4
|
+
# There are two ways to define the schema of an array:
|
5
|
+
# * All items in the array have the same definition (integer, specific hash, etc.)
|
6
|
+
# * The order of the items in the array matters
|
7
|
+
# * each item in the array can have a different definition (e.g.: [1,"hello", { :hash=>"here" }])
|
8
|
+
# * The array definition can also include an optional set of items.
|
9
|
+
#
|
10
|
+
# These two types cannot be mixed
|
11
|
+
#
|
12
|
+
# The Array Schema also accepts some options during definition:
|
13
|
+
# * validates_uniqueness: if true, the array is invalid if it has duplicates (false by default)
|
14
|
+
# * min_size: the array must be at least this large (nil by default)
|
15
|
+
# * max_size: the array cannot be any larger (nil by default)
|
16
|
+
#
|
17
|
+
# Validation is performed in the following steps:
|
18
|
+
# 1. If the object is nil and nil objects are allowed, then return true
|
19
|
+
# 2. Check that the object is an array
|
20
|
+
# 3. If the `validates_uniqueness` flag is set, check that the array's entries are unique.
|
21
|
+
# 4. If the schema is a single-type:
|
22
|
+
# 1. Check that it falls within the specified min/max range
|
23
|
+
# 2. Check that every entry is valid according to the defined single type property
|
24
|
+
# 5. If the Schema is a tuple:
|
25
|
+
# 1. Check that the total number of entries is within the following range:
|
26
|
+
# (tuple_properties) <= (number of enties) <= (tuple_properties + optional_tuple_properties)
|
27
|
+
# 2. Check that each entry is valid according to the defined type in the tuple properties
|
28
|
+
include Schemas::SchemaDefinition
|
29
|
+
include Schemas::PropertyLookup
|
30
|
+
|
31
|
+
DUPLICATE_ITEMS_NOT_ALLOWED_MESSAGE = "duplicate items are not allowed"
|
32
|
+
|
33
|
+
def initialize(var={})
|
34
|
+
DuckHunt::HashHelpers.stringify_keys!(var)
|
35
|
+
options = {"validates_uniqueness" => false, "min_size" => nil, "max_size" => nil, "allow_nil" => false}.merge(var)
|
36
|
+
@single_type_property = nil
|
37
|
+
@tuple_properties = nil
|
38
|
+
@optional_tuple_properties = nil
|
39
|
+
@validates_uniqueness = options["validates_uniqueness"]
|
40
|
+
@min_size = options["min_size"]
|
41
|
+
@max_size = options["max_size"]
|
42
|
+
@allow_nil = options["allow_nil"]
|
43
|
+
@errors = {}
|
44
|
+
end
|
45
|
+
|
46
|
+
def single_type_property
|
47
|
+
@single_type_property
|
48
|
+
end
|
49
|
+
|
50
|
+
def tuple_properties
|
51
|
+
return nil if @tuple_properties.nil?
|
52
|
+
@tuple_properties.properties
|
53
|
+
end
|
54
|
+
|
55
|
+
def optional_tuple_properties
|
56
|
+
return nil if @optional_tuple_properties.nil?
|
57
|
+
@optional_tuple_properties.properties
|
58
|
+
end
|
59
|
+
|
60
|
+
def validates_uniqueness
|
61
|
+
return @validates_uniqueness
|
62
|
+
end
|
63
|
+
|
64
|
+
def validates_uniqueness?
|
65
|
+
return @validates_uniqueness
|
66
|
+
end
|
67
|
+
|
68
|
+
def allow_nil
|
69
|
+
return @allow_nil
|
70
|
+
end
|
71
|
+
|
72
|
+
def allow_nil?
|
73
|
+
return @allow_nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def min_size
|
77
|
+
return @min_size
|
78
|
+
end
|
79
|
+
|
80
|
+
def max_size
|
81
|
+
return @max_size
|
82
|
+
end
|
83
|
+
|
84
|
+
def errors
|
85
|
+
return DuckHunt::HashHelpers.stringify_keys(@errors)
|
86
|
+
end
|
87
|
+
|
88
|
+
def items(&block)
|
89
|
+
raise InvalidSchema, "cannot mix single-type and tuple definitions" unless @single_type_property.nil?
|
90
|
+
@tuple_properties = TupleProperties.new(&block)
|
91
|
+
end
|
92
|
+
|
93
|
+
def optional_items(&block)
|
94
|
+
raise InvalidSchema, "cannot mix single-type and tuple definitions" unless @single_type_property.nil?
|
95
|
+
@optional_tuple_properties = TupleProperties.new(&block)
|
96
|
+
end
|
97
|
+
|
98
|
+
def validate?(object)
|
99
|
+
@errors.clear
|
100
|
+
if object.nil?
|
101
|
+
return true if allow_nil?
|
102
|
+
add_base_error_message(NIL_OBJECT_NOT_ALLOWED_MESSAGE)
|
103
|
+
return false
|
104
|
+
end
|
105
|
+
|
106
|
+
return false unless matches_type?(object)
|
107
|
+
|
108
|
+
if validates_uniqueness? and not has_unique_entries?(object)
|
109
|
+
add_base_error_message(DUPLICATE_ITEMS_NOT_ALLOWED_MESSAGE)
|
110
|
+
return false
|
111
|
+
end
|
112
|
+
|
113
|
+
return validate_single_type?(object) if single_type_schema?
|
114
|
+
return validate_tuple?(object) if tuple_schema?
|
115
|
+
end
|
116
|
+
|
117
|
+
protected
|
118
|
+
|
119
|
+
def add_property(property_constant, *args, &block)
|
120
|
+
raise InvalidSchema, "cannot mix single-type and tuple definitions" unless @tuple_properties.nil? and @optional_tuple_properties.nil?
|
121
|
+
raise InvalidSchema, "single type property has already been defined for this schema" unless @single_type_property.nil?
|
122
|
+
@single_type_property = property_constant.new(*args, &block)
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
def matches_type?(object_being_validated)
|
127
|
+
unless object_being_validated.is_a? Array
|
128
|
+
add_base_error_message(DuckHunt::TYPE_MISMATCH_MESSAGE)
|
129
|
+
return false
|
130
|
+
end
|
131
|
+
return true
|
132
|
+
end
|
133
|
+
|
134
|
+
def add_base_error_message(message)
|
135
|
+
@errors[:base] = [] if @errors[:base].nil?
|
136
|
+
@errors[:base] << message
|
137
|
+
end
|
138
|
+
|
139
|
+
def single_type_schema?
|
140
|
+
return !@single_type_property.nil?
|
141
|
+
end
|
142
|
+
|
143
|
+
def tuple_schema?
|
144
|
+
@tuple_properties.nil? == false or @optional_tuple_properties.nil? == false
|
145
|
+
end
|
146
|
+
|
147
|
+
def has_unique_entries?(object)
|
148
|
+
return object.uniq.size == object.size
|
149
|
+
end
|
150
|
+
|
151
|
+
def validate_single_type?(object)
|
152
|
+
object_size = object.size
|
153
|
+
object_valid = true
|
154
|
+
|
155
|
+
if !min_size.nil? and object_size < min_size
|
156
|
+
add_base_error_message(generate_under_minimum_message(min_size, object_size))
|
157
|
+
return false
|
158
|
+
end
|
159
|
+
|
160
|
+
if !max_size.nil? and object_size > max_size
|
161
|
+
add_base_error_message(generate_over_maximum_message(max_size, object_size))
|
162
|
+
return false
|
163
|
+
end
|
164
|
+
|
165
|
+
object.each_with_index{ |item, index|
|
166
|
+
unless single_type_property.valid?(item)
|
167
|
+
@errors[index.to_s] = single_type_property.errors
|
168
|
+
object_valid = false
|
169
|
+
end
|
170
|
+
}
|
171
|
+
|
172
|
+
return object_valid
|
173
|
+
end
|
174
|
+
|
175
|
+
def validate_tuple?(object)
|
176
|
+
object_size = object.size
|
177
|
+
object_valid = true
|
178
|
+
|
179
|
+
if object_size < minimum_tuple_size
|
180
|
+
add_base_error_message(generate_under_minimum_message(minimum_tuple_size, object_size))
|
181
|
+
return false
|
182
|
+
end
|
183
|
+
|
184
|
+
if object_size > maximum_tuple_size
|
185
|
+
add_base_error_message(generate_over_maximum_message(maximum_tuple_size, object_size))
|
186
|
+
return false
|
187
|
+
end
|
188
|
+
|
189
|
+
unless tuple_properties.nil?
|
190
|
+
# check that each required entry has the correct type in the tuple
|
191
|
+
tuple_properties.each_with_index{ |property, index|
|
192
|
+
unless property.valid?(object[index])
|
193
|
+
@errors[index.to_s] = property.errors
|
194
|
+
object_valid = false
|
195
|
+
end
|
196
|
+
}
|
197
|
+
end
|
198
|
+
|
199
|
+
if !optional_tuple_properties.nil? and object.size > 0
|
200
|
+
# check that each optional entry has the correct type in the tuple
|
201
|
+
optional_tuple_properties.each_with_index{ |property, index|
|
202
|
+
object_index = minimum_tuple_size + index
|
203
|
+
if object_index > (object.size - 1)
|
204
|
+
break #stop looking at optional properties if we've reached the end of the array
|
205
|
+
end
|
206
|
+
unless property.valid?(object[object_index])
|
207
|
+
@errors[object_index.to_s] = property.errors
|
208
|
+
object_valid = false
|
209
|
+
end
|
210
|
+
}
|
211
|
+
end
|
212
|
+
|
213
|
+
return object_valid
|
214
|
+
end
|
215
|
+
|
216
|
+
def minimum_tuple_size
|
217
|
+
return tuple_properties.nil? ? 0 : tuple_properties.size
|
218
|
+
end
|
219
|
+
|
220
|
+
def maximum_tuple_size
|
221
|
+
optional_tuple_size = optional_tuple_properties.nil? ? 0 : optional_tuple_properties.size
|
222
|
+
return minimum_tuple_size + optional_tuple_size
|
223
|
+
end
|
224
|
+
|
225
|
+
def generate_under_minimum_message(minimum_size, object_size)
|
226
|
+
return "expected at least #{minimum_size} item(s) but got #{object_size} item(s)"
|
227
|
+
end
|
228
|
+
|
229
|
+
def generate_over_maximum_message(maximum_size, object_size)
|
230
|
+
return "expected at most #{maximum_size} item(s) but got #{object_size} item(s)"
|
231
|
+
end
|
232
|
+
|
233
|
+
class TupleProperties
|
234
|
+
include Schemas::PropertyLookup
|
235
|
+
|
236
|
+
def initialize(*var)
|
237
|
+
raise ArgumentError, "a block of properties must be given to define the tuple items" unless block_given?
|
238
|
+
@properties = []
|
239
|
+
yield self
|
240
|
+
end
|
241
|
+
|
242
|
+
def properties
|
243
|
+
@properties.dup
|
244
|
+
end
|
245
|
+
|
246
|
+
protected
|
247
|
+
|
248
|
+
def add_property(property_constant, *args, &block)
|
249
|
+
@properties << property_constant.new(*args, &block)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|