dry-types 0.15.0 → 1.0.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +18 -2
  4. data/.travis.yml +4 -5
  5. data/.yardopts +6 -2
  6. data/CHANGELOG.md +69 -1
  7. data/Gemfile +3 -0
  8. data/README.md +2 -1
  9. data/Rakefile +2 -0
  10. data/benchmarks/hash_schemas.rb +2 -0
  11. data/benchmarks/lax_schema.rb +16 -0
  12. data/benchmarks/profile_invalid_input.rb +15 -0
  13. data/benchmarks/profile_lax_schema_valid.rb +16 -0
  14. data/benchmarks/profile_valid_input.rb +15 -0
  15. data/benchmarks/schema_valid_vs_invalid.rb +21 -0
  16. data/benchmarks/setup.rb +17 -0
  17. data/dry-types.gemspec +4 -2
  18. data/lib/dry-types.rb +2 -0
  19. data/lib/dry/types.rb +51 -13
  20. data/lib/dry/types/any.rb +21 -10
  21. data/lib/dry/types/array.rb +11 -1
  22. data/lib/dry/types/array/member.rb +65 -13
  23. data/lib/dry/types/builder.rb +48 -4
  24. data/lib/dry/types/builder_methods.rb +9 -8
  25. data/lib/dry/types/coercions.rb +71 -19
  26. data/lib/dry/types/coercions/json.rb +22 -3
  27. data/lib/dry/types/coercions/params.rb +98 -30
  28. data/lib/dry/types/compiler.rb +35 -12
  29. data/lib/dry/types/constrained.rb +73 -27
  30. data/lib/dry/types/constrained/coercible.rb +36 -6
  31. data/lib/dry/types/constraints.rb +15 -1
  32. data/lib/dry/types/constructor.rb +90 -43
  33. data/lib/dry/types/constructor/function.rb +201 -0
  34. data/lib/dry/types/container.rb +5 -0
  35. data/lib/dry/types/core.rb +7 -5
  36. data/lib/dry/types/decorator.rb +36 -9
  37. data/lib/dry/types/default.rb +48 -16
  38. data/lib/dry/types/enum.rb +30 -16
  39. data/lib/dry/types/errors.rb +73 -7
  40. data/lib/dry/types/extensions.rb +2 -0
  41. data/lib/dry/types/extensions/maybe.rb +43 -4
  42. data/lib/dry/types/fn_container.rb +5 -0
  43. data/lib/dry/types/hash.rb +22 -3
  44. data/lib/dry/types/hash/constructor.rb +13 -0
  45. data/lib/dry/types/inflector.rb +2 -0
  46. data/lib/dry/types/json.rb +4 -6
  47. data/lib/dry/types/{safe.rb → lax.rb} +34 -17
  48. data/lib/dry/types/map.rb +63 -29
  49. data/lib/dry/types/meta.rb +51 -0
  50. data/lib/dry/types/module.rb +7 -2
  51. data/lib/dry/types/nominal.rb +105 -13
  52. data/lib/dry/types/options.rb +12 -25
  53. data/lib/dry/types/params.rb +5 -3
  54. data/lib/dry/types/printable.rb +5 -1
  55. data/lib/dry/types/printer.rb +58 -57
  56. data/lib/dry/types/result.rb +26 -0
  57. data/lib/dry/types/schema.rb +169 -66
  58. data/lib/dry/types/schema/key.rb +34 -39
  59. data/lib/dry/types/spec/types.rb +41 -1
  60. data/lib/dry/types/sum.rb +70 -21
  61. data/lib/dry/types/type.rb +49 -0
  62. data/lib/dry/types/version.rb +3 -1
  63. metadata +14 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e14386ca189be49fd569c297a7d1ac385806569218fb172a63761ba0045a7fd6
4
- data.tar.gz: 353f074f3cf5fe2718eb4e5eb4f3ff4273f3c565a67f2267f0b2014ba67f22dd
3
+ metadata.gz: '09c9bc944d01d8cb892cd29f540352981bc6e266ac7d2017cbd1827b7ca81b3c'
4
+ data.tar.gz: 9a652f7888d52bdc6684b9ba142792460b45c1d27157d0176c0daa322af1826f
5
5
  SHA512:
