dry-types 0.14.1 → 0.15.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +15 -0
  3. data/.rubocop.yml +43 -0
  4. data/.travis.yml +12 -11
  5. data/CHANGELOG.md +115 -4
  6. data/Gemfile +2 -3
  7. data/benchmarks/hash_schemas.rb +5 -5
  8. data/dry-types.gemspec +1 -1
  9. data/lib/dry/types.rb +67 -45
  10. data/lib/dry/types/any.rb +11 -2
  11. data/lib/dry/types/array.rb +1 -4
  12. data/lib/dry/types/array/member.rb +2 -2
  13. data/lib/dry/types/builder.rb +23 -3
  14. data/lib/dry/types/builder_methods.rb +10 -11
  15. data/lib/dry/types/coercions/params.rb +2 -0
  16. data/lib/dry/types/compat.rb +0 -2
  17. data/lib/dry/types/compiler.rb +25 -33
  18. data/lib/dry/types/constrained.rb +5 -4
  19. data/lib/dry/types/constructor.rb +32 -11
  20. data/lib/dry/types/container.rb +2 -0
  21. data/lib/dry/types/core.rb +22 -12
  22. data/lib/dry/types/default.rb +4 -4
  23. data/lib/dry/types/enum.rb +10 -3
  24. data/lib/dry/types/errors.rb +1 -1
  25. data/lib/dry/types/extensions/maybe.rb +11 -1
  26. data/lib/dry/types/hash.rb +70 -63
  27. data/lib/dry/types/hash/constructor.rb +20 -0
  28. data/lib/dry/types/json.rb +7 -7
  29. data/lib/dry/types/map.rb +6 -1
  30. data/lib/dry/types/module.rb +115 -0
  31. data/lib/dry/types/{definition.rb → nominal.rb} +10 -4
  32. data/lib/dry/types/options.rb +2 -2
  33. data/lib/dry/types/params.rb +11 -11
  34. data/lib/dry/types/printable.rb +12 -0
  35. data/lib/dry/types/printer.rb +309 -0
  36. data/lib/dry/types/result.rb +2 -2
  37. data/lib/dry/types/safe.rb +4 -2
  38. data/lib/dry/types/schema.rb +298 -0
  39. data/lib/dry/types/schema/key.rb +130 -0
  40. data/lib/dry/types/spec/types.rb +12 -4
  41. data/lib/dry/types/sum.rb +14 -15
  42. data/lib/dry/types/version.rb +1 -1
  43. metadata +18 -8
  44. data/lib/dry/types/compat/form_types.rb +0 -27
  45. data/lib/dry/types/compat/int.rb +0 -14
  46. data/lib/dry/types/hash/schema.rb +0 -199
  47. data/lib/dry/types/hash/schema_builder.rb +0 -75
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 56b7935633ed4d23fa17d30082254f98e3e7f642a482c17bdb6f77629f88a399
4
- data.tar.gz: f9860f5500c746b10dcdd0aec29c70737d31615dc2342ee2ad7bc1f3e12c9250
3
+ metadata.gz: e14386ca189be49fd569c297a7d1ac385806569218fb172a63761ba0045a7fd6
4
+ data.tar.gz: 353f074f3cf5fe2718eb4e5eb4f3ff4273f3c565a67f2267f0b2014ba67f22dd
5
5
  SHA512:
