rschema 3.0.1.pre3 → 3.0.1.pre4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +278 -330
  3. data/lib/rschema.rb +104 -17
  4. data/lib/rschema/coercers.rb +3 -0
  5. data/lib/rschema/coercers/any.rb +40 -0
  6. data/lib/rschema/coercers/boolean.rb +30 -0
  7. data/lib/rschema/coercers/chain.rb +41 -0
  8. data/lib/rschema/coercers/date.rb +25 -0
  9. data/lib/rschema/coercers/fixed_hash/default_booleans_to_false.rb +62 -0
  10. data/lib/rschema/coercers/fixed_hash/remove_extraneous_attributes.rb +42 -0
  11. data/lib/rschema/coercers/fixed_hash/symbolize_keys.rb +62 -0
  12. data/lib/rschema/coercers/float.rb +18 -0
  13. data/lib/rschema/coercers/integer.rb +18 -0
  14. data/lib/rschema/coercers/symbol.rb +21 -0
  15. data/lib/rschema/coercers/time.rb +25 -0
  16. data/lib/rschema/coercion_wrapper.rb +46 -0
  17. data/lib/rschema/coercion_wrapper/rack_params.rb +21 -0
  18. data/lib/rschema/dsl.rb +271 -42
  19. data/lib/rschema/error.rb +12 -30
  20. data/lib/rschema/options.rb +2 -2
  21. data/lib/rschema/result.rb +18 -4
  22. data/lib/rschema/schemas.rb +3 -0
  23. data/lib/rschema/schemas/anything.rb +14 -12
  24. data/lib/rschema/schemas/boolean.rb +20 -21
  25. data/lib/rschema/schemas/coercer.rb +37 -0
  26. data/lib/rschema/schemas/convenience.rb +53 -0
  27. data/lib/rschema/schemas/enum.rb +25 -25
  28. data/lib/rschema/schemas/fixed_hash.rb +110 -91
  29. data/lib/rschema/schemas/fixed_length_array.rb +48 -48
  30. data/lib/rschema/schemas/maybe.rb +18 -17
  31. data/lib/rschema/schemas/pipeline.rb +20 -19
  32. data/lib/rschema/schemas/predicate.rb +24 -21
  33. data/lib/rschema/schemas/set.rb +40 -45
  34. data/lib/rschema/schemas/sum.rb +24 -28
  35. data/lib/rschema/schemas/type.rb +22 -21
  36. data/lib/rschema/schemas/variable_hash.rb +53 -52
  37. data/lib/rschema/schemas/variable_length_array.rb +39 -38
  38. data/lib/rschema/version.rb +1 -1
  39. metadata +49 -5
  40. data/lib/rschema/http_coercer.rb +0 -218
@@ -1,67 +1,68 @@
1
1
  module RSchema
2
- module Schemas
3
- class VariableHash
4
- attr_reader :key_schema, :value_schema
2
+ module Schemas
3
+ class VariableHash
4
+ attr_reader :key_schema, :value_schema
5
5
 
6
- def initialize(key_schema, value_schema)
7
- @key_schema = key_schema
8
- @value_schema = value_schema
9
- end
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
- result, key_errors, value_errors = apply_subschemas(value, options)
11
+ def call(value, options)
12
+ return not_a_hash_result(value) unless value.is_a?(Hash)
15
13
 
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
14
+ result, key_errors, value_errors = apply_subschemas(value, options)
22
15
 
23
- def with_wrapped_subschemas(wrapper)
24
- self.class.new(
25
- wrapper.wrap(key_schema),
26
- wrapper.wrap(value_schema),
27
- )
28
- end
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
- private
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
- 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
30
+ private
39
31
 
40
- def apply_subschemas(value, options)
41
- result = {}
42
- key_errors = {}
43
- value_errors = {}
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
- 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
40
+ def apply_subschemas(value, options)
41
+ result = {}
42
+ key_errors = {}
43
+ value_errors = {}
51
44
 
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
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
- if key_result.valid? && subvalue_result.valid?
59
- result[key_result.value] = subvalue_result.value
60
- end
61
- end
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
- return result, key_errors, value_errors
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
- end
65
+
66
+ end
67
+ end
67
68
  end
@@ -1,49 +1,50 @@
1
1
  module RSchema
2
- module Schemas
3
- class VariableLengthArray
4
- attr_accessor :element_schema
2
+ module Schemas
3
+ class VariableLengthArray
4
+ attr_accessor :element_schema
5
5
 
6
- def initialize(element_schema)
7
- @element_schema = element_schema
8
- end
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
- def with_wrapped_subschemas(wrapper)
28
- self.class.new(wrapper.wrap(element_schema))
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
- def validate_elements(array, options)
32
- errors = {}
33
- validated_values = []
27
+ def with_wrapped_subschemas(wrapper)
28
+ self.class.new(wrapper.wrap(element_schema))
29
+ end
34
30
 
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?
42
- end
43
- end
31
+ def validate_elements(array, options)
32
+ errors = {}
33
+ validated_values = []
44
34
 
45
- [validated_values, errors]
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
@@ -1,3 +1,3 @@
1
1
  module RSchema
2
- VERSION = '3.0.1.pre3'
2
+ VERSION = '3.0.1.pre4'
3
3
  end
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.pre3
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-02-02 00:00:00.000000000 Z
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
@@ -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