dry-interface 1.0.0 → 1.0.1

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: 24db806ee3d1f2da11074d2478d9de936a992f5ca834855c3d98cfdefbaab771
4
+ data.tar.gz: 7d84cd10027df4ecc162eda1728da6048a6d2fb78638cbc909806bbef814eac8
5
5
  SHA512:
6
- metadata.gz: ca69ef2474fc1c3aaa39f0e52183dc938dcfc3f2f7b4154a58e5e25972c807d32cd4f1b7ef69708ea7a5f6b40e7b73a9b3badbc026de9912d73e9606edc5e2fb
7
- data.tar.gz: 205d5ad822c71d32561ed6875a0aeee5045278af6e8cc6cda33fe063a4712e0f3df6e4146f5c5e78d58a0cbae52317cb6240e10afdf837a2d33576ae70494896
6
+ metadata.gz: 31158c8bd2ad52614c2c278f04279ee08ae22d9b73600f9b2aa272736919f24a65677d9cc20f06eac97190b4bdfbc1771ec9dc725a7d816ddbf4094ef4035938
7
+ data.tar.gz: e017702e3900a9113fa681d17eb4b210aedecf1b4a498881403f989f64c5380b04fca6fad4dade6be26dbc5c8b7008784bddbcd03766e04507ae1f44303f2615
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ class Interface
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,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/struct"
4
+ require "dry/types"
5
+
6
+ module Dry
7
+ class Interface
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 Module do
88
+ # Ensures passed value includes module
89
+ #
90
+ # @example Check for enumerable values
91
+ # type = Enumerable.to_type
92
+ #
93
+ # type.valid?([]) # => true
94
+ # type.valid?({}) # => true
95
+ # type.valid?(nil) # => false
96
+ #
97
+ # @return [Dry::Types::Constrained]
98
+ def to_type
99
+ Types::Any.constrained(type: self)
100
+ end
101
+ end
102
+
103
+ refine Class do
104
+ # Wrapps class in a type constructor using ::new as initializer
105
+ #
106
+ # @example With a custom class
107
+ # type = Struct.new(:value).to_type
108
+ #
109
+ # type.valid?('value') # => true
110
+ # type.valid? # => false
111
+ #
112
+ # @example With a native Ruby class
113
+ # type = String.to_type
114
+ #
115
+ # type.valid?('value') # => true
116
+ # type.valid?(:symbol) # => false
117
+ #
118
+ # @example With an instance of the class
119
+ # Person = Struct.new(:name)
120
+ #
121
+ # type = Person.to_type
122
+ #
123
+ # type.valid?('value') # => true
124
+ # type.valid?(Person.new('John')) # => true
125
+ # type.valid? # => false
126
+ #
127
+ # @example With a class without constructor args
128
+ # type = Mutex.to_type
129
+ #
130
+ # type.valid? # => true
131
+ # type.valid?('value') # => false
132
+ #
133
+ # @return [Dry::Types::Constructor]
134
+ def to_type
135
+ Types.const_get(name).then do |maybe_type|
136
+ maybe_type == self ? to_constructor : maybe_type
137
+ end
138
+ rescue NameError, TypeError
139
+ to_constructor
140
+ end
141
+
142
+ private
143
+
144
+ def to_constructor
145
+ Types.Instance(self) | Types.Constructor(self, method(:new))
146
+ end
147
+ end
148
+
149
+ refine Hash do
150
+ # Recursively creates a hash type from {keys} & {values}
151
+ #
152
+ # @example With dynamic key and static value
153
+ # type = { String => 'value' }
154
+ #
155
+ # type.valid?('string' => 'value') # => true
156
+ # type.valid?(symbol: 'value') # => false
157
+ # type.valid?('string' => 'other') # => false
158
+ #
159
+ # @example With dynamic key and value
160
+ # type = { String => Enumerable }.to_type
161
+ #
162
+ # type.valid?('string' => []) # => true
163
+ # type.valid?(symbol: []) # => false
164
+ # type.valid?('string' => :symbol) # => false
165
+ #
166
+ # @return [Dry::Types::Constrained, Dry::Types::Map]
167
+ def to_type
168
+ return Types::Hash if empty?
169
+
170
+ map { |k, v| Types::Hash.map(k.to_type, v.to_type) }.reduce(:|)
171
+ end
172
+ end
173
+
174
+ refine Array do
175
+ # Recursively creates an array type from {self}
176
+ #
177
+ # @example With member type
178
+ # type = [String].to_type
179
+ #
180
+ # type.valid?(['string']) # => true
181
+ # type.valid?([:symbol]) # => false
182
+ #
183
+ # @example Without member type
184
+ # type = [].to_type
185
+ #
186
+ # type.valid?(['anything']) # => true
187
+ # type.valid?('not-an-array') # => false
188
+ #
189
+ # @example With nested members
190
+ # type = [[String]].to_type
191
+ #
192
+ # type.valid?([['string']]) # => true
193
+ # type.valid?([[:symbol]]) # => false
194
+ # type.valid?(['string']) # => false
195
+ #
196
+ # @example With combined types
197
+ # type = [String, Symbol].to_type
198
+ #
199
+ # type.valid?(['string', :symbol]) # => true
200
+ # type.valid?(['string']) # => true
201
+ # type.valid?([:symbol]) # => true
202
+ # type.valid?([]) # => true
203
+ # type.valid?(:symbol) # => false
204
+ #
205
+ # @return [Dry::Types::Constrained]
206
+ def to_type
207
+ return Types::Array if empty?
208
+
209
+ Types.Array(map(&:to_type).reduce(:|))
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ class Interface
5
+ module Extensions
6
+ autoload :Default, "dry/interface/extensions/default"
7
+ autoload :Type, "dry/interface/extensions/type"
8
+ end
9
+ end
10
+ 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
@@ -12,13 +12,35 @@ require "dry/types"
12
12
  module Dry
