dry-types 0.14.1 → 0.15.0

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