rschema 3.1.1 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/rschema.rb +13 -5
  3. data/lib/rschema/coercers.rb +2 -0
  4. data/lib/rschema/coercers/any.rb +37 -31
  5. data/lib/rschema/coercers/boolean.rb +33 -23
  6. data/lib/rschema/coercers/chain.rb +38 -32
  7. data/lib/rschema/coercers/date.rb +29 -20
  8. data/lib/rschema/coercers/fixed_hash/default_arrays_to_empty.rb +57 -56
  9. data/lib/rschema/coercers/fixed_hash/default_booleans_to_false.rb +56 -55
  10. data/lib/rschema/coercers/fixed_hash/remove_extraneous_attributes.rb +43 -39
  11. data/lib/rschema/coercers/fixed_hash/symbolize_keys.rb +55 -51
  12. data/lib/rschema/coercers/float.rb +22 -15
  13. data/lib/rschema/coercers/integer.rb +21 -15
  14. data/lib/rschema/coercers/nil_empty_strings.rb +20 -17
  15. data/lib/rschema/coercers/symbol.rb +20 -17
  16. data/lib/rschema/coercers/time.rb +29 -20
  17. data/lib/rschema/coercion_wrapper.rb +25 -26
  18. data/lib/rschema/coercion_wrapper/rack_params.rb +18 -19
  19. data/lib/rschema/dsl.rb +20 -13
  20. data/lib/rschema/error.rb +9 -4
  21. data/lib/rschema/options.rb +5 -0
  22. data/lib/rschema/rails.rb +60 -0
  23. data/lib/rschema/result.rb +9 -11
  24. data/lib/rschema/schemas.rb +2 -0
  25. data/lib/rschema/schemas/anything.rb +23 -24
  26. data/lib/rschema/schemas/boolean.rb +36 -30
  27. data/lib/rschema/schemas/coercer.rb +47 -46
  28. data/lib/rschema/schemas/convenience.rb +122 -123
  29. data/lib/rschema/schemas/enum.rb +41 -34
  30. data/lib/rschema/schemas/fixed_hash.rb +165 -162
  31. data/lib/rschema/schemas/fixed_length_array.rb +66 -58
  32. data/lib/rschema/schemas/maybe.rb +28 -28
  33. data/lib/rschema/schemas/pipeline.rb +35 -35
  34. data/lib/rschema/schemas/predicate.rb +40 -34
  35. data/lib/rschema/schemas/set.rb +57 -48
  36. data/lib/rschema/schemas/sum.rb +31 -34
  37. data/lib/rschema/schemas/type.rb +44 -38
  38. data/lib/rschema/schemas/variable_hash.rb +63 -61
  39. data/lib/rschema/schemas/variable_length_array.rb +57 -51
  40. data/lib/rschema/version.rb +3 -1
  41. metadata +54 -25
@@ -1,25 +1,28 @@
1
- module RSchema
2
- module Coercers
1
+ # frozen_string_literal: true
3
2
 
4
- module Symbol
5
- extend self
3
+ module RSchema
4
+ module Coercers
5
+ #
6
+ # Coerces `String`s to `Symbol`s
7
+ #
8
+ module Symbol
9
+ extend self
6
10
 
7
- def build(schema)
8
- self
9
- end
11
+ def build(_schema)
12
+ self
13
+ end
10
14
 
11
- def call(value)
12
- case value
13
- when ::Symbol then Result.success(value)
14
- when ::String then Result.success(value.to_sym)
15
- else Result.failure
15
+ def call(value)
16
+ case value
17
+ when ::Symbol then Result.success(value)
18
+ when ::String then Result.success(value.to_sym)
19
+ else Result.failure
20
+ end
16
21
  end
17
- end
18
22
 
19
- def will_affect?(value)
20
- not value.is_a?(Symbol)
23
+ def will_affect?(value)
24
+ !value.is_a?(Symbol)
25
+ end
21
26
  end
22
27
  end
23
-
24
- end
25
28
  end
@@ -1,29 +1,38 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RSchema
2
- module Coercers
4
+ module Coercers
5
+ #
6
+ # Coerces `String`s to `Time`s using `Time.parse`
7
+ module Time
8
+ extend self
3
9
 
4
- module Time
5
- extend self
10
+ def build(_schema)
11
+ self
12
+ end
6
13
 
