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.
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