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