dry-interface 1.0.0 → 1.0.1

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