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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d720955c9a91f41ca082da8683dcf7bed5d10ab6
4
- data.tar.gz: 6d374b1df2484fa2ec0957ca3d5943289ecb9714
3
+ metadata.gz: 34db15e35dacc01ece4a64f6bc6f1d699d56a82f
4
+ data.tar.gz: 0104cab32868f52759495fe90c1139ac7f6ec2b7
5
5
  SHA512:
6
- metadata.gz: 2152a4189beed6ab21c9fd11afbf668b32cbca48eb58c89820b424473c83ac766e2819f49dc10e456690c4e75a7c2dcb3be1f52cfdc4e89528f34c01c0c45209
7
- data.tar.gz: d4ccfbf1529449d6f13633898dde3c5c65f363ba041724503214e68f23720a02c2870c0b0215ed5fd3324e71ff6f49a590ac4eb2bd8c6e4973e9fc2b0ca732cf
6
+ metadata.gz: ea3e3ea16cc3c1416b03a6df47f6b288f69cca1ed8dd53dfffb3ed8ffd0344eb9eefa7f5695f0d85ec529225b94b298dc9c6a23845558086881dc89f907d60a9
7
+ data.tar.gz: 649d06c7033f88cd0594f1c89943f991fd351703334780bfe1d4be349bbb4edba29156ecd24c8534d5496ef51b64a6942afb797a24423460e498ca7ec135d709
@@ -1,5 +1,6 @@
1
- require 'docile'
1
+ # frozen_string_literal: true
2
2
 
3
+ require 'docile'
3
4
  require 'rschema/options'
4
5
  require 'rschema/error'
5
6
  require 'rschema/result'
@@ -12,7 +13,6 @@ require 'rschema/coercion_wrapper'
12
13
  # Schema-based validation and coercion
13
14
  #
14
15
  module RSchema
15
-
16
16
  #
17
17
  # Creates a schema object using a DSL
18
18
  #
@@ -46,6 +46,10 @@ module RSchema
46
46
  # end
47
47
  #
48
48
  def self.dsl_eval(dsl = nil, &block)
