dry-interface 1.0.1 → 1.0.2

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: 24db806ee3d1f2da11074d2478d9de936a992f5ca834855c3d98cfdefbaab771
4
- data.tar.gz: 7d84cd10027df4ecc162eda1728da6048a6d2fb78638cbc909806bbef814eac8
3
+ metadata.gz: b05534847a5f500b3095bc1e1f752006395f4de8b9f3da99e6a6535a26ada354
4
+ data.tar.gz: d8bf8f9f0d1fcf5eeaf10bfcaa29d83630c0ffbbd9bcc4274436e90bfe7a935f
5
5
  SHA512:
6
- metadata.gz: 31158c8bd2ad52614c2c278f04279ee08ae22d9b73600f9b2aa272736919f24a65677d9cc20f06eac97190b4bdfbc1771ec9dc725a7d816ddbf4094ef4035938
7
- data.tar.gz: e017702e3900a9113fa681d17eb4b210aedecf1b4a498881403f989f64c5380b04fca6fad4dade6be26dbc5c8b7008784bddbcd03766e04507ae1f44303f2615
6
+ metadata.gz: 8a31c77b1aefa20e3ea3f763d1bbde2630f9599134ddbce053c9da574f65e77c84c4b8bf1b56812de401ab94fdd36695f3c36c9bd97d74c6ce830217f49ef8a2
7
+ data.tar.gz: 2d7f3a2ad607d2ac0628840eaf0a6ac6dcffd244c2736a18cfa8ac166170a06defb08b5e0a77cc9fdb8622fe06cf2fe8a29f3843fb85169bbb1ec2c8d21193c6
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dry
4
- class Interface
4
+ class Concrete
5
5
  module Extensions
6
6
  module Default
7
7
  refine Proc do
@@ -4,7 +4,7 @@ require "dry/struct"
4
4
  require "dry/types"
5
5
 
6
6
  module Dry
7
- class Interface
7
+ class Concrete
8
8
  module Extensions
9
9
  module Type
10
10
  module Types
@@ -84,6 +84,20 @@ module Dry
84
84
  alias_method :to_type, :itself
85
85
  end
86
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
+
87
101
  refine Module do
88
102
  # Ensures passed value includes module
89
103
  #
@@ -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,21 @@
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, "[#{self}] has more than one attribute: #{attribute_names.join(', ')}"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,121 @@
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(*%i[
34
+ Constructor
35
+ Interface
36
+ Instance
37
+ Constant
38
+ Nominal
39
+ Value
40
+ Array
41
+ Hash
42
+ Any
43
+ ], to: :Types)
44
+
45
+ def self.initializer(owner, &block)
46
+ owner.schema owner.schema.constructor(&block)
47
+ end
48
+
49
+ # Adds attribute {name} to the struct
50
+ #
51
+ # @example Add a new attribute
52
+ # class User < Dry::Struct
53
+ # attribute :name, String
54
+ # end
55
+ #
56
+ # @example Add a new attribute with a default value
57
+ # class User < Dry::Struct
58
+ # attribute :name, String, default: "John"
59
+ # end
60
+ #
61
+ # @example Add a new attribute with constraints
62
+ # class User < Dry::Struct
63
+ # attribute :name, String, size: 3..20
64
+ # end
65
+ #
66
+ # @example Add a new attribute with array type
67
+ # class User < Dry::Struct
68
+ # attribute :name, [String]
69
+ # end
70
+ #
71
+ # @param name [Symbol]
72
+ # @param constrains [Array<#to_type>]
73
+ # @option default [#call, Any]
74
+ # @return [void]
75
+ def self.attribute(field, *constrains, **options, &block)
76
+ alias_fields(field, **options) do |inner_options|
77
+ super(field, build_type_from(*constrains, **inner_options), &block)
78
+ end
79
+ end
80
+
81
+ # Optional version of {#attribute}
82
+ #
83
+ # @see #attribute
84
+ def self.attribute?(field, *constrains, **options, &block)
85
+ alias_fields(field, **options) do |inner_options|
86
+ super(field, build_type_from(*constrains, **inner_options), &block)
87
+ end
88
+ end
89
+
90
+ def self.alias_fields(field, aliases: [], **options, &block)
91
+ if options.key?(:alias)
92
+ aliases << options.delete(:alias)
93
+ end
94
+
95
+ block[options]
96
+
97
+ aliases.each do |alias_name|
98
+ alias_method alias_name, field
99
+ end
100
+ end
101
+
102
+ # @api private
103
+ def self.build_type_from(*constrains, **options)
104
+ unless (type = constrains.map(&:to_type).reduce(:|))
105
+ return build_type_from(Dry::Types["any"], **options)
106
+ end
107
+
108
+ if options.key?(:default)
109
+ options.delete(:default).to_default.then do |default_proc|
110
+ return build_type_from(type.default(&default_proc), **options)
111
+ end
112
+ end
113
+
114
+ if options.empty?
115
+ return type
116
+ end
117
+
118
+ build_type_from(type.constrained(**options))
119
+ end
120
+ end
121
+ 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,49 @@
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, "[#{self}] has more than one attribute: #{attribute_names.join(', ')}"
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ 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
data/lib/dry/interface.rb CHANGED
@@ -6,170 +6,118 @@ 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"
11
10
 