6
- metadata.gz: cff40f66df4f87bee0bed14f033c49d8e6b1ffa98200f6f076def32a4bb4fdecf5906e9d3be6ee5a6cb611a21fa2f1fae6653c40ef49e3f21cfaf285ae4455b4
7
- data.tar.gz: 0cdadfae930f5833efd33f8e73189b9880d4606b0ebf3f1fe0f2e948d1eec8cb4896abccd2b0e4f2d9269c92d6b5aa41c29431fb247f56337b9dc82a7d998488
6
+ metadata.gz: 3252ede1f749a14dfd09dcc5ab6c9872a7dc5f50cb31858712dd76f4f927d133b9ce8d03bce042bfd2bd832b5e21cae7723abf93618d6d746b38506a0aeeefc1
7
+ data.tar.gz: 1e71e67c449fadfa5989f211a019bf75b0c5dac6240934d819af2f4401622833e31304906f99dd8a2d9ac6e9f14a6bb07d37e2c63ce28408b7b685bd4d88a1dd
data/.gitignore CHANGED
@@ -8,3 +8,4 @@
8
8
  /spec/reports/
9
9
  /tmp/
10
10
  log/
11
+ .rubocop.yml
@@ -1,6 +1,9 @@
1
1
  AllCops:
2
2
  TargetRubyVersion: 2.4
3
3
 
4
+ Style/EachWithObject:
5
+ Enabled: false
6
+
4
7
  Style/StringLiterals:
5
8
  Enabled: true
6
9
  EnforcedStyle: single_quotes
@@ -14,12 +17,18 @@ Style/LambdaCall:
14
17
  Style/StabbyLambdaParentheses:
15
18
  Enabled: false
16
19
 
20
+ Style/FormatString:
21
+ Enabled: false
22
+
23
+ Layout/SpaceInLambdaLiteral:
24
+ Enabled: false
25
+
17
26
  Layout/MultilineMethodCallIndentation:
18
27
  Enabled: true
19
28
  EnforcedStyle: indented
20
29
 
21
- Metrics/LineLength:
22
- Max: 100
30
+ Metrics/LineLength:
31
+ Max: 100
23
32
 
24
33
  Metrics/MethodLength:
25
34
  Max: 22
@@ -41,3 +50,10 @@ Metrics/CyclomaticComplexity:
41
50
 
42
51
  Lint/BooleanSymbol:
43
52
  Enabled: false
53
+
54
+ Style/AccessModifierDeclarations:
55
+ Enabled: false
56
+
57
+ Style/BlockDelimiters:
58
+ EnforcedStyle: semantic
59
+
@@ -1,6 +1,5 @@
1
1
  language: ruby
2
2
  cache: bundler
3
- sudo: false
4
3
  bundler_args: --without benchmarks tools
5
4
  before_script:
6
5
  - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
@@ -11,10 +10,10 @@ after_script:
11
10
  script:
12
11
  - bundle exec rake
13
12
  rvm:
14
- - 2.6.1
15
- - 2.5.3
16
- - 2.4.5
17
- - jruby-9.2.6.0
13
+ - 2.6.3
14
+ - 2.5.5
15
+ - 2.4.6
16
+ - jruby-9.2.7.0
18
17
  env:
19
18
  global:
20
19
  - COVERAGE=true
data/.yardopts CHANGED
@@ -1,5 +1,9 @@
1
1
  --title 'dry-types'
2
- --markup markdown
2
+ --query '@api.text != "private"'
3
+ --embed-mixins
4
+ --output doc
3
5
  --readme README.md
4
- --private
6
+ --files CHANGELOG.md
7
+ --markup markdown
8
+ --markup-provider=redcarpet
5
9
  lib/**/*.rb
