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