6
- metadata.gz: 363ff8a7854062a51c71ff646d5e6c3c9ae1c964be5b99267c77b4cbfc5efa9d99b0466cdc736d142859aa624be99bb9fba1cf89bf9282eeb71f234eb5e3898f
7
- data.tar.gz: 6f6b1714103ea16df7a837c9e0a5b9488adf59f6eddeb8f28228c09daeb843edabacdae54ce9d4b461010c4851f75f8da5860f210b943b1a879105dd85dfd2bf
6
+ metadata.gz: cff40f66df4f87bee0bed14f033c49d8e6b1ffa98200f6f076def32a4bb4fdecf5906e9d3be6ee5a6cb611a21fa2f1fae6653c40ef49e3f21cfaf285ae4455b4
7
+ data.tar.gz: 0cdadfae930f5833efd33f8e73189b9880d4606b0ebf3f1fe0f2e948d1eec8cb4896abccd2b0e4f2d9269c92d6b5aa41c29431fb247f56337b9dc82a7d998488
data/.codeclimate.yml ADDED
@@ -0,0 +1,15 @@
1
+ version: "2"
2
+
3
+ prepare:
4
+ fetch:
5
+ - url: "https://raw.githubusercontent.com/dry-rb/devtools/master/.rubocop.yml"
6
+ path: ".rubocop.yml"
7
+
8
+ exclude_patterns:
9
+ - "benchmarks/"
10
+ - "examples/"
11
+ - "spec/"
12
+
13
+ plugins:
14
+ rubocop:
15
+ enabled: true
data/.rubocop.yml ADDED
@@ -0,0 +1,43 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.4
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: single_quotes
7
+
8
+ Style/Alias:
9
+ Enabled: false
10
+
11
+ Style/LambdaCall:
12
+ Enabled: false
13
+
14
+ Style/StabbyLambdaParentheses:
15
+ Enabled: false
16
+
17
+ Layout/MultilineMethodCallIndentation:
18
+ Enabled: true
19
+ EnforcedStyle: indented
20
+
21
+ Metrics/LineLength:
22
+ Max: 100
23
+
24
+ Metrics/MethodLength:
25
+ Max: 22
26
+
27
+ Metrics/ClassLength:
28
+ Max: 150
29
+
30
+ Metrics/AbcSize:
31
+ Max: 20
32
+
33
+ Metrics/BlockLength:
34
+ Enabled: true
35
+ Exclude:
36
+ - 'spec/**/*_spec.rb'
37
+
38
+ Metrics/CyclomaticComplexity:
39
+ Enabled: true
40
+ Max: 10
41
+
42
+ Lint/BooleanSymbol:
43
+ Enabled: false
data/.travis.yml CHANGED
@@ -1,27 +1,28 @@
1
1
  language: ruby
2
- dist: trusty
3
- sudo: required
4
2
  cache: bundler
3
+ sudo: false
5
4
  bundler_args: --without benchmarks tools
5
+ before_script:
6
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
7
+ - chmod +x ./cc-test-reporter
8
+ - ./cc-test-reporter before-build
9
+ after_script:
10
+ - "[ -d coverage ] && ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT"
6
11
  script:
7
12
  - bundle exec rake
8
- after_success:
9
- - '[ -d coverage ] && bundle exec codeclimate-test-reporter'
10
13
  rvm:
11
- - 2.3.8
14
+ - 2.6.1
15
+ - 2.5.3
12
16
  - 2.4.5
13
- - 2.5.5
14
- - 2.6.2
15
17
  - jruby-9.2.6.0
16
18
  env:
17
19
  global:
18
20
  - COVERAGE=true
19
- - JRUBY_OPTS='--dev -J-Xmx1024M'
20
21
  notifications:
21
22
  email: false
22
23
  webhooks:
23
24
  urls:
24
25
  - https://webhooks.gitter.im/e/19098b4253a72c9796db