12
11
  module Dry
13
- class Interface < Dry::Struct
12
+ autoload :Concrete, "dry/concrete"
13
+
14
+ class Interface < Concrete
14
15
  autoload :Interfaces, "dry/interface/interfaces"
15
- autoload :Extensions, "dry/interface/extensions"
16
- autoload :Types, "dry/interface/types"
17
-
18
- extend ActiveSupport::DescendantsTracker
19
- extend ActiveSupport::Inflector
20
-
21
- using Extensions::Default
22
- using Extensions::Type
23
-
24
- schema schema.strict(true)
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
16
 
44
- concerning :New, prepend: true do
45
- prepended do
46
- class << self
47
- alias_method :_new, :new
48
- end
49
- end
50
- end
17
+ include ActiveSupport::Configurable
51
18
 
52
- module Types
53
- include Dry.Types()
54
- end
19
+ config.order = Hash.new(-1)
55
20
 
56
- def self.Value(...)
57
- Types.Value(...)
58
- end
59
-
60
- def self.type
61
- direct_descendants.map(&:type).reduce(&:|)
62
- end
21
+ class << self
22
+ alias _new new
23
+ alias _call call
24
+ alias _call_safe call_safe
25
+ alias _call_unsafe call_unsafe
63
26
 
64
- def self.named
65
- format "%<name>s<[%<names>s]>", { name: name, names: direct_descendants.map(&:named).join(" | ") }
27
+ delegate :call, to: :subtype
28
+ delegate :call_safe, to: :subtype
29
+ delegate :call_unsafe, to: :subtype
66
30
  end
67
31
 
68
- def self.new(...)
69
- return super unless type
70
-
71
- type.call(...)
72
- end
32
+ # Allow types structs to be ordered
33
+ #
34
+ # @param names [Array<Symbol>]
35
+ def self.order(*names)
36
+ result = names.each_with_index.reduce(EMPTY_HASH) do |acc, (name, index)|
37
+ acc.merge(name.to_s => index)
38
+ end
73
39
 
74
- def self.initializer(owner, &block)
75
- owner.schema owner.schema.constructor(&block)
40
+ config.order = result
76
41
  end
77
42
 
78
- def self.otherwise(&block)
79
- initializer(self) do |input, type, &error|
80
- type[input] { block[input, type, &error] }
81
- end
43
+ # @return [String]
44
+ def self.to_s
45
+ format("%<name>s<[%<types>s]>", name: name, types: subtypes.map(&:to_s).join(" | "))
82
46
  end
83
47
 
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)
48
+ def self.reduce(input, subtype)
49
+ case input
50
+ in { result: }
51
+ input
52
+ in { value: }
53
+ { result: subtype.call(value) }
113
54
  end
