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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Dir.glob(File.join(__dir__, 'schemas/**/*.rb')).each do |path|
2
4
  require path
3
5
  end
@@ -1,29 +1,28 @@
1
- module RSchema
2
- module Schemas
3
-
4
- #
5
- # A schema that matches literally any value
6
- #
7
- # @example The anything schema
8
- # schema = RSchema.define { anything }
9
- # schema.valid?(nil) #=> true
10
- # schema.valid?(6.2) #=> true
11
- # schema.valid?({ hello: Time.now }) #=> true
12
- #
13
- class Anything
1
+ # frozen_string_literal: true
14
2
 
15
- def self.instance
16
- @instance ||= new
17
- end
3
+ module RSchema
4
+ module Schemas
5
+ #
6
+ # A schema that matches literally any value
7
+ #
8
+ # @example The anything schema
9
+ # schema = RSchema.define { anything }
10
+ # schema.valid?(nil) #=> true
11
+ # schema.valid?(6.2) #=> true
12
+ # schema.valid?({ hello: Time.now }) #=> true
13
+ #
14
+ class Anything
15
+ def self.instance
16
+ @instance ||= new
17
+ end
18
18
 
19
- def call(value, options)
20
- Result.success(value)
21
- end
19
+ def call(value, _options)
20
+ Result.success(value)
21
+ end
22
22
 
23
- def with_wrapped_subschemas(wrapper)
24
- self
23
+ def with_wrapped_subschemas(_wrapper)
24
+ self
25
+ end
26
+ end
25
27
  end
26
-
27
- end
28
- end
29
28
  end
@@ -1,36 +1,42 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RSchema
2
- module Schemas
4
+ module Schemas
5
+ #
6
+ # A schema that matches only `true` and `false`
7
+ #
8
+ # @example The boolean schema
9
+ # schema = RSchema.define { boolean }
10
+ # schema.valid?(true) #=> true
11
+ # schema.valid?(false) #=> true
12
+ # schema.valid?(nil) #=> false
13
+ #
14
+ class Boolean
15
+ def self.instance
16
+ @instance ||= new
17
+ end
3
18
 
4
- #
5
- # A schema that matches only `true` and `false`
6
- #
7
- # @example The boolean schema
8
- # schema = RSchema.define { boolean }
9
- # schema.valid?(true) #=> true
10
- # schema.valid?(false) #=> true
11
- # schema.valid?(nil) #=> false
12
- #
13
- class Boolean
14
- def self.instance
15
- @instance ||= new
16
- end
19
+ def call(value, _options)
20
+ if value.equal?(true) || value.equal?(false)
21
+ Result.success(value)
22
+ else
23
+ Result.failure(error(value))
24
+ end
25
+ end
17
26
 
18
- def call(value, options)
19
- if value.equal?(true) || value.equal?(false)
20
- Result.success(value)
21
- else
22
- Result.failure(Error.new(
23
- schema: self,
24
- value: value,
25
- symbolic_name: :not_a_boolean,
26
- ))
27
- end
28
- end
27
+ def with_wrapped_subschemas(_wrapper)
28
+ self
29
+ end
29
30
 
30
- def with_wrapped_subschemas(wrapper)
31
- self
32
- end
31
+ private
33
32
 
34
- end
35
- end
33
+ def error(value)
34
+ Error.new(
35
+ schema: self,
36
+ value: value,
37
+ symbolic_name: :not_a_boolean,
38
+ )
39
+ end
40
+ end
41
+ end
36
42
  end
@@ -1,50 +1,51 @@
1
- module RSchema
2
- module Schemas
3
-
4
- #
5
- # A schema that applies a coercer to a value, before passing the coerced
6
- # value to a subschema.
7
- #
8
- # This is not a type of schema that you would typically create yourself.
9
- # It is used internally to implement RSchema's coercion functionality.
10
- #
11
- class Coercer
12
- attr_reader :coercer, :subschema
13
-
14
- def initialize(coercer, subschema)
15
- byebug if coercer.is_a?(Array)
16
- @coercer = coercer
17
- @subschema = subschema
18
- end
1
+ # frozen_string_literal: true
19
2
 