7
- def build(schema)
8
- self
9
- end
14
+ def call(value)
15
+ case value
16
+ when ::Time then Result.success(value)
17
+ when ::String then coerce_string(value)
18
+ else Result.failure
19
+ end
20
+ end
10
21
 
11
- def call(value)
12
- case value
13
- when ::Time
14
- Result.success(value)
15
- when ::String
16
- time = ::Time.parse(value) rescue nil
17
- time ? Result.success(time) : Result.failure
18
- else
19
- Result.failure
22
+ def will_affect?(value)
23
+ !value.is_a?(Time)
20
24
  end
21
- end
22
25
 
23
- def will_affect?(value)
24
- not value.is_a?(Time)
26
+ private
27
+
28
+ def coerce_string(str)
29
+ time = begin
30
+ ::Time.parse(str)
31
+ rescue
32
+ nil
33
+ end
34
+ time ? Result.success(time) : Result.failure
35
+ end
25
36
  end
26
37
  end
27
-
28
- end
29
38
  end
@@ -1,4 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RSchema
4
+ #
5
+ # Builds coercing schemas, by wrapping coercers around an existing schema.
6
+ #
2
7
  class CoercionWrapper
3
8
  def initialize(&initializer)
4
9
  @builder_by_schema = {}
@@ -21,36 +26,30 @@ module RSchema
21
26
 
22
27
  private
23
28
 
24
- def builder_for_schema(schema)
25
- @builder_by_schema.fetch(schema.class) do
26
- if schema.is_a?(Schemas::Type)
27
- builder_for_type(schema.type)
28
- else
29
- nil
30
- end
31
- end
29
+ def builder_for_schema(schema)
30
+ @builder_by_schema.fetch(schema.class) do
31
+ builder_for_type(schema.type) if schema.is_a?(Schemas::Type)
32
32
  end
33
+ end
33
34
 
34
- def builder_for_type(type)
35
- # polymorphic lookup
36
- type.ancestors.each do |ancestor|
37
- builder = @builder_by_type[ancestor]
38
- return builder if builder
39
- end
40
-
41
- nil
35
+ def builder_for_type(type)
36
+ # polymorphic lookup
37
+ type.ancestors.each do |ancestor|
38
+ builder = @builder_by_type[ancestor]
39
+ return builder if builder
42
40
  end
43
41
 
44
- def wrap_with_coercer(schema)
45
- builder = builder_for_schema(schema)
46
- if builder
47
- coercer = builder.build(schema)
48
- Schemas::Coercer.new(coercer, schema)
49
- else
50
- schema
51
- end
52
- end
42
+ nil
43
+ end
53
44
 
45
+ def wrap_with_coercer(schema)
46
+ builder = builder_for_schema(schema)
47
+ if builder
48
+ coercer = builder.build(schema)
49
+ Schemas::Coercer.new(coercer, schema)
50
+ else
51
+ schema
52
+ end
53
+ end
54
54
  end
55
55
  end
56
-
@@ -1,25 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'date'
2
4
 
3
5
  module RSchema
4
- class CoercionWrapper
5
-
6
- RACK_PARAMS = CoercionWrapper.new do
7
- coerce_type Symbol, with: Coercers::Symbol
8
- coerce_type Integer, with: Coercers::Integer
9
- coerce_type Float, with: Coercers::Float
10
- coerce_type Time, with: Coercers::Time
11
- coerce_type Date, with: Coercers::Date
6
+ class CoercionWrapper
7
+ RACK_PARAMS = CoercionWrapper.new do
8
+ coerce_type Symbol, with: Coercers::Symbol
9
+ coerce_type Integer, with: Coercers::Integer
10
+ coerce_type Float, with: Coercers::Float
11
+ coerce_type Time, with: Coercers::Time
12
+ coerce_type Date, with: Coercers::Date
12
13
 
13
- coerce Schemas::Maybe, with: Coercers::NilEmptyStrings
14
- coerce Schemas::Boolean, with: Coercers::Boolean
15
- coerce Schemas::FixedHash, with: Coercers::Chain[
16
- Coercers::FixedHash::SymbolizeKeys,
17
- Coercers::FixedHash::RemoveExtraneousAttributes,
18
- Coercers::FixedHash::DefaultBooleansToFalse,
19
- Coercers::FixedHash::DefaultArraysToEmpty,
20
- ]
14
+ coerce Schemas::Maybe, with: Coercers::NilEmptyStrings
15
+ coerce Schemas::Boolean, with: Coercers::Boolean
16
+ coerce Schemas::FixedHash, with: Coercers::Chain[
17
+ Coercers::FixedHash::SymbolizeKeys,
18
+ Coercers::FixedHash::RemoveExtraneousAttributes,
19
+ Coercers::FixedHash::DefaultBooleansToFalse,
20
+ Coercers::FixedHash::DefaultArraysToEmpty,
21
+ ]
22
+ end
21
23
  end
