dry-types 0.6.0 → 0.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: db688f074a70acff2e5acd2b23ad2dd14d5292fe
4
- data.tar.gz: e148638d952426127fd886ee25bad640cf8d7839
3
+ metadata.gz: 02e8392346549c5801d50d1fb805951bea41e722
4
+ data.tar.gz: 6e4873ce2f3f8f92eb1bbbfccfbe68aed67524c2
5
5
  SHA512:
6
- metadata.gz: 3e1dc84f874a91638488e15eff1dbd0f97a3ee6b25cd0ecf7deab7300441382f9b05912830cab13570ced7840e5030aec17175fd6cfee6ce27c12b386ba4bcf9
7
- data.tar.gz: 6eaf193a169e9a4e951657dbae09272098ee369ccc9c6e08b730d9d486e9ee94c71ea05e59b14cd3925919ebe0a480402f8e12f35037cfa5a21392754d2854cc
6
+ metadata.gz: 1153d031efd2277c552a3b47e26395da775d5fa70600bc815c9f1a4658cb8200ba99c595b90c52c8128bd896bb017f2bcd1a0fcf80227bbb57118d2a1305f03d
7
+ data.tar.gz: 0d6c974e7bf9fbb552d4abd95e15a51368685507e21eda31909c9b002c43652dd74c08244728242330797be64e5b196bc7f1763c91d204f6b51ad181d69f65a1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,37 @@
1
+ # v0.7.0 2016-03-30
2
+
3
+ Major focus of this release is to make complex type composition possible and improving constraint errors to be more meaningful.
4
+
5
+ ## Added
6
+
7
+ - `Type#try` interface that tries to process the input and return a result object which can be either a success or failure (solnic)
8
+ - `#meta` interface for setting arbitrary meta data on types (solnic)
9
+ - `ConstraintError` has a message which includes information about the predicate which failed ie `nil violates constraints (type?(String) failed)` (solnic)
10
+ - `Struct` uses `Dry::Equalizer` too, just like `Value` (AMHOL)
11
+ - `Sum::Constrained` which has a disjunction rule built from its types (solnic)
12
+ - Compiler supports `[:constructor, [primitive, fn_proc]]` nodes (solnic)
13
+ - Compiler supports building schema-less `form.hash` types (solnic)
14
+
15
+ ## Fixed
16
+
17
+ - `Sum` now supports complex types like `Array` or `Hash` with member types and/or constraints (solnic)
18
+ - `Default#constrained` will properly wrap a new constrained type (solnic)
19
+
20
+ ## Changed
21
+
22
+ - [BREAKING] Renamed `Type#{optional=>maybe}` (AMHOL)
23
+ - [BREAKING] `Type#optional(other)` builds a sum: `Strict::Nil | other` (AMHOL)
24
+ - [BREAKING] Type objects are now frozen (solnic)
25
+ - [BREAKING] `Value` instances are frozen (AMHOL)
26
+ - `Array` is no longer a constructor and has a `Array::Member` subclass (solnic)
27
+ - `Hash` is no longer a constructor and is split into `Hash::Safe`, `Hash::Strict` and `Hash::Symbolized` (solnic)
28
+ - `Constrained` has now a `Constrained::Coercible` subclass which will try to apply its type prior applying its rule (solnic)
29
+ - `#maybe` uses `Strict::Nil` now (solnic)
30
+ - `Type#default` will raise if `nil` was passed for `Maybe` type (solnic)
31
+ - `Hash` with a schema will set maybe values for missing keys or nils (flash-gordon)
32
+
33
+ [Compare v0.6.0...v0.7.0](https://github.com/dryrb/dry-types/compare/v0.6.0...v0.7.0)
34
+
1
35
  # v0.6.0 2016-03-16
2
36
 
3
37
  Renamed from `dry-data` to `dry-types` and:
data/Gemfile CHANGED
@@ -8,6 +8,8 @@ end
8
8
 
9
9
  group :tools do
10
10
  gem 'byebug', platform: :mri
11
+ gem 'mutant'
12
+ gem 'mutant-rspec'
11
13
  end
12
14
 
13
15
  group :benchmarks do
data/dry-types.gemspec CHANGED
@@ -27,15 +27,15 @@ Gem::Specification.new do |spec|
27
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ["lib"]
29
29
 
30
- spec.add_runtime_dependency 'thread_safe', '~> 0.3'
31
- spec.add_runtime_dependency 'dry-container', '~> 0.2'
30
+ spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
31
+ spec.add_runtime_dependency 'dry-container', '~> 0.3'
32
32
  spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
33
33
  spec.add_runtime_dependency 'dry-configurable', '~> 0.1'
34
34
  spec.add_runtime_dependency 'dry-logic', '~> 0.2', '>= 0.2.0'
35
35
  spec.add_runtime_dependency 'inflecto', '~> 0.0.0', '>= 0.0.2'
36
36
  spec.add_runtime_dependency 'kleisli', '~> 0.2'
37
37
 
38
- spec.add_development_dependency "bundler", "~> 1.7"
39
- spec.add_development_dependency "rake", "~> 10.0"
38
+ spec.add_development_dependency "bundler", "~> 1.6"
39
+ spec.add_development_dependency "rake", "~> 11.0"
40
40
  spec.add_development_dependency "rspec", "~> 3.3"
41
41
  end
data/lib/dry/types.rb CHANGED
@@ -3,7 +3,7 @@ require 'date'
3
3
  require 'set'
4
4
 
5
5
  require 'inflecto'
6
- require 'thread_safe'
6
+ require 'concurrent'
7
7
 
8
8
  require 'dry-container'
9
9
  require 'dry-equalizer'
@@ -15,27 +15,14 @@ require 'dry/types/constructor'
15
15
  require 'dry/types/struct'
16
16
  require 'dry/types/value'
17
17
 
18
+ require 'dry/types/errors'
19
+
18
20
  module Dry
19
21
  module Types
20
22
  extend Dry::Configurable
21
23
 
22
24
  setting :namespace, self
23
25
 
24
- class SchemaError < TypeError
25
- def initialize(key, value)
26
- super("#{value.inspect} (#{value.class}) has invalid type for :#{key}")
27
- end
28
- end
29
-
30
- class SchemaKeyError < KeyError
31
- def initialize(key)
32
- super(":#{key} is missing in Hash input")
33
- end
34
- end
35
-
36
- StructError = Class.new(TypeError)
37
- ConstraintError = Class.new(TypeError)
38
-
39
26
  TYPE_SPEC_REGEX = %r[(.+)<(.+)>].freeze
40
27
 
41
28
  def self.module
@@ -103,7 +90,7 @@ module Dry
103
90
  end
104
91
 
105
92
  def self.type_map
106
- @type_map ||= ThreadSafe::Cache.new
93
+ @type_map ||= Concurrent::Map.new
107
94
  end
108
95
 
109
96
  def self.type_keys
@@ -0,0 +1,17 @@
1
+ require 'dry/types/array/member'
2
+
3
+ module Dry
4
+ module Types
5
+ class Array < Definition
6
+ def member(type)
7
+ member =
8
+ case type
9
+ when String, Class then Types[type]
10
+ else type
11
+ end
12
+
13
+ Array::Member.new(primitive, options.merge(member: member))
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ module Dry
2
+ module Types
3
+ class Array < Definition
4
+ class Member < Array
5
+ attr_reader :member
6
+
7
+ def initialize(primitive, options = {})
8
+ @member = options.fetch(:member)
9
+ super
10
+ end
11
+
12
+ def call(input, meth = :call)
13
+ input.map { |el| member.__send__(meth, el) }
14
+ end
15
+ alias_method :[], :call
16
+
17
+ def try(input, &block)
18
+ result = call(input, :try)
19
+
20
+ if result.all?(&:success?)
21
+ success(result.map(&:input))
22
+ else
23
+ failure = failure(input, result.select(&:failure?))
24
+ block ? yield(failure) : failure
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,16 +1,25 @@
1
1
  module Dry
2
2
  module Types
3
3
  module Builder
4
+ def constrained_type
5
+ Constrained
6
+ end
7
+
4
8
  def |(other)
5
- Sum.new(self, other)
9
+ klass = is_a?(Constrained) && other.is_a?(Constrained) ? Sum::Constrained : Sum
10
+ klass.new(self, other)
6
11
  end
7
12
 
8
13
  def optional
9
- Optional.new(Types['nil'] | self)
14
+ Types['strict.nil'] | self
15
+ end
16
+
17
+ def maybe
18
+ Maybe.new(Types['strict.nil'] | self)
10
19
  end
11
20
 
12
21
  def constrained(options)
13
- Constrained.new(self, rule: Types.Rule(options))
22
+ constrained_type.new(self, rule: Types.Rule(options))
14
23
  end
15
24
 
16
25
  def default(input = nil, &block)
@@ -31,8 +40,8 @@ module Dry
31
40
  Safe.new(self)
32
41
  end
33
42
 
34
- def constructor(constructor, options = {})
35
- Constructor.new(with(options), fn: constructor)
43
+ def constructor(constructor = nil, **options, &block)
44
+ Constructor.new(with(options), fn: constructor || block)
36
45
  end
37
46
  end
38
47
  end
@@ -41,6 +50,6 @@ end
41
50
  require 'dry/types/default'
42
51
  require 'dry/types/constrained'
43
52
  require 'dry/types/enum'
44
- require 'dry/types/optional'
53
+ require 'dry/types/maybe'
45
54
  require 'dry/types/safe'
46
55
  require 'dry/types/sum'
@@ -1,6 +1,7 @@
1
1
  require 'date'
2
2
  require 'bigdecimal'
3
3
  require 'bigdecimal/util'
4
+ require 'time'
4
5
 
5
6
  module Dry
6
7
  module Types
@@ -8,7 +9,7 @@ module Dry
8
9
  module Form
9
10
  TRUE_VALUES = %w[1 on t true y yes].freeze
10
11
  FALSE_VALUES = %w[0 off f false n no].freeze
11
- BOOLEAN_MAP = Hash[TRUE_VALUES.product([true]) + FALSE_VALUES.product([false])].freeze
12
+ BOOLEAN_MAP = ::Hash[TRUE_VALUES.product([true]) + FALSE_VALUES.product([false])].freeze
12
13
 
13
14
  def self.to_nil(input)
14
15
  if input.is_a?(String) && input == ''
@@ -15,6 +15,11 @@ module Dry
15
15
  send(:"visit_#{node[0]}", node[1], *args)
16
16
  end
17
17
 
18
+ def visit_constructor(node)
19
+ primitive, fn = node
20
+ Types::Constructor.new(primitive, &fn)
21
+ end
22
+
18
23
  def visit_type(node)
19
24
  type, args = node
20
25
  meth = :"visit_#{type.tr('.', '_')}"
@@ -44,8 +49,12 @@ module Dry
44
49
  end
45
50
 
46
51
  def visit_form_hash(node)
47
- constructor, schema = node
48
- merge_with('form.hash', constructor, schema)
52
+ if node
53
+ constructor, schema = node
54
+ merge_with('form.hash', constructor, schema)
55
+ else
56
+ registry['form.hash']
57
+ end
49
58
  end
50
59
 
51
60
  def visit_key(node)
@@ -1,5 +1,6 @@
1
1
  require 'dry/types/decorator'
2
2
  require 'dry/types/constraints'
3
+ require 'dry/types/constrained/coercible'
3
4
 
4
5
  module Dry
5
6
  module Types
@@ -15,22 +16,24 @@ module Dry
15
16
  end
16
17
 
17
18
  def call(input)
18
- result = try(input)
19
-
20
- if valid?(result)
21
- result
22
- else
23
- raise ConstraintError, "#{input.inspect} violates constraints"
24
- end
19
+ try(input) do |result|
20
+ raise ConstraintError, result
21
+ end.input
25
22
  end
26
23
  alias_method :[], :call
27
24
 
28
- def try(input)
29
- type[input]
25
+ def try(input, &block)
26
+ validation = rule.(input)
27
+
28
+ if validation.success?
29
+ type.try(input, &block)
30
+ else
31
+ block ? yield(validation) : validation
32
+ end
30
33
  end
31
34
 
32
- def valid?(input)
33
- rule.(input).success?
35
+ def valid?(value)
36
+ rule.(value).success?
34
37
  end
35
38
 
36
39
  def constrained(options)
@@ -0,0 +1,23 @@
1
+ module Dry
2
+ module Types
3
+ class Constrained
4
+ class Coercible < Constrained
5
+ def try(input, &block)
6
+ result = type.try(input)
7
+
8
+ if result.success?
9
+ validation = rule.(result.input)
10
+
11
+ if validation.success?
12
+ result
13
+ else
14
+ block ? yield(validation) : validation
15
+ end
16
+ else
17
+ block ? yield(result) : result
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -5,21 +5,19 @@ module Dry
5
5
  class Constructor < Definition
6
6
  include Dry::Equalizer(:type)
7
7
 
8
- undef_method :primitive
9
-
10
8
  attr_reader :fn
11
9
 
12
10
  attr_reader :type
13
11
 
14
- def self.new(input, options = {})
15
- type = input.is_a?(Definition) ? input : Definition.new(input)
16
- super(type, options)
12
+ def self.new(input, options = {}, &block)
13
+ type = input.is_a?(Builder) ? input : Definition.new(input)
14
+ super(type, options, &block)
17
15
  end
18
16
 
19
- def initialize(type, options = {})
20
- super
17
+ def initialize(type, options = {}, &block)
21
18
  @type = type
22
- @fn = options.fetch(:fn)
19
+ @fn = options.fetch(:fn, block)
20
+ super
23
21
  end
24
22
 
25
23
  def primitive
@@ -27,26 +25,43 @@ module Dry
27
25
  end
28
26
 
29
27
  def call(input)
30
- fn[input]
28
+ type[fn[input]]
31
29
  end
32
30
  alias_method :[], :call
33
31
 
34
- def constructor(new_fn, options = {})
35
- with(options.merge(fn: -> input { new_fn[fn[input]] }))
32
+ def try(input, &block)
33
+ type.try(fn[input], &block)
34
+ rescue TypeError => e
35
+ failure(input, e.message)
36
36
  end
37
37
 
38
- def respond_to_missing?(meth, include_private = false)
39
- super || type.respond_to?(meth)
38
+ def constructor(new_fn = nil, **options, &block)
39
+ left = new_fn || block
40
+ right = fn
41
+
42
+ with(options.merge(fn: -> input { left[right[input]] }))
43
+ end
44
+
45
+ def valid?(value)
46
+ super && type.valid?(value)
47
+ end
48
+
49
+ def constrained_type
50
+ Constrained::Coercible
40
51
  end
41
52
 
42
53
  private
43
54
 
55
+ def respond_to_missing?(meth, include_private = false)
56
+ super || type.respond_to?(meth)
57
+ end
58
+
44
59
  def method_missing(meth, *args, &block)
45
60
  if type.respond_to?(meth)
46
61
  response = type.__send__(meth, *args, &block)
47
62
 
48
- if response.is_a?(Constructor)
49
- constructor(response.fn, options.merge(response.options))
63
+ if response.kind_of?(Builder)
64
+ self.class.new(response, options)
50
65
  else
51
66
  response
52
67
  end
@@ -5,8 +5,8 @@ module Dry
5
5
  int: Integer,
6
6
  float: Float,
7
7
  decimal: BigDecimal,
8
- array: Array,
9
- hash: Hash
8
+ array: ::Array,
9
+ hash: ::Hash
10
10
  }.freeze
