dry-interface 1.0.0 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 35ad221861d5f292e3b7161879eee7b19ce7153dbda2db6f99623881978e1779
4
- data.tar.gz: 5d7e84b70f23f0b898292ebc9fcecb825f3c602644e393e58cb3a4f12e618c27
3
+ metadata.gz: 4079619043b7b01b90eb4b6def6b3c6ce28280bbb9597d0bb62ee4791fc37110
4
+ data.tar.gz: bef3b4af52fb863ab59402ab196f0909bffbfc3408c3898976f07d47d13c3813
5
5
  SHA512:
6
- metadata.gz: ca69ef2474fc1c3aaa39f0e52183dc938dcfc3f2f7b4154a58e5e25972c807d32cd4f1b7ef69708ea7a5f6b40e7b73a9b3badbc026de9912d73e9606edc5e2fb
7
- data.tar.gz: 205d5ad822c71d32561ed6875a0aeee5045278af6e8cc6cda33fe063a4712e0f3df6e4146f5c5e78d58a0cbae52317cb6240e10afdf837a2d33576ae70494896
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,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ class Concrete
5
+ module Extensions
6
+ autoload :Default, "dry/concrete/extensions/default"
7
+ autoload :Type, "dry/concrete/extensions/type"
8
+ end
9
+ end
10
+ 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
@@ -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
- def type
12
- direct_descendants.map(&:type).reduce(&:|) or raise "No types defined for [#{self}]"
13
+ # Class name without parent module
14
+ #
15
+ # @return [String]
16
+ def name
17
+ demodulize(super)
13
18
  end
14
19
 
15
- def named
16
- format "%<name>s<[%<names>s]>", { name: name, names: direct_descendants.map(&:named).join(" | ") }
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
- included do
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
- def type
18
- direct_descendants.first or self
19
- end
20
-
21
- def named
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 :Value, "dry/interface/interfaces/value"
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
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ class Interface
5
+ module Patch
6
+ autoload :Value, "dry/interface/patch/value"
7
+ autoload :Abstract, "dry/interface/patch/abstract"
8
+ autoload :Concrete, "dry/interface/patch/concrete"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ class Interface
5
+ module Types
6
+ include Dry.Types(:strict, :nominal, :coercible)
7
+ end
8
+ end
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
- class Interface < Dry::Struct
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
- extend ActiveSupport::DescendantsTracker
18
- extend ActiveSupport::Inflector
18
+ include ActiveSupport::Configurable
19
19
 
20
- schema schema.strict(true)
20
+ config.order = Hash.new(-1)
21
21
 
22
- concerning :New, prepend: true do
23
- prepended do
24
- class << self
25
- alias_method :_new, :new
26
- end
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
- module Types
31
- include Dry.Types()
28
+ delegate :call, to: :subtype
29
+ delegate :call_safe, to: :subtype
30
+ delegate :call_unsafe, to: :subtype
32
31
  end
33
32
 
34
- def self.Value(...)
35
- Types.Value(...)
36
- end
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
- def self.type
39
- direct_descendants.map(&:type).reduce(&:|)
41
+ config.order = result
40
42
  end
41
43
 
42
- def self.named
43
- format "%<name>s<[%<names>s]>", { name: name, names: direct_descendants.map(&:named).join(" | ") }
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.new(...)
47
- type.call(...)
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
- def self.initializer(owner, &block)
51
- owner.schema owner.schema.constructor(&block)
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
- def self.otherwise(&block)
55
- initializer(self) do |input, type, &error|
56
- type[input] { block[input, type, &error] }
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
- when :Abstract
63
- return Class.new(self) do
64
- include Interfaces::Abstract
107
+ in :Concrete
108
+ Class.new(self) do
109
+ prepend Interfaces::Concrete
65
110
  end
66
- when :Concrete
67
- return Class.new(self) do
68
- include Interfaces::Concrete
111
+ in :Abstract
112
+ Class.new(self) do
113
+ prepend Interfaces::Abstract
69
114
  end
70
- when :Value
71
- return Class.new(self) do
72
- include Interfaces::Value
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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ autoload :Value, "dry/value"
5
+ end
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.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-21 00:00:00.000000000 Z
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/value.rb
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: 3.0.0
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.22
96
+ rubygems_version: 3.1.2
89
97
  signing_key:
90
98
  specification_version: 4
91
99
  summary: Dry::Interface
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Dry
4
- class Interface
5
- module Interfaces
6
- module Common
7
- extend ActiveSupport::Concern
8
-
9
- class_methods do
10
- def name
11
- demodulize(super)
12
- end
13
- end
14
- end
15
- end
16
- end
17
- end
@@ -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