22
-
23
24
  end
24
- end
25
-
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RSchema
2
4
  #
3
5
  # A mixin containing all the standard RSchema DSL methods.
@@ -45,15 +47,15 @@ module RSchema
45
47
  # Creates a {Schemas::VariableLengthArray} if given one argument, otherwise
46
48
  # creates a {Schemas::FixedLengthArray}
47
49
  #
48
- # @param subschemas [Array<schema>] one or more schema objects representing elements
49
- # in the array.
50
+ # @param subschemas [Array<schema>] one or more schema objects representing
51
+ # elements in the array.
50
52
  # @return [Schemas::VariableLengthArray, Schemas::FixedLengthArray]
51
53
  #
52
54
  # @example (see Schemas::VariableLengthArray)
53
55
  # @example (see Schemas::FixedLengthArray)
54
56
  #
55
57
  def array(*subschemas)
56
- subschemas = subschemas.map{ |ss| inconvenience(ss) }
58
+ subschemas = subschemas.map { |ss| inconvenience(ss) }
57
59
 
58
60
  if subschemas.count == 1
59
61
  Schemas::VariableLengthArray.new(subschemas.first)
@@ -87,7 +89,7 @@ module RSchema
87
89
  def fixed_hash(attribute_hash)
88
90
  Schemas::FixedHash.new(attributes(attribute_hash))
89
91
  end
90
- alias_method :hash, :fixed_hash
92
+ alias hash fixed_hash
91
93
 
92
94
  #
93
95
  # Creates a {Schemas::Set} schema
@@ -143,8 +145,9 @@ module RSchema
143
145
  end
144
146
 
145
147
  #
146
- # Turns an "attribute hash" into an array of {Schemas::FixedHash::Attribute}.
147
- # Primarily for use with {Schemas::FixedHash#merge}.
148
+ # Turns an "attribute hash" into an array of
149
+ # {Schemas::FixedHash::Attribute}. Primarily for use with
150
+ # {Schemas::FixedHash#merge}.
148
151
  #
149
152
  # @param attribute_hash [Hash<key, schema>] A hash of keys to subschemas.
150
153
  # The values of this hash must be schema objects.
@@ -161,7 +164,9 @@ module RSchema
161
164
  attribute_hash.map do |dsl_key, value_schema|
162
165
  optional = dsl_key.is_a?(OptionalWrapper)
163
166
  key = optional ? dsl_key.key : dsl_key
164
- Schemas::FixedHash::Attribute.new(key, inconvenience(value_schema), optional)
167
+ Schemas::FixedHash::Attribute.new(
168
+ key, inconvenience(value_schema), optional,
169
+ )
165
170
  end
166
171
  end
167
172
 
@@ -190,9 +195,11 @@ module RSchema
190
195
  #
191
196
  # @example (see Schemas::Enum)
192
197
  #
193
- def enum(valid_values, subschema=nil)
198
+ def enum(valid_values, subschema = nil)
194
199
  subschema = inconvenience(subschema) if subschema
195
- Schemas::Enum.new(valid_values, subschema || type(valid_values.first.class))
200
+ Schemas::Enum.new(
201
+ valid_values, subschema || type(valid_values.first.class),
202
+ )
196
203
  end
197
204
 
198
205
  #
@@ -205,7 +212,7 @@ module RSchema
205
212
  # @example (see Schemas::Sum)
206
213
  #
207
214
  def either(*subschemas)
208
- subschemas = subschemas.map{ |ss| inconvenience(ss) }
215
+ subschemas = subschemas.map { |ss| inconvenience(ss) }
209
216
  Schemas::Sum.new(subschemas)
210
217
  end
211
218
 
@@ -239,7 +246,7 @@ module RSchema
239
246
  # @example (see Schemas::Pipeline)
240
247
  #
241
248
  def pipeline(*subschemas)
242
- subschemas = subschemas.map{ |ss| inconvenience(ss) }
249
+ subschemas = subschemas.map { |ss| inconvenience(ss) }
243
250
  Schemas::Pipeline.new(subschemas)
