dry-types 0.15.0 → 1.0.0

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