rschema 3.1.1 → 3.2.0

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 (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,35 +1,35 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RSchema
2
- module Schemas
4
+ module Schemas
5
+ #
6
+ # A schema representing that a value may be `nil`
7
+ #
8
+ # If the value is not `nil`, it must conform to the subschema
9
+ #
10
+ # @example A nil-able Integer
11
+ # schema = RSchema.define{ maybe(_Integer) }
12
+ # schema.valid?(5) #=> true
13
+ # schema.valid?(nil) #=> true
14
+ #
15
+ class Maybe
16
+ attr_reader :subschema
3
17
 
4
- #
5
- # A schema representing that a value may be `nil`
6
- #
7
- # If the value is not `nil`, it must conform to the subschema
8
- #
9
- # @example A nil-able Integer
10
- # schema = RSchema.define{ maybe(_Integer) }
11
- # schema.valid?(5) #=> true
12
- # schema.valid?(nil) #=> true
13
- #
14
- class Maybe
15
- attr_reader :subschema
18
+ def initialize(subschema)
19
+ @subschema = subschema
20
+ end
16
21
 
17
- def initialize(subschema)
18
- @subschema = subschema
19
- end
22
+ def call(value, options)
23
+ if value.nil?
24
+ Result.success(value)
25
+ else
26
+ @subschema.call(value, options)
27
+ end
28
+ end
20
29
 
21
- def call(value, options)
22
- if nil == value
23
- Result.success(value)
24
- else
25
- @subschema.call(value, options)
30
+ def with_wrapped_subschemas(wrapper)
31
+ self.class.new(wrapper.wrap(subschema))
32
+ end
26
33
  end
27
34
  end
28
-
29
- def with_wrapped_subschemas(wrapper)
30
- self.class.new(wrapper.wrap(subschema))
31
- end
32
-
33
- end
34
- end
35
35
  end
@@ -1,43 +1,43 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RSchema
2
- module Schemas
4
+ module Schemas
5
+ #
6
+ # A schema that chains together an ordered list of other schemas
7
+ #
8
+ # @example A schema for positive floats
9
+ # schema = RSchema.define do
10
+ # pipeline(
11
+ # _Float,
12
+ # predicate{ |f| f > 0.0 },
13
+ # )
14
+ # end
15
+ # schema.valid?(6.2) #=> true
16
+ # schema.valid?('hi') #=> false (because it's not a Float)
17
+ # schema.valid?(-6.2) #=> false (because predicate failed)
18
+ #
19
+ class Pipeline
20
+ attr_reader :subschemas
3
21
 
4
- #
5
- # A schema that chains together an ordered list of other schemas
6
- #
7
- # @example A schema for positive floats
8
- # schema = RSchema.define do
9
- # pipeline(
10
- # _Float,
11
- # predicate{ |f| f > 0.0 },
12
- # )
13
- # end
14
- # schema.valid?(6.2) #=> true
15
- # schema.valid?('hi') #=> false (because it's not a Float)
16
- # schema.valid?(-6.2) #=> false (because predicate failed)
17
- #
18
- class Pipeline
19
- attr_reader :subschemas
22
+ def initialize(subschemas)
23
+ @subschemas = subschemas
24
+ end
20
25
 
21
- def initialize(subschemas)
22
- @subschemas = subschemas
23
- end
26
+ def call(value, options)
27
+ result = Result.success(value)
24
28
 
25
- def call(value, options)
26
- result = Result.success(value)
29
+ subschemas.each do |subsch|
30
+ result = subsch.call(result.value, options)
31
+ break if result.invalid?
32
+ end
27
33
 
28
- subschemas.each do |subsch|
29
- result = subsch.call(result.value, options)
30
- break if result.invalid?
31
- end
32
-
33
- result
34
- end
34
+ result
35
+ end
35
36
 
36
- def with_wrapped_subschemas(wrapper)
37
- wrapped_subschemas = subschemas.map{ |ss| wrapper.wrap(ss) }
38
- self.class.new(wrapped_subschemas)
37
+ def with_wrapped_subschemas(wrapper)
38
+ wrapped_subschemas = subschemas.map { |ss| wrapper.wrap(ss) }
39
+ self.class.new(wrapped_subschemas)
40
+ end
41
+ end
39
42
  end
40
-
41
- end
42
- end
43
43
  end
@@ -1,41 +1,47 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RSchema
2
- module Schemas
4
+ module Schemas
5
+ #
6
+ # A schema that uses a given block to determine whether a value is valid
7
+ #
8
+ # @example A predicate that checks if numbers are odd
9
+ # schema = RSchema.define do
10
+ # predicate('odd'){ |x| x.odd? }
11
+ # end
12
+ # schema.valid?(5) #=> true
13
+ # schema.valid?(6) #=> false
14
+ #
15
+ class Predicate
16
+ attr_reader :block, :name
3
17
 
4
- #
5
- # A schema that uses a given block to determine whether a value is valid
6
- #
7
- # @example A predicate that checks if numbers are odd
8
- # schema = RSchema.define do
9
- # predicate('odd'){ |x| x.odd? }
10
- # end
11
- # schema.valid?(5) #=> true
12
- # schema.valid?(6) #=> false
13
- #
14
- class Predicate
15
- attr_reader :block, :name
18
+ def initialize(name = nil, &block)
19
+ @block = block
20
+ @name = name
21
+ end
16
22
 
17
- def initialize(name = nil, &block)
18
- @block = block
19
- @name = name
20
- end
23
+ def call(value, _options)
24
+ if block.call(value)
25
+ Result.success(value)
26
+ else
27
+ Result.failure(error(value))
28
+ end
29
+ end
21
30
 
22
- def call(value, options)
23
- if block.call(value)
24
- Result.success(value)
25
- else
26
- Result.failure(Error.new(
27
- schema: self,
28
- value: value,
29
- symbolic_name: :false,
30
- vars: { predicate_name: name }
31
- ))
32
- end
33
- end
31
+ def with_wrapped_subschemas(_wrapper)
32
+ self
33
+ end
34
34
 
35
- def with_wrapped_subschemas(wrapper)
36
- self
37
- end
35
+ private
38
36
 
39
- end
40
- end
37
+ def error(value)
38
+ Error.new(
39
+ schema: self,
40
+ value: value,
41
+ symbolic_name: :false,
42
+ vars: { predicate_name: name },
43
+ )
44
+ end
45
+ end
46
+ end
41
47
  end
@@ -1,61 +1,70 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
 
3
5
  module RSchema
4
- module Schemas
5
-
6
- #
7
- # A schema that matches `Set` objects (from the Ruby standard library)
8
- #
9
- # @example A set of integers
10
- # require 'set'
11
- # schema = RSchema.define { set(_Integer) }
12
- # schema.valid?(Set[1, 2, 3]) #=> true
13
- # schema.valid?(Set[:a, :b, :c]) #=> false
14
- #
15
- class Set
16
- attr_reader :subschema
17
-
18
- def initialize(subschema)
19
- @subschema = subschema
20
- end
6
+ module Schemas
7
+ #
8
+ # A schema that matches `Set` objects (from the Ruby standard library)
9
+ #
10
+ # @example A set of integers
11
+ # require 'set'
12
+ # schema = RSchema.define { set(_Integer) }
13
+ # schema.valid?(Set[1, 2, 3]) #=> true
14
+ # schema.valid?(Set[:a, :b, :c]) #=> false
15
+ #
16
+ class Set
17
+ attr_reader :subschema
18
+
19
+ def initialize(subschema)
20
+ @subschema = subschema
21
+ end
21
22
 
22
- def call(value, options)
23
- return not_a_set_result(value) unless value.is_a?(::Set)
23
+ def call(value, options)
24
+ return not_a_set_result(value) unless value.is_a?(::Set)
24
25
 
25
- result_value = ::Set.new
26
- result_errors = {}
26
+ validated_set, errors = apply_subschema(value, options)
27
27
 
28
- value.each do |subvalue|
29
- subresult = subschema.call(subvalue, options)
30
- if subresult.valid?
31
- result_value << subresult.value
32
- else
33
- result_errors[subvalue] = subresult.error
28
+ if errors.empty?
29
+ Result.success(validated_set)
30
+ else
31
+ Result.failure(errors)
32
+ end
34
33
  end
35
34
 
36
- break if options.fail_fast?
37
- end
35
+ def with_wrapped_subschemas(wrapper)
36
+ wrapped_subschema = wrapper.wrap(subschema)
37
+ self.class.new(wrapped_subschema)
38
+ end
38
39
 
39
- if result_errors.empty?
40
- Result.success(result_value)
41
- else
42
- Result.failure(result_errors)
43
- end
44
- end
40
+ private
45
41
 
46
- def with_wrapped_subschemas(wrapper)
47
- wrapped_subschema = wrapper.wrap(subschema)
48
- self.class.new(wrapped_subschema)
49
- end
42
+ def apply_subschema(set, options)
43
+ validated_set = ::Set.new
44
+ errors = {}
45
+
46
+ set.each do |subvalue|
47
+ subresult = subschema.call(subvalue, options)
48
+ if subresult.valid?
49
+ validated_set << subresult.value
50
+ else
51
+ errors[subvalue] = subresult.error
52
+ break if options.fail_fast?
53
+ end
54
+ end
55
+
56
+ [validated_set, errors]
57
+ end
50
58
 
51
- private
52
- def not_a_set_result(value)
53
- Result.failure(Error.new(
54
- schema: self,
55
- symbolic_name: :not_a_set,
56
- value: value,
57
- ))
59
+ def not_a_set_result(value)
60
+ Result.failure(
61
+ Error.new(
62
+ schema: self,
63
+ symbolic_name: :not_a_set,
64
+ value: value,
65
+ ),
66
+ )
67
+ end
58
68
  end
59
- end
60
- end
69
+ end
61
70
  end
@@ -1,43 +1,40 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RSchema
2
- module Schemas
4
+ module Schemas
5
+ #
6
+ # A schema that represents a "sum type"
7
+ #
8
+ # Values must conform to one of the subschemas.
9
+ #
10
+ # @example A schema that matches both Integers and Strings
11
+ # schema = RSchema.define { either(_String, _Integer) }
12
+ # schema.valid?("hello") #=> true
13
+ # schema.valid?(5) #=> true
14
+ #
15
+ class Sum
16
+ attr_reader :subschemas
3
17
 
4
- #
5
- # A schema that represents a "sum type"
6
- #
7
- # Values must conform to one of the subschemas.
8
- #
9
- # @example A schema that matches both Integers and Strings
10
- # schema = RSchema.define { either(_String, _Integer) }
11
- # schema.valid?("hello") #=> true
12
- # schema.valid?(5) #=> true
13
- #
14
- class Sum
15
- attr_reader :subschemas
18
+ def initialize(subschemas)
19
+ @subschemas = subschemas
20
+ end
16
21
 
17
- def initialize(subschemas)
18
- @subschemas = subschemas
19
- end
22
+ def call(value, options)
23
+ suberrors = []
20
24
 
21
- def call(value, options)
22
- suberrors = []
25
+ @subschemas.each do |ss|
26
+ result = ss.call(value, options)
27
+ return result if result.valid?
28
+ suberrors << result.error
29
+ end
23
30
 
24
- @subschemas.each do |subsch|
25
- result = subsch.call(value, options)
26
- if result.valid?
27
- return result
28
- else
29
- suberrors << result.error
31
+ Result.failure(suberrors)
30
32
  end
31
- end
32
-
33
- Result.failure(suberrors)
34
- end
35
33
 
36
- def with_wrapped_subschemas(wrapper)
37
- wrapped_subschemas = subschemas.map{ |ss| wrapper.wrap(ss) }
38
- self.class.new(wrapped_subschemas)
34
+ def with_wrapped_subschemas(wrapper)
35
+ wrapped_subschemas = subschemas.map { |ss| wrapper.wrap(ss) }
36
+ self.class.new(wrapped_subschemas)
37
+ end
38
+ end
39
39
  end
40
-
41
- end
42
- end
43
40
  end
@@ -1,45 +1,51 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RSchema
2
- module Schemas
4
+ module Schemas
5
+ #
6
+ # A schema that matches values of a given type (i.e. `value.is_a?(type)`)
7
+ #
8
+ # @example An Integer schema
9
+ # schema = RSchema.define { _Integer }
10
+ # schema.valid?(5) #=> true
11
+ #
12
+ # @example A namespaced type
13
+ # schema = RSchema.define do
14
+ # # This will not work:
15
+ # # _ActiveWhatever::Thing
16
+ #
17
+ # # This will work:
18
+ # type(ActiveWhatever::Thing)
19
+ # end
20
+ #
21
+ class Type
22
+ attr_reader :type
3
23
 
4
- #
5
- # A schema that matches values of a given type (i.e. `value.is_a?(type)`)
6
- #
7
- # @example An Integer schema
8
- # schema = RSchema.define { _Integer }
9
- # schema.valid?(5) #=> true
10
- #
11
- # @example A namespaced type
12
- # schema = RSchema.define do
13
- # # This will not work:
14
- # # _ActiveWhatever::Thing
15
- #
16
- # # This will work:
17
- # type(ActiveWhatever::Thing)
18
- # end
19
- #
20
- class Type
21
- attr_reader :type
24
+ def initialize(type)
25
+ @type = type
26
+ end
22
27
 
23
- def initialize(type)
24
- @type = type
25
- end
28
+ def call(value, _options)
29
+ if value.is_a?(@type)
30
+ Result.success(value)
31
+ else
32
+ Result.failure(error(value))
33
+ end
34
+ end
26
35
 
27
- def call(value, options)
28
- if value.is_a?(@type)
29
- Result.success(value)
30
- else
31
- Result.failure(Error.new(
32
- schema: self,
33
- value: value,
34
- symbolic_name: :wrong_type,
35
- ))
36
- end
37
- end
36
+ def with_wrapped_subschemas(_wrapper)
37
+ self
38
+ end
38
39
 
39
- def with_wrapped_subschemas(wrapper)
40
- self
41
- end
40
+ private
42
41
 
43
- end
44
- end
42
+ def error(value)
43
+ Error.new(
44
+ schema: self,
45
+ value: value,
46
+ symbolic_name: :wrong_type,
47
+ )
48
+ end
49
+ end
50
+ end
45
51
  end