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 +4 -4
- data/CHANGELOG.md +34 -0
- data/Gemfile +2 -0
- data/dry-types.gemspec +4 -4
- data/lib/dry/types.rb +4 -17
- data/lib/dry/types/array.rb +17 -0
- data/lib/dry/types/array/member.rb +30 -0
- data/lib/dry/types/builder.rb +15 -6
- data/lib/dry/types/coercions/form.rb +2 -1
- data/lib/dry/types/compiler.rb +11 -2
- data/lib/dry/types/constrained.rb +14 -11
- data/lib/dry/types/constrained/coercible.rb +23 -0
- data/lib/dry/types/constructor.rb +30 -15
- data/lib/dry/types/core.rb +15 -4
- data/lib/dry/types/decorator.rb +6 -6
- data/lib/dry/types/default.rb +4 -0
- data/lib/dry/types/definition.rb +33 -13
- data/lib/dry/types/errors.rb +47 -0
- data/lib/dry/types/form.rb +1 -1
- data/lib/dry/types/hash.rb +27 -0
- data/lib/dry/types/hash/schema.rb +83 -0
- data/lib/dry/types/{optional.rb → maybe.rb} +9 -1
- data/lib/dry/types/options.rb +23 -0
- data/lib/dry/types/result.rb +25 -0
- data/lib/dry/types/safe.rb +8 -2
- data/lib/dry/types/struct.rb +13 -1
- data/lib/dry/types/sum.rb +33 -8
- data/lib/dry/types/value.rb +2 -13
- data/lib/dry/types/version.rb +1 -1
- metadata +21 -15
- data/lib/dry/types/definition/array.rb +0 -24
- data/lib/dry/types/definition/hash.rb +0 -68
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 02e8392346549c5801d50d1fb805951bea41e722
|
4
|
+
data.tar.gz: 6e4873ce2f3f8f92eb1bbbfccfbe68aed67524c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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 '
|
31
|
-
spec.add_runtime_dependency 'dry-container', '~> 0.
|
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.
|
39
|
-
spec.add_development_dependency "rake", "~>
|
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 '
|
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 ||=
|
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
|
data/lib/dry/types/builder.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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/
|
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 == ''
|
data/lib/dry/types/compiler.rb
CHANGED
@@ -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
|
-
|
48
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
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?(
|
33
|
-
rule.(
|
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?(
|
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
|
35
|
-
|
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
|
39
|
-
|
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.
|
49
|
-
|
63
|
+
if response.kind_of?(Builder)
|
64
|
+
self.class.new(response, options)
|
50
65
|
else
|
51
66
|
response
|
52
67
|
end
|
data/lib/dry/types/core.rb
CHANGED
@@ -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}"].
|
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}"].
|
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 :(
|
data/lib/dry/types/decorator.rb
CHANGED
@@ -12,16 +12,16 @@ module Dry
|
|
12
12
|
type.constructor
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
16
|
-
type.
|
15
|
+
def with(new_options)
|
16
|
+
self.class.new(type, options.merge(new_options))
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
20
|
-
|
19
|
+
def valid?(value)
|
20
|
+
type.valid?(value)
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
24
|
-
|
23
|
+
def respond_to_missing?(meth, include_private = false)
|
24
|
+
super || type.respond_to?(meth)
|
25
25
|
end
|
26
26
|
|
27
27
|
private
|
data/lib/dry/types/default.rb
CHANGED
data/lib/dry/types/definition.rb
CHANGED
@@ -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
|
-
|
18
|
+
Types::Array
|
16
19
|
elsif primitive == ::Hash
|
17
|
-
|
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
|
-
|
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
|
46
|
-
|
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/
|
53
|
-
require 'dry/types/
|
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
|
data/lib/dry/types/form.rb
CHANGED
@@ -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
|
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
|
data/lib/dry/types/safe.rb
CHANGED
@@ -7,14 +7,20 @@ module Dry
|
|
7
7
|
include Builder
|
8
8
|
|
9
9
|
def call(input)
|
10
|
-
if input.is_a?(
|
11
|
-
type
|
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)
|
data/lib/dry/types/struct.rb
CHANGED
@@ -7,7 +7,18 @@ module Dry
|
|
7
7
|
|
8
8
|
def self.inherited(klass)
|
9
9
|
super
|
10
|
-
|
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
|
-
|
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
|
-
|
30
|
+
try(input) do |result|
|
31
|
+
raise ConstraintError, result
|
32
|
+
end.input
|
33
|
+
end
|
34
|
+
alias_method :[], :call
|
20
35
|
|
21
|
-
|
22
|
-
|
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
|
-
|
46
|
+
result
|
25
47
|
end
|
26
48
|
end
|
27
|
-
alias_method :[], :call
|
28
49
|
|
29
|
-
def
|
30
|
-
left.
|
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
|
data/lib/dry/types/value.rb
CHANGED
@@ -3,19 +3,8 @@ require 'dry/types/struct'
|
|
3
3
|
module Dry
|
4
4
|
module Types
|
5
5
|
class Value < Struct
|
6
|
-
def self.
|
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
|
data/lib/dry/types/version.rb
CHANGED
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.
|
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-
|
11
|
+
date: 2016-03-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
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
|
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
|
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.
|
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.
|
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.
|
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.
|
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: '
|
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: '
|
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/
|
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.
|
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
|