25
- on_success: change # options: [always|never|change] default: always
26
- on_failure: always # options: [always|never|change] default: always
27
- on_start: false # default: false
26
+ on_success: change # options: [always|never|change] default: always
27
+ on_failure: always # options: [always|never|change] default: always
28
+ on_start: false # default: false
data/CHANGELOG.md CHANGED
@@ -1,11 +1,122 @@
1
- # v0.14.1 2019-03-25
1
+ # 0.15.0 2019-03-22
2
+
3
+ ## Changed
4
+
5
+ - [BREAKING] Internal representation of hash schemas was changed to be a simple list of key types (flash-gordon)
6
+ `Dry::Types::Hash#with_type_transform` now yields a key type instead of type + name:
7
+ ```ruby
8
+ Dry::Types['strict.hash'].with_type_transform { |key| key.name == :age ? key.required(false) : key }
9
+ ```
10
+ - [BREAKING] Definition types were renamed to nominal (flash-gordon)
11
+ - [BREAKING] Top-level types returned by `Dry::Types.[]` are now strict (flash-gordon)
12
+ ```ruby
13
+ # before
14
+ Dry::Types['integer']
15
+ # => #<Dry::Types[Nominal<Integer>]>
16
+ # now
17
+ Dry::Types['integer']
18
+ # => <Dry::Types[Constrained<Nominal<Integer> rule=[type?(Integer)]>]>
19
+ # you can still access nominal types using namespace
20
+ Dry::Types['nominal.integer']
21
+ # => #<Dry::Types[Nominal<Integer>]>
22
+ ```
23
+ - [BREAKING] Default values are not evaluated if the decorated type returns `nil`. They are triggered on `Undefined` instead (GustavoCaso + flash-gordon)
24
+ - [BREAKING] Support for old hash schemas was fully removed. This makes dry-types not compatible with dry-validation < 1.0 (flash-gordon)
25
+ - `Dry::Types.module` is deprecated in favor of `Dry.Types` (flash-gordon)
26
+ Keep in mind `Dry.Types` uses strict types for top-level names, that is after
27
+ ```ruby
28
+ module Types
29
+ include Dry.Types
30
+ end
31
+ ```
32
+ `Types::Integer` is a strict type. If you want it to be nominal, use `include Dry.Types(default: :nominal)`. See other options below.
33
+ - `params.integer` now always converts strings to decimal numbers, this means `09` will be coerced to `9` (threw an error before) (skryukov)
34
+ - Ruby 2.3 is EOL and not officially supported. It may work but we don't test it.
35
+
36
+ ## Added
37
+
38
+ - Improved string representation of types (flash-gordon)
39
+ ```ruby
40
+ Dry::Types['nominal.integer']
41
+ # => #<Dry::Types[Nominal<Integer>]>
42
+ Dry::Types['params.integer']
43
+ # => #<Dry::Types[Constructor<Nominal<Integer> fn=Dry::Types::Coercions::Params.to_int>]>
44
+ Dry::Types['hash'].schema(age?: 'integer')
45
+ # => #<Dry::Types[Constrained<Schema<keys={age?: Constrained<Nominal<Integer> rule=[type?(Integer)]>}> rule=[type?(Hash)]>]>
46
+ Dry::Types['array<integer>']
47
+ # => #<Dry::Types[Constrained<Array<Constrained<Nominal<Integer> rule=[type?(Integer)]>> rule=[type?(Array)]>]>
48
+ ```
49
+ - Options for the list of types you want to import with `Dry.Types` (flash-gordon)
50
+ Cherry-pick only certain types:
51
+ ```ruby
52
+ module Types
53
+ include Dry.Types(:strict, :nominal, :coercible)
54
+ end
55
+ Types.constants
56
+ # => [:Strict, :Nominal, :Coercible]
57
+ ```
58
+ Change default top-level types:
59
+ ```ruby
60
+ module Types
61
+ include Dry.Types(default: :coercible)
62
+ end
63
+ # => #<Dry::Types[Constructor<Nominal<Integer> fn=Kernel.Integer>]>
64
+ ```
65
+ Rename type namespaces:
66
+ ```ruby
67
+ module Types
68
+ include Dry.Types(strict: :Strong, coercible: :Kernel)
69
+ end
70
+ ```
71
+ - Optional keys for schemas can be provided with ?-ending symbols (flash-gordon)
72
+ ```ruby
73
+ Dry::Types['hash'].schema(name: 'string', age?: 'integer')
74
+ ```
75
+ - Another way of making keys optional is setting `required: false` to meta. In fact, it is the preferable
76
+ way if you have to store this information in `meta`, otherwise use the Key's API (see below) (flash-gordon)
77
+ ```ruby
78
+ Dry::Types['hash'].schema(
79
+ name: Dry::Types['string'],
80
+ age: Dry::Types['integer'].meta(required: false)
81
+ )
82
+ ```
83
+ - Key types have API for making keys omittable and back (flash-gordon)
84
+ ```ruby
85
+ # defining a base schema with optional keys
86
+ lax_hash = Dry::Types['hash'].with_type_transform { |key| key.required(false) }
87
+ # same as
88
+ lax_hash = Dry::Types['hash'].with_type_transform(&:omittable)
89
+
90
+ # keys in user_schema are not required
91
+ user_schema = lax_hash.schema(name: 'string', age: 'integer')
92
+ ```
93
+ - `Type#optional?` now recognizes more cases where `nil` is an allowed value (flash-gordon)
94
+ - `Constructor#{prepend,append}` with `<<` and `>>` as aliases. `Constructor#append` works the same way `Constructor#constrcutor` does. `Constuctor#prepend` chains functions in the reverse order, see examples (flash-gordon)
95
+ ```ruby
96
+ to_int = Types::Coercible::Integer
97
+ inc = to_int.append { |x| x + 2 }
98
+ inc.("1") # => "1" -> 1 -> 3
99
+
100
+ inc = to_int.prepend { |x| x + "2" }
101
+ inc.("1") # => "1" -> "12" -> 12
102
+ ```
103
+ - Partial schema application for cases when you want to validate only a subset of keys (flash-gordon)
104
+ This is useful when you want to update a key or two in an already-validated hash. A perfect example is `Dry::Struct#new` where this feature is now used.
105
+ ```ruby
106
+ schema = Dry::Types['hash'].schema(name: 'string', age: 'integer')
107
+ value = schema.(name: 'John', age: 20)
108
+ update = schema.apply({ age: 21 }, skip_missing: true)
109
+ value.merge(update)
110
+ ```
2
111
 
