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 +4 -4
- data/lib/dry/interface/extensions/default.rb +29 -0
- data/lib/dry/interface/extensions/type.rb +215 -0
- data/lib/dry/interface/extensions.rb +10 -0
- data/lib/dry/interface/types.rb +9 -0
- data/lib/dry/interface.rb +97 -1
- metadata +5 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 24db806ee3d1f2da11074d2478d9de936a992f5ca834855c3d98cfdefbaab771
|
4
|
+
data.tar.gz: 7d84cd10027df4ecc162eda1728da6048a6d2fb78638cbc909806bbef814eac8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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 :
|
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.
|
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
|