20
- def call(value, options)
21
- unless coercer.will_affect?(value)
22
- # short-circuit the coercer
23
- return @subschema.call(value, options)
24
- end
25
-
26
- result = coercer.call(value)
27
- if result.valid?
28
- @subschema.call(result.value, options)
29
- else
30
- failure(value, result.error)
3
+ module RSchema
4
+ module Schemas
5
+ #
6
+ # A schema that applies a coercer to a value, before passing the coerced
7
+ # value to a subschema.
8
+ #
9
+ # This is not a type of schema that you would typically create yourself.
10
+ # It is used internally to implement RSchema's coercion functionality.
11
+ #
12
+ class Coercer
13
+ attr_reader :coercer, :subschema
14
+
15
+ def initialize(coercer, subschema)
16
+ @coercer = coercer
17
+ @subschema = subschema
18
+ end
19
+
20
+ def call(value, options)
21
+ unless coercer.will_affect?(value)
22
+ # short-circuit the coercer
23
+ return @subschema.call(value, options)
24
+ end
25
+
26
+ result = coercer.call(value)
27
+ if result.valid?
28
+ @subschema.call(result.value, options)
29
+ else
30
+ failure(value, result.error)
31
+ end
32
+ end
33
+
34
+ def with_wrapped_subschemas(wrapper)
35
+ self.class.new(coercer, wrapper.wrap(subschema))
36
+ end
37
+
38
+ private
39
+
40
+ def failure(value, name)
41
+ Result.failure(
42
+ Error.new(
43
+ schema: self,
44
+ value: value,
45
+ symbolic_name: name || :coercion_failure,
46
+ ),
47
+ )
48
+ end
31
49
  end
32
50
  end
33
-
34
- def with_wrapped_subschemas(wrapper)
35
- self.class.new(coercer, wrapper.wrap(subschema))
36
- end
37
-
38
- private
39
-
40
- def failure(value, name)
41
- return Result.failure(Error.new(
42
- schema: self,
43
- value: value,
44
- symbolic_name: name || :coercion_failure,
45
- ))
46
- end
47
-
48
- end
49
- end
50
51
  end
@@ -1,138 +1,137 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'delegate'
2
4
 
3
5
  module RSchema
4
- module Schemas
6
+ module Schemas
7
+ #
8
+ # A wrapper that provides convenience methods for schema objects.
9
+ #
10
+ # Because this class inherits from `SimpleDelegator`, convenience wrappers
11
+ # behave like their underlying schemas. That is, you can call methods on the
12
+ # underlying schema object through the convenience wrapper.
13
+ #
14
+ # Schema objects only need to implement the `call` method to validate
15
+ # values. This small interface is simple for schema classes to implement,
16
+ # but not very descriptive when actually using the schema objects. So, to
17
+ # make schema objects nicer to use, this class provides a variety of
18
+ # more-descriptive methods like {#validate}, {#validate!}, {#valid?}, and
19
+ # {#invalid?}.
20
+ #
21
+ class Convenience < SimpleDelegator
22
+ def initialize(underlying_schema)
23
+ super
24
+ end
5
25
 
6
- #
7
- # A wrapper that provides convenience methods for schema objects.
8
- #
9
- # Because this class inherits from `SimpleDelegator`, convenience wrappers
10
- # behave like their underlying schemas. That is, you can call methods on the
11
- # underlying schema object through the convenience wrapper.
12
- #
13
- # Schema objects only need to implement the `call` method to validate values.
14
- # This small interface is simple for schema classes to implement, but not
15
- # very descriptive when actually using the schema objects. So, to make
16
- # schema objects nicer to use, this class provides a variety of
17
- # more-descriptive methods like {#validate}, {#validate!}, {#valid?}, and
18
- # {#invalid?}.
19
- #
20
- class Convenience < SimpleDelegator
21
- def initialize(underlying_schema)
22
- super
23
- end
26
+ # @return [schema] the underlying schema object
27
+ def underlying_schema
28
+ __getobj__
29
+ end
24
30
 
25
- # @return [schema] the underlying schema object
26
- def underlying_schema
27
- __getobj__
28
- end
31
+ #
32
+ # Applies the schema to a value
33
+ #
34
+ # This is that same as the `call` method available on all schema objects,
35
+ # except that the `options` param is optional.
36
+ #
37
+ # @param value [Object] The value to validate
38
+ # @param options [RSchema::Options]
39
+ #
40
+ # @return [RSchema::Result]
41
+ #
42
+ def validate(value, options = Options.default)
43
+ call(value, options)
44
+ end
29
45
 
30
- #
31
- # Applies the schema to a value
32
- #
33
- # This is that same as the `call` method available on all schema objects,
34
- # except that the `options` param is optional.
35
- #
36
- # @param value [Object] The value to validate
37
- # @param options [RSchema::Options]
38
- #
39
- # @return [RSchema::Result]
40
- #
41
- def validate(value, options=Options.default)
42
- call(value, options)
43
- end
46
+ #
47
+ # Returns the validation error for the given value
48
+ #
49
+ # @param value [Object] The value to validate
50
+ # @param options [RSchema::Options]
51
+ #
52
+ # @return The error object if `value` is invalid, otherwise `nil`.
53
+ #
54
+ # @see Result#error
55
+ #
56
+ def error_for(value, options = Options.default)
57
+ result = underlying_schema.call(value, options)
58
+ if result.valid?
59
+ nil
60
+ else
61
+ result.error
62
+ end
63
+ end
44
64
 
