dry-types 0.12.3 → 0.14.1

Sign up to get free protection for your applications and to get access to all the features.
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