13
13
  class Interface < Dry::Struct
14
14
  autoload :Interfaces, "dry/interface/interfaces"
15
- autoload :VERSION, "dry/interface/version"
15
+ autoload :Extensions, "dry/interface/extensions"
16
+ autoload :Types, "dry/interface/types"
16
17
 
17
18
  extend ActiveSupport::DescendantsTracker
18
19
  extend ActiveSupport::Inflector
19
20
 
21
+ using Extensions::Default
22
+ using Extensions::Type
23
+
20
24
  schema schema.strict(true)
21
25
 
26
+ delegate(*%i[
27
+ Constructor
28
+ Interface
29
+ Instance
30
+ Constant
31
+ Nominal
32
+ Value
33
+ Array
34
+ Hash
35
+ Any
36
+ ], to: Types)
37
+
38
+ Types.constants.each do |constant|
39
+ raise "#{const_get(constant)} is already defined"
40
+ rescue NameError
41
+ const_set(constant, Types.const_get(constant))
42
+ end
43
+
22
44
  concerning :New, prepend: true do
23
45
  prepended do
24
46
  class << self
@@ -44,6 +66,8 @@ module Dry
44
66
  end
45
67
 
46
68
  def self.new(...)
69
+ return super unless type
70
+
47
71
  type.call(...)
48
72
  end
49
73
 
@@ -57,6 +81,47 @@ module Dry
57
81
  end
58
82
  end
59
83
 
84
+ # Adds attribute {name} to the struct
85
+ #
86
+ # @example Add a new attribute
87
+ # class User < Dry::Struct
88
+ # attribute :name, String
89
+ # end
90
+ #
91
+ # @example Add a new attribute with a default value
92
+ # class User < Dry::Struct
93
+ # attribute :name, String, default: "John"
94
+ # end
95
+ #
96
+ # @example Add a new attribute with constraints
97
+ # class User < Dry::Struct
98
+ # attribute :name, String, size: 3..20
99
+ # end
100
+ #
101
+ # @example Add a new attribute with array type
102
+ # class User < Dry::Struct
103
+ # attribute :name, [String]
104
+ # end
105
+ #
106
+ # @param name [Symbol]
107
+ # @param constrains [Array<#to_type>]
108
+ # @option default [#call, Any]
109
+ # @return [void]
110
+ def self.attribute(field, *constrains, **options, &block)
111
+ alias_fields(field, **options) do |inner_options|
112
+ super(field, build_type_from(*constrains, **inner_options), &block)
113
+ end
114
+ end
115
+
116
+ # Optional version of {#attribute}
117
+ #
118
+ # @see #attribute
119
+ def self.attribute?(field, *constrains, **options, &block)
120
+ alias_fields(field, **options) do |inner_options|
121
+ super(field, build_type_from(*constrains, **inner_options), &block)
122
+ end
123
+ end
124
+
60
125
  def self.const_missing(name)
61
126
  case name
62
127
  when :Abstract
@@ -75,5 +140,36 @@ module Dry
75
140
 
76
141
  super
77
142
  end
143
+
144
+ def self.alias_fields(_field, aliases: [], **options, &block)
145
+ if options.key?(:alias)
146
+ aliases << options.delete(:alias)
147
+ end
148
+
149
+ block[options]
150
+
151
+ aliases.each do |alias_name|
152
+ alias_method alias_name, name
153
+ end
154
+ end
155
+
156
+ # @api private
157
+ def self.build_type_from(*constrains, **options)
158
+ unless (type = constrains.map(&:to_type).reduce(:|))
159
+ return build_type_from(Dry::Types["any"], **options)
160
+ end
161
+
162
+ if options.key?(:default)
163
+ options.delete(:default).to_default.then do |default_proc|
164
+ return build_type_from(type.default(&default_proc), **options)
165
+ end
166
+ end
167
+
168
+ if options.empty?
169
+ return type
170
+ end
171
+
172
+ build_type_from(type.constrained(**options))
173
+ end
78
174
  end
79
175
  end
metadata CHANGED
@@ -1,7 +1,7 @@
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.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Linus Oleander
@@ -60,11 +60,15 @@ extensions: []
60
60
  extra_rdoc_files: []
61
61
  files:
62
62
  - lib/dry/interface.rb
63
+ - lib/dry/interface/extensions.rb
64
+ - lib/dry/interface/extensions/default.rb
65
+ - lib/dry/interface/extensions/type.rb
63
66
  - lib/dry/interface/interfaces.rb
64
67
  - lib/dry/interface/interfaces/abstract.rb
65
68
  - lib/dry/interface/interfaces/common.rb
66
69
  - lib/dry/interface/interfaces/concrete.rb
67
70
  - lib/dry/interface/interfaces/value.rb
71
+ - lib/dry/interface/types.rb
68
72
  homepage: https://github.com/oleander/dry-interface
69
73
  licenses:
70
74
  - MIT