45
- #
46
- # Returns the validation error for the given value
47
- #
48
- # @param value [Object] The value to validate
49
- # @param options [RSchema::Options]
50
- #
51
- # @return The error object if `value` is invalid, otherwise `nil`.
52
- #
53
- # @see Result#error
54
- #
55
- def error_for(value, options=Options.default)
56
- result = underlying_schema.call(value, options)
57
- if result.valid?
58
- nil
59
- else
60
- result.error
61
- end
62
- end
65
+ #
66
+ # Applies the schema to a value, raising an exception if the value is
67
+ # invalid
68
+ #
69
+ # @param value [Object] The value to validate
70
+ # @param options [RSchema::Options]
71
+ #
72
+ # @raise [RSchema::Invalid] If the value is not valid
73
+ # @return [Object] The validated value
74
+ #
75
+ # @see Result#value
76
+ #
77
+ def validate!(value, options = Options.default)
78
+ result = underlying_schema.call(value, options)
79
+ if result.valid?
80
+ result.value
81
+ else
82
+ raise RSchema::Invalid, result.error
83
+ end
84
+ end
63
85
 
64
- #
65
- # Applies the schema to a value, raising an exception if the value is invalid
66
- #
67
- # @param value [Object] The value to validate
68
- # @param options [RSchema::Options]
69
- #
70
- # @raise [RSchema::Invalid] If the value is not valid
71
- # @return [Object] The validated value
72
- #
73
- # @see Result#value
74
- #
75
- def validate!(value, options=Options.default)
76
- result = underlying_schema.call(value, options)
77
- if result.valid?
78
- result.value
79
- else
80
- raise RSchema::Invalid.new(result.error)
81
- end
82
- end
86
+ #
87
+ # Checks whether a value is valid or not
88
+ #
89
+ # @param value [Object] The value to validate
90
+ # @return [Boolean] `true` if the value is valid, otherwise `false`
91
+ #
92
+ def valid?(value)
93
+ result = underlying_schema.call(value, Options.fail_fast)
94
+ result.valid?
95
+ end
83
96
 
84
- #
85
- # Checks whether a value is valid or not
86
- #
87
- # @param value [Object] The value to validate
88
- # @return [Boolean] `true` if the value is valid, otherwise `false`
89
- #
90
- def valid?(value)
91
- result = underlying_schema.call(value, Options.fail_fast)
92
- result.valid?
93
- end
97
+ #
98
+ # The opposite of {#valid?}
99
+ #
100
+ # @see #valid?
101
+ #
102
+ def invalid?(value)
103
+ !valid?(value)
104
+ end
94
105
 
95
- #
96
- # The opposite of {#valid?}
97
- #
98
- # @see #valid?
99
- #
100
- def invalid?(value)
101
- not valid?(value)
102
- end
106
+ #
107
+ # Wraps the given schema in a {Convenience}, if it isn't already wrapped.
108
+ #
109
+ # @param schema [schema] The schema to wrap
110
+ # @return {Convenience}
111
+ #
112
+ def self.wrap(schema)
113
+ if schema.is_a?(self)
114
+ schema
115
+ else
116
+ new(schema)
117
+ end
118
+ end
103
119
 
104
- #
105
- # Wraps the given schema in a {Convenience}, if it isn't already wrapped.
106
- #
107
- # @param schema [schema] The schema to wrap
108
- # @return {Convenience}
109
- #
110
- def self.wrap(schema)
111
- if schema.is_a?(self)
112
- schema
113
- else
114
- new(schema)
115
- end
116
- end
120
+ #
121
+ # Removes any {Convenience} wrappers from a schema
122
+ #
123
+ # @param schema [schema] The schema to unwrap
124
+ # @return [schema] The underlying schema, with all {Convenience} wrappers
125
+ # removed
126
+ def self.unwrap(schema)
127
+ schema = schema.underlying_schema while schema.is_a?(self)
128
+ schema
129
+ end
117
130
 
118
- #
119
- # Removes any {Convenience} wrappers from a schema
120
- #
121
- # @param schema [schema] The schema to unwrap
122
- # @return [schema] The underlying schema, with all {Convenience} wrappers
123
- # removed
124
- def self.unwrap(schema)
125
- while schema.is_a?(self)
126
- schema = schema.underlying_schema
131
+ # @!visibility private
132
+ def with_wrapped_subschemas(wrapper)
133
+ self.class.new(wrapper.wrap(underlying_schema))
134
+ end
127
135
  end
128
- schema
129
- end
130
-
131
- # @!visibility private
132
- def with_wrapped_subschemas(wrapper)
133
- self.class.new(wrapper.wrap(underlying_schema))
134
136
  end
135
-
136
- end
137
- end
138
137
  end