@@ -1,3 +1,64 @@
1
+ # 1.0.0 2019-04-23
2
+
3
+ ## Changed
4
+
5
+ - [BREAKING] Behavior of built-in constructor types was changed to be more strict. They will always raise an error on failed coercion (flash-gordon)
6
+ Compare:
7
+ ```ruby
8
+ # 0.15.0
9
+ Types::Params::Integer.('foo')
10
+ # => "foo"
11
+
12
+ # 1.0.0
13
+ Types::Params::Integer.('foo')
14
+ # => Dry::Types::CoercionError: invalid value for Integer(): "foo"
15
+ ```
16
+
17
+ To handle coercion errors `Type#call` now yields a block:
18
+
19
+ ```ruby
20
+ Types::Params::Integer.('foo') { :invalid } # => :invalid
21
+ ```
22
+ This makes work with coercions more straightforward and way faster.
23
+ - [BREAKING] Safe types were renamed to Lax, this name better serves their purpose. The previous name is available but prints a warning (flash-gordon)
24
+ - [BREAKING] Metadata is now pushed down to the decorated type. It is not likely you will notice a difference but this a breaking change that enables some use cases in rom related to the usage of default types in relations (flash-gordon)
25
+ - Nominal types are now completely unconstrained. This fixes some inconsistencies when using them with constraints. `Nominal#try` will always return a successful result, for the previous behavior use `Nominal#try_coerce` or switch to strict types with passing a block to `#call` (flash-gordon)
26
+
27
+ ## Performance improvements
28
+
29
+ - During the work on this release, a lot of performance improvements were made. dry-types 1.0 combined with dry-logic 1.0 are multiple times faster than dry-types 0.15 and dry-logic 0.5 for common cases including constraints checking and coercion (flash-gordon)
30
+
31
+ ## Added
32
+
33
+ - API for custom constructor types was enhanced. If you pass your own callable to `.constructor` it can have a block in its signature. If a block is passed, you must call it on failied coercion, otherwise raise a type coercion error (flash-gordon)
34
+ Example:
35
+ ```ruby
36
+ proc do |input, &block|
37
+ if input.is_a? String
38
+ Integer(input, 10)
39
+ else
40
+ Integer(input)
41
+ end
42
+ rescue ArgumentError, TypeError => error
43
+ if block
44
+ block.call
45
+ else
46
+ raise Dry::Types::CoercionError.new(
47
+ error.message,
48
+ backtrace: error.backtrace
49
+ )
50
+ end
51
+ end
52
+ ```
53
+ This makes the exception handling your job so that dry-types won't have to catch and re-wrap all possible errors (this is not safe, generally speaking).
54
+ - Types now can be converted to procs thus you can pass them as blocks (flash-gordon)
55
+ ```ruby
56
+ %w(1 2 3).map(&Types::Coercible::Integer)
57
+ # => [1, 2, 3]
58
+ ```
59
+
60
+ [Compare v0.15.0...v1.0.0](https://github.com/dry-rb/dry-types/compare/v0.15.0...v1.0.0)
61
+
1
62
  # 0.15.0 2019-03-22
2
63
 
3
64
  ## Changed
@@ -116,7 +177,14 @@
116
177
 
117
178
  [Compare v0.14.0...v0.15.0](https://github.com/dry-rb/dry-types/compare/v0.14.0...v0.15.0)
118
179
 
119
- # 0.14.0 2019-01-29
180
+ # v0.14.1 2019-03-25
181
+
182
+ ## Fixed
183
+ - `coercible.integer` now doesn't blow up on invalid strings (exterm)
184
+
185
+ [Compare v0.14.0...v0.14.1](https://github.com/dry-rb/dry-types/compare/v0.14.0...v0.14.1)
186
+
187
+ # v0.14.0 2019-01-29
120
188
 
121
189
  ## Changed
122
190
 
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
@@ -16,6 +18,7 @@ end
16
18
 
17
19
  group :benchmarks do
18
20
  gem 'benchmark-ips'
21
+ gem 'hotch'
19
22
  gem 'virtus'
20
23
  gem 'fast_attributes'
21
24
  gem 'attrio'
data/README.md CHANGED
@@ -3,8 +3,9 @@
3
3
  [codeclimate]: https://codeclimate.com/github/dry-rb/dry-types
4
4
  [coveralls]: https://coveralls.io/r/dry-rb/dry-types
5
5
  [inchpages]: http://inch-ci.org/github/dry-rb/dry-types
6
+ [chat]: https://dry-rb.zulipchat.com
6
7
 
7
- # dry-types [![Join the chat at https://gitter.im/dry-rb/chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dry-rb/chat)
8
+ # dry-types [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat]
8
9
 
9
10
  [![Gem Version](https://badge.fury.io/rb/dry-types.svg)][gem]
10
11
  [![Build Status](https://travis-ci.org/dry-rb/dry-types.svg?branch=master)][travis]
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
4
  require "rspec/core/rake_task"
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  $LOAD_PATH.unshift('lib')
2
4
 
3
5
  require 'bundler/setup'
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'setup'
4
+
5
+ schema = Dry::Types['params.hash'].schema(
6
+ email?: 'string',
7
+ age?: 'params.integer'
8
+ ).lax
9
+
10
+ params = { email: 'jane@doe.org', age: '19' }
11
+
12
+ Benchmark.ips do |x|
13
+ x.report("valid input") { schema.(params) }
14
+ x.compare!
15
+ end
16
+
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'setup'
4
+
5
+ INVALID_INPUT = {
6
+ name: :John,
7
+ age: '20',
8
+ email: nil
9
+ }
10
+
11
+ profile do
12
+ 10_000.times do
13
+ PersonSchema.(INVALID_INPUT)
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'setup'
4
+
5
+ Schema = Dry::Types['params.hash'].schema(
6
+ email?: 'string',
7
+ age?: 'coercible.integer'
8
+ ).lax
9
+
10
+ ValidInput = { email: 'jane@doe.org', age: '19' }
11
+
12
+ profile do
13
+ 10_000.times do
14
+ Schema.(ValidInput)
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'setup'
4
+
5
+ VALID_INPUT = {
6
+ name: 'John',
7
+ age: 20,
8
+ email: 'john@doe.com'
9
+ }
10
+
11
+ profile do
12
+ 10_000.times do
13
+ PersonSchema.(VALID_INPUT)
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'setup'
4
+
5
+ VALID_INPUT = {
6
+ name: 'John',
7
+ age: 20,
8
+ email: 'john@doe.com'
9
+ }
10
+
11
+ INVALID_INPUT = {
12
+ name: :John,
13
+ age: '20',
14
+ email: nil
15
+ }
16
+
17
+ Benchmark.ips do |x|
18
+ x.report("valid input") { PersonSchema.(VALID_INPUT) }
19
+ x.report("invalid input") { PersonSchema.(INVALID_INPUT) }
20
+ x.compare!
21
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'benchmark/ips'
4
+ require 'hotch'
5
+ ENV['HOTCH_VIEWER'] ||= 'open'
6
+
7
+ require 'dry/types'
8
+
9
+ PersonSchema = Dry::Types['hash'].schema(
10
+ name: 'string',
11
+ age: 'integer',
12
+ email: 'string'
13
+ ).lax
14
+
15
+ def profile(&block)
16
+ Hotch(filter: 'Dry', &block)
17
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  lib = File.expand_path('../lib', __FILE__)
2
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
5
  require 'dry/types/version'
@@ -28,14 +30,14 @@ Gem::Specification.new do |spec|
28
30
  spec.bindir = "exe"
29
31
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
32
  spec.require_paths = ["lib"]
31
- spec.required_ruby_version = ">= 2.3.0"
33
+ spec.required_ruby_version = ">= 2.4.0"
32
34
 
33
35
  spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
34
36
  spec.add_runtime_dependency 'dry-core', '~> 0.4', '>= 0.4.4'
35
37
  spec.add_runtime_dependency 'dry-inflector', '~> 0.1', '>= 0.1.2'
36
38
  spec.add_runtime_dependency 'dry-container', '~> 0.3'
37
39
  spec.add_runtime_dependency 'dry-equalizer', '~> 0.2', '>= 0.2.2'
38
- spec.add_runtime_dependency 'dry-logic', '~> 0.5', '>= 0.5'
40
+ spec.add_runtime_dependency 'dry-logic', '~> 1.0'
39
41
 
40
42
  spec.add_development_dependency "bundler"
41
43
  spec.add_development_dependency "rake", "~> 11.0"
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/types'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bigdecimal'
2
4
  require 'date'
3
5
  require 'set'
@@ -22,6 +24,9 @@ require 'dry/types/module'
22
24
  require 'dry/types/errors'
23
25
 
24
26
  module Dry
27
+ # Main library namespace
28
+ #
29
+ # @api public
25
30
  module Types
26
31
  extend Dry::Core::Extensions
27
32
  extend Dry::Core::ClassAttributes
@@ -43,27 +48,44 @@ module Dry
43
48
  raise RuntimeError, "Import Dry.Types, not Dry::Types"
44
49
  end
45
50
 
51
+ # Return container with registered built-in type objects
52
+ #
46
53
  # @return [Container{String => Nominal}]
54
+ #
55
+ # @api private
47
56
  def self.container
48
57
  @container ||= Container.new
49
58
  end
50
59
 
60
+ # Check if a give type is registered
61
+ #
62
+ # @return [Boolean]
63
+ #
51
64
  # @api private
52
65
  def self.registered?(class_or_identifier)
53
66
  container.key?(identifier(class_or_identifier))
54
67
  end
55
68
 
69
+ # Register a new built-in type
70
+ #
56
71
  # @param [String] name
57
72
  # @param [Type] type
58
73
  # @param [#call,nil] block
74
+ #
59
75
  # @return [Container{String => Nominal}]
76
+ #
60
77
  # @api private
61
78
  def self.register(name, type = nil, &block)
62
79
  container.register(name, type || block.call)
63
80
  end
64
81
 
82
+ # Get a built-in type by its name
83
+ #
65
84
  # @param [String,Class] name
85
+ #
66
86
  # @return [Type,Class]
87
+ #
88
+ # @api public
67
89
  def self.[](name)
68
90
  type_map.fetch_or_store(name) do
69
91
  case name
@@ -88,19 +110,29 @@ module Dry
88
110
  end
89
111
  end
90
112
 
113
+ # Infer a type identifier from the provided class
114
+ #
91
115
  # @param [#to_s] klass
116
+ #
92
117
  # @return [String]
93
118
  def self.identifier(klass)
94
119
  Inflector.underscore(klass).tr('/', '.')
95
120
  end
96
121
 
122
+ # Cached type map
123
+ #
97
124
  # @return [Concurrent::Map]
125
+ #
126
+ # @api private
98
127
  def self.type_map
99
128
  @type_map ||= Concurrent::Map.new
100
129
  end
101
130
 
102
- # List of type keys defined in {Dry::Types.container}
103
- # @return [<String>]
131
+ # List of type keys defined in {Dry::Types.container}
132
+ #
133
+ # @return [String]
134
+ #
135
+ # @api private
104
136
  def self.type_keys
105
137
  container.keys
106
138
  end
@@ -112,8 +144,8 @@ module Dry
112
144
  if container.keys.any? { |key| key.split('.')[0] == underscored }
113
145
  raise NameError,
114
146
  'dry-types does not define constants for default types. '\
115
- 'You can access the predefined types with [], e.g. Dry::Types["strict.integer"] '\
116
- 'or generate a module with types using Dry::Types.module'
147
+ 'You can access the predefined types with [], e.g. Dry::Types["integer"] '\
148
+ 'or generate a module with types using Dry.Types()'
117
149
  else
118
150
  super
119
151
  end
@@ -126,26 +158,26 @@ module Dry
126
158
  #
127
159
  # module Types
128
160
  # # imports all types as constants, uses modules for namespaces
129
- # include Dry::Types.module
161
+ # include Dry::Types()
130
162
  # end
131
- # # nominal types are exported by default
163
+ # # strict types are exported by default
132
164
  # Types::Integer
133
- # # => #<Dry::Types[Nominal<Integer>]>
134
- # Types::Strict::Integer
135
165
  # # => #<Dry::Types[Constrained<Nominal<Integer> rule=[type?(Integer)]>]>
166
+ # Types::Nominal::Integer
167
+ # # => #<Dry::Types[Nominal<Integer>]>
136
168
  #
137
169
  # @example changing default types
138
170
  #
139
171
  # module Types
140
- # include Dry::Types(default: :strict)
172
+ # include Dry::Types(default: :nominal)
141
173
  # end
142
174
  # Types::Integer
143
- # # => #<Dry::Types[Constrained<Nominal<Integer> rule=[type?(Integer)]>]>
175
+ # # => #<Dry::Types[Nominal<Integer>]>
144
176
  #
145
177
  # @example cherry-picking namespaces
146
178
  #
147
179
  # module Types
148
- # include Dry::Types.module(:strict, :coercible)
180
+ # include Dry::Types(:strict, :coercible)
149
181
  # end
150
182
  # # cherry-picking discards default types,
151
183
  # # provide the :default option along with the list of
@@ -154,7 +186,7 @@ module Dry
154
186
  #
155
187
  # @example custom names
156
188
  # module Types
157
- # include Dry::Types.module(coercible: :Kernel)
189
+ # include Dry::Types(coercible: :Kernel)
158
190
  # end
159
191
  # Types::Kernel::Integer
160
192
  # # => #<Dry::Types[Constructor<Nominal<Integer> fn=Kernel.Integer>]>
@@ -162,12 +194,18 @@ module Dry
162
194
  # @param [Array<Symbol>] namespaces List of type namespaces to export
163
195
  # @param [Symbol] default Default namespace to export
164
196
  # @param [Hash{Symbol => Symbol}] aliases Optional renamings, like strict: :Draconian
197
+ #
165
198
  # @return [Dry::Types::Module]
166
199
  #
167
- # @see Dry::types::Module
200
+ # @see Dry::Types::Module
201
+ #
202
+ # @api public
203
+ #
204
+ # rubocop:disable Naming/MethodName
168
205
  def self.Types(*namespaces, default: Types::Undefined, **aliases)
169
206
  Types::Module.new(Types.container, *namespaces, default: default, **aliases)
170
207
  end
208
+ # rubocop:enable Naming/MethodName
171
209
  end
172
210
 
173
211
  require 'dry/types/core' # load built-in types