3
112
  ## Fixed
4
- - `coercible.integer` now doesn't blow up on invalid strings (exterm)
5
113
 
6
- [Compare v0.14.0...v0.14.1](https://github.com/dry-rb/dry-types/compare/v0.14.0...v0.14.1)
114
+ * `Hash::Map` now behaves as a constrained type if its values are constrained (flash-gordon)
115
+ * `coercible.integer` now doesn't blow up on invalid strings (exterm)
116
+
117
+ [Compare v0.14.0...v0.15.0](https://github.com/dry-rb/dry-types/compare/v0.14.0...v0.15.0)
7
118
 
8
- # v0.14.0 2019-01-29
119
+ # 0.14.0 2019-01-29
9
120
 
10
121
  ## Changed
11
122
 
data/Gemfile CHANGED
@@ -1,18 +1,17 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
+
3
5
  gemspec
4
6
 
5
7
  group :test do
6
8
  platform :mri do
7
- gem "codeclimate-test-reporter", require: false
8
9
  gem 'simplecov', require: false
9
10
  end
10
11
  end
11
12
 
12
13
  group :tools do
13
14
  gem 'pry-byebug', platform: :mri
14
- gem 'mutant'
15
- gem 'mutant-rspec'
16
15
  end
17
16
 
18
17
  group :benchmarks do
@@ -5,13 +5,13 @@ require 'dry-types'
5
5
 
6
6
  module SchemaBench
7
7
  def self.hash_schema(type)
8
- Dry::Types['hash'].public_send(type,
9
- email: Dry::Types['string'],
8
+ Dry::Types['nominal.hash'].public_send(type,
9
+ email: Dry::Types['nominal.string'],
10
10
  age: Dry::Types['params.integer'],
11
11
  admin: Dry::Types['params.bool'],
12
- address: Dry::Types['hash'].public_send(type,
13
- city: Dry::Types['string'],
14
- street: Dry::Types['string']
12
+ address: Dry::Types['nominal.hash'].public_send(type,
13
+ city: Dry::Types['nominal.string'],
14
+ street: Dry::Types['nominal.string']
15
15
  )
16
16
  )
17
17
  end
data/dry-types.gemspec CHANGED
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
34
34
  spec.add_runtime_dependency 'dry-core', '~> 0.4', '>= 0.4.4'
35
35
  spec.add_runtime_dependency 'dry-inflector', '~> 0.1', '>= 0.1.2'
36
36
  spec.add_runtime_dependency 'dry-container', '~> 0.3'
37
- spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
37
+ spec.add_runtime_dependency 'dry-equalizer', '~> 0.2', '>= 0.2.2'
38
38
  spec.add_runtime_dependency 'dry-logic', '~> 0.5', '>= 0.5'
39
39
 
40
40
  spec.add_development_dependency "bundler"
data/lib/dry/types.rb CHANGED
@@ -14,9 +14,10 @@ require 'dry/types/version'
14
14
  require 'dry/types/container'
15
15
  require 'dry/types/inflector'
16
16
  require 'dry/types/type'
17
- require 'dry/types/definition'
17
+ require 'dry/types/printable'
18
+ require 'dry/types/nominal'
18
19
  require 'dry/types/constructor'
19
- require 'dry/types/builder_methods'
20
+ require 'dry/types/module'
20
21
 
21
22
  require 'dry/types/errors'
22
23
 
@@ -24,34 +25,25 @@ module Dry
24
25
  module Types
25
26
  extend Dry::Core::Extensions
26
27
  extend Dry::Core::ClassAttributes
28
+ extend Dry::Core::Deprecations[:'dry-types']
27
29
  include Dry::Core::Constants
28
30
 
29
- # @!attribute [r] namespace
30
- # @return [Container{String => Definition}]
31
- defines :namespace
32
-
33
- namespace self
34
-
35
31
  TYPE_SPEC_REGEX = %r[(.+)<(.+)>].freeze
36
32
 
37
- # @return [Module]
38
- def self.module
39
- namespace = Module.new
40
- define_constants(namespace, type_keys)
41
- namespace.extend(BuilderMethods)
42
- namespace
33
+ # @see Dry.Types
34
+ def self.module(*namespaces, default: :nominal, **aliases)
35
+ Module.new(container, *namespaces, default: default, **aliases)
43
36
  end
37
+ deprecate_class_method :module, message: <<~DEPRECATION
38
+ Use Dry.Types() instead. Beware, it exports strict types by default, for old behavior use Dry.Types(default: :nominal). See more options in the changelog
39
+ DEPRECATION
44
40
 
45
- # @deprecated Include {Dry::Types.module} instead
46
- def self.finalize
47
- warn 'Dry::Types.finalize and configuring namespace is deprecated. Just'\
48
- ' do `include Dry::Types.module` in places where you want to have access'\
49
- ' to built-in types'
50
-
51
- define_constants(self.namespace, type_keys)
41
+ # @api private
42
+ def self.included(*)
43
+ raise RuntimeError, "Import Dry.Types, not Dry::Types"
52
44
  end
53
45
 
54
- # @return [Container{String => Definition}]
46
+ # @return [Container{String => Nominal}]
55
47
  def self.container
56
48
  @container ||= Container.new
57
49
  end
@@ -64,7 +56,7 @@ module Dry
64
56
  # @param [String] name
65
57
  # @param [Type] type
66
58
  # @param [#call,nil] block
67
- # @return [Container{String => Definition}]
59
+ # @return [Container{String => Nominal}]
68
60
  # @api private
69
61
  def self.register(name, type = nil, &block)
70
62
  container.register(name, type || block.call)
@@ -96,24 +88,6 @@ module Dry
96
88
  end
97
89
  end
98
90
 
99
- # @param [Module] namespace
100
- # @param [<String>] identifiers
101
- # @return [<Definition>]
102
- def self.define_constants(namespace, identifiers)
103
- names = identifiers.map do |id|
104
- parts = id.split('.')
105
- [Inflector.camelize(parts.pop), parts.map(&Inflector.method(:camelize))]
106
- end
107
-
108
- names.map do |(klass, parts)|
109
- mod = parts.reduce(namespace) do |a, e|
110
- a.constants.include?(e.to_sym) ? a.const_get(e) : a.const_set(e, Module.new)
111
- end
112
-
113
- mod.const_set(klass, self[identifier((parts + [klass]).join('::'))])
114
- end
115
- end
116
-
117
91
  # @param [#to_s] klass
118
92
  # @return [String]
119
93
  def self.identifier(klass)
@@ -125,19 +99,17 @@ module Dry
125
99
  @type_map ||= Concurrent::Map.new
126
100
  end
127
101
 
128
- # List of type keys defined in {Dry::Types.container}
102
+ # List of type keys defined in {Dry::Types.container}
129
103
  # @return [<String>]
130
104
  def self.type_keys
131
105
  container.keys
132
106
  end
133
107
 
134
- private
135
-
136
108
  # @api private
137
109
  def self.const_missing(const)
138
110
  underscored = Inflector.underscore(const)
139
111
 
140
- if type_keys.any? { |key| key.split('.')[0] == underscored }
112
+ if container.keys.any? { |key| key.split('.')[0] == underscored }
141
113
  raise NameError,
142
114
  'dry-types does not define constants for default types. '\
143
115
  'You can access the predefined types with [], e.g. Dry::Types["strict.integer"] '\
@@ -147,7 +119,57 @@ module Dry
147
119
  end
148
120
  end
149
121
  end
122
+
123
+ # Export registered types as a module with constants
124
+ #
125
+ # @example no options
126
+ #
127
+ # module Types
128
+ # # imports all types as constants, uses modules for namespaces
129
+ # include Dry::Types.module
130
+ # end
131
+ # # nominal types are exported by default
132
+ # Types::Integer
133
+ # # => #<Dry::Types[Nominal<Integer>]>
134
+ # Types::Strict::Integer
135
+ # # => #<Dry::Types[Constrained<Nominal<Integer> rule=[type?(Integer)]>]>
136
+ #
137
+ # @example changing default types
138
+ #
139
+ # module Types
140
+ # include Dry::Types(default: :strict)
141
+ # end
142
+ # Types::Integer
143
+ # # => #<Dry::Types[Constrained<Nominal<Integer> rule=[type?(Integer)]>]>
144
+ #
145
+ # @example cherry-picking namespaces
146
+ #
147
+ # module Types
148
+ # include Dry::Types.module(:strict, :coercible)
149
+ # end
150
+ # # cherry-picking discards default types,
151
+ # # provide the :default option along with the list of
152
+ # # namespaces if you want the to be exported
153
+ # Types.constants # => [:Coercible, :Strict]
154
+ #
155
+ # @example custom names
156
+ # module Types
157
+ # include Dry::Types.module(coercible: :Kernel)
158
+ # end
159
+ # Types::Kernel::Integer
160
+ # # => #<Dry::Types[Constructor<Nominal<Integer> fn=Kernel.Integer>]>
161
+ #
162
+ # @param [Array<Symbol>] namespaces List of type namespaces to export
163
+ # @param [Symbol] default Default namespace to export
164
+ # @param [Hash{Symbol => Symbol}] aliases Optional renamings, like strict: :Draconian
165
+ # @return [Dry::Types::Module]
166
+ #
167
+ # @see Dry::types::Module
168
+ def self.Types(*namespaces, default: Types::Undefined, **aliases)
169
+ Types::Module.new(Types.container, *namespaces, default: default, **aliases)
170
+ end
150
171
  end
151
172
 
152
173
  require 'dry/types/core' # load built-in types
153
174
  require 'dry/types/extensions'
175
+ require 'dry/types/printer'