55
+ rescue Dry::Struct::Error => e
56
+ em = Dry::Types::ConstraintError.new(e.message, input.fetch(:value))
57
+ input.merge(errors: input.fetch(:errors, []) + [em])
58
+ rescue Dry::Types::CoercionError => e
59
+ input.merge(errors: input.fetch(:errors, []) + [e])
114
60
  end
115
61
 
116
- # Optional version of {#attribute}
62
+ # Internal type represented by {self}
117
63
  #
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
-
125
- def self.const_missing(name)
126
- case name
127
- when :Abstract
128
- return Class.new(self) do
129
- include Interfaces::Abstract
64
+ # @return [Dry::Struct::Sum, Dry::Struct::Class]
65
+ def self.subtype
66
+ Constructor(self) do |value, _type, &error|
67
+ error ||= lambda do |error|
68
+ raise error
130
69
  end
131
- when :Concrete
132
- return Class.new(self) do
133
- include Interfaces::Concrete
70
+
71
+ if subtypes.empty?
72
+ raise NotImplementedError, "No subtypes defined for #{name}"
134
73
  end
135
- when :Value
136
- return Class.new(self) do
137
- include Interfaces::Value
74
+
75
+ output = subtypes.reduce({ value: value }, &method(:reduce))
76
+
77
+ case output
78
+ in { result: }
79
+ result
80
+ in { errors: }
81
+ error[Dry::Types::MultipleError.new(errors)]
82
+ in Dry::Struct
83
+ output
138
84
  end
139
85
  end
140
-
141
- super
142
86
  end
143
87
 
144
- def self.alias_fields(_field, aliases: [], **options, &block)
145
- if options.key?(:alias)
146
- aliases << options.delete(:alias)
147
- end
88
+ # Internal types represented by {self}
89
+ #
90
+ # @return [Dry::Struct::Class]
91
+ def self.subtypes
92
+ types = subclasses.flat_map(&:subclasses)
148
93
 
149
- block[options]
94
+ return types if config.order.empty?
150
95
 
151
- aliases.each do |alias_name|
152
- alias_method alias_name, name
96
+ types.sort_by do |type|
97
+ config.order.fetch(demodulize(type.name))
153
98
  end
154
99
  end
155
100
 
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)
101
+ # @param name [Symbol]
102
+ #
103
+ # @return [Abstract::Class]
104
+ def self.const_missing(name)
105
+ case name
106
+ in :Concrete
107
+ Class.new(self) do
108
+ prepend Interfaces::Concrete
165
109
  end
110
+ in :Abstract
111
+ Class.new(self) do
112
+ prepend Interfaces::Abstract
113
+ end
114
+ in :Unit
115
+ Class.new(self) do
116
+ prepend Interfaces::Unit
117
+ end
118
+ else
119
+ super
166
120
  end
167
-
168
- if options.empty?
169
- return type
170
- end
171
-
172
- build_type_from(type.constrained(**options))
173
121
  end
174
122
  end
175
123
  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.1
4
+ version: 1.0.2
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-11-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -59,15 +59,17 @@ executables: []
59
59
  extensions: []
60
60
  extra_rdoc_files: []
61
61
  files:
62
+ - lib/dry/concrete.rb
63
+ - lib/dry/concrete/extensions.rb
64
+ - lib/dry/concrete/extensions/default.rb
65
+ - lib/dry/concrete/extensions/type.rb
66
+ - lib/dry/concrete/value.rb
62
67
  - lib/dry/interface.rb
63
- - lib/dry/interface/extensions.rb
64
- - lib/dry/interface/extensions/default.rb
65
- - lib/dry/interface/extensions/type.rb
66
68
  - lib/dry/interface/interfaces.rb
67
69
  - lib/dry/interface/interfaces/abstract.rb
68
- - lib/dry/interface/interfaces/common.rb
69
70
  - lib/dry/interface/interfaces/concrete.rb
70
- - lib/dry/interface/interfaces/value.rb
71
+ - lib/dry/interface/interfaces/unit.rb
72
+ - lib/dry/interface/patch.rb
71
73
  - lib/dry/interface/types.rb
72
74
  homepage: https://github.com/oleander/dry-interface
73
75
  licenses:
@@ -1,10 +0,0 @@
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
@@ -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