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