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 +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
|