49
+ if block.nil?
50
+ raise ArgumentError, 'Must provide a block for the RSchema DSL'
51
+ end
52
+
49
53
  Docile::Execution.exec_in_proxy_context(
50
54
  dsl || default_dsl,
51
55
  Docile::FallbackContextProxy,
@@ -63,7 +67,8 @@ module RSchema
63
67
  # end
64
68
  #
65
69
  # @yield (see .dsl_eval)
66
- # @yieldreturn The attributes of the hash schema (the argument to {DSL#fixed_hash}).
70
+ # @yieldreturn The attributes of the hash schema
71
+ # (the argument to {DSL#fixed_hash}).
67
72
  # @return [Schemas::Convenience] A {Schemas::FixedHash} schema wrapped in a
68
73
  # {Schemas::Convenience}.
69
74
  #
@@ -75,7 +80,7 @@ module RSchema
75
80
  #
76
81
  def self.define_hash(&block)
77
82
  Schemas::Convenience.wrap(
78
- default_dsl.fixed_hash(dsl_eval(&block))
83
+ default_dsl.fixed_hash(dsl_eval(&block)),
79
84
  )
80
85
  end
81
86
 
@@ -103,7 +108,7 @@ module RSchema
103
108
  #
104
109
  def self.define_predicate(name = nil, &block)
105
110
  Schemas::Convenience.wrap(
106
- default_dsl.predicate(name, &block)
111
+ default_dsl.predicate(name, &block),
107
112
  )
108
113
  end
109
114
 
@@ -127,6 +132,9 @@ module RSchema
127
132
  include RSchema::DSL
128
133
  end
129
134
 
135
+ #
136
+ # Indicates that validation has failed
137
+ #
130
138
  class Invalid < StandardError
131
139
  attr_reader :validation_error
132
140
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Dir.glob(File.join(__dir__, 'coercers/**/*.rb')).each do |path|
2
4
  require path
3
5
  end
@@ -1,44 +1,50 @@
1
- module RSchema
2
- module Coercers
3
-
4
- class Any
5
- attr_reader :subcoercers
1
+ # frozen_string_literal: true
6
2
 
7
- def self.[](*subbuilders)
8
- Builder.new(subbuilders)
9
- end
3
+ module RSchema
4
+ module Coercers
5
+ #
6
+ # Applies subcoercers, in order, until one succeeds
7
+ #
8
+ class Any
9
+ attr_reader :subcoercers
10
+
11
+ def self.[](*subbuilders)
12
+ Builder.new(subbuilders)
13
+ end
10
14
 
11
- def initialize(subcoercers)
12
- @subcoercers = subcoercers
13
- end
15
+ def initialize(subcoercers)
16
+ @subcoercers = subcoercers
17
+ end
14
18
 
15
- def call(value)
16
- subcoercers.each do |coercer|
17
- result = coercer.call(value)
18
- return result if result.valid?
19
+ def call(value)
20
+ subcoercers.each do |coercer|
21
+ result = coercer.call(value)
22
+ return result if result.valid?
23
+ end
24
+ Result.failure
19
25
  end
20
- Result.failure
21
- end
22
26
 
23
- def will_affect?(value)
24
- subcoercers.any?{ |sc| sc.will_affect?(value) }
25
- end
27
+ def will_affect?(value)
28
+ subcoercers.any? { |sc| sc.will_affect?(value) }
29
+ end
26
30
 
27
- class Builder
28
- attr_reader :subbuilders
31
+ #
32
+ # Builder for Coercers::Any
33
+ #
34
+ class Builder
35
+ attr_reader :subbuilders
29
36
 
30
- def initialize(subbuilders)
31
- @subbuilders = subbuilders
32
- end
37
+ def initialize(subbuilders)
38
+ @subbuilders = subbuilders
39
+ end
33
40
 
34
- def build(schema)
35
- subcoercers = subbuilders.map do |builder|
36
- builder.build(schema)
41
+ def build(schema)
42
+ subcoercers = subbuilders.map do |builder|
43
+ builder.build(schema)
44
+ end
45
+ Any.new(subcoercers)
37
46
  end
38
- Any.new(subcoercers)
39
47
  end
40
48
  end
41
49
  end
42
-
43
- end
44
50
  end
@@ -1,34 +1,44 @@
1
- module RSchema
2
- module Coercers
1
+ # frozen_string_literal: true
3
2
 
4
- module Boolean
5
- extend self
3
+ module RSchema
4
+ module Coercers
5
+ #
6
+ # Coerces certain strings, and nil, to true or false
7
+ #
8
+ module Boolean
9
+ extend self
6
10
 
7
- TRUTHY_STRINGS = ['on', '1', 'true', 'yes']
8
- FALSEY_STRINGS = ['off', '0', 'false', 'no']
11
+ TRUTHY_STRINGS = %w[on 1 true yes].freeze
12
+ FALSEY_STRINGS = %w[off 0 false no].freeze
9
13
 
10
- def build(schema)
11
- self
12
- end
14
+ def build(_schema)
15
+ self
16
+ end
13
17
 
14
- def call(value)
15
- case value
16
- when true, false then Result.success(value)
17
- when nil then Result.success(false)
18
- when String
19
- case
20
- when TRUTHY_STRINGS.include?(value.downcase) then Result.success(true)
21
- when FALSEY_STRINGS.include?(value.downcase) then Result.success(false)
18
+ def call(value)
19
+ case value
20
+ when true, false then Result.success(value)
21
+ when nil then Result.success(false)
22
+ when String then coerce_string(value)
22
23
  else Result.failure
23
24
  end
24
- else Result.failure
25
25
  end
26
- end
27
26
 
28
- def will_affect?(value)
29
- true != value && false != value
27
+ def will_affect?(value)
28
+ value != true && value != false
29
+ end
30
+
31
+ private
32
+
33
+ def coerce_string(str)
34
+ if TRUTHY_STRINGS.include?(str.downcase)
35
+ Result.success(true)
36
+ elsif FALSEY_STRINGS.include?(str.downcase)
37
+ Result.success(false)
38
+ else
39
+ Result.failure
40
+ end
41
+ end
30
42
  end
31
43
  end
32
-
33
- end
34
44
  end
@@ -1,45 +1,51 @@
1
- module RSchema
2
- module Coercers
3
-
4
- class Chain
5
- attr_reader :subcoercers
1
+ # frozen_string_literal: true
6
2
 
7
- def self.[](*subbuilders)
8
- Builder.new(subbuilders)
9
- end
3
+ module RSchema
4
+ module Coercers
5
+ #
6
+ # Applies a list of coercers, in order
7
+ #
8
+ class Chain
9
+ attr_reader :subcoercers
10
+
11
+ def self.[](*subbuilders)
12
+ Builder.new(subbuilders)
13
+ end
10
14
 
11
- def initialize(subcoercers)
12
- @subcoercers = subcoercers
13
- end
15
+ def initialize(subcoercers)
16
+ @subcoercers = subcoercers
17
+ end
14
18
 
15
- def call(value)
16
- result = Result.success(value)
17
- subcoercers.each do |coercer|
18
- result = coercer.call(result.value)
19
- break if result.invalid?
19
+ def call(value)
20
+ result = Result.success(value)
21
+ subcoercers.each do |coercer|
22
+ result = coercer.call(result.value)
23
+ break if result.invalid?
24
+ end
25
+ result
20
26
  end
21
- result
22
- end
23
27
 
24
- def will_affect?(value)
25
- subcoercers.any? { |sc| sc.will_affect?(value) }
26
- end
28
+ def will_affect?(value)
29
+ subcoercers.any? { |sc| sc.will_affect?(value) }
30
+ end
27
31
 
28
- class Builder
29
- attr_reader :subbuilders
32
+ #
33
+ # Builder for Coercers::Chain
34
+ #
35
+ class Builder
36
+ attr_reader :subbuilders
30
37
 
31
- def initialize(subbuilders)
32
- @subbuilders = subbuilders
33
- end
38
+ def initialize(subbuilders)
39
+ @subbuilders = subbuilders
40
+ end
34
41
 
35
- def build(schema)
36
- subcoercers = subbuilders.map do |builder|
37
- builder.build(schema)
42
+ def build(schema)
43
+ subcoercers = subbuilders.map do |builder|
44
+ builder.build(schema)
45
+ end
46
+ Chain.new(subcoercers)
38
47
  end
39
- Chain.new(subcoercers)
40
48
  end
41
49
  end
42
50
  end
43
-
44
- end
45
51
  end
@@ -1,29 +1,38 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RSchema
2
- module Coercers
4
+ module Coercers
5
+ #
6
+ # Coerces strings into `Date` objects using `Date.parse`
7
+ module Date
8
+ extend self
3
9
 
4
- module Date
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 ::Date 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 ::Date
14
- Result.success(value)
15
- when ::String
16
- date = ::Date.parse(value) rescue nil
17
- date ? Result.success(date) : Result.failure
18
- else
19
- Result.failure
22
+ def will_affect?(value)
23
+ !value.is_a?(::Date)
20
24
  end
21
- end
22
25
 
23
- def will_affect?(value)
24
- not value.is_a?(::Date)
26
+ private
27
+
28
+ def coerce_string(str)
29
+ date = begin
30
+ ::Date.parse(str)
31
+ rescue
32
+ nil
33
+ end
34
+ date ? Result.success(date) : Result.failure
35
+ end
25
36
  end
26
37
  end
27
-
28
- end
29
38
  end
@@ -1,75 +1,76 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
 
3
5
  module RSchema
4
- module Coercers
5
- module FixedHash
6
+ module Coercers
7
+ module FixedHash
8
+ # The HTTP standard says that when a form is submitted, all unchecked
9
+ # check boxes will _not_ be sent to the server. That is, they will not
10
+ # be present at all in the params hash.
11
+ #
12
+ # This class coerces these missing values into an empty array, where an
13
+ # array is expected.
14
+ class DefaultArraysToEmpty
15
+ attr_reader :hash_attributes
6
16
 
7
- # The HTTP standard says that when a form is submitted, all unchecked
8
- # check boxes will _not_ be sent to the server. That is, they will not
9
- # be present at all in the params hash.
10
- #
11
- # This class coerces these missing values into an empty array, where an array
12
- # is expected.
13
- class DefaultArraysToEmpty
14
- attr_reader :hash_attributes
17
+ def self.build(schema)
18
+ new(schema)
19
+ end
15
20
 
16
- def self.build(schema)
17
- new(schema)
18
- end
21
+ def initialize(fixed_hash_schema)
22
+ # TODO: make fixed hash attributes frozen, and eliminate dup
23
+ @hash_attributes = fixed_hash_schema.attributes.map(&:dup)
24
+ end
19
25
 
20
- def initialize(fixed_hash_schema)
21
- #TODO: make fixed hash attributes frozen, and eliminate dup
22
- @hash_attributes = fixed_hash_schema.attributes.map(&:dup)
23
- end
26
+ def call(value)
27
+ Result.success(default_arrays_to_empty(value))
28
+ end
24
29
 
25
- def call(value)
26
- Result.success(default_arrays_to_empty(value))
27
- end
30
+ def will_affect?(value)
31
+ keys_to_default(value).any?
32
+ end
28
33
 
29
- def will_affect?(value)
30
- keys_to_default(value).any?
31
- end
34
+ private
32
35
 
33
- private
34
- def default_arrays_to_empty(hash)
35
- missing_keys = keys_to_default(hash)
36
+ def default_arrays_to_empty(hash)
37
+ missing_keys = keys_to_default(hash)
36
38
 
37
- if missing_keys.any?
38
- defaults = missing_keys.map{ |k| [k, []] }.to_h
39
- hash.merge(defaults)
40
- else
41
- hash # no coercion necessary
39
+ if missing_keys.any?
40
+ defaults = missing_keys.map { |k| [k, []] }.to_h
41
+ hash.merge(defaults)
42
+ else
43
+ hash # no coercion necessary
44
+ end
42
45
  end
43
- end
44
46
 
45
- def keys_to_default(value)
46
- if value.is_a?(Hash)
47
- keys_for_array_defaulting - value.keys
48
- else
49
- []
47
+ def keys_to_default(value)
48
+ if value.is_a?(Hash)
49
+ keys_for_array_defaulting - value.keys
50
+ else
51
+ []
52
+ end
50
53
  end
51
- end
52
-
53
- def keys_for_array_defaulting
54
- @keys_for_array_defaulting ||= Set.new(
55
- hash_attributes
56
- .reject(&:optional)
57
- .select { |attr| is_array_schema?(attr.value_schema) }
58
- .map(&:key)
59
- )
60
- end
61
54
 
62
- def is_array_schema?(schema)
63
- # dig through all the coercers
64
- non_coercer = schema
65
- while non_coercer.is_a?(Schemas::Coercer)
66
- non_coercer = non_coercer.subschema
55
+ def keys_for_array_defaulting
56
+ @keys_for_array_defaulting ||= Set.new(
57
+ hash_attributes
58
+ .reject(&:optional)
59
+ .select { |attr| array_schema?(attr.value_schema) }
60
+ .map(&:key),
61
+ )
67
62
  end
68
63
 
69
- non_coercer.is_a?(Schemas::VariableLengthArray)
64
+ def array_schema?(schema)
65
+ # dig through all the coercers
66
+ non_coercer = schema
67
+ while non_coercer.is_a?(Schemas::Coercer)
68
+ non_coercer = non_coercer.subschema
69
+ end
70
+
71
+ non_coercer.is_a?(Schemas::VariableLengthArray)
72
+ end
70
73
  end
74
+ end
71
75
  end
72
-
73
- end
74
- end
75
76
  end