11
11
 
12
12
  NON_COERCIBLE = {
@@ -40,12 +40,23 @@ module Dry
40
40
  # Register non-coercible maybe types
41
41
  ALL_PRIMITIVES.each_key do |name|
42
42
  next if name == :nil
43
- register("maybe.strict.#{name}", self["strict.#{name}"].optional)
43
+ register("maybe.strict.#{name}", self["strict.#{name}"].maybe)
44
44
  end
45
45
 
46
46
  # Register coercible maybe types
47
47
  COERCIBLE.each_key do |name|
48
- register("maybe.coercible.#{name}", self["coercible.#{name}"].optional)
48
+ register("maybe.coercible.#{name}", self["coercible.#{name}"].maybe)
49
+ end
50
+
51
+ # Register non-coercible optional types
52
+ ALL_PRIMITIVES.each_key do |name|
53
+ next if name == :nil
54
+ register("optional.strict.#{name}", self["strict.#{name}"].optional)
55
+ end
56
+
57
+ # Register coercible optional types
58
+ COERCIBLE.each_key do |name|
59
+ register("optional.coercible.#{name}", self["coercible.#{name}"].optional)
49
60
  end
50
61
 
51
62
  # Register :bool since it's common and not a built-in Ruby type :(
@@ -12,16 +12,16 @@ module Dry
12
12
  type.constructor
13
13
  end
14
14
 
15
- def valid?(input)
16
- type.valid?(input)
15
+ def with(new_options)
16
+ self.class.new(type, options.merge(new_options))
17
17
  end
18
18
 
19
- def respond_to_missing?(meth, include_private = false)
20
- super || type.respond_to?(meth)
19
+ def valid?(value)
20
+ type.valid?(value)
21
21
  end
22
22
 
23
- def with(new_options)
24
- self.class.new(type, options.merge(new_options))
23
+ def respond_to_missing?(meth, include_private = false)
24
+ super || type.respond_to?(meth)
25
25
  end
26
26
 
27
27
  private
@@ -29,6 +29,10 @@ module Dry
29
29
  @value = options.fetch(:value)
30
30
  end
31
31
 
32
+ def constrained(*args)
33
+ type.constrained(*args).default(value)
34
+ end
35
+
32
36
  def call(input)
33
37
  if input.nil?
34
38
  evaluate
@@ -1,9 +1,12 @@
1
1
  require 'dry/types/builder'
2
+ require 'dry/types/result'
3
+ require 'dry/types/options'
2
4
 
3
5
  module Dry
4
6
  module Types
5
7
  class Definition
6
8
  include Dry::Equalizer(:primitive, :options)
9
+ include Options
7
10
  include Builder
8
11
 
9
12
  attr_reader :options
@@ -12,21 +15,18 @@ module Dry
12
15
 
13
16
  def self.[](primitive)
14
17
  if primitive == ::Array
15
- Definition::Array
18
+ Types::Array
16
19
  elsif primitive == ::Hash
17
- Definition::Hash
20
+ Types::Hash
18
21
  else
19
22
  self
20
23
  end
21
24
  end
22
25
 
23
26
  def initialize(primitive, options = {})
27
+ super
24
28
  @primitive = primitive
25
- @options = options
26
- end
27
-
28
- def with(new_options)
29
- self.class.new(primitive, options.merge(new_options))
29
+ freeze
30
30
  end
31
31
 
32
32
  def name
@@ -38,16 +38,36 @@ module Dry
38
38
  end
39
39
  alias_method :[], :call
40
40
 
41
- def try(input)
42
- call(input)
41
+ def try(input, &block)
42
+ output = call(input)
43
+
44
+ if valid?(output)
45
+ success(output)
46
+ else
47
+ failure = failure(output, "#{output.inspect} must be an instance of #{primitive}")
48
+ block ? yield(failure) : failure
49
+ end
50
+ end
51
+
52
+ def success(*args)
53
+ result(Result::Success, *args)
54
+ end
55
+
56
+ def failure(*args)
57
+ result(Result::Failure, *args)
58
+ end
59
+
60
+ def result(klass, *args)
61
+ klass.new(*args)
43
62
  end
44
63
 
45
- def valid?(input)
46
- input.is_a?(primitive)
64
+ def primitive?(value)
65
+ value.is_a?(primitive)
47
66
  end
67
+ alias_method :valid?, :primitive?
48
68
  end
49
69
  end
50
70
  end
51
71
 
52
- require 'dry/types/definition/array'
53
- require 'dry/types/definition/hash'
72
+ require 'dry/types/array'
73
+ require 'dry/types/hash'
@@ -0,0 +1,47 @@
1
+ module Dry
2
+ module Types
3
+ extend Dry::Configurable
4
+
5
+ setting :namespace, self
6
+
7
+ class SchemaError < TypeError
8
+ def initialize(key, value)
9
+ super("#{value.inspect} (#{value.class}) has invalid type for :#{key}")
10
+ end
11
+ end
12
+
13
+ class SchemaKeyError < KeyError
14
+ def initialize(key)
15
+ super(":#{key} is missing in Hash input")
16
+ end
17
+ end
18
+
19
+ StructError = Class.new(TypeError)
20
+
21
+ ConstraintError = Class.new(TypeError) do
22
+ attr_reader :result
23
+
24
+ def initialize(result)
25
+ @result = result
26
+ if result.is_a?(String)
27
+ super
28
+ else
29
+ super("#{result.input.inspect} violates constraints (#{failure_message})")
30
+ end
31
+ end
32
+
33
+ def input
34
+ result.input
35
+ end
36
+
37
+ def failure_message
38
+ if result.respond_to?(:rule)
39
+ rule = result.rule
40
+ "#{rule.predicate.id}(#{rule.predicate.args.map(&:inspect).join(', ')}) failed"
41
+ else
42
+ result.inspect
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -27,7 +27,7 @@ module Dry
27
27
  end
28
28
 
29
29
  register('form.bool') do
30
- self['form.true'] | self['form.false']
30
+ (self['form.true'] | self['form.false']).safe
31
31
  end
32
32
 
33
33
  register('form.int') do
@@ -0,0 +1,27 @@
1
+ require 'dry/types/hash/schema'
2
+
3
+ module Dry
4
+ module Types
5
+ class Hash < Definition
6
+ def schema(type_map, klass = Safe)
7
+ member_types = type_map.each_with_object({}) { |(name, type), result|
8
+ result[name] =
9
+ case type
10
+ when String, Class then Types[type]
11
+ else type
12
+ end
13
+ }
14
+
15
+ klass.new(primitive, options.merge(member_types: member_types))
16
+ end
17
+
18
+ def strict(type_map)
19
+ schema(type_map, Strict)
20
+ end
21
+
22
+ def symbolized(type_map)
23
+ schema(type_map, Symbolized)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,83 @@
1
+ module Dry
2
+ module Types
3
+ class Hash < Definition
4
+ class Schema < Hash
5
+ attr_reader :member_types
6
+
7
+ def initialize(primitive, options = {})
8
+ @member_types = options.fetch(:member_types)
9
+ super
10
+ end
11
+
12
+ def try(hash, &block)
13
+ result = call(hash, :try)
14
+
15
+ if result.values.all?(&:success?)
16
+ success(result.each_with_object({}) { |(key, res), h| h[key] = res.input })
17
+ else
18
+ failure = failure(hash, result)
19
+ block ? yield(failure) : failure
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def resolve_missing_value(result, key, type)
26
+ if type.is_a?(Default)
27
+ result[key] = type.evaluate
28
+ elsif type.is_a?(Maybe)
29
+ result[key] = type[nil]
30
+ end
31
+ end
32
+ end
33
+
34
+ class Safe < Schema
35
+ def call(hash, meth = :call)
36
+ member_types.each_with_object({}) do |(key, type), result|
37
+ if hash.key?(key)
38
+ result[key] = type.__send__(meth, hash[key])
39
+ else
40
+ resolve_missing_value(result, key, type)
41
+ end
42
+ end
43
+ end
44
+ alias_method :[], :call
45
+ end
46
+
47
+ class Symbolized < Schema
48
+ def call(hash, meth = :call)
49
+ member_types.each_with_object({}) do |(key, type), result|
50
+ if hash.key?(key)
51
+ result[key] = type.__send__(meth, hash[key])
52
+ else
53
+ key_name = key.to_s
54
+
55
+ if hash.key?(key_name)
56
+ result[key] = type.__send__(meth, hash[key_name])
57
+ else
58
+ resolve_missing_value(result, key, type)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ alias_method :[], :call
64
+ end
65
+
66
+ class Strict < Schema
67
+ def call(hash, meth = :call)
68
+ member_types.each_with_object({}) do |(key, type), result|
69
+ begin
70
+ value = hash.fetch(key)
71
+ result[key] = type.__send__(meth, value)
72
+ rescue TypeError
73
+ raise SchemaError.new(key, value)
74
+ rescue KeyError
75
+ raise SchemaKeyError.new(key)
76
+ end
77
+ end
78
+ end
79
+ alias_method :[], :call
80
+ end
81
+ end
82
+ end
83
+ end
@@ -3,7 +3,7 @@ require 'dry/types/decorator'
3
3
 
4
4
  module Dry
5
5
  module Types
6
- class Optional
6
+ class Maybe
7
7
  include Decorator
8
8
  include Builder
9
9
 
@@ -11,6 +11,14 @@ module Dry
11
11
  input.is_a?(Kleisli::Maybe) ? input : Maybe(type[input])
12
12
  end
13
13
  alias_method :[], :call
14
+
15
+ def default(value)
16
+ if value.nil?
17
+ raise ArgumentError, "nil cannot be used as a default of an optional type"
18
+ else
19
+ super
20
+ end
21
+ end
14
22
  end
15
23
  end
16
24
  end
@@ -0,0 +1,23 @@
1
+ module Dry
2
+ module Types
3
+ module Options
4
+ attr_reader :options
5
+
6
+ attr_reader :meta
7
+
8
+ def initialize(*args, **options )
9
+ @__args__ = args
10
+ @options = options
11
+ @meta = options.fetch(:meta, {})
12
+ end
13
+
14
+ def with(new_options)
15
+ self.class.new(*@__args__, options.merge(new_options))
16
+ end
17
+
18
+ def meta(data = nil)
19
+ data ? with(meta: data) : @meta
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ module Dry
2
+ module Types
3
+ module Result
4
+ class Success < Struct.new(:input)
5
+ def success?
6
+ true
7
+ end
8
+
9
+ def failure?
10
+ false
11
+ end
12
+ end
13
+
14
+ class Failure < Struct.new(:input, :error)
15
+ def success?
16
+ false
17
+ end
18
+
19
+ def failure?
20
+ true
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -7,14 +7,20 @@ module Dry
7
7
  include Builder
8
8
 
9
9
  def call(input)
10
- if input.is_a?(primitive)
11
- type.call(input)
10
+ if type.primitive?(input) || type.is_a?(Sum) || type.is_a?(Constructor)
11
+ type[input]
12
12
  else
13
13
  input
14
14
  end
15
+ rescue TypeError
16
+ input
15
17
  end
16
18
  alias_method :[], :call
17
19
 
20
+ def try(input, &block)
21
+ type.try(input, &block)
22
+ end
23
+
18
24
  private
19
25
 
20
26
  def decorate?(response)
@@ -7,7 +7,18 @@ module Dry
7
7
 
8
8
  def self.inherited(klass)
9
9
  super
10
- Types.register_class(klass) unless klass == Value
10
+
11
+ klass.instance_variable_set('@equalizer', Equalizer.new)
12
+ klass.send(:include, klass.equalizer)
13
+
14
+ unless klass == Value
15
+ klass.instance_variable_set('@constructor', Types['coercible.hash'])
16
+ Types.register_class(klass)
17
+ end
18
+ end
19
+
20
+ def self.equalizer
21
+ @equalizer
11
22
  end
12
23
 
13
24
  def self.attribute(name, type)
@@ -21,6 +32,7 @@ module Dry
21
32
  @constructor = Types['coercible.hash'].public_send(constructor_type, schema)
22
33
 
23
34
  attr_reader(*(new_schema.keys - prev_schema.keys))
35
+ equalizer.instance_variable_get('@keys').concat(schema.keys).uniq!
24
36
 
25
37
  self
26
38
  end
data/lib/dry/types/sum.rb CHANGED
@@ -1,14 +1,25 @@
1
+ require 'dry/types/options'
2
+
1
3
  module Dry
2
4
  module Types
3
5
  class Sum
4
6
  include Builder
7
+ include Options
5
8
 
6
9
  attr_reader :left
7
10
 
8
11
  attr_reader :right
9
12
 
10
- def initialize(left, right)
13
+ class Constrained < Sum
14
+ def rule
15
+ left.rule | right.rule
16
+ end
17
+ end
18
+
19
+ def initialize(left, right, options = {})
20
+ super
11
21
  @left, @right = left, right
22
+ freeze
12
23
  end
13
24
 
14
25
  def name
@@ -16,18 +27,32 @@ module Dry
16
27
  end
17
28
 
18
29
  def call(input)
19
- value = left.try(input)
30
+ try(input) do |result|
31
+ raise ConstraintError, result
32
+ end.input
33
+ end
34
+ alias_method :[], :call
20
35
 
21
- if left.valid?(value)
22
- value
36
+ def try(input, &block)
37
+ result = left.try(input) do
38
+ right.try(input)
39
+ end
40
+
41
+ return result if result.success?
42
+
43
+ if block
44
+ yield(result)
23
45
  else
24
- right[value]
46
+ result
25
47
  end
26
48
  end
27
- alias_method :[], :call
28
49
 
29
- def valid?(input)
30
- left.valid?(input) || right.valid?(input)
50
+ def primitive?(value)
51
+ left.primitive?(value) || right.primitive?(value)
52
+ end
53
+
54
+ def valid?(value)
55
+ left.valid?(value) || right.valid?(value)
31
56
  end
32
57
  end
33
58
  end
@@ -3,19 +3,8 @@ require 'dry/types/struct'
3
3
  module Dry
4
4
  module Types
5
5
  class Value < Struct
6
- def self.inherited(klass)
7
- super
8
- klass.instance_variable_set('@equalizer', Equalizer.new)
9
- klass.send(:include, klass.equalizer)
10
- end
11
-
12
- def self.attributes(*args)
13
- super
14
- equalizer.instance_variable_get('@keys').concat(schema.keys).uniq!
15
- end
16
-
17
- def self.equalizer
18
- @equalizer
6
+ def self.new(*, &_block)
7
+ super.freeze
19
8
  end
20
9
  end
21
10
  end
@@ -1,5 +1,5 @@
1
1
  module Dry
2
2
  module Types
3
- VERSION = '0.6.0'.freeze
3
+ VERSION = '0.7.0'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-types
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-03-16 00:00:00.000000000 Z
11
+ date: 2016-03-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: thread_safe
14
+ name: concurrent-ruby
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.3'
19
+ version: '1.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.3'
26
+ version: '1.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: dry-container
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.2'
33
+ version: '0.3'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.2'
40
+ version: '0.3'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: dry-equalizer
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -126,28 +126,28 @@ dependencies:
126
126
  requirements:
127
127
  - - "~>"
128
128
  - !ruby/object:Gem::Version
129
- version: '1.7'
129
+ version: '1.6'
130
130
  type: :development
131
131
  prerelease: false
132
132
  version_requirements: !ruby/object:Gem::Requirement
133
133
  requirements:
134
134
  - - "~>"
135
135
  - !ruby/object:Gem::Version
136
- version: '1.7'
136
+ version: '1.6'
137
137
  - !ruby/object:Gem::Dependency
138
138
  name: rake
139
139
  requirement: !ruby/object:Gem::Requirement
140
140
  requirements:
141
141
  - - "~>"
142
142
  - !ruby/object:Gem::Version
143
- version: '10.0'
143
+ version: '11.0'
144
144
  type: :development
145
145
  prerelease: false
146
146
  version_requirements: !ruby/object:Gem::Requirement
147
147
  requirements:
148
148
  - - "~>"
149
149
  - !ruby/object:Gem::Version
150
- version: '10.0'
150
+ version: '11.0'
151
151
  - !ruby/object:Gem::Dependency
152
152
  name: rspec
153
153
  requirement: !ruby/object:Gem::Requirement
@@ -185,10 +185,13 @@ files:
185
185
  - dry-types.gemspec
186
186
  - lib/dry-types.rb
187
187
  - lib/dry/types.rb
188
+ - lib/dry/types/array.rb
189
+ - lib/dry/types/array/member.rb
188
190
  - lib/dry/types/builder.rb
189
191
  - lib/dry/types/coercions/form.rb
190
192
  - lib/dry/types/compiler.rb
191
193
  - lib/dry/types/constrained.rb
194
+ - lib/dry/types/constrained/coercible.rb
192
195
  - lib/dry/types/constraints.rb
193
196
  - lib/dry/types/constructor.rb
194
197
  - lib/dry/types/container.rb
@@ -196,11 +199,14 @@ files:
196
199
  - lib/dry/types/decorator.rb
197
200
  - lib/dry/types/default.rb
198
201
  - lib/dry/types/definition.rb
199
- - lib/dry/types/definition/array.rb
200
- - lib/dry/types/definition/hash.rb
201
202
  - lib/dry/types/enum.rb
203
+ - lib/dry/types/errors.rb
202
204
  - lib/dry/types/form.rb
203
- - lib/dry/types/optional.rb
205
+ - lib/dry/types/hash.rb
206
+ - lib/dry/types/hash/schema.rb
207
+ - lib/dry/types/maybe.rb
208
+ - lib/dry/types/options.rb
209
+ - lib/dry/types/result.rb
204
210
  - lib/dry/types/safe.rb
205
211
  - lib/dry/types/struct.rb
206
212
  - lib/dry/types/sum.rb
@@ -227,7 +233,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
227
233
  version: '0'
228
234
  requirements: []
229
235
  rubyforge_project:
230
- rubygems_version: 2.4.5.1
236
+ rubygems_version: 2.5.1
231
237
  signing_key:
232
238
  specification_version: 4
233
239
  summary: Type system for Ruby supporting coercions, constraints and complex types
@@ -1,24 +0,0 @@
1
- module Dry
2
- module Types
3
- class Definition
4
- class Array < Definition
5
- def self.constructor(member_constructor, array)
6
- array.map { |value| member_constructor[value] }
7
- end
8
-
9
- def member(type)
10
- member_constructor =
11
- case type
12
- when String, Class then Types[type]
13
- else type
14
- end
15
-
16
- array_constructor = self.class
17
- .method(:constructor).to_proc.curry.(member_constructor)
18
-
19
- constructor(array_constructor, member: member_constructor)
20
- end
21
- end
22
- end
23
- end
24
- end
@@ -1,68 +0,0 @@
1
- module Dry
2
- module Types
3
- class Definition
4
- class Hash < Definition
5
- def self.safe_constructor(types, hash)
6
- types.each_with_object({}) do |(key, type), result|
7
- if hash.key?(key)
8
- result[key] = type[hash[key]]
9
- elsif type.is_a?(Default)
10
- result[key] = type.evaluate
11
- end
12
- end
13
- end
14
-
15
- def self.symbolized_constructor(types, hash)
16
- types.each_with_object({}) do |(key, type), result|
17
- if hash.key?(key)
18
- result[key] = type[hash[key]]
19
- else
20
- key_name = key.to_s
21
-
22
- if hash.key?(key_name)
23
- result[key] = type[hash[key_name]]
24
- elsif type.is_a?(Default)
25
- result[key] = type.evaluate
26
- end
27
- end
28
- end
29
- end
30
-
31
- def self.strict_constructor(types, hash)
32
- types.each_with_object({}) do |(key, type), result|
33
- begin
34
- value = hash.fetch(key)
35
- result[key] = type[value]
36
- rescue TypeError
37
- raise SchemaError.new(key, value)
38
- rescue KeyError
39
- raise SchemaKeyError.new(key)
40
- end
41
- end
42
- end
43
-
44
- def strict(type_map)
45
- schema(type_map, :strict_constructor)
46
- end
47
-
48
- def symbolized(type_map)
49
- schema(type_map, :symbolized_constructor)
50
- end
51
-
52
- def schema(type_map, meth = :safe_constructor)
53
- types = type_map.each_with_object({}) { |(name, type), result|
54
- result[name] =
55
- case type
56
- when String, Class then Types[type]
57
- else type
58
- end
59
- }
60
-
61
- fn = self.class.method(meth).to_proc.curry.(types)
62
-
63
- constructor(fn, schema: types)
64
- end
65
- end
66
- end
67
- end
68
- end