244
251
  end
245
252
 
@@ -331,9 +338,9 @@ module RSchema
331
338
  end
332
339
 
333
340
  # @!visibility private
334
- def respond_to?(sym, include_all=false)
341
+ def respond_to_missing?(sym, include_all = false)
335
342
  # check if method starts with an underscore followed by a capital
336
- super || !!sym.to_s.match(/\A_[A-Z]/)
343
+ super || sym.to_s.match(/\A_[A-Z]/)
337
344
  end
338
345
  end
339
346
  end
@@ -1,9 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RSchema
4
+ #
5
+ # Contains info about how a schema failed validation
6
+ #
2
7
  class Error
3
8
  attr_reader :schema, :value, :symbolic_name, :vars
4
9
 
5
10
  def initialize(schema:, value:, symbolic_name:, vars: {})
6
- raise ArgumentError.new("vars must be a hash") unless vars.is_a?(Hash)
11
+ raise ArgumentError.new('vars must be a hash') unless vars.is_a?(Hash)
7
12
 
8
13
  @schema = schema
9
14
  @value = value
@@ -19,10 +24,10 @@ module RSchema
19
24
 
20
25
  def inspect
21
26
  attrs = vars.merge(value: value)
22
- .map{ |k, v| "#{k}=#{v.inspect}" }
23
- .join(' ')
27
+ .map { |k, v| "#{k}=#{v.inspect}" }
28
+ .join(' ')
24
29
 
25
- "<#{self.class} #{to_s} #{attrs}>"
30
+ "<#{self.class} #{self} #{attrs}>"
26
31
  end
27
32
  end
28
33
  end
@@ -1,4 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RSchema
4
+ #
5
+ # Settings, passed in as an argument when calling schemas
6
+ #
2
7
  class Options
3
8
  def self.default
4
9
  @default ||= new
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rschema'
4
+ require 'rschema/coercion_wrapper/rack_params'
5
+
6
+ module RSchema
7
+ module Rails
8
+ #
9
+ # A mixin for ActionController that provides methods for validating params.
10
+ #
11
+ module Controller
12
+ def self.included(klass)
13
+ klass.include(InstanceMethods)
14
+ klass.extend(ClassMethods)
15
+ end
16
+
17
+ #
18
+ # Instance methods added to ActionController classes
19
+ #
20
+ module InstanceMethods
21
+ def param_schema(&schema_block)
22
+ self.class.param_schema(&schema_block)
23
+ end
24
+
25
+ def validate_params(schema = nil, &schema_block)
26
+ schema ||= param_schema(&schema_block)
27
+ schema.validate(request.parameters.to_hash)
28
+ end
29
+
30
+ def validate_params!(*args, &block)
31
+ result = validate_params(*args, &block)
32
+ raise InvalidParams.new(result.error) if result.invalid?
33
+ result.value
34
+ end
35
+ end
36
+
37
+ #
38
+ # Class methods added to ActionController classes
39
+ #
40
+ module ClassMethods
41
+ def param_schema(&schema_block)
42
+ schema = RSchema.define_hash(&schema_block)
43
+ RSchema::CoercionWrapper::RACK_PARAMS.wrap(schema)
44
+ end
45
+ end
46
+ end
47
+
48
+ #
49
+ # Raised when {validate_params!} fails
50
+ #
51
+ class InvalidParams < StandardError
52
+ attr_reader :error
53
+
54
+ def initialize(error)
55
+ @error = error
56
+ super('Parameters do not conform to schema')
57
+ end
58
+ end
59
+ end
60
+ end
@@ -1,4 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RSchema
4
+ #
5
+ # The return value when calling a schema
6
+ #
2
7
  class Result
3
8
  def self.success(value = nil)
4
9
  if value.nil?
@@ -28,22 +33,15 @@ module RSchema
28
33
  end
29
34
 
30
35
  def invalid?
31
- not valid?
36
+ !valid?
32
37
  end
33
38
 
34
39
  def value
35
- if valid?
36
- @value
37
- else
38
- raise InvalidError
39
- end
40
- end
41
-
42
- def error
43
- @error
40
+ raise RSchema::Invalid.new(error) if invalid?
41
+ @value
44
42
  end
45
43
 
46
- class InvalidError < StandardError; end
44
+ attr_reader :error
47
45
 
48
46
  # @!visibility private
49
47
  NIL_SUCCESS = new(true, nil, nil)