dry-interface 1.0.1 → 1.0.2

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