dry-types 0.12.3 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a8c05eebb29de4255fa90d96c51bc4050c76272d59ba3f71a992785fdaefcadf
4
- data.tar.gz: f8c457dd897b0dceaa696751b787bc607f355553c53be88a94cd0f05f8e063ae
3
+ metadata.gz: a75136625920c87b6bc3a3086ce8e057b281e26dbde7ad783f00e905e68d4209
4
+ data.tar.gz: fb425d0891a89dd65f05527664edcaca3511a51627cc84f9872c2b01e3ffa585
5
5
  SHA512:
6
- metadata.gz: bc10a547c544015cc3b384db0362df51f5a72520de44ea3594507f56a7513bb2e925061d3bf62e1e3d87bf85652f3df2ffec893ab6d30bc9c1ae7da7070e613c
7
- data.tar.gz: 40cbc9bf37b49e0b1c8dd3738ef11a787ae2bf8a759c3511f17e9e8df17a834990fb1087fe53081f1f0e418fd685f25cc9a53023cd1f081c3cf243179ac5f5b9
6
+ metadata.gz: 893b870ed93db86a97184ff07873df4d58e3379b3a561e6f3962c2aa656c52e4cbe856d9f5f401da0d85da86b5a78880e1173a8c78fcbf4ff2c86b70417a9313
7
+ data.tar.gz: 05db55e6352c55dc2a8357f4062f0bef409babe4a7702ab1e8a6c4d3a1d12e90a7be7b0865bf197ce6ede56abeb0c59b22a580428b6cf10ade5148ee3c763a81
@@ -3,15 +3,18 @@ dist: trusty
3
3
  sudo: required
4
4
  cache: bundler
5
5
  bundler_args: --without benchmarks tools
6
- after_success:
7
- - '[ -d coverage ] && bundle exec codeclimate-test-reporter'
8
6
  script:
9
7
  - bundle exec rake
8
+ before_install:
9
+ - gem update --system
10
+ after_success:
11
+ - '[ -d coverage ] && bundle exec codeclimate-test-reporter'
10
12
  rvm:
11
- - 2.2.7
12
- - 2.3.4
13
- - 2.4.1
14
- - jruby-9.1.10.0
13
+ - 2.2.9
14
+ - 2.3.6
15
+ - 2.4.3
16
+ - 2.5.0
17
+ - jruby-9.1.15.0
15
18
  env:
16
19
  global:
17
20
  - COVERAGE=true
