dry-interface 1.0.0 → 1.0.4
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/lib/dry/concrete/extensions/default.rb +29 -0
- data/lib/dry/concrete/extensions/type.rb +229 -0
- data/lib/dry/concrete/extensions.rb +10 -0
- data/lib/dry/concrete/value.rb +22 -0
- data/lib/dry/concrete.rb +118 -0
- data/lib/dry/interface/interfaces/abstract.rb +14 -5
- data/lib/dry/interface/interfaces/concrete.rb +28 -8
- data/lib/dry/interface/interfaces/unit.rb +50 -0
- data/lib/dry/interface/interfaces.rb +1 -2
- data/lib/dry/interface/patch.rb +11 -0
- data/lib/dry/interface/types.rb +9 -0
- data/lib/dry/interface.rb +85 -40
- data/lib/dry/value.rb +22 -0
- data/lib/dry.rb +5 -0
- metadata +14 -6
- data/lib/dry/interface/interfaces/common.rb +0 -17
- data/lib/dry/interface/interfaces/value.rb +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4079619043b7b01b90eb4b6def6b3c6ce28280bbb9597d0bb62ee4791fc37110
|
4
|
+
data.tar.gz: bef3b4af52fb863ab59402ab196f0909bffbfc3408c3898976f07d47d13c3813
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8770345dae87d66406df5d677ecde817b69384d0ae1a7ebe355528d45e2800856821463349fdd262d1240231fb0f94d54f946d72b3160286087bb059b63e827b
|
7
|
+
data.tar.gz: dd28081ce094249862cffb93325908c90a813eac687c74f95ad91441db9493917fb84d5f15c131202542e281240b6cbf6f4ba8b31d30be61c72dc1a6fb81dd72
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
class Concrete
|
5
|
+
module Extensions
|
6
|
+
module Default
|
7
|
+
refine Proc do
|
8
|
+
# @example A string type with a proc default value
|
9
|
+
# Dry::Types['string'].default { 'default' }
|
10
|
+
#
|
11
|
+
# @return [#call(Dry::Types::Type)]
|
12
|
+
def to_default
|
13
|
+
-> type { type[call] }.freeze
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
refine Object do
|
18
|
+
# @example String type with default value
|
19
|
+
# Dry::Types['string'].default('foo')
|
20
|
+
#
|
21
|
+
# @return [#call(Dry::Types::Type)]
|
22
|
+
def to_default
|
23
|
+
-> type = Dry::Types["any"] { type[self] }.to_default
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/struct"
|
4
|
+
require "dry/types"
|
5
|
+
|
6
|
+
module Dry
|
7
|
+
class Concrete
|
8
|
+
module Extensions
|
9
|
+
module Type
|
10
|
+
module Types
|
11
|
+
include Dry::Types()
|
12
|
+
end
|
13
|
+
|
14
|
+
refine String do
|
15
|
+
# Converts type references into types
|
16
|
+
#
|
17
|
+
# @example A strict string into a type
|
18
|
+
# type = "strict.string".to_type
|
19
|
+
#
|
20
|
+
# type.valid?("string") # => true
|
21
|
+
# type.valid?(:symbol) # => false
|
22
|
+
#
|
23
|
+
# @example A (strict) symbol into a type
|
24
|
+
# type = "symbol".to_type
|
25
|
+
#
|
26
|
+
# type.valid?(:symbol) # => true
|
27
|
+
# type.valid?("string") # => false
|
28
|
+
#
|
29
|
+
# @return [Dry::Types::Type]
|
30
|
+
# @raise [ArgumentError] if the type is not a valid type
|
31
|
+
def to_type
|
32
|
+
Dry::Types[self]
|
33
|
+
rescue Dry::Container::Error
|
34
|
+
raise ArgumentError, "Type reference [#{inspect}] not found in Dry::Types"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
refine Object do
|
39
|
+
def to_type
|
40
|
+
raise ArgumentError, <<~ERROR
|
41
|
+
Cannot convert [#{inspect}] (#{self.class}) into a type using [#{inspect}#to_type]
|
42
|
+
Expected value of type Dry::Struct, Dry::Types or native Ruby module or class
|
43
|
+
|
44
|
+
ProTip: Replace [#{inspect}] with [Value(#{inspect})] to allow for [#{inspect}]
|
45
|
+
|
46
|
+
General examples:
|
47
|
+
Dry::Types:
|
48
|
+
Dry::Types["coercible.string"]
|
49
|
+
"strict.string"
|
50
|
+
"string"
|
51
|
+
Fixed values:
|
52
|
+
Value('undefined')
|
53
|
+
Value(:id)
|
54
|
+
Instance of class:
|
55
|
+
Hash
|
56
|
+
Array
|
57
|
+
String
|
58
|
+
[String]
|
59
|
+
[String, Symbol]
|
60
|
+
[[String, Symbol]]
|
61
|
+
{ Symbol => Hash }
|
62
|
+
{ Symbol => [Pathname] }
|
63
|
+
Constructors:
|
64
|
+
Struct.new(:value)
|
65
|
+
OpenStruct
|
66
|
+
Modules:
|
67
|
+
Enumerable
|
68
|
+
Comparable
|
69
|
+
ERROR
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
refine Dry::Types::Type do
|
74
|
+
# Dry::Types::Type is already a type in itself
|
75
|
+
# Used to streamline the API for all objects
|
76
|
+
#
|
77
|
+
# @example Dry type to dry type
|
78
|
+
# type = Dry::Types['string'].to_type
|
79
|
+
#
|
80
|
+
# type.valid?("string") # => true
|
81
|
+
# type.valid?(:string) # => false
|
82
|
+
#
|
83
|
+
# @return [Dry::Types::Type]
|
84
|
+
alias_method :to_type, :itself
|
85
|
+
end
|
86
|
+
|
87
|
+
refine Dry::Concrete.singleton_class do
|
88
|
+
# Dry::Types::Type is already a type in itself
|
89
|
+
# Used to streamline the API for all objects
|
90
|
+
#
|
91
|
+
# @example Dry type to dry type
|
92
|
+
# type = Dry::Types['string'].to_type
|
93
|
+
#
|
94
|
+
# type.valid?("string") # => true
|
95
|
+
# type.valid?(:string) # => false
|
96
|
+
#
|
97
|
+
# @return [Dry::Types::Type]
|
98
|
+
alias_method :to_type, :itself
|
99
|
+
end
|
100
|
+
|
101
|
+
refine Module do
|
102
|
+
# Ensures passed value includes module
|
103
|
+
#
|
104
|
+
# @example Check for enumerable values
|
105
|
+
# type = Enumerable.to_type
|
106
|
+
#
|
107
|
+
# type.valid?([]) # => true
|
108
|
+
# type.valid?({}) # => true
|
109
|
+
# type.valid?(nil) # => false
|
110
|
+
#
|
111
|
+
# @return [Dry::Types::Constrained]
|
112
|
+
def to_type
|
113
|
+
Types::Any.constrained(type: self)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
refine Class do
|
118
|
+
# Wrapps class in a type constructor using ::new as initializer
|
119
|
+
#
|
120
|
+
# @example With a custom class
|
121
|
+
# type = Struct.new(:value).to_type
|
122
|
+
#
|
123
|
+
# type.valid?('value') # => true
|
124
|
+
# type.valid? # => false
|
125
|
+
#
|
126
|
+
# @example With a native Ruby class
|
127
|
+
# type = String.to_type
|
128
|
+
#
|
129
|
+
# type.valid?('value') # => true
|
130
|
+
# type.valid?(:symbol) # => false
|
131
|
+
#
|
132
|
+
# @example With an instance of the class
|
133
|
+
# Person = Struct.new(:name)
|
134
|
+
#
|
135
|
+
# type = Person.to_type
|
136
|
+
#
|
137
|
+
# type.valid?('value') # => true
|
138
|
+
# type.valid?(Person.new('John')) # => true
|
139
|
+
# type.valid? # => false
|
140
|
+
#
|
141
|
+
# @example With a class without constructor args
|
142
|
+
# type = Mutex.to_type
|
143
|
+
#
|
144
|
+
# type.valid? # => true
|
145
|
+
# type.valid?('value') # => false
|
146
|
+
#
|
147
|
+
# @return [Dry::Types::Constructor]
|
148
|
+
def to_type
|
149
|
+
Types.const_get(name).then do |maybe_type|
|
150
|
+
maybe_type == self ? to_constructor : maybe_type
|
151
|
+
end
|
152
|
+
rescue NameError, TypeError
|
153
|
+
to_constructor
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
def to_constructor
|
159
|
+
Types.Instance(self) | Types.Constructor(self, method(:new))
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
refine Hash do
|
164
|
+
# Recursively creates a hash type from {keys} & {values}
|
165
|
+
#
|
166
|
+
# @example With dynamic key and static value
|
167
|
+
# type = { String => 'value' }
|
168
|
+
#
|
169
|
+
# type.valid?('string' => 'value') # => true
|
170
|
+
# type.valid?(symbol: 'value') # => false
|
171
|
+
# type.valid?('string' => 'other') # => false
|
172
|
+
#
|
173
|
+
# @example With dynamic key and value
|
174
|
+
# type = { String => Enumerable }.to_type
|
175
|
+
#
|
176
|
+
# type.valid?('string' => []) # => true
|
177
|
+
# type.valid?(symbol: []) # => false
|
178
|
+
# type.valid?('string' => :symbol) # => false
|
179
|
+
#
|
180
|
+
# @return [Dry::Types::Constrained, Dry::Types::Map]
|
181
|
+
def to_type
|
182
|
+
return Types::Hash if empty?
|
183
|
+
|
184
|
+
map { |k, v| Types::Hash.map(k.to_type, v.to_type) }.reduce(:|)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
refine Array do
|
189
|
+
# Recursively creates an array type from {self}
|
190
|
+
#
|
191
|
+
# @example With member type
|
192
|
+
# type = [String].to_type
|
193
|
+
#
|
194
|
+
# type.valid?(['string']) # => true
|
195
|
+
# type.valid?([:symbol]) # => false
|
196
|
+
#
|
197
|
+
# @example Without member type
|
198
|
+
# type = [].to_type
|
199
|
+
#
|
200
|
+
# type.valid?(['anything']) # => true
|
201
|
+
# type.valid?('not-an-array') # => false
|
202
|
+
#
|
203
|
+
# @example With nested members
|
204
|
+
# type = [[String]].to_type
|
205
|
+
#
|
206
|
+
# type.valid?([['string']]) # => true
|
207
|
+
# type.valid?([[:symbol]]) # => false
|
208
|
+
# type.valid?(['string']) # => false
|
209
|
+
#
|
210
|
+
# @example With combined types
|
211
|
+
# type = [String, Symbol].to_type
|
212
|
+
#
|
213
|
+
# type.valid?(['string', :symbol]) # => true
|
214
|
+
# type.valid?(['string']) # => true
|
215
|
+
# type.valid?([:symbol]) # => true
|
216
|
+
# type.valid?([]) # => true
|
217
|
+
# type.valid?(:symbol) # => false
|
218
|
+
#
|
219
|
+
# @return [Dry::Types::Constrained]
|
220
|
+
def to_type
|
221
|
+
return Types::Array if empty?
|
222
|
+
|
223
|
+
Types.Array(map(&:to_type).reduce(:|))
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
class Concrete
|
5
|
+
class Value < self
|
6
|
+
def self.new(value, *other, &block)
|
7
|
+
case value
|
8
|
+
in Hash => attributes then super(attributes, *other, &block)
|
9
|
+
in Dry::Struct => instance then instance
|
10
|
+
else
|
11
|
+
case attribute_names
|
12
|
+
in [] then raise ArgumentError, "[#{self}] has no attributes, one is required"
|
13
|
+
in [key] then super({ key => value }, *other, &block)
|
14
|
+
else
|
15
|
+
raise ArgumentError,
|
16
|
+
"[#{self}] has more than one attribute: #{attribute_names.join(', ')}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/dry/concrete.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/module/concerning"
|
4
|
+
require "active_support/core_ext/module/delegation"
|
5
|
+
require "active_support/descendants_tracker"
|
6
|
+
require "active_support/configurable"
|
7
|
+
require "active_support/inflector"
|
8
|
+
require "active_support/concern"
|
9
|
+
require "dry/struct"
|
10
|
+
require "dry/types"
|
11
|
+
|
12
|
+
module Dry
|
13
|
+
autoload :Interface, "dry/interface"
|
14
|
+
|
15
|
+
class Concrete < Dry::Struct
|
16
|
+
autoload :Extensions, "dry/concrete/extensions"
|
17
|
+
autoload :Value, "dry/concrete/value"
|
18
|
+
autoload :Types, "dry/interface/types"
|
19
|
+
|
20
|
+
schema schema.strict(true)
|
21
|
+
|
22
|
+
include Dry.Types(:strict, :nominal, :coercible)
|
23
|
+
|
24
|
+
extend ActiveSupport::DescendantsTracker
|
25
|
+
include ActiveSupport::Configurable
|
26
|
+
extend ActiveSupport::Inflector
|
27
|
+
|
28
|
+
using Extensions::Default
|
29
|
+
using Extensions::Type
|
30
|
+
|
31
|
+
config.order = Hash.new(-1)
|
32
|
+
|
33
|
+
delegate(
|
34
|
+
:Constructor, :Interface, :Instance, :Constant,
|
35
|
+
:Nominal, :Value, :Array, :Hash, :Any, to: :Types
|
36
|
+
)
|
37
|
+
|
38
|
+
def self.initializer(owner, &block)
|
39
|
+
owner.schema owner.schema.constructor(&block)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Adds attribute {name} to the struct
|
43
|
+
#
|
44
|
+
# @example Add a new attribute
|
45
|
+
# class User < Dry::Struct
|
46
|
+
# attribute :name, String
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# @example Add a new attribute with a default value
|
50
|
+
# class User < Dry::Struct
|
51
|
+
# attribute :name, String, default: "John"
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# @example Add a new attribute with constraints
|
55
|
+
# class User < Dry::Struct
|
56
|
+
# attribute :name, String, size: 3..20
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# @example Add a new attribute with array type
|
60
|
+
# class User < Dry::Struct
|
61
|
+
# attribute :name, [String]
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# @param name [Symbol]
|
65
|
+
# @param constrains [Array<#to_type>]
|
66
|
+
# @option default [#call, Any]
|
67
|
+
# @return [void]
|
68
|
+
def self.attribute(field, *constrains, **options, &block)
|
69
|
+
alias_fields(field, **options) do |inner_options|
|
70
|
+
super(field, *build_type_from(*constrains, **inner_options, &block))
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Optional version of {#attribute}
|
75
|
+
#
|
76
|
+
# @see #attribute
|
77
|
+
def self.attribute?(field, *constrains, **options, &block)
|
78
|
+
alias_fields(field, **options) do |inner_options|
|
79
|
+
super(field, *build_type_from(*constrains, **inner_options, &block))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.alias_fields(field, aliases: [], **options, &block)
|
84
|
+
if options.key?(:alias)
|
85
|
+
aliases << options.delete(:alias)
|
86
|
+
end
|
87
|
+
|
88
|
+
block[options]
|
89
|
+
|
90
|
+
aliases.each do |alias_name|
|
91
|
+
alias_method alias_name, field
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# @api private
|
96
|
+
def self.build_type_from(*constrains, **options, &block)
|
97
|
+
if block_given?
|
98
|
+
return [Class.new(Concrete, &block)]
|
99
|
+
end
|
100
|
+
|
101
|
+
unless (type = constrains.map(&:to_type).reduce(:|))
|
102
|
+
return EMPTY_ARRAY
|
103
|
+
end
|
104
|
+
|
105
|
+
if options.key?(:default)
|
106
|
+
options.delete(:default).to_default.then do |default_proc|
|
107
|
+
return build_type_from(type.default(&default_proc), **options)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
if options.empty?
|
112
|
+
return [type]
|
113
|
+
end
|
114
|
+
|
115
|
+
build_type_from(type.constrained(**options))
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -1,19 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/core_ext/module/introspection"
|
4
|
+
require "active_support/core_ext/module/attribute_accessors"
|
5
|
+
|
3
6
|
module Dry
|
4
7
|
class Interface
|
5
8
|
module Interfaces
|
6
9
|
module Abstract
|
7
10
|
extend ActiveSupport::Concern
|
8
|
-
include Common
|
9
11
|
|
10
12
|
class_methods do
|
11
|
-
|
12
|
-
|
13
|
+
# Class name without parent module
|
14
|
+
#
|
15
|
+
# @return [String]
|
16
|
+
def name
|
17
|
+
demodulize(super)
|
13
18
|
end
|
14
19
|
|
15
|
-
def
|
16
|
-
|
20
|
+
def new(input, safe = false, &block)
|
21
|
+
if safe
|
22
|
+
call_safe(input, &block)
|
23
|
+
else
|
24
|
+
call_unsafe(input)
|
25
|
+
end
|
17
26
|
end
|
18
27
|
end
|
19
28
|
end
|
@@ -1,25 +1,45 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# module C
|
4
|
+
# def structs
|
5
|
+
# [self]
|
6
|
+
# end
|
7
|
+
|
8
|
+
# def named
|
9
|
+
# to_s
|
10
|
+
# end
|
11
|
+
|
12
|
+
# def type
|
13
|
+
# self
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
|
3
17
|
module Dry
|
4
18
|
class Interface
|
5
19
|
module Interfaces
|
6
20
|
module Concrete
|
7
21
|
extend ActiveSupport::Concern
|
8
|
-
include Common
|
9
22
|
|
10
|
-
|
23
|
+
prepended do
|
11
24
|
class << self
|
25
|
+
alias_method :call_safe, :_call_safe
|
26
|
+
alias_method :call_unsafe, :_call_unsafe
|
12
27
|
alias_method :new, :_new
|
28
|
+
alias_method :call, :_call
|
13
29
|
end
|
14
30
|
end
|
31
|
+
# included do
|
32
|
+
# class << self
|
33
|
+
# alias_method :call, :_call
|
34
|
+
# end
|
35
|
+
# end
|
15
36
|
|
16
37
|
class_methods do
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
direct_descendants.first&.name or name
|
38
|
+
# Class name without parent module
|
39
|
+
#
|
40
|
+
# @return [String]
|
41
|
+
def to_s
|
42
|
+
demodulize(name)
|
23
43
|
end
|
24
44
|
end
|
25
45
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
class Interface
|
5
|
+
module Interfaces
|
6
|
+
module Unit
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
prepended do
|
10
|
+
class << self
|
11
|
+
alias_method :call, :_call
|
12
|
+
alias_method :call_unsafe, :_call_unsafe
|
13
|
+
alias_method :call_safe, :_call_safe
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class_methods do
|
18
|
+
# Class name without parent module
|
19
|
+
#
|
20
|
+
# @return [String]
|
21
|
+
def to_s
|
22
|
+
demodulize(name)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Allows a struct to be called without a hash
|
26
|
+
#
|
27
|
+
# @param value [Dry::Struct, Hash, Any]
|
28
|
+
# @param block [Proc]
|
29
|
+
#
|
30
|
+
# @return [Dry::Struct]
|
31
|
+
|
32
|
+
def new(value, *other, &block)
|
33
|
+
case value
|
34
|
+
in Hash => attributes then _new(attributes, *other, &block)
|
35
|
+
in Dry::Struct => instance then instance
|
36
|
+
else
|
37
|
+
case attribute_names
|
38
|
+
in [] then raise ArgumentError, "[#{self}] has no attributes, one is required"
|
39
|
+
in [key] then _new({ key => value }, *other, &block)
|
40
|
+
else
|
41
|
+
raise ArgumentError,
|
42
|
+
"[#{self}] has more than one attribute: #{attribute_names.join(', ')}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -3,8 +3,7 @@
|
|
3
3
|
module Dry
|
4
4
|
class Interface
|
5
5
|
module Interfaces
|
6
|
-
autoload :
|
7
|
-
autoload :Common, "dry/interface/interfaces/common"
|
6
|
+
autoload :Unit, "dry/interface/interfaces/unit"
|
8
7
|
autoload :Abstract, "dry/interface/interfaces/abstract"
|
9
8
|
autoload :Concrete, "dry/interface/interfaces/concrete"
|
10
9
|
end
|
data/lib/dry/interface.rb
CHANGED
@@ -6,74 +6,119 @@ require "active_support/descendants_tracker"
|
|
6
6
|
require "active_support/configurable"
|
7
7
|
require "active_support/inflector"
|
8
8
|
require "active_support/concern"
|
9
|
-
require "dry/struct"
|
10
9
|
require "dry/types"
|
10
|
+
require "dry"
|
11
11
|
|
12
12
|
module Dry
|
13
|
-
|
13
|
+
autoload :Concrete, "dry/concrete"
|
14
|
+
|
15
|
+
class Interface < Concrete
|
14
16
|
autoload :Interfaces, "dry/interface/interfaces"
|
15
|
-
autoload :VERSION, "dry/interface/version"
|
16
17
|
|
17
|
-
|
18
|
-
extend ActiveSupport::Inflector
|
18
|
+
include ActiveSupport::Configurable
|
19
19
|
|
20
|
-
|
20
|
+
config.order = Hash.new(-1)
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
28
|
-
end
|
22
|
+
class << self
|
23
|
+
alias _new new
|
24
|
+
alias _call call
|
25
|
+
alias _call_safe call_safe
|
26
|
+
alias _call_unsafe call_unsafe
|
29
27
|
|
30
|
-
|
31
|
-
|
28
|
+
delegate :call, to: :subtype
|
29
|
+
delegate :call_safe, to: :subtype
|
30
|
+
delegate :call_unsafe, to: :subtype
|
32
31
|
end
|
33
32
|
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
# Allow types structs to be ordered
|
34
|
+
#
|
35
|
+
# @param names [Array<Symbol>]
|
36
|
+
def self.order(*names)
|
37
|
+
result = names.each_with_index.reduce(EMPTY_HASH) do |acc, (name, index)|
|
38
|
+
acc.merge(name.to_s => index)
|
39
|
+
end
|
37
40
|
|
38
|
-
|
39
|
-
direct_descendants.map(&:type).reduce(&:|)
|
41
|
+
config.order = result
|
40
42
|
end
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
+
# @return [String]
|
45
|
+
def self.to_s
|
46
|
+
"%s<[%s]>" % [name, subtypes.map(&:to_s).join(" | ")]
|
44
47
|
end
|
45
48
|
|
46
|
-
def self.
|
47
|
-
|
49
|
+
def self.reduce(input, subtype)
|
50
|
+
case input
|
51
|
+
in { result: }
|
52
|
+
input
|
53
|
+
in { value: }
|
54
|
+
{ result: subtype.call(value) }
|
55
|
+
end
|
56
|
+
rescue Dry::Struct::Error => e
|
57
|
+
em = Dry::Types::ConstraintError.new(e.message, input.fetch(:value))
|
58
|
+
input.merge(errors: input.fetch(:errors, []) + [em])
|
59
|
+
rescue Dry::Types::CoercionError => e
|
60
|
+
input.merge(errors: input.fetch(:errors, []) + [e])
|
48
61
|
end
|
49
62
|
|
50
|
-
|
51
|
-
|
63
|
+
# Internal type represented by {self}
|
64
|
+
#
|
65
|
+
# @return [Dry::Struct::Sum, Dry::Struct::Class]
|
66
|
+
def self.subtype
|
67
|
+
Constructor(self) do |value, _type, &error|
|
68
|
+
error ||= -> error do
|
69
|
+
raise error
|
70
|
+
end
|
71
|
+
|
72
|
+
if subtypes.empty?
|
73
|
+
raise NotImplementedError, "No subtypes defined for #{name}"
|
74
|
+
end
|
75
|
+
|
76
|
+
output = subtypes.reduce({ value: value }, &method(:reduce))
|
77
|
+
|
78
|
+
case output
|
79
|
+
in { result: }
|
80
|
+
result
|
81
|
+
in { errors: }
|
82
|
+
error[Dry::Types::MultipleError.new(errors)]
|
83
|
+
in Dry::Struct
|
84
|
+
output
|
85
|
+
end
|
86
|
+
end
|
52
87
|
end
|
53
88
|
|
54
|
-
|
55
|
-
|
56
|
-
|
89
|
+
# Internal types represented by {self}
|
90
|
+
#
|
91
|
+
# @return [Dry::Struct::Class]
|
92
|
+
def self.subtypes
|
93
|
+
types = subclasses.flat_map(&:subclasses)
|
94
|
+
|
95
|
+
return types if config.order.empty?
|
96
|
+
|
97
|
+
types.sort_by do |type|
|
98
|
+
config.order.fetch(demodulize(type.name))
|
57
99
|
end
|
58
100
|
end
|
59
101
|
|
102
|
+
# @param name [Symbol]
|
103
|
+
#
|
104
|
+
# @return [Abstract::Class]
|
60
105
|
def self.const_missing(name)
|
61
106
|
case name
|
62
|
-
|
63
|
-
|
64
|
-
|
107
|
+
in :Concrete
|
108
|
+
Class.new(self) do
|
109
|
+
prepend Interfaces::Concrete
|
65
110
|
end
|
66
|
-
|
67
|
-
|
68
|
-
|
111
|
+
in :Abstract
|
112
|
+
Class.new(self) do
|
113
|
+
prepend Interfaces::Abstract
|
69
114
|
end
|
70
|
-
|
71
|
-
|
72
|
-
|
115
|
+
in :Unit
|
116
|
+
Class.new(self) do
|
117
|
+
prepend Interfaces::Unit
|
73
118
|
end
|
119
|
+
else
|
120
|
+
super
|
74
121
|
end
|
75
|
-
|
76
|
-
super
|
77
122
|
end
|
78
123
|
end
|
79
124
|
end
|
data/lib/dry/value.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
class Value < Dry::Struct
|
5
|
+
schema schema.strict(true)
|
6
|
+
|
7
|
+
def self.new(value, *other, &block)
|
8
|
+
case value
|
9
|
+
in Hash => attributes then super(attributes, *other, &block)
|
10
|
+
in Dry::Struct => instance then instance
|
11
|
+
else
|
12
|
+
case attribute_names
|
13
|
+
in [] then raise ArgumentError, "[#{self}] has no attributes, one is required"
|
14
|
+
in [key] then super({ key => value }, *other, &block)
|
15
|
+
else
|
16
|
+
raise ArgumentError,
|
17
|
+
"[#{self}] has more than one attribute: #{attribute_names.join(', ')}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/dry.rb
ADDED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-interface
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Linus Oleander
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -59,12 +59,20 @@ executables: []
|
|
59
59
|
extensions: []
|
60
60
|
extra_rdoc_files: []
|
61
61
|
files:
|
62
|
+
- lib/dry.rb
|
63
|
+
- lib/dry/concrete.rb
|
64
|
+
- lib/dry/concrete/extensions.rb
|
65
|
+
- lib/dry/concrete/extensions/default.rb
|
66
|
+
- lib/dry/concrete/extensions/type.rb
|
67
|
+
- lib/dry/concrete/value.rb
|
62
68
|
- lib/dry/interface.rb
|
63
69
|
- lib/dry/interface/interfaces.rb
|
64
70
|
- lib/dry/interface/interfaces/abstract.rb
|
65
|
-
- lib/dry/interface/interfaces/common.rb
|
66
71
|
- lib/dry/interface/interfaces/concrete.rb
|
67
|
-
- lib/dry/interface/interfaces/
|
72
|
+
- lib/dry/interface/interfaces/unit.rb
|
73
|
+
- lib/dry/interface/patch.rb
|
74
|
+
- lib/dry/interface/types.rb
|
75
|
+
- lib/dry/value.rb
|
68
76
|
homepage: https://github.com/oleander/dry-interface
|
69
77
|
licenses:
|
70
78
|
- MIT
|
@@ -78,14 +86,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
78
86
|
requirements:
|
79
87
|
- - ">="
|
80
88
|
- !ruby/object:Gem::Version
|
81
|
-
version:
|
89
|
+
version: '2.7'
|
82
90
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
91
|
requirements:
|
84
92
|
- - ">="
|
85
93
|
- !ruby/object:Gem::Version
|
86
94
|
version: '0'
|
87
95
|
requirements: []
|
88
|
-
rubygems_version: 3.2
|
96
|
+
rubygems_version: 3.1.2
|
89
97
|
signing_key:
|
90
98
|
specification_version: 4
|
91
99
|
summary: Dry::Interface
|
@@ -1,24 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Dry
|
4
|
-
class Interface
|
5
|
-
module Interfaces
|
6
|
-
module Value
|
7
|
-
extend ActiveSupport::Concern
|
8
|
-
include Concrete
|
9
|
-
|
10
|
-
included do |child|
|
11
|
-
otherwise do |value, type, &error|
|
12
|
-
names = child.type.attribute_names
|
13
|
-
|
14
|
-
unless names.one?
|
15
|
-
raise ArgumentError, "Value classes must have exactly one attribute, got [#{names.join(', ')}] (#{names.count}) for [#{child}]"
|
16
|
-
end
|
17
|
-
|
18
|
-
type[names.first => value, &error]
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|