dry-types 0.6.0 → 0.7.0

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