rschema 3.0.1.pre3 → 3.0.1.pre4
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.
- checksums.yaml +4 -4
- data/README.md +278 -330
- data/lib/rschema.rb +104 -17
- data/lib/rschema/coercers.rb +3 -0
- data/lib/rschema/coercers/any.rb +40 -0
- data/lib/rschema/coercers/boolean.rb +30 -0
- data/lib/rschema/coercers/chain.rb +41 -0
- data/lib/rschema/coercers/date.rb +25 -0
- data/lib/rschema/coercers/fixed_hash/default_booleans_to_false.rb +62 -0
- data/lib/rschema/coercers/fixed_hash/remove_extraneous_attributes.rb +42 -0
- data/lib/rschema/coercers/fixed_hash/symbolize_keys.rb +62 -0
- data/lib/rschema/coercers/float.rb +18 -0
- data/lib/rschema/coercers/integer.rb +18 -0
- data/lib/rschema/coercers/symbol.rb +21 -0
- data/lib/rschema/coercers/time.rb +25 -0
- data/lib/rschema/coercion_wrapper.rb +46 -0
- data/lib/rschema/coercion_wrapper/rack_params.rb +21 -0
- data/lib/rschema/dsl.rb +271 -42
- data/lib/rschema/error.rb +12 -30
- data/lib/rschema/options.rb +2 -2
- data/lib/rschema/result.rb +18 -4
- data/lib/rschema/schemas.rb +3 -0
- data/lib/rschema/schemas/anything.rb +14 -12
- data/lib/rschema/schemas/boolean.rb +20 -21
- data/lib/rschema/schemas/coercer.rb +37 -0
- data/lib/rschema/schemas/convenience.rb +53 -0
- data/lib/rschema/schemas/enum.rb +25 -25
- data/lib/rschema/schemas/fixed_hash.rb +110 -91
- data/lib/rschema/schemas/fixed_length_array.rb +48 -48
- data/lib/rschema/schemas/maybe.rb +18 -17
- data/lib/rschema/schemas/pipeline.rb +20 -19
- data/lib/rschema/schemas/predicate.rb +24 -21
- data/lib/rschema/schemas/set.rb +40 -45
- data/lib/rschema/schemas/sum.rb +24 -28
- data/lib/rschema/schemas/type.rb +22 -21
- data/lib/rschema/schemas/variable_hash.rb +53 -52
- data/lib/rschema/schemas/variable_length_array.rb +39 -38
- data/lib/rschema/version.rb +1 -1
- metadata +49 -5
- data/lib/rschema/http_coercer.rb +0 -218
@@ -1,67 +1,68 @@
|
|
1
1
|
module RSchema
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
module Schemas
|
3
|
+
class VariableHash
|
4
|
+
attr_reader :key_schema, :value_schema
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def call(value, options=Options.default)
|
12
|
-
return not_a_hash_result(value) unless value.is_a?(Hash)
|
6
|
+
def initialize(key_schema, value_schema)
|
7
|
+
@key_schema = key_schema
|
8
|
+
@value_schema = value_schema
|
9
|
+
end
|
13
10
|
|
14
|
-
|
11
|
+
def call(value, options)
|
12
|
+
return not_a_hash_result(value) unless value.is_a?(Hash)
|
15
13
|
|
16
|
-
|
17
|
-
Result.success(result)
|
18
|
-
else
|
19
|
-
Result.failure(keys: key_errors, values: value_errors)
|
20
|
-
end
|
21
|
-
end
|
14
|
+
result, key_errors, value_errors = apply_subschemas(value, options)
|
22
15
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
16
|
+
if key_errors.empty? && value_errors.empty?
|
17
|
+
Result.success(result)
|
18
|
+
else
|
19
|
+
Result.failure(keys: key_errors, values: value_errors)
|
20
|
+
end
|
21
|
+
end
|
29
22
|
|
30
|
-
|
23
|
+
def with_wrapped_subschemas(wrapper)
|
24
|
+
self.class.new(
|
25
|
+
wrapper.wrap(key_schema),
|
26
|
+
wrapper.wrap(value_schema),
|
27
|
+
)
|
28
|
+
end
|
31
29
|
|
32
|
-
|
33
|
-
Result.failure(Error.new(
|
34
|
-
schema: self,
|
35
|
-
value: value,
|
36
|
-
symbolic_name: :not_a_hash,
|
37
|
-
))
|
38
|
-
end
|
30
|
+
private
|
39
31
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
32
|
+
def not_a_hash_result(value)
|
33
|
+
Result.failure(Error.new(
|
34
|
+
schema: self,
|
35
|
+
value: value,
|
36
|
+
symbolic_name: :not_a_hash,
|
37
|
+
))
|
38
|
+
end
|
44
39
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
break if options.fail_fast?
|
50
|
-
end
|
40
|
+
def apply_subschemas(value, options)
|
41
|
+
result = {}
|
42
|
+
key_errors = {}
|
43
|
+
value_errors = {}
|
51
44
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
45
|
+
value.each do |key, subvalue|
|
46
|
+
key_result = key_schema.call(key, options)
|
47
|
+
if key_result.invalid?
|
48
|
+
key_errors[key] = key_result.error
|
49
|
+
break if options.fail_fast?
|
50
|
+
end
|
57
51
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
52
|
+
subvalue_result = value_schema.call(subvalue, options)
|
53
|
+
if subvalue_result.invalid?
|
54
|
+
value_errors[key] = subvalue_result.error
|
55
|
+
break if options.fail_fast?
|
56
|
+
end
|
62
57
|
|
63
|
-
|
58
|
+
if key_result.valid? && subvalue_result.valid?
|
59
|
+
result[key_result.value] = subvalue_result.value
|
64
60
|
end
|
61
|
+
end
|
62
|
+
|
63
|
+
return result, key_errors, value_errors
|
65
64
|
end
|
66
|
-
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
67
68
|
end
|
@@ -1,49 +1,50 @@
|
|
1
1
|
module RSchema
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
module Schemas
|
3
|
+
class VariableLengthArray
|
4
|
+
attr_accessor :element_schema
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
def call(value, options = RSchema::Options.default)
|
11
|
-
if value.kind_of?(Array)
|
12
|
-
validated_values, errors = validate_elements(value, options)
|
13
|
-
if errors.empty?
|
14
|
-
Result.success(validated_values)
|
15
|
-
else
|
16
|
-
Result.failure(errors)
|
17
|
-
end
|
18
|
-
else
|
19
|
-
Result.failure(Error.new(
|
20
|
-
schema: self,
|
21
|
-
value: value,
|
22
|
-
symbolic_name: :not_an_array,
|
23
|
-
))
|
24
|
-
end
|
25
|
-
end
|
6
|
+
def initialize(element_schema)
|
7
|
+
@element_schema = element_schema
|
8
|
+
end
|
26
9
|
|
27
|
-
|
28
|
-
|
10
|
+
def call(value, options)
|
11
|
+
if value.kind_of?(Array)
|
12
|
+
validated_values, errors = validate_elements(value, options)
|
13
|
+
if errors.empty?
|
14
|
+
Result.success(validated_values)
|
15
|
+
else
|
16
|
+
Result.failure(errors)
|
29
17
|
end
|
18
|
+
else
|
19
|
+
Result.failure(Error.new(
|
20
|
+
schema: self,
|
21
|
+
value: value,
|
22
|
+
symbolic_name: :not_an_array,
|
23
|
+
))
|
24
|
+
end
|
25
|
+
end
|
30
26
|
|
31
|
-
|
32
|
-
|
33
|
-
|
27
|
+
def with_wrapped_subschemas(wrapper)
|
28
|
+
self.class.new(wrapper.wrap(element_schema))
|
29
|
+
end
|
34
30
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
validated_values[idx] = result.value
|
39
|
-
else
|
40
|
-
errors[idx] = result.error
|
41
|
-
break if options.fail_fast?
|
42
|
-
end
|
43
|
-
end
|
31
|
+
def validate_elements(array, options)
|
32
|
+
errors = {}
|
33
|
+
validated_values = []
|
44
34
|
|
45
|
-
|
35
|
+
array.each_with_index do |subvalue, idx|
|
36
|
+
result = @element_schema.call(subvalue, options)
|
37
|
+
if result.valid?
|
38
|
+
validated_values[idx] = result.value
|
39
|
+
else
|
40
|
+
errors[idx] = result.error
|
41
|
+
break if options.fail_fast?
|
46
42
|
end
|
47
43
|
end
|
44
|
+
|
45
|
+
[validated_values, errors]
|
48
46
|
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
49
50
|
end
|
data/lib/rschema/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rschema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0.1.
|
4
|
+
version: 3.0.1.pre4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tom Dalling
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-05-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: docile
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.1'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: rspec
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -56,14 +70,28 @@ dependencies:
|
|
56
70
|
name: pry
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
59
|
-
- - "
|
73
|
+
- - ">="
|
60
74
|
- !ruby/object:Gem::Version
|
61
75
|
version: '0'
|
62
76
|
type: :development
|
63
77
|
prerelease: false
|
64
78
|
version_requirements: !ruby/object:Gem::Requirement
|
65
79
|
requirements:
|
66
|
-
- - "
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: byebug
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
67
95
|
- !ruby/object:Gem::Version
|
68
96
|
version: '0'
|
69
97
|
description: |2
|
@@ -78,13 +106,29 @@ files:
|
|
78
106
|
- LICENSE.txt
|
79
107
|
- README.md
|
80
108
|
- lib/rschema.rb
|
109
|
+
- lib/rschema/coercers.rb
|
110
|
+
- lib/rschema/coercers/any.rb
|
111
|
+
- lib/rschema/coercers/boolean.rb
|
112
|
+
- lib/rschema/coercers/chain.rb
|
113
|
+
- lib/rschema/coercers/date.rb
|
114
|
+
- lib/rschema/coercers/fixed_hash/default_booleans_to_false.rb
|
115
|
+
- lib/rschema/coercers/fixed_hash/remove_extraneous_attributes.rb
|
116
|
+
- lib/rschema/coercers/fixed_hash/symbolize_keys.rb
|
117
|
+
- lib/rschema/coercers/float.rb
|
118
|
+
- lib/rschema/coercers/integer.rb
|
119
|
+
- lib/rschema/coercers/symbol.rb
|
120
|
+
- lib/rschema/coercers/time.rb
|
121
|
+
- lib/rschema/coercion_wrapper.rb
|
122
|
+
- lib/rschema/coercion_wrapper/rack_params.rb
|
81
123
|
- lib/rschema/dsl.rb
|
82
124
|
- lib/rschema/error.rb
|
83
|
-
- lib/rschema/http_coercer.rb
|
84
125
|
- lib/rschema/options.rb
|
85
126
|
- lib/rschema/result.rb
|
127
|
+
- lib/rschema/schemas.rb
|
86
128
|
- lib/rschema/schemas/anything.rb
|
87
129
|
- lib/rschema/schemas/boolean.rb
|
130
|
+
- lib/rschema/schemas/coercer.rb
|
131
|
+
- lib/rschema/schemas/convenience.rb
|
88
132
|
- lib/rschema/schemas/enum.rb
|
89
133
|
- lib/rschema/schemas/fixed_hash.rb
|
90
134
|
- lib/rschema/schemas/fixed_length_array.rb
|
data/lib/rschema/http_coercer.rb
DELETED
@@ -1,218 +0,0 @@
|
|
1
|
-
module RSchema
|
2
|
-
module HTTPCoercer
|
3
|
-
class CanNotBeWrappedError < StandardError; end
|
4
|
-
|
5
|
-
def self.wrap(schema)
|
6
|
-
coercer_klass = begin
|
7
|
-
case schema
|
8
|
-
when Schemas::Type then TYPE_COERCERS[schema.type]
|
9
|
-
when Schemas::Boolean then BoolCoercer
|
10
|
-
when Schemas::FixedHash then FixedHashCoercer
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
wrapped_schema = schema.with_wrapped_subschemas(self)
|
15
|
-
coercer_klass ? coercer_klass.new(wrapped_schema) : wrapped_schema
|
16
|
-
end
|
17
|
-
|
18
|
-
class Coercer
|
19
|
-
attr_reader :subschema
|
20
|
-
|
21
|
-
def initialize(subschema)
|
22
|
-
@subschema = subschema
|
23
|
-
end
|
24
|
-
|
25
|
-
def call(value, options=RSchema::Options.default)
|
26
|
-
@subschema.call(coerce(value), options)
|
27
|
-
rescue CoercionFailedError
|
28
|
-
return Result.failure(Error.new(
|
29
|
-
schema: self,
|
30
|
-
value: value,
|
31
|
-
symbolic_name: :coercion_failure,
|
32
|
-
))
|
33
|
-
end
|
34
|
-
|
35
|
-
def with_wrapped_subschemas(wrapper)
|
36
|
-
# Double wrapping is potentially a problem. Coercers expect their
|
37
|
-
# subschemas to be a particular type. If their subschema gets wrapped
|
38
|
-
# again, the type changes, so if the coercer tries to use its subschema
|
39
|
-
# during coercion, it will crash.
|
40
|
-
#
|
41
|
-
# For this reason, coercers must not rely upon the type of their
|
42
|
-
# subschemas within `#call`. Coercer schemas should store any required
|
43
|
-
# info from their subschemas within `#initialize`.
|
44
|
-
|
45
|
-
self.class.new(wrapper.wrap(subschema))
|
46
|
-
end
|
47
|
-
|
48
|
-
def invalid!
|
49
|
-
raise CoercionFailedError
|
50
|
-
end
|
51
|
-
|
52
|
-
class CoercionFailedError < StandardError; end
|
53
|
-
end
|
54
|
-
|
55
|
-
class TimeCoercer < Coercer
|
56
|
-
def coerce(value)
|
57
|
-
case value
|
58
|
-
when Time then value
|
59
|
-
when String then Time.iso8601(value) rescue invalid!
|
60
|
-
else invalid!
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
class DateCoercer < Coercer
|
66
|
-
def coerce(value)
|
67
|
-
case value
|
68
|
-
when Date then value
|
69
|
-
when String then Date.iso8601(value) rescue invalid!
|
70
|
-
else invalid!
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
class SymbolCoercer < Coercer
|
76
|
-
def coerce(value)
|
77
|
-
case value
|
78
|
-
when Symbol then value
|
79
|
-
when String then value.to_sym
|
80
|
-
else invalid!
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
class IntegerCoercer < Coercer
|
86
|
-
def coerce(value)
|
87
|
-
Integer(value)
|
88
|
-
rescue ArgumentError
|
89
|
-
invalid!
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
class FloatCoercer < Coercer
|
94
|
-
def coerce(value)
|
95
|
-
Float(value)
|
96
|
-
rescue ArgumentError
|
97
|
-
invalid!
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
class BoolCoercer < Coercer
|
102
|
-
TRUTHY_STRINGS = ['on', '1', 'true']
|
103
|
-
FALSEY_STRINGS = ['off', '0', 'false']
|
104
|
-
|
105
|
-
def coerce(value)
|
106
|
-
case value
|
107
|
-
when true, false then value
|
108
|
-
when nil then false
|
109
|
-
when String
|
110
|
-
case
|
111
|
-
when TRUTHY_STRINGS.include?(value.downcase) then true
|
112
|
-
when FALSEY_STRINGS.include?(value.downcase) then false
|
113
|
-
else invalid!
|
114
|
-
end
|
115
|
-
else invalid!
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
class FixedHashCoercer < Coercer
|
121
|
-
attr_reader :hash_attributes
|
122
|
-
|
123
|
-
def initialize(fixed_hash_schema, attributes = nil)
|
124
|
-
super(fixed_hash_schema)
|
125
|
-
|
126
|
-
@hash_attributes = attributes || fixed_hash_schema.attributes.map(&:dup)
|
127
|
-
end
|
128
|
-
|
129
|
-
def coerce(value)
|
130
|
-
[value]
|
131
|
-
.map(&method(:symbolize_keys))
|
132
|
-
.map(&method(:remove_extraneous_elements))
|
133
|
-
.map(&method(:default_bools_to_false))
|
134
|
-
.last
|
135
|
-
end
|
136
|
-
|
137
|
-
def symbolize_keys(hash)
|
138
|
-
keys = keys_to_symbolize(hash)
|
139
|
-
if keys.any?
|
140
|
-
hash.dup.tap do |new_hash|
|
141
|
-
keys.each { |k| new_hash[k.to_sym] = new_hash.delete(k) }
|
142
|
-
end
|
143
|
-
else
|
144
|
-
hash
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
def keys_to_symbolize(hash)
|
149
|
-
# some of this could be memoized
|
150
|
-
symbol_keys = hash_attributes
|
151
|
-
.map(&:key)
|
152
|
-
.select{ |k| k.is_a?(Symbol) }
|
153
|
-
.map(&:to_s)
|
154
|
-
|
155
|
-
string_keys = hash_attributes
|
156
|
-
.map(&:key)
|
157
|
-
.select{ |k| k.is_a?(String) }
|
158
|
-
|
159
|
-
hash.keys.select do |k|
|
160
|
-
symbol_keys.include?(k) && !string_keys.include?(k)
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
def remove_extraneous_elements(hash)
|
165
|
-
valid_keys = hash_attributes.map(&:key)
|
166
|
-
keys_to_remove = hash.keys - valid_keys
|
167
|
-
|
168
|
-
if keys_to_remove.any?
|
169
|
-
hash.dup.tap do |stripped_hash|
|
170
|
-
keys_to_remove.each { |k| stripped_hash.delete(k) }
|
171
|
-
end
|
172
|
-
else
|
173
|
-
hash
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
def default_bools_to_false(hash)
|
178
|
-
# The HTTP standard says that when a form is submitted, all unchecked
|
179
|
-
# check boxes will _not_ be sent to the server. That is, they will not
|
180
|
-
# be present at all in the params hash.
|
181
|
-
#
|
182
|
-
# This method coerces these missing values into `false`.
|
183
|
-
|
184
|
-
missing_keys = keys_for_bool_defaulting
|
185
|
-
.reject { |key| hash.has_key?(key) }
|
186
|
-
|
187
|
-
if missing_keys.any?
|
188
|
-
defaults = missing_keys.map{ |k| [k, false] }.to_h
|
189
|
-
hash.merge(defaults)
|
190
|
-
else
|
191
|
-
hash # no coercion necessary
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
def with_wrapped_subschemas(wrapper)
|
196
|
-
self.class.new(wrapper.wrap(subschema), hash_attributes)
|
197
|
-
end
|
198
|
-
|
199
|
-
private
|
200
|
-
|
201
|
-
def keys_for_bool_defaulting
|
202
|
-
# this could be memoized
|
203
|
-
hash_attributes
|
204
|
-
.reject(&:optional)
|
205
|
-
.select { |attr| attr.value_schema.is_a?(BoolCoercer) }
|
206
|
-
.map(&:key)
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
TYPE_COERCERS = {
|
211
|
-
Symbol => SymbolCoercer,
|
212
|
-
Integer => IntegerCoercer,
|
213
|
-
Float => FloatCoercer,
|
214
|
-
Time => TimeCoercer,
|
215
|
-
Date => DateCoercer,
|
216
|
-
}
|
217
|
-
end
|
218
|
-
end
|