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