@@ -1,10 +1,99 @@
1
- # v0.12.3 2018-06-12
1
+ # v0.13.0 2018-03-03
2
+
3
+ ## Changed
4
+
5
+ * [BREAKING] Renamed `Types::Form` to `Types::Params`. You can opt-in the former name with `require 'dry/types/compat/form_types'`. It will be dropped in the next release (ndrluis)
6
+ * [BREAKING] The `Int` types was renamed to `Integer`, this was the only type named differently from the standard Ruby classes so it has been made consistent. The former name is available with `require 'dry/types/compat/int'` (GustavoCaso + flash-gordon)
7
+ * [BREAKING] Default types are not evaluated on `nil`. Default values are evaluated _only_ if no value were given.
8
+ ```ruby
9
+ type = Types::Strict::String.default("hello")
10
+ type[nil] # => constraint error
11
+ type[] # => "hello"
12
+ ```
13
+ This change allowed to greatly simplify hash schemas, make them a lot more flexible yet predictable (see below).
14
+ * [BREAKING] `Dry::Types.register_class` was removed, `Dry::Types.register` was made private API, do not register your types in the global `dry-types` container, use a module instead, e.g. `Types` (flash-gordon)
15
+ * [BREAKING] Enum types don't accept value index anymore. Instead, explicit mapping is supported, see below (flash-gordon)
16
+
17
+
18
+ ## Added
19
+
20
+ * Hash schemas were rewritten. The old API is still around but is going to be deprecated and removed before 1.0. The new API is simpler and more flexible. Instead of having a bunch of predefined schemas you can build your own by combining the following methods:
21
+
22
+ 1. `Schema#with_key_transform`—transforms keys of input hashes, for things like symbolizing etc.
23
+ 2. `Schema#strict`—makes a schema intolerant to unknown keys.
24
+ 3. `Hash#with_type_transform`—transforms member types with an arbitrary block. For instance,
25
+
26
+ ```ruby
27
+ optional_keys = Types::Hash.with_type_transform { |t, _key| t.optional }
28
+ schema = optional_keys.schema(name: 'strict.string', age: 'strict.int')
29
+ schema.(name: "Jane", age: nil) # => {name: "Jane", age: nil}
30
+ ```
31
+
32
+ Note that by default all keys are required, if a key is expected to absent, add to the corresponding type's meta `omittable: true`:
33
+
34
+ ```ruby
35
+ intolerant = Types::Hash.schema(name: Types::Strict::String)
36
+ intolerant[{}] # => Dry::Types::MissingKeyError
37
+ tolerant = Types::Hash.schema(name: Types::Strict::String.meta(omittable: true))
38
+ tolerant[{}] # => {}
39
+ tolerant_with_default = Types::Hash.schema(name: Types::Strict::String.meta(omittable: true).default("John"))
40
+ tolerant[{}] # => {name: "John"}
41
+ ```
42
+
43
+ The new API is composable in a natural way:
44
+
45
+ ```ruby
46
+ TOLERANT = Types::Hash.with_type_transform { |t| t.meta(omittable: true) }.freeze
47
+ user = TOLERANT.schema(name: 'strict.string', age: 'strict.int')
48
+ user.(name: "Jane") # => {name: "Jane"}
49
+
50
+ TOLERANT_SYMBOLIZED = TOLERANT.with_key_transform(&:to_sym)
51
+ user_sym = TOLERANT_SYMBOLIZED.schema(name: 'strict.string', age: 'strict.int')
52
+ user_sym.("name" => "Jane") # => {name: "Jane"}
53
+ ```
54
+
55
+ (flash-gordon)
56
+
57
+ * `Types.Strict` is an alias for `Types.Instance` (flash-gordon)
58
+ ```ruby
59
+ strict_range = Types.Strict(Range)
60
+ strict_range == Types.Instance(Range) # => true
61
+ ```
62
+ * `Enum#include?` is an alias to `Enum#valud?` (d-Pixie + flash-gordon)
63
+ * `Range` was added (GustavoCaso)
64
+ * `Array` types filter out `Undefined` values, if you have an array type with a constructor type as its member, the constructor now can return `Dry::Types::Undefined` to indicate empty value:
65
+ ```ruby
66
+ filter_empty_strings = Types::Strict::Array.of(
67
+ Types::Strict::String.constructor { |input|
68
+ input.to_s.yield_self { |s| s.empty? ? Dry::Types::Undefined : s }
69
+ }
70
+ )
71
+ filter_empty_strings.(["John", nil, "", "Jane"]) # => ["John", "Jane"]
72
+ ```
73
+ * `Types::Map` was added for homogeneous hashes, when only types of keys and values are known in advance, not specific key names (fledman + flash-gordon)
74
+ ```ruby
75
+ int_to_string = Types::Hash.map('strict.integer', 'strict.string')
76
+ int_to_string[0 => 'foo'] # => { 0 => "foo" }
77
+ int_to_string[0 => 1] # Dry::Types::MapError: input value 1 for key 0 is invalid: type?(String, 1)
78
+ ```
79
+ * Enum supports mappings (bolshakov + flash-gordon)
80
+ ```ruby
81
+ dict = Types::Strict::String.enum('draft' => 0, 'published' => 10, 'archived' => 20)
82
+ dict['published'] # => 'published'
83
+ dict[10] # => 'published'
84
+ ```
2
85
 
3
86
  ## Fixed
4
87
 
5
- * Fixed `constrained?` for constructor types (solnic)
88
+ * Fixed applying constraints to optional type, i.e. `.optional.constrained` works correctly (flash-gordon)
89
+ * Fixed enum working with optionals (flash-gordon)
90
+
91
+ ## Internal
92
+
93
+ * Dropped the `dry-configurable` dependency (GustavoCaso)
94
+ * The gem now uses `dry-inflector` for inflections instead of `inflecto` (GustavoCaso)
6
95
 
