dry-types 0.12.3 → 0.14.1

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.
data/README.md CHANGED
@@ -1,6 +1,5 @@
1
1
  [gem]: https://rubygems.org/gems/dry-types
2
2
  [travis]: https://travis-ci.org/dry-rb/dry-types
3
- [gemnasium]: https://gemnasium.com/dry-rb/dry-types
4
3
  [codeclimate]: https://codeclimate.com/github/dry-rb/dry-types
5
4
  [coveralls]: https://coveralls.io/r/dry-rb/dry-types
6
5
  [inchpages]: http://inch-ci.org/github/dry-rb/dry-types
@@ -9,7 +8,6 @@
9
8
 
10
9
  [![Gem Version](https://badge.fury.io/rb/dry-types.svg)][gem]
11
10
  [![Build Status](https://travis-ci.org/dry-rb/dry-types.svg?branch=master)][travis]
12
- [![Dependency Status](https://gemnasium.com/dry-rb/dry-types.svg)][gemnasium]
13
11
  [![Code Climate](https://codeclimate.com/github/dry-rb/dry-types/badges/gpa.svg)][codeclimate]
14
12
  [![Test Coverage](https://codeclimate.com/github/dry-rb/dry-types/badges/coverage.svg)][codeclimate]
15
13
  [![Inline docs](http://inch-ci.org/github/dry-rb/dry-types.svg?branch=master)][inchpages]
data/Rakefile CHANGED
@@ -4,11 +4,13 @@ require "rspec/core/rake_task"
4
4
  task :run_specs do
5
5
  require 'rspec/core'
6
6
 
7
- RSpec::Core::Runner.run(['spec/dry'])
7
+ types_result = RSpec::Core::Runner.run(['spec/dry'])
8
8
  RSpec.clear_examples
9
9
 
10
10
  Dry::Types.load_extensions(:maybe)
11
- RSpec::Core::Runner.run(['spec'])
11
+ ext_result = RSpec::Core::Runner.run(['spec'])
12
+
13
+ exit [types_result, ext_result].max
12
14
  end
13
15
 
14
16
  task default: :run_specs
@@ -7,8 +7,8 @@ module SchemaBench
7
7
  def self.hash_schema(type)
8
8
  Dry::Types['hash'].public_send(type,
9
9
  email: Dry::Types['string'],
10
- age: Dry::Types['form.int'],
11
- admin: Dry::Types['form.bool'],
10
+ age: Dry::Types['params.integer'],
11
+ admin: Dry::Types['params.bool'],
12
12
  address: Dry::Types['hash'].public_send(type,
13
13
  city: Dry::Types['string'],
14
14
  street: Dry::Types['string']
data/dry-types.gemspec CHANGED
@@ -17,6 +17,9 @@ Gem::Specification.new do |spec|
17
17
  # delete this section to allow pushing this gem to any host.
18
18
  if spec.respond_to?(:metadata)
19
19
  spec.metadata['allowed_push_host'] = "https://rubygems.org"
20
+ spec.metadata['changelog_uri'] = "https://github.com/dry-rb/dry-types/blob/master/CHANGELOG.md"
21
+ spec.metadata['source_code_uri'] = "https://github.com/dry-rb/dry-types"
22
+ spec.metadata['bug_tracker_uri'] = "https://github.com/dry-rb/dry-types/issues"
20
23
  else
21
24
  raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
22
25
  end
@@ -25,17 +28,16 @@ Gem::Specification.new do |spec|
25
28
  spec.bindir = "exe"
26
29
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
30
  spec.require_paths = ["lib"]
28
- spec.required_ruby_version = ">= 2.2.0"
31
+ spec.required_ruby_version = ">= 2.3.0"
29
32
 
30
33
  spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
31
- spec.add_runtime_dependency 'dry-core', '~> 0.2', '>= 0.2.1'
34
+ spec.add_runtime_dependency 'dry-core', '~> 0.4', '>= 0.4.4'
35
+ spec.add_runtime_dependency 'dry-inflector', '~> 0.1', '>= 0.1.2'
32
36
  spec.add_runtime_dependency 'dry-container', '~> 0.3'
33
37
  spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
34
- spec.add_runtime_dependency 'dry-configurable', '~> 0.1'
35
- spec.add_runtime_dependency 'dry-logic', '~> 0.4', '>= 0.4.2'
36
- spec.add_runtime_dependency 'inflecto', '~> 0.0.0', '>= 0.0.2'
38
+ spec.add_runtime_dependency 'dry-logic', '~> 0.5', '>= 0.5'
37
39
 
38
- spec.add_development_dependency "bundler", "~> 1.6"
40
+ spec.add_development_dependency "bundler"
39
41
  spec.add_development_dependency "rake", "~> 11.0"
40
42
  spec.add_development_dependency "rspec", "~> 3.3"
41
43
  spec.add_development_dependency 'dry-monads', '~> 0.2'
@@ -34,7 +34,7 @@ module Dry
34
34
  # @return [Result,Logic::Result]
35
35
  def try(input, &block)
36
36
  if input.is_a?(::Array)
37
- result = call(input, :try)
37
+ result = call(input, :try).reject { |r| r.input.equal?(Undefined) }
38
38
  output = result.map(&:input)
39
39
 
40
40
  if result.all?(&:success?)
@@ -53,7 +53,11 @@ module Dry
53
53
  #
54
54
  # @see Definition#to_ast
55
55
  def to_ast(meta: true)
56
- [:array, [member.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]
56
+ if member.respond_to?(:to_ast)
57
+ [:array, [member.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]
58
+ else
59
+ [:array, [member, meta ? self.meta : EMPTY_HASH]]
60
+ end
57
61
  end
58
62
  end
59
63
  end
@@ -11,14 +11,12 @@ module Dry
11
11
  def of(type)
12
12
  member =
13
13
  case type
14
- when String, Class then Types[type]
14
+ when String then Types[type]
15
15
  else type
16
16
  end
17
17
 
18
18
  Array::Member.new(primitive, **options, member: member)
19
19
  end
20
-
21
- deprecate :member, :of
22
20
  end
23
21
  end
24
22
  end
@@ -31,7 +31,7 @@ module Dry
31
31
  # @raise [ConstraintError]
32
32
  # @return [Default]
33
33
  def default(input = Undefined, &block)
34
- value = input == Undefined ? block : input
34
+ value = input.equal?(Undefined) ? block : input
35
35
 
36
36
  if value.is_a?(Proc) || valid?(value)
37
37
  Default[value].new(self, value)
@@ -43,7 +43,14 @@ module Dry
43
43
  # @param [Array] values
44
44
  # @return [Enum]
45
45
  def enum(*values)
46
- Enum.new(constrained(included_in: values), values: values)
46
+ mapping =
47
+ if values.length == 1 && values[0].is_a?(::Hash)
48
+ values[0]
49
+ else
50
+ ::Hash[values.zip(values)]
51
+ end
52
+
53
+ Enum.new(constrained(included_in: mapping.keys), mapping: mapping)
47
54
  end
48
55
 
49
56
  # @return [Safe]
@@ -34,6 +34,11 @@ module Dry
34
34
  # Build a type which values are instances of a given class
35
35
  # Values are checked using `is_a?` call
36
36
  #
37
+ # @example
38
+ # Types::Error = Types.Instance(StandardError)
39
+ # Types::Error = Types.Strict(StandardError)
40
+ # Types.Strict(Integer) == Types::Strict::Int # => true
41
+ #
37
42
  # @param [Class,Module] klass Class or module
38
43
  #
39
44
  # @return [Dry::Types::Type]
@@ -41,6 +46,7 @@ module Dry
41
46
  def Instance(klass)
42
47
  Definition.new(klass).constrained(type: klass)
43
48
  end
49
+ alias_method :Strict, :Instance
44
50
 
45
51
  # Build a type with a single value
46
52
  # The equality check done with `eql?`
@@ -77,7 +83,7 @@ module Dry
77
83
  Definition.new(klass).constructor(cons || block || klass.method(:new))
78
84
  end
79
85
 
80
- # Build a definiton type
86
+ # Build a definition type
81
87
  #
82
88
  # @param [Class] klass
83
89
  #
@@ -86,6 +92,21 @@ module Dry
86
92
  def Definition(klass)
87
93
  Definition.new(klass)
88
94
  end
95
+
96
+ # Build a map type
97
+ #
98
+ # @example
99
+ # Types::IntMap = Types.Map(Types::Strict::Integer, 'any')
100
+ # Types::IntStringMap = Types.Map(Types::Strict::Integer, Types::Strict::String)
101
+ #
102
+ # @param [Type] key_type Key type
103
+ # @param [Type] value_type Value type
104
+ #
105
+ # @return [Dry::Types::Map]
106
+ # @api public
107
+ def Map(key_type, value_type)
108
+ Types['hash'].map(key_type, value_type)
109
+ end
89
110
  end
90
111
  end
91
112
  end
@@ -4,7 +4,7 @@ require 'bigdecimal/util'
4
4
  module Dry
5
5
  module Types
6
6
  module Coercions
7
- module Form
7
+ module Params
8
8
  TRUE_VALUES = %w[1 on On ON t true True TRUE T y yes Yes YES Y].freeze
9
9
  FALSE_VALUES = %w[0 off Off OFF f false False FALSE F n no No NO N].freeze
10
10
  BOOLEAN_MAP = ::Hash[TRUE_VALUES.product([true]) + FALSE_VALUES.product([false])].freeze
@@ -0,0 +1,27 @@
1
+ require 'dry/core/deprecations'
2
+
3
+ Dry::Core::Deprecations.warn('Form types were renamed to Params', tag: :'dry-types')
4
+
5
+ module Dry
6
+ module Types
7
+ container.keys.grep(/^params\./).each do |key|
8
+ next if key.start_with?('params.int')
9
+ register(key.sub('params.', 'form.'), container[key])
10
+ end
11
+
12
+ register('form.int', self['params.integer'])
13
+ register('form.integer', self['params.integer'])
14
+
15
+ class Compiler
16
+ def visit_form_hash(node)
17
+ schema, meta = node
18
+ merge_with('params.hash', :symbolized, schema).meta(meta)
19
+ end
20
+
21
+ def visit_form_array(node)
22
+ member, meta = node
23
+ registry['params.array'].of(visit(member)).meta(meta)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,14 @@
1
+ require 'dry/core/deprecations'
2
+
3
+ Dry::Core::Deprecations.warn('Int type was renamed to Integer', tag: :'dry-types')
4
+
5
+ module Dry
6
+ module Types
7
+ register('int', self['integer'])
8
+ register('strict.int', self['strict.integer'])
9
+ register('coercible.int', self['coercible.integer'])
10
+ register('optional.strict.int', self['optional.strict.integer'])
11
+ register('optional.coercible.int', self['optional.coercible.integer'])
12
+ register('params.int', self['params.integer'])
13
+ end
14
+ end
@@ -0,0 +1,2 @@
1
+ require 'dry/types/compat/int'
2
+ require 'dry/types/compat/form_types'
@@ -54,7 +54,8 @@ module Dry
54
54
 
55
55
  def visit_array(node)
56
56
  member, meta = node
57
- registry['array'].of(visit(member)).meta(meta)
57
+ member = member.is_a?(Class) ? member : visit(member)
58
+ registry['array'].of(member).meta(meta)
58
59
  end
59
60
 
60
61
  def visit_hash(node)
@@ -62,6 +63,11 @@ module Dry
62
63
  merge_with('hash', constructor, schema).meta(meta)
63
64
  end
64
65
 
66
+ def visit_hash_schema(node)
67
+ schema, meta = node
68
+ merge_with_schema('hash', schema).meta(meta)
69
+ end
70
+
65
71
  def visit_json_hash(node)
66
72
  schema, meta = node
67
73
  merge_with('json.hash', :symbolized, schema).meta(meta)
@@ -72,14 +78,14 @@ module Dry
72
78
  registry['json.array'].of(visit(member)).meta(meta)
73
79
  end
74
80
 
75
- def visit_form_hash(node)
81
+ def visit_params_hash(node)
76
82
  schema, meta = node
77
- merge_with('form.hash', :symbolized, schema).meta(meta)
83
+ merge_with('params.hash', :symbolized, schema).meta(meta)
78
84
  end
79
85
 
80
- def visit_form_array(node)
86
+ def visit_params_array(node)
81
87
  member, meta = node
82
- registry['form.array'].of(visit(member)).meta(meta)
88
+ registry['params.array'].of(visit(member)).meta(meta)
83
89
  end
84
90
 
85
91
  def visit_member(node)
@@ -87,9 +93,26 @@ module Dry
87
93
  { name => visit(type) }
88
94
  end
89
95
 
96
+ def visit_enum(node)
97
+ type, mapping, meta = node
98
+ Enum.new(visit(type), mapping: mapping, meta: meta)
99
+ end
100
+
101
+ def visit_map(node)
102
+ key_type, value_type, meta = node
103
+ registry['hash'].map(visit(key_type), visit(value_type)).meta(meta)
104
+ end
105
+
90
106
  def merge_with(hash_id, constructor, schema)
91
- registry[hash_id].__send__(
92
- constructor, schema.map { |key| visit(key) }.reduce({}, :update)
107
+ registry[hash_id].schema(
108
+ schema.map { |key| visit(key) }.reduce({}, :update),
109
+ constructor
110
+ )
111
+ end
112
+
113
+ def merge_with_schema(hash_id, schema)
114
+ registry[hash_id].instantiate(
115
+ schema.map { |key| visit(key) }.reduce({}, :update)
93
116
  )
94
117
  end
95
118
  end
@@ -1,4 +1,4 @@
1
- require 'dry/types/decorator'
1
+ require 'dry/types/fn_container'
2
2
 
3
3
  module Dry
4
4
  module Types
@@ -16,15 +16,15 @@ module Dry
16
16
  # @param [Builder, Object] input
17
17
  # @param [Hash] options
18
18
  # @param [#call, nil] block
19
- def self.new(input, options = {}, &block)
19
+ def self.new(input, **options, &block)
20
20
  type = input.is_a?(Builder) ? input : Definition.new(input)
21
- super(type, options, &block)
21
+ super(type, **options, &block)
22
22
  end
23
23
 
24
24
  # @param [Type] type
25
25
  # @param [Hash] options
26
26
  # @param [#call, nil] block
27
- def initialize(type, options = {}, &block)
27
+ def initialize(type, **options, &block)
28
28
  @type = type
29
29
  @fn = options.fetch(:fn, block)
30
30
 
@@ -43,6 +43,10 @@ module Dry
43
43
  type.name
44
44
  end
45
45
 
46
+ def default?
47
+ type.default?
48
+ end
49
+
46
50
  # @param [Object] input
47
51
  # @return [Object]
48
52
  def call(input)
@@ -74,7 +78,11 @@ module Dry
74
78
  # @param [Object] value
75
79
  # @return [Boolean]
76
80
  def valid?(value)
77
- super && type.valid?(value)
81
+ constructed_value = fn[value]
82
+ rescue NoMethodError, TypeError, ArgumentError
83
+ false
84
+ else
85
+ type.valid?(constructed_value)
78
86
  end
79
87
  alias_method :===, :valid?
80
88
 
@@ -4,7 +4,7 @@ module Dry
4
4
  module Types
5
5
  COERCIBLE = {
6
6
  string: String,
7
- int: Integer,
7
+ integer: Integer,
8
8
  float: Float,
9
9
  decimal: BigDecimal,
10
10
  array: ::Array,
@@ -19,7 +19,8 @@ module Dry
19
19
  false: FalseClass,
20
20
  date: Date,
21
21
  date_time: DateTime,
22
- time: Time
22
+ time: Time,
23
+ range: Range
23
24
  }.freeze
24
25
 
25
26
  ALL_PRIMITIVES = COERCIBLE.merge(NON_COERCIBLE).freeze
@@ -61,5 +62,5 @@ module Dry
61
62
  end
62
63
 
63
64
  require 'dry/types/coercions'
64
- require 'dry/types/form'
65
+ require 'dry/types/params'
65
66
  require 'dry/types/json'
@@ -39,6 +39,11 @@ module Dry
39
39
  type.constrained?
40
40
  end
41
41
 
42
+ # @return [Sum]
43
+ def optional
44
+ Types['strict.nil'] | self
45
+ end
46
+
42
47
  # @param [Symbol] meth
43
48
  # @param [Boolean] include_private
44
49
  # @return [Boolean]
@@ -63,7 +68,7 @@ module Dry
63
68
  response = type.__send__(meth, *args, &block)
64
69
 
65
70
  if decorate?(response)
66
- self.class.new(response, options)
71
+ __new__(response)
67
72
  else
68
73
  response
69
74
  end
@@ -71,6 +76,11 @@ module Dry
71
76
  super
72
77
  end
73
78
  end
79
+
80
+ # Replace underlying type
81
+ def __new__(type)
82
+ self.class.new(type, options)
83
+ end
74
84
  end
75
85
  end
76
86
  end
@@ -35,7 +35,7 @@ module Dry
35
35
 
36
36
  # @param [Type] type
37
37
  # @param [Object] value
38
- def initialize(type, value, *)
38
+ def initialize(type, value, **options)
39
39
  super
40
40
  @value = value
41
41
  end
@@ -57,10 +57,14 @@ module Dry
57
57
  success(call(input))
58
58
  end
59
59
 
60
+ def valid?(value = Undefined)
61
+ value.equal?(Undefined) || super
62
+ end
63
+
60
64
  # @param [Object] input
61
65
  # @return [Object] value passed through {#type} or {#default} value
62
- def call(input)
63
- if input.nil?
66
+ def call(input = Undefined)
67
+ if input.equal?(Undefined)
64
68
  evaluate
65
69
  else
66
70
  output = type[input]
@@ -68,6 +72,13 @@ module Dry
68
72
  end
69
73
  end
70
74
  alias_method :[], :call
75
+
76
+ private
77
+
78
+ # Replace underlying type
79
+ def __new__(type)
80
+ self.class.new(type, value, options)
81
+ end
71
82
  end
72
83
  end
73
84
  end
@@ -6,12 +6,9 @@ module Dry
6
6
  module Types
7
7
  class Definition
8
8
  include Type
9
- include Dry::Equalizer(:primitive, :options, :meta)
10
9
  include Options
11
10
  include Builder
12
-
13
- # @return [Hash]
14
- attr_reader :options
11
+ include Dry::Equalizer(:primitive, :options, :meta)
15
12
 
16
13
  # @return [Class]
17
14
  attr_reader :primitive
@@ -30,7 +27,7 @@ module Dry
30
27
 
31
28
  # @param [Type,Class] primitive
32
29
  # @param [Hash] options
33
- def initialize(primitive, options = {})
30
+ def initialize(primitive, **options)
34
31
  super
35
32
  @primitive = primitive
36
33
  freeze
@@ -84,20 +81,12 @@ module Dry
84
81
  Result::Success.new(input)
85
82
  end
86
83
 
87
-
88
84
  # @param (see Failure#initialize)
89
85
  # @return [Result::Failure]
90
86
  def failure(input, error)
91
87
  Result::Failure.new(input, error)
92
88
  end
93
89
 
94
- # @param [Object] klass class of the result instance
95
- # @param [Array] args arguments for the +klass#initialize+ method
96
- # @return [Object] new instance of the given +klass+
97
- def result(klass, *args)
98
- klass.new(*args)
99
- end
100
-
101
90
  # Checks whether value is of a #primitive class
102
91
  # @param [Object] value
103
92
  # @return [Boolean]
@@ -121,3 +110,4 @@ end
121
110
 
122
111
  require 'dry/types/array'
123
112
  require 'dry/types/hash'
113
+ require 'dry/types/map'
@@ -4,7 +4,7 @@ module Dry
4
4
  module Types
5
5
  class Enum
6
6
  include Type
7
- include Dry::Equalizer(:type, :options, :values)
7
+ include Dry::Equalizer(:type, :options, :mapping)
8
8
  include Decorator
9
9
 
10
10
  # @return [Array]
@@ -13,40 +13,68 @@ module Dry
13
13
  # @return [Hash]
14
14
  attr_reader :mapping
15
15
 
16
+ # @return [Hash]
17
+ attr_reader :inverted_mapping
18
+
16
19
  # @param [Type] type
17
20
  # @param [Hash] options
18
21
  # @option options [Array] :values
19
22
  def initialize(type, options)
20
23
  super
21
- @values = options.fetch(:values).freeze
22
- @values.each(&:freeze)
23
- @mapping = values.each_with_object({}) { |v, h| h[values.index(v)] = v }.freeze
24
+ @mapping = options.fetch(:mapping).freeze
25
+ @values = @mapping.keys.freeze
26
+ @inverted_mapping = @mapping.invert.freeze
27
+ freeze
24
28
  end
25
29
 
26
30
  # @param [Object] input
27
31
  # @return [Object]
28
- def call(input)
29
- value =
30
- if values.include?(input)
31
- input
32
- elsif mapping.key?(input)
33
- mapping[input]
34
- end
35
-
36
- type[value || input]
32
+ def call(input = Undefined)
33
+ type[map_value(input)]
37
34
  end
38
35
  alias_method :[], :call
39
36
 
37
+ # @param [Object] input
38
+ # @yieldparam [Failure] failure
39
+ # @yieldreturn [Result]
40
+ # @return [Logic::Result]
41
+ # @return [Object] if coercion fails and a block is given
42
+ def try(input)
43
+ super(map_value(input))
44
+ end
45
+
40
46
  def default(*)
41
47
  raise '.enum(*values).default(value) is not supported. Call '\
42
48
  '.default(value).enum(*values) instead'
43
49
  end
44
50
 
51
+ # Check whether a value is in the enum
52
+ alias_method :include?, :valid?
53
+
45
54
  # @api public
46
55
  #
47
56
  # @see Definition#to_ast
48
57
  def to_ast(meta: true)
49
- [:enum, [type.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]
58
+ [:enum, [type.to_ast(meta: meta),
59
+ mapping,
60
+ meta ? self.meta : EMPTY_HASH]]
61
+ end
62
+
63
+ private
64
+
65
+ # Maps a value
66
+ #
67
+ # @params [Object]
68
+ # @return [Object]
69
+ # @api private
70
+ def map_value(input)
71
+ if input.equal?(Undefined)
72
+ type.call
73
+ elsif mapping.key?(input)
74
+ input
75
+ else
76
+ inverted_mapping.fetch(input, input)
77
+ end
50
78
  end
51
79
  end
52
80
  end
@@ -1,10 +1,12 @@
1
1
  module Dry
2
2
  module Types
3
- extend Dry::Configurable
3
+ extend Dry::Core::ClassAttributes
4
4
 
5
5
  # @!attribute [r] namespace
6
6
  # @return [Container{String => Definition}]
7
- setting :namespace, self
7
+ defines :namespace
8
+
9
+ namespace self
8
10
 
9
11
  class SchemaError < TypeError
10
12
  # @param [String,Symbol] key
@@ -15,6 +17,8 @@ module Dry
15
17
  end
16
18
  end
17
19
 
20
+ MapError = Class.new(TypeError)
21
+
18
22
  SchemaKeyError = Class.new(KeyError)
19
23
  private_constant(:SchemaKeyError)
20
24