dry-types 0.6.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.
@@ -0,0 +1,7 @@
1
+ module Dry
2
+ module Types
3
+ class Container
4
+ include Dry::Container::Mixin
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,57 @@
1
+ module Dry
2
+ module Types
3
+ COERCIBLE = {
4
+ string: String,
5
+ int: Integer,
6
+ float: Float,
7
+ decimal: BigDecimal,
8
+ array: Array,
9
+ hash: Hash
10
+ }.freeze
11
+
12
+ NON_COERCIBLE = {
13
+ nil: NilClass,
14
+ symbol: Symbol,
15
+ class: Class,
16
+ true: TrueClass,
17
+ false: FalseClass,
18
+ date: Date,
19
+ date_time: DateTime,
20
+ time: Time
21
+ }.freeze
22
+
23
+ ALL_PRIMITIVES = COERCIBLE.merge(NON_COERCIBLE).freeze
24
+
25
+ # Register built-in types that are non-coercible through kernel methods
26
+ ALL_PRIMITIVES.each do |name, primitive|
27
+ register(name.to_s, Definition[primitive].new(primitive))
28
+ end
29
+
30
+ # Register strict built-in types that are non-coercible through kernel methods
31
+ ALL_PRIMITIVES.each do |name, primitive|
32
+ register("strict.#{name}", self[name.to_s].constrained(type: primitive))
33
+ end
34
+
35
+ # Register built-in primitive types with kernel coercion methods
36
+ COERCIBLE.each do |name, primitive|
37
+ register("coercible.#{name}", self[name.to_s].constructor(Kernel.method(primitive.name)))
38
+ end
39
+
40
+ # Register non-coercible maybe types
41
+ ALL_PRIMITIVES.each_key do |name|
42
+ next if name == :nil
43
+ register("maybe.strict.#{name}", self["strict.#{name}"].optional)
44
+ end
45
+
46
+ # Register coercible maybe types
47
+ COERCIBLE.each_key do |name|
48
+ register("maybe.coercible.#{name}", self["coercible.#{name}"].optional)
49
+ end
50
+
51
+ # Register :bool since it's common and not a built-in Ruby type :(
52
+ register("bool", self["true"] | self["false"])
53
+ register("strict.bool", self["strict.true"] | self["strict.false"])
54
+ end
55
+ end
56
+
57
+ require 'dry/types/form'
@@ -0,0 +1,48 @@
1
+ module Dry
2
+ module Types
3
+ module Decorator
4
+ attr_reader :type, :options
5
+
6
+ def initialize(type, options = {})
7
+ @type = type
8
+ @options = options
9
+ end
10
+
11
+ def constructor
12
+ type.constructor
13
+ end
14
+
15
+ def valid?(input)
16
+ type.valid?(input)
17
+ end
18
+
19
+ def respond_to_missing?(meth, include_private = false)
20
+ super || type.respond_to?(meth)
21
+ end
22
+
23
+ def with(new_options)
24
+ self.class.new(type, options.merge(new_options))
25
+ end
26
+
27
+ private
28
+
29
+ def decorate?(response)
30
+ response.kind_of?(type.class)
31
+ end
32
+
33
+ def method_missing(meth, *args, &block)
34
+ if type.respond_to?(meth)
35
+ response = type.__send__(meth, *args, &block)
36
+
37
+ if decorate?(response)
38
+ self.class.new(response, options)
39
+ else
40
+ response
41
+ end
42
+ else
43
+ super
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,43 @@
1
+ require 'dry/types/decorator'
2
+
3
+ module Dry
4
+ module Types
5
+ class Default
6
+ include Decorator
7
+ include Builder
8
+
9
+ class Callable < Default
10
+ def evaluate
11
+ value.call
12
+ end
13
+ end
14
+
15
+ attr_reader :value
16
+
17
+ alias_method :evaluate, :value
18
+
19
+ def self.[](value)
20
+ if value.respond_to?(:call)
21
+ Callable
22
+ else
23
+ self
24
+ end
25
+ end
26
+
27
+ def initialize(type, options)
28
+ super
29
+ @value = options.fetch(:value)
30
+ end
31
+
32
+ def call(input)
33
+ if input.nil?
34
+ evaluate
35
+ else
36
+ output = type[input]
37
+ output.nil? ? evaluate : output
38
+ end
39
+ end
40
+ alias_method :[], :call
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,53 @@
1
+ require 'dry/types/builder'
2
+
3
+ module Dry
4
+ module Types
5
+ class Definition
6
+ include Dry::Equalizer(:primitive, :options)
7
+ include Builder
8
+
9
+ attr_reader :options
10
+
11
+ attr_reader :primitive
12
+
13
+ def self.[](primitive)
14
+ if primitive == ::Array
15
+ Definition::Array
16
+ elsif primitive == ::Hash
17
+ Definition::Hash
18
+ else
19
+ self
20
+ end
21
+ end
22
+
23
+ def initialize(primitive, options = {})
24
+ @primitive = primitive
25
+ @options = options
26
+ end
27
+
28
+ def with(new_options)
29
+ self.class.new(primitive, options.merge(new_options))
30
+ end
31
+
32
+ def name
33
+ primitive.name
34
+ end
35
+
36
+ def call(input)
37
+ input
38
+ end
39
+ alias_method :[], :call
40
+
41
+ def try(input)
42
+ call(input)
43
+ end
44
+
45
+ def valid?(input)
46
+ input.is_a?(primitive)
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ require 'dry/types/definition/array'
53
+ require 'dry/types/definition/hash'
@@ -0,0 +1,24 @@
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
@@ -0,0 +1,68 @@
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
@@ -0,0 +1,30 @@
1
+ require 'dry/types/decorator'
2
+
3
+ module Dry
4
+ module Types
5
+ class Enum
6
+ include Decorator
7
+
8
+ attr_reader :values, :mapping
9
+
10
+ def initialize(type, options)
11
+ super
12
+ @values = options.fetch(:values).freeze
13
+ @values.each(&:freeze)
14
+ @mapping = values.each_with_object({}) { |v, h| h[values.index(v)] = v }.freeze
15
+ end
16
+
17
+ def call(input)
18
+ value =
19
+ if values.include?(input)
20
+ input
21
+ elsif mapping.key?(input)
22
+ mapping[input]
23
+ end
24
+
25
+ type[value || input]
26
+ end
27
+ alias_method :[], :call
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,53 @@
1
+ require 'dry/types/coercions/form'
2
+
3
+ module Dry
4
+ module Types
5
+ register('form.nil') do
6
+ self['nil'].constructor(Coercions::Form.method(:to_nil))
7
+ end
8
+
9
+ register('form.date') do
10
+ self['date'].constructor(Coercions::Form.method(:to_date))
11
+ end
12
+
13
+ register('form.date_time') do
14
+ self['date_time'].constructor(Coercions::Form.method(:to_date_time))
15
+ end
16
+
17
+ register('form.time') do
18
+ self['time'].constructor(Coercions::Form.method(:to_time))
19
+ end
20
+
21
+ register('form.true') do
22
+ self['true'].constructor(Coercions::Form.method(:to_true))
23
+ end
24
+
25
+ register('form.false') do
26
+ self['false'].constructor(Coercions::Form.method(:to_false))
27
+ end
28
+
29
+ register('form.bool') do
30
+ self['form.true'] | self['form.false']
31
+ end
32
+
33
+ register('form.int') do
34
+ self['int'].constructor(Coercions::Form.method(:to_int))
35
+ end
36
+
37
+ register('form.float') do
38
+ self['float'].constructor(Coercions::Form.method(:to_float))
39
+ end
40
+
41
+ register('form.decimal') do
42
+ self['decimal'].constructor(Coercions::Form.method(:to_decimal))
43
+ end
44
+
45
+ register('form.array') do
46
+ self['array'].safe
47
+ end
48
+
49
+ register('form.hash') do
50
+ self['hash'].safe
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,16 @@
1
+ require 'kleisli/maybe'
2
+ require 'dry/types/decorator'
3
+
4
+ module Dry
5
+ module Types
6
+ class Optional
7
+ include Decorator
8
+ include Builder
9
+
10
+ def call(input)
11
+ input.is_a?(Kleisli::Maybe) ? input : Maybe(type[input])
12
+ end
13
+ alias_method :[], :call
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,25 @@
1
+ require 'dry/types/decorator'
2
+
3
+ module Dry
4
+ module Types
5
+ class Safe
6
+ include Decorator
7
+ include Builder
8
+
9
+ def call(input)
10
+ if input.is_a?(primitive)
11
+ type.call(input)
12
+ else
13
+ input
14
+ end
15
+ end
16
+ alias_method :[], :call
17
+
18
+ private
19
+
20
+ def decorate?(response)
21
+ super || response.kind_of?(Constructor)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,64 @@
1
+ module Dry
2
+ module Types
3
+ class Struct
4
+ class << self
5
+ attr_reader :constructor
6
+ end
7
+
8
+ def self.inherited(klass)
9
+ super
10
+ Types.register_class(klass) unless klass == Value
11
+ end
12
+
13
+ def self.attribute(name, type)
14
+ attributes(name => type)
15
+ end
16
+
17
+ def self.attributes(new_schema)
18
+ prev_schema = schema
19
+
20
+ @schema = prev_schema.merge(new_schema)
21
+ @constructor = Types['coercible.hash'].public_send(constructor_type, schema)
22
+
23
+ attr_reader(*(new_schema.keys - prev_schema.keys))
24
+
25
+ self
26
+ end
27
+
28
+ def self.constructor_type(type = :strict)
29
+ @constructor_type ||= type
30
+ end
31
+
32
+ def self.schema
33
+ super_schema = superclass.respond_to?(:schema) ? superclass.schema : {}
34
+ super_schema.merge(@schema || {})
35
+ end
36
+
37
+ def self.new(attributes = {})
38
+ if attributes.is_a?(self)
39
+ attributes
40
+ else
41
+ super(constructor[attributes])
42
+ end
43
+ rescue SchemaError, SchemaKeyError => e
44
+ raise StructError, "[#{self}.new] #{e.message}"
45
+ end
46
+
47
+ def initialize(attributes)
48
+ attributes.each { |key, value| instance_variable_set("@#{key}", value) }
49
+ end
50
+
51
+ def [](name)
52
+ public_send(name)
53
+ end
54
+
55
+ def to_hash
56
+ self.class.schema.keys.each_with_object({}) { |key, result|
57
+ value = self[key]
58
+ result[key] = value.respond_to?(:to_hash) ? value.to_hash : value
59
+ }
60
+ end
61
+ alias_method :to_h, :to_hash
62
+ end
63
+ end
64
+ end