7
- [Compare v0.12.2...v0.12.3](https://github.com/dry-rb/dry-types/compare/v0.12.2...v0.12.3)
96
+ [Compare v0.12.2...v0.13.0](https://github.com/dry-rb/dry-types/compare/v0.12.2...v0.13.0)
8
97
 
9
98
  # v0.12.2 2017-11-04
10
99
 
@@ -10,7 +10,7 @@ Report a feature request **only after discussing it first on [discourse.dry-rb.o
10
10
 
11
11
  ## Reporting questions, support requests, ideas, concerns etc.
12
12
 
13
- **PLEASE DON'T** - use [discourse.dry-rb.org](http://discourse.dry-rb.org) instead.
13
+ **PLEASE DON'T** - use [discourse.dry-rb.org](https://discourse.dry-rb.org) instead.
14
14
 
15
15
  # Pull Request Guidelines
16
16
 
data/Gemfile CHANGED
@@ -22,5 +22,3 @@ group :benchmarks do
22
22
  gem 'attrio'
23
23
  gem 'dry-struct'
24
24
  end
25
-
26
- gem 'dry-logic', git: 'https://github.com/dry-rb/dry-logic'
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']
@@ -28,12 +28,11 @@ Gem::Specification.new do |spec|
28
28
  spec.required_ruby_version = ">= 2.2.0"
29
29
 
30
30
  spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
31
- spec.add_runtime_dependency 'dry-core', '~> 0.2', '>= 0.2.1'
31
+ spec.add_runtime_dependency 'dry-core', '~> 0.4', '>= 0.4.4'
32
+ spec.add_runtime_dependency 'dry-inflector', '~> 0.1', '>= 0.1.2'
32
33
  spec.add_runtime_dependency 'dry-container', '~> 0.3'
33
34
  spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
34
- spec.add_runtime_dependency 'dry-configurable', '~> 0.1'
35
35
  spec.add_runtime_dependency 'dry-logic', '~> 0.4', '>= 0.4.2'
36
- spec.add_runtime_dependency 'inflecto', '~> 0.0.0', '>= 0.0.2'
37
36
 
38
37
  spec.add_development_dependency "bundler", "~> 1.6"
39
38
  spec.add_development_dependency "rake", "~> 11.0"
@@ -2,33 +2,35 @@ require 'bigdecimal'
2
2
  require 'date'
3
3
  require 'set'
4
4
 
5
- require 'inflecto'
6
5
  require 'concurrent'
7
6
 
8
7
  require 'dry-container'
9
8
  require 'dry-equalizer'
10
9
  require 'dry/core/extensions'
11
10
  require 'dry/core/constants'
11
+ require 'dry/core/class_attributes'
12
12
 
13
13
  require 'dry/types/version'
14
14
  require 'dry/types/container'
15
+ require 'dry/types/inflector'
15
16
  require 'dry/types/type'
16
17
  require 'dry/types/definition'
17
18
  require 'dry/types/constructor'
18
- require 'dry/types/fn_container'
19
19
  require 'dry/types/builder_methods'
20
20
 
21
21
  require 'dry/types/errors'
22
22
 
23
23
  module Dry
24
24
  module Types
25
- extend Dry::Configurable
26
25
  extend Dry::Core::Extensions
26
+ extend Dry::Core::ClassAttributes
27
27
  include Dry::Core::Constants
28
28
 
29
29
  # @!attribute [r] namespace
30
30
  # @return [Container{String => Definition}]
31
- setting :namespace, self
31
+ defines :namespace
32
+
33
+ namespace self
32
34
 
33
35
  TYPE_SPEC_REGEX = %r[(.+)<(.+)>].freeze
34
36
 
@@ -46,7 +48,7 @@ module Dry
46
48
  ' do `include Dry::Types.module` in places where you want to have access'\
47
49
  ' to built-in types'
48
50
 
49
- define_constants(config.namespace, type_keys)
51
+ define_constants(self.namespace, type_keys)
50
52
  end
51
53
 
52
54
  # @return [Container{String => Definition}]
@@ -63,19 +65,11 @@ module Dry
63
65
  # @param [Type] type
64
66
  # @param [#call,nil] block
65
67
  # @return [Container{String => Definition}]
68
+ # @api private
66
69
  def self.register(name, type = nil, &block)
67
70
  container.register(name, type || block.call)
68
71
  end
69
72
 
70
- # Registers given +klass+ in {#container} using +meth+ constructor
71
- # @param [Class] klass
72
- # @param [Symbol] meth
73
- # @return [Container{String => Definition}]
74
- def self.register_class(klass, meth = :new)
75
- type = Definition.new(klass).constructor(klass.method(meth))
76
- container.register(identifier(klass), type)
77
- end
78
-
79
73
  # @param [String,Class] name
80
74
  # @return [Type,Class]
81
75
  def self.[](name)
@@ -108,7 +102,7 @@ module Dry
108
102
  def self.define_constants(namespace, identifiers)
109
103
  names = identifiers.map do |id|
110
104
  parts = id.split('.')
111
- [Inflecto.camelize(parts.pop), parts.map(&Inflecto.method(:camelize))]
105
+ [Inflector.camelize(parts.pop), parts.map(&Inflector.method(:camelize))]
112
106
  end
113
107
 
114
108
  names.map do |(klass, parts)|
@@ -123,7 +117,7 @@ module Dry
123
117
  # @param [#to_s] klass
124
118
  # @return [String]
125
119
  def self.identifier(klass)
126
- Inflecto.underscore(klass).tr('/', '.')
120
+ Inflector.underscore(klass).tr('/', '.')
127
121
  end
128
122
 
129
123
  # @return [Concurrent::Map]
@@ -141,12 +135,12 @@ module Dry
141
135
 
142
136
  # @api private
143
137
  def self.const_missing(const)
144
- underscored = Inflecto.underscore(const)
138
+ underscored = Inflector.underscore(const)
145
139
 
146
140
  if type_keys.any? { |key| key.split('.')[0] == underscored }
147
141
  raise NameError,
148
142
  'dry-types does not define constants for default types. '\
149
- 'You can access the predefined types with [], e.g. Dry::Types["strict.int"] '\
143
+ 'You can access the predefined types with [], e.g. Dry::Types["strict.integer"] '\
150
144
  'or generate a module with types using Dry::Types.module'
151
145
  else
152
146
  super
@@ -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
@@ -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
@@ -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,2 @@
1
+ require 'dry/types/compat/int'
2
+ require 'dry/types/compat/form_types'
@@ -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 == 'params.integer'
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