strict 1.0.0 → 1.2.0
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/.rubocop.yml +1 -1
- data/.tool-versions +1 -0
- data/Gemfile.lock +6 -6
- data/README.md +65 -0
- data/lib/strict/accessor/attributes.rb +1 -1
- data/lib/strict/accessor/module.rb +7 -7
- data/lib/strict/assignment_error.rb +1 -1
- data/lib/strict/attribute.rb +6 -1
- data/lib/strict/attributes/{configured.rb → class.rb} +5 -1
- data/lib/strict/attributes/coercer.rb +32 -0
- data/lib/strict/attributes/configuration.rb +1 -1
- data/lib/strict/attributes/dsl.rb +3 -2
- data/lib/strict/attributes/instance.rb +3 -3
- data/lib/strict/coercers/array.rb +22 -0
- data/lib/strict/coercers/hash.rb +34 -0
- data/lib/strict/dsl/coercible.rb +14 -0
- data/lib/strict/implementation_does_not_conform_error.rb +95 -0
- data/lib/strict/initialization_error.rb +9 -2
- data/lib/strict/interface.rb +22 -0
- data/lib/strict/interfaces/instance.rb +54 -0
- data/lib/strict/method.rb +22 -2
- data/lib/strict/method_call_error.rb +8 -2
- data/lib/strict/method_definition_error.rb +9 -3
- data/lib/strict/method_return_error.rb +1 -1
- data/lib/strict/methods/dsl.rb +3 -2
- data/lib/strict/methods/verifiable_method.rb +9 -5
- data/lib/strict/parameter.rb +6 -1
- data/lib/strict/reader/attributes.rb +1 -1
- data/lib/strict/reader/module.rb +1 -1
- data/lib/strict/return.rb +1 -1
- data/lib/strict/version.rb +1 -1
- data/strict.gemspec +1 -1
- metadata +13 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82ad76d91d34f1bd4cc46ea38e4cef3c03c7d7e328f04d3dcc3c555da1eabdb4
|
4
|
+
data.tar.gz: 73f3ca6bc1d03caa42c23f61a3615ccb55285a1dcd962e978be3a87064abf8a3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c442d7d203a993681c89694052856d125ab7c35965fb965ed64250a3eb83476069d844488d64798afc7ddc57b1edafbbbe0d6807b92b3c3a228bf9f3891beecd
|
7
|
+
data.tar.gz: 545e4e331d8ed3e578e72dbb52347bb34a0512fa899ca1da60f3181a2836d06a188658b224feed112a2e21464723833230b750628a1d837346566730375ce803
|
data/.rubocop.yml
CHANGED
data/.tool-versions
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby 3.0.0
|
data/Gemfile.lock
CHANGED
@@ -1,22 +1,22 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
strict (1.
|
4
|
+
strict (1.2.0)
|
5
5
|
zeitwerk (~> 2.6)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
10
|
ast (2.4.2)
|
11
|
-
debug (1.6.
|
11
|
+
debug (1.6.2)
|
12
12
|
irb (>= 1.3.6)
|
13
13
|
reline (>= 0.3.1)
|
14
14
|
gem-release (2.2.2)
|
15
15
|
io-console (0.5.11)
|
16
|
-
irb (1.4.
|
16
|
+
irb (1.4.2)
|
17
17
|
reline (>= 0.3.0)
|
18
18
|
json (2.6.2)
|
19
|
-
minitest (5.16.
|
19
|
+
minitest (5.16.3)
|
20
20
|
minitest-spec-context (0.0.4)
|
21
21
|
parallel (1.22.1)
|
22
22
|
parser (3.1.2.1)
|
@@ -45,7 +45,7 @@ GEM
|
|
45
45
|
rubocop (~> 1.0)
|
46
46
|
ruby-progressbar (1.11.0)
|
47
47
|
unicode-display_width (2.3.0)
|
48
|
-
zeitwerk (2.6.
|
48
|
+
zeitwerk (2.6.1)
|
49
49
|
|
50
50
|
PLATFORMS
|
51
51
|
arm64-darwin-21
|
@@ -63,4 +63,4 @@ DEPENDENCIES
|
|
63
63
|
strict!
|
64
64
|
|
65
65
|
BUNDLED WITH
|
66
|
-
2.3.
|
66
|
+
2.3.23
|
data/README.md
CHANGED
@@ -108,6 +108,71 @@ UpdateEmail.new.call(user_id: "123", email: "456")
|
|
108
108
|
# => Strict::MethodReturnError
|
109
109
|
```
|
110
110
|
|
111
|
+
### `Strict::Interface`
|
112
|
+
|
113
|
+
```rb
|
114
|
+
class Storage
|
115
|
+
extend Strict::Interface
|
116
|
+
|
117
|
+
expose(:write) do
|
118
|
+
key String
|
119
|
+
contents String
|
120
|
+
returns Boolean()
|
121
|
+
end
|
122
|
+
|
123
|
+
expose(:read) do
|
124
|
+
key String
|
125
|
+
returns AnyOf(String, nil)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
module Storages
|
130
|
+
class Memory
|
131
|
+
def initialize
|
132
|
+
@storage = {}
|
133
|
+
end
|
134
|
+
|
135
|
+
def write(key:, contents:)
|
136
|
+
storage[key] = contents
|
137
|
+
true
|
138
|
+
end
|
139
|
+
|
140
|
+
def read(key:)
|
141
|
+
storage[key]
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
attr_reader :storage
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
storage = Storage.new(Storages::Memory.new)
|
151
|
+
# => #<Storage implementation=#<Storages::Memory>>
|
152
|
+
|
153
|
+
storage.write(key: "some/path/to/file.rb", contents: "Hello")
|
154
|
+
# => true
|
155
|
+
|
156
|
+
storage.write(key: "some/path/to/file.rb", contents: {})
|
157
|
+
# => Strict::MethodCallError
|
158
|
+
|
159
|
+
storage.read(key: "some/path/to/file.rb")
|
160
|
+
# => "Hello"
|
161
|
+
|
162
|
+
storage.read(key: "some/path/to/other.rb")
|
163
|
+
# => nil
|
164
|
+
|
165
|
+
module Storages
|
166
|
+
class Wat
|
167
|
+
def write(key:)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
storage = Storage.new(Storages::Wat.new)
|
173
|
+
# => Strict::ImplementationDoesNotConformError
|
174
|
+
```
|
175
|
+
|
111
176
|
## Development
|
112
177
|
|
113
178
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -10,13 +10,13 @@ module Strict
|
|
10
10
|
super()
|
11
11
|
|
12
12
|
@configuration = configuration
|
13
|
-
const_set(Strict::Attributes::
|
13
|
+
const_set(Strict::Attributes::Class::CONSTANT, configuration)
|
14
14
|
configuration.attributes.each do |attribute|
|
15
|
-
module_eval(
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
15
|
+
module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
16
|
+
def #{attribute.name} # def name
|
17
|
+
#{attribute.instance_variable} # @instance_variable
|
18
|
+
end # end
|
19
|
+
RUBY
|
20
20
|
|
21
21
|
module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
22
22
|
def #{attribute.name}=(value) # def name=(value)
|
@@ -28,7 +28,7 @@ module Strict
|
|
28
28
|
raise Strict::AssignmentError.new( # raise Strict::AssignmentError.new(
|
29
29
|
assignable_class: self.class, # assignable_class: self.class,
|
30
30
|
invalid_attribute: attribute, # invalid_attribute: attribute,
|
31
|
-
value:
|
31
|
+
value: value # value: value
|
32
32
|
) # )
|
33
33
|
end # end
|
34
34
|
end # end
|
@@ -5,7 +5,7 @@ module Strict
|
|
5
5
|
attr_reader :invalid_attribute, :value
|
6
6
|
|
7
7
|
def initialize(assignable_class:, invalid_attribute:, value:)
|
8
|
-
super(message_from(assignable_class
|
8
|
+
super(message_from(assignable_class: assignable_class, invalid_attribute: invalid_attribute, value: value))
|
9
9
|
|
10
10
|
@invalid_attribute = invalid_attribute
|
11
11
|
@value = value
|
data/lib/strict/attribute.rb
CHANGED
@@ -10,7 +10,12 @@ module Strict
|
|
10
10
|
raise ArgumentError, "Only one of 'default', 'default_value', or 'default_generator' can be provided"
|
11
11
|
end
|
12
12
|
|
13
|
-
new(
|
13
|
+
new(
|
14
|
+
name: name.to_sym,
|
15
|
+
validator: validator,
|
16
|
+
default_generator: make_default_generator(**defaults),
|
17
|
+
coercer: coerce
|
18
|
+
)
|
14
19
|
end
|
15
20
|
|
16
21
|
private
|
@@ -2,12 +2,16 @@
|
|
2
2
|
|
3
3
|
module Strict
|
4
4
|
module Attributes
|
5
|
-
module
|
5
|
+
module Class
|
6
6
|
CONSTANT = :STRICT_INTERNAL_ATTRIBUTES_CONFIGURATION__
|
7
7
|
|
8
8
|
def strict_attributes
|
9
9
|
self::STRICT_INTERNAL_ATTRIBUTES_CONFIGURATION__
|
10
10
|
end
|
11
|
+
|
12
|
+
def coercer
|
13
|
+
Coercer.new(self)
|
14
|
+
end
|
11
15
|
end
|
12
16
|
end
|
13
17
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
module Attributes
|
5
|
+
class Coercer
|
6
|
+
attr_reader :attributes_class
|
7
|
+
|
8
|
+
def initialize(attributes_class)
|
9
|
+
@attributes_class = attributes_class
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(value)
|
13
|
+
return value if value.nil? || !value.respond_to?(:to_h)
|
14
|
+
|
15
|
+
coerce(value.to_h)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
NOT_PROVIDED = ::Object.new.freeze
|
21
|
+
|
22
|
+
def coerce(hash)
|
23
|
+
attributes_class.new(
|
24
|
+
**attributes_class.strict_attributes.each_with_object({}) do |attribute, attributes|
|
25
|
+
value = hash.fetch(attribute.name) { hash.fetch(attribute.name.to_s, NOT_PROVIDED) }
|
26
|
+
attributes[attribute.name] = value unless value.equal?(NOT_PROVIDED)
|
27
|
+
end
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -4,13 +4,14 @@ module Strict
|
|
4
4
|
module Attributes
|
5
5
|
class Dsl < BasicObject
|
6
6
|
class << self
|
7
|
-
def run(&)
|
7
|
+
def run(&block)
|
8
8
|
dsl = new
|
9
|
-
dsl.instance_eval(&)
|
9
|
+
dsl.instance_eval(&block)
|
10
10
|
::Strict::Attributes::Configuration.new(attributes: dsl.__strict_dsl_internal_attributes.values)
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
include ::Strict::Dsl::Coercible
|
14
15
|
include ::Strict::Dsl::Validatable
|
15
16
|
|
16
17
|
attr_reader :__strict_dsl_internal_attributes
|
@@ -33,9 +33,9 @@ module Strict
|
|
33
33
|
|
34
34
|
raise InitializationError.new(
|
35
35
|
initializable_class: self.class,
|
36
|
-
remaining_attributes
|
37
|
-
invalid_attributes
|
38
|
-
missing_attributes:
|
36
|
+
remaining_attributes: remaining_attributes,
|
37
|
+
invalid_attributes: invalid_attributes,
|
38
|
+
missing_attributes: missing_attributes
|
39
39
|
)
|
40
40
|
end
|
41
41
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
module Coercers
|
5
|
+
class Array
|
6
|
+
attr_reader :element_coercer
|
7
|
+
|
8
|
+
def initialize(element_coercer = nil)
|
9
|
+
@element_coercer = element_coercer
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(value)
|
13
|
+
return value if value.nil? || !value.respond_to?(:to_a)
|
14
|
+
|
15
|
+
array = value.to_a
|
16
|
+
return array unless element_coercer
|
17
|
+
|
18
|
+
array.map { |element| element_coercer.call(element) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
module Coercers
|
5
|
+
class Hash
|
6
|
+
attr_reader :key_coercer, :value_coercer
|
7
|
+
|
8
|
+
def initialize(key_coercer = nil, value_coercer = nil)
|
9
|
+
@key_coercer = key_coercer
|
10
|
+
@value_coercer = value_coercer
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(value)
|
14
|
+
return value if value.nil? || !value.respond_to?(:to_h)
|
15
|
+
|
16
|
+
if key_coercer && value_coercer
|
17
|
+
coerce_keys_and_values(value.to_h)
|
18
|
+
elsif key_coercer
|
19
|
+
coerce_keys(value.to_h)
|
20
|
+
elsif value_coercer
|
21
|
+
coerce_values(value.to_h)
|
22
|
+
else
|
23
|
+
value.to_h
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def coerce_keys_and_values(hash) = hash.to_h { |key, value| [key_coercer.call(key), value_coercer.call(value)] }
|
30
|
+
def coerce_keys(hash) = hash.transform_keys { |key| key_coercer.call(key) }
|
31
|
+
def coerce_values(hash) = hash.transform_values { |value| value_coercer.call(value) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
module Dsl
|
5
|
+
module Coercible
|
6
|
+
# rubocop:disable Naming/MethodName
|
7
|
+
|
8
|
+
def ToArray(with: nil) = ::Strict::Coercers::Array.new(with)
|
9
|
+
def ToHash(with_keys: nil, with_values: nil) = ::Strict::Coercers::Hash.new(with_keys, with_values)
|
10
|
+
|
11
|
+
# rubocop:enable Naming/MethodName
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
class ImplementationDoesNotConformError < Error
|
5
|
+
attr_reader :interface, :receiver, :missing_methods, :invalid_method_definitions
|
6
|
+
|
7
|
+
def initialize(interface:, receiver:, missing_methods:, invalid_method_definitions:) # rubocop:disable Metrics/MethodLength
|
8
|
+
super(
|
9
|
+
message_from(
|
10
|
+
interface: interface,
|
11
|
+
receiver: receiver,
|
12
|
+
missing_methods: missing_methods,
|
13
|
+
invalid_method_definitions: invalid_method_definitions
|
14
|
+
)
|
15
|
+
)
|
16
|
+
|
17
|
+
@interface = interface
|
18
|
+
@receiver = receiver
|
19
|
+
@missing_methods = missing_methods
|
20
|
+
@invalid_method_definitions = invalid_method_definitions
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def message_from(interface:, receiver:, missing_methods:, invalid_method_definitions:)
|
26
|
+
details = [
|
27
|
+
missing_methods_message_from(missing_methods),
|
28
|
+
invalid_method_definitions_message_from(invalid_method_definitions)
|
29
|
+
].compact.join("\n")
|
30
|
+
|
31
|
+
case receiver
|
32
|
+
when ::Class, ::Module
|
33
|
+
"#{receiver}'s implementation of #{interface} does not conform:\n#{details}"
|
34
|
+
else
|
35
|
+
"#{receiver.class}'s implementation of #{interface} does not conform:\n#{details}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def missing_methods_message_from(missing_methods)
|
40
|
+
return nil unless missing_methods
|
41
|
+
|
42
|
+
details = missing_methods.map do |method_name|
|
43
|
+
" - #{method_name}"
|
44
|
+
end.join("\n")
|
45
|
+
|
46
|
+
" Some methods exposed in the interface were not defined in the implementation:\n#{details}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def invalid_method_definitions_message_from(invalid_method_definitions)
|
50
|
+
return nil if invalid_method_definitions.empty?
|
51
|
+
|
52
|
+
methods_details = invalid_method_definitions.map do |name, invalid_method_definition|
|
53
|
+
method_details = [
|
54
|
+
missing_parameters_message_from(invalid_method_definition.fetch(:missing_parameters)),
|
55
|
+
additional_parameters_message_from(invalid_method_definition.fetch(:additional_parameters)),
|
56
|
+
non_keyword_parameters_message_from(invalid_method_definition.fetch(:non_keyword_parameters))
|
57
|
+
].compact.join("\n")
|
58
|
+
|
59
|
+
" #{name}:\n#{method_details}"
|
60
|
+
end.join("\n")
|
61
|
+
|
62
|
+
" Some methods defined in the implementation did not conform to their interface:\n#{methods_details}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def missing_parameters_message_from(missing_parameters)
|
66
|
+
return nil unless missing_parameters.any?
|
67
|
+
|
68
|
+
details = missing_parameters.map do |parameter_name|
|
69
|
+
" - #{parameter_name}"
|
70
|
+
end.join("\n")
|
71
|
+
|
72
|
+
" Some parameters were expected, but were not in the parameter list:\n#{details}"
|
73
|
+
end
|
74
|
+
|
75
|
+
def additional_parameters_message_from(additional_parameters)
|
76
|
+
return nil unless additional_parameters.any?
|
77
|
+
|
78
|
+
details = additional_parameters.map do |parameter_name|
|
79
|
+
" - #{parameter_name}"
|
80
|
+
end.join("\n")
|
81
|
+
|
82
|
+
" Some parameters were not expected, but were in the parameter list:\n#{details}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def non_keyword_parameters_message_from(non_keyword_parameters)
|
86
|
+
return nil unless non_keyword_parameters.any?
|
87
|
+
|
88
|
+
details = non_keyword_parameters.map do |parameter_name|
|
89
|
+
" - #{parameter_name}"
|
90
|
+
end.join("\n")
|
91
|
+
|
92
|
+
" Some parameters were not keywords, but only keywords are supported:\n#{details}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -4,8 +4,15 @@ module Strict
|
|
4
4
|
class InitializationError < Error
|
5
5
|
attr_reader :remaining_attributes, :invalid_attributes, :missing_attributes
|
6
6
|
|
7
|
-
def initialize(initializable_class:, remaining_attributes:, invalid_attributes:, missing_attributes:)
|
8
|
-
super(
|
7
|
+
def initialize(initializable_class:, remaining_attributes:, invalid_attributes:, missing_attributes:) # rubocop:disable Metrics/MethodLength
|
8
|
+
super(
|
9
|
+
message_from(
|
10
|
+
initializable_class: initializable_class,
|
11
|
+
remaining_attributes: remaining_attributes,
|
12
|
+
invalid_attributes: invalid_attributes,
|
13
|
+
missing_attributes: missing_attributes
|
14
|
+
)
|
15
|
+
)
|
9
16
|
|
10
17
|
@remaining_attributes = remaining_attributes
|
11
18
|
@invalid_attributes = invalid_attributes
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
module Interface
|
5
|
+
def self.extended(mod)
|
6
|
+
mod.extend(Strict::Method)
|
7
|
+
mod.include(Interfaces::Instance)
|
8
|
+
end
|
9
|
+
|
10
|
+
def expose(method_name, &block)
|
11
|
+
sig = sig(&block)
|
12
|
+
parameter_list = sig.parameters.map { |parameter| "#{parameter.name}:" }.join(", ")
|
13
|
+
argument_list = sig.parameters.map { |parameter| "#{parameter.name}: #{parameter.name}" }.join(", ")
|
14
|
+
|
15
|
+
module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
16
|
+
def #{method_name}(#{parameter_list}, &block) # def method_name(one:, two:, three:, &block)
|
17
|
+
implementation.#{method_name}(#{argument_list}, &block) # implementation.method_name(one: one, two: two, three: three, &block)
|
18
|
+
end # end
|
19
|
+
RUBY
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
module Interfaces
|
5
|
+
module Instance
|
6
|
+
attr_reader :implementation
|
7
|
+
|
8
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
9
|
+
def initialize(implementation)
|
10
|
+
missing_methods = nil
|
11
|
+
invalid_method_definitions = Hash.new do |h, k|
|
12
|
+
h[k] = { additional_parameters: [], missing_parameters: [], non_keyword_parameters: [] }
|
13
|
+
end
|
14
|
+
|
15
|
+
self.class.strict_instance_methods.each do |method_name, strict_method|
|
16
|
+
unless implementation.respond_to?(method_name)
|
17
|
+
missing_methods ||= []
|
18
|
+
missing_methods << method_name
|
19
|
+
next
|
20
|
+
end
|
21
|
+
|
22
|
+
expected_parameters = Set.new(strict_method.parameters.map(&:name))
|
23
|
+
defined_parameters = Set.new
|
24
|
+
|
25
|
+
implementation.method(method_name).parameters.each do |kind, parameter_name|
|
26
|
+
next if kind == :block
|
27
|
+
|
28
|
+
if expected_parameters.include?(parameter_name)
|
29
|
+
defined_parameters.add(parameter_name)
|
30
|
+
invalid_method_definitions[method_name][:non_keyword_parameters] << parameter_name if kind != :keyreq
|
31
|
+
else
|
32
|
+
invalid_method_definitions[method_name][:additional_parameters] << parameter_name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
missing_parameters = expected_parameters - defined_parameters
|
37
|
+
invalid_method_definitions[method_name][:missing_parameters] = missing_parameters if missing_parameters.any?
|
38
|
+
end
|
39
|
+
|
40
|
+
if missing_methods || !invalid_method_definitions.empty?
|
41
|
+
raise Strict::ImplementationDoesNotConformError.new(
|
42
|
+
interface: self.class,
|
43
|
+
receiver: implementation,
|
44
|
+
missing_methods: missing_methods,
|
45
|
+
invalid_method_definitions: invalid_method_definitions
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
@implementation = implementation
|
50
|
+
end
|
51
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/strict/method.rb
CHANGED
@@ -8,9 +8,27 @@ module Strict
|
|
8
8
|
mod.singleton_class.extend(self)
|
9
9
|
end
|
10
10
|
|
11
|
-
def sig(&)
|
11
|
+
def sig(&block)
|
12
12
|
instance = singleton_class? ? self : singleton_class
|
13
|
-
instance.instance_variable_set(:@__strict_method_internal_last_sig_configuration, Methods::Dsl.run(&))
|
13
|
+
instance.instance_variable_set(:@__strict_method_internal_last_sig_configuration, Methods::Dsl.run(&block))
|
14
|
+
end
|
15
|
+
|
16
|
+
def strict_class_methods
|
17
|
+
instance = singleton_class? ? self : singleton_class
|
18
|
+
if instance.instance_variable_defined?(:@__strict_method_internal_class_methods)
|
19
|
+
instance.instance_variable_get(:@__strict_method_internal_class_methods)
|
20
|
+
else
|
21
|
+
instance.instance_variable_set(:@__strict_method_internal_class_methods, {})
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def strict_instance_methods
|
26
|
+
instance = singleton_class? ? self : singleton_class
|
27
|
+
if instance.instance_variable_defined?(:@__strict_method_internal_instance_methods)
|
28
|
+
instance.instance_variable_get(:@__strict_method_internal_instance_methods)
|
29
|
+
else
|
30
|
+
instance.instance_variable_set(:@__strict_method_internal_instance_methods, {})
|
31
|
+
end
|
14
32
|
end
|
15
33
|
|
16
34
|
# rubocop:disable Metrics/MethodLength
|
@@ -28,6 +46,7 @@ module Strict
|
|
28
46
|
instance: false
|
29
47
|
)
|
30
48
|
verifiable_method.verify_definition!
|
49
|
+
strict_class_methods[method_name] = verifiable_method
|
31
50
|
singleton_class.prepend(Methods::Module.new(verifiable_method))
|
32
51
|
end
|
33
52
|
|
@@ -45,6 +64,7 @@ module Strict
|
|
45
64
|
instance: true
|
46
65
|
)
|
47
66
|
verifiable_method.verify_definition!
|
67
|
+
strict_instance_methods[method_name] = verifiable_method
|
48
68
|
prepend(Methods::Module.new(verifiable_method))
|
49
69
|
end
|
50
70
|
# rubocop:enable Metrics/MethodLength
|
@@ -4,9 +4,15 @@ module Strict
|
|
4
4
|
class MethodCallError < Error
|
5
5
|
attr_reader :verifiable_method, :remaining_args, :remaining_kwargs, :invalid_parameters, :missing_parameters
|
6
6
|
|
7
|
-
def initialize(verifiable_method:, remaining_args:, remaining_kwargs:, invalid_parameters:, missing_parameters:)
|
7
|
+
def initialize(verifiable_method:, remaining_args:, remaining_kwargs:, invalid_parameters:, missing_parameters:) # rubocop:disable Metrics/MethodLength
|
8
8
|
super(
|
9
|
-
message_from(
|
9
|
+
message_from(
|
10
|
+
verifiable_method: verifiable_method,
|
11
|
+
remaining_args: remaining_args,
|
12
|
+
remaining_kwargs: remaining_kwargs,
|
13
|
+
invalid_parameters: invalid_parameters,
|
14
|
+
missing_parameters: missing_parameters
|
15
|
+
)
|
10
16
|
)
|
11
17
|
|
12
18
|
@verifiable_method = verifiable_method
|
@@ -5,7 +5,13 @@ module Strict
|
|
5
5
|
attr_reader :verifiable_method, :missing_parameters, :additional_parameters
|
6
6
|
|
7
7
|
def initialize(verifiable_method:, missing_parameters:, additional_parameters:)
|
8
|
-
super(
|
8
|
+
super(
|
9
|
+
message_from(
|
10
|
+
verifiable_method: verifiable_method,
|
11
|
+
missing_parameters: missing_parameters,
|
12
|
+
additional_parameters: additional_parameters
|
13
|
+
)
|
14
|
+
)
|
9
15
|
|
10
16
|
@verifiable_method = verifiable_method
|
11
17
|
@missing_parameters = missing_parameters
|
@@ -30,7 +36,7 @@ module Strict
|
|
30
36
|
" - #{parameter_name}"
|
31
37
|
end.join("\n")
|
32
38
|
|
33
|
-
" Some parameters were in the
|
39
|
+
" Some parameters were in the sig, but were not in the parameter list:\n#{details}"
|
34
40
|
end
|
35
41
|
|
36
42
|
def additional_parameters_message_from(additional_parameters)
|
@@ -40,7 +46,7 @@ module Strict
|
|
40
46
|
" - #{parameter_name}"
|
41
47
|
end.join("\n")
|
42
48
|
|
43
|
-
" Some parameters were not in the
|
49
|
+
" Some parameters were not in the sig, but were in the parameter list:\n#{details}"
|
44
50
|
end
|
45
51
|
end
|
46
52
|
end
|
@@ -5,7 +5,7 @@ module Strict
|
|
5
5
|
attr_reader :verifiable_method, :value
|
6
6
|
|
7
7
|
def initialize(verifiable_method:, value:)
|
8
|
-
super(message_from(verifiable_method
|
8
|
+
super(message_from(verifiable_method: verifiable_method, value: value))
|
9
9
|
|
10
10
|
@verifiable_method = verifiable_method
|
11
11
|
@value = value
|
data/lib/strict/methods/dsl.rb
CHANGED
@@ -4,9 +4,9 @@ module Strict
|
|
4
4
|
module Methods
|
5
5
|
class Dsl < BasicObject
|
6
6
|
class << self
|
7
|
-
def run(&)
|
7
|
+
def run(&block)
|
8
8
|
dsl = new
|
9
|
-
dsl.instance_eval(&)
|
9
|
+
dsl.instance_eval(&block)
|
10
10
|
::Strict::Methods::Configuration.new(
|
11
11
|
parameters: dsl.__strict_dsl_internal_parameters.values,
|
12
12
|
returns: dsl.__strict_dsl_internal_returns
|
@@ -14,6 +14,7 @@ module Strict
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
+
include ::Strict::Dsl::Coercible
|
17
18
|
include ::Strict::Dsl::Validatable
|
18
19
|
|
19
20
|
attr_reader :__strict_dsl_internal_parameters, :__strict_dsl_internal_returns
|
@@ -11,7 +11,7 @@ module Strict
|
|
11
11
|
attr_reader :parameter_name
|
12
12
|
|
13
13
|
def initialize(parameter_name:)
|
14
|
-
super(message_from(parameter_name:))
|
14
|
+
super(message_from(parameter_name: parameter_name))
|
15
15
|
|
16
16
|
@parameter_name = parameter_name
|
17
17
|
end
|
@@ -47,7 +47,11 @@ module Strict
|
|
47
47
|
|
48
48
|
missing_parameters = expected_parameters - defined_parameters
|
49
49
|
additional_parameters = defined_parameters - expected_parameters
|
50
|
-
raise Strict::MethodDefinitionError.new(
|
50
|
+
raise Strict::MethodDefinitionError.new(
|
51
|
+
verifiable_method: self,
|
52
|
+
missing_parameters: missing_parameters,
|
53
|
+
additional_parameters: additional_parameters
|
54
|
+
)
|
51
55
|
end
|
52
56
|
|
53
57
|
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/BlockLength
|
@@ -113,8 +117,8 @@ module Strict
|
|
113
117
|
verifiable_method: self,
|
114
118
|
remaining_args: args,
|
115
119
|
remaining_kwargs: kwargs,
|
116
|
-
invalid_parameters
|
117
|
-
missing_parameters:
|
120
|
+
invalid_parameters: invalid_parameters,
|
121
|
+
missing_parameters: missing_parameters
|
118
122
|
)
|
119
123
|
end
|
120
124
|
end
|
@@ -124,7 +128,7 @@ module Strict
|
|
124
128
|
value = returns.coerce(value)
|
125
129
|
return if returns.valid?(value)
|
126
130
|
|
127
|
-
raise Strict::MethodReturnError.new(verifiable_method: self, value:)
|
131
|
+
raise Strict::MethodReturnError.new(verifiable_method: self, value: value)
|
128
132
|
end
|
129
133
|
|
130
134
|
private
|
data/lib/strict/parameter.rb
CHANGED
@@ -10,7 +10,12 @@ module Strict
|
|
10
10
|
raise ArgumentError, "Only one of 'default', 'default_value', or 'default_generator' can be provided"
|
11
11
|
end
|
12
12
|
|
13
|
-
new(
|
13
|
+
new(
|
14
|
+
name: name.to_sym,
|
15
|
+
validator: validator,
|
16
|
+
default_generator: make_default_generator(**defaults),
|
17
|
+
coercer: coerce
|
18
|
+
)
|
14
19
|
end
|
15
20
|
|
16
21
|
private
|
data/lib/strict/reader/module.rb
CHANGED
@@ -9,7 +9,7 @@ module Strict
|
|
9
9
|
super()
|
10
10
|
|
11
11
|
@configuration = configuration
|
12
|
-
const_set(Strict::Attributes::
|
12
|
+
const_set(Strict::Attributes::Class::CONSTANT, configuration)
|
13
13
|
configuration.attributes.each do |attribute|
|
14
14
|
module_eval(
|
15
15
|
"def #{attribute.name} = #{attribute.instance_variable}", # def name = @instance_variable
|
data/lib/strict/return.rb
CHANGED
data/lib/strict/version.rb
CHANGED
data/strict.gemspec
CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
11
11
|
spec.summary = "Strictly define a contract for your objects and methods"
|
12
12
|
spec.homepage = "https://github.com/kylekthompson/strict"
|
13
13
|
spec.license = "MIT"
|
14
|
-
spec.required_ruby_version = ">= 3.
|
14
|
+
spec.required_ruby_version = ">= 3.0.0"
|
15
15
|
|
16
16
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
17
17
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strict
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kyle Thompson
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-10-
|
11
|
+
date: 2022-10-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: zeitwerk
|
@@ -144,6 +144,7 @@ extensions: []
|
|
144
144
|
extra_rdoc_files: []
|
145
145
|
files:
|
146
146
|
- ".rubocop.yml"
|
147
|
+
- ".tool-versions"
|
147
148
|
- CHANGELOG.md
|
148
149
|
- CODE_OF_CONDUCT.md
|
149
150
|
- Gemfile
|
@@ -156,13 +157,20 @@ files:
|
|
156
157
|
- lib/strict/accessor/module.rb
|
157
158
|
- lib/strict/assignment_error.rb
|
158
159
|
- lib/strict/attribute.rb
|
160
|
+
- lib/strict/attributes/class.rb
|
161
|
+
- lib/strict/attributes/coercer.rb
|
159
162
|
- lib/strict/attributes/configuration.rb
|
160
|
-
- lib/strict/attributes/configured.rb
|
161
163
|
- lib/strict/attributes/dsl.rb
|
162
164
|
- lib/strict/attributes/instance.rb
|
165
|
+
- lib/strict/coercers/array.rb
|
166
|
+
- lib/strict/coercers/hash.rb
|
167
|
+
- lib/strict/dsl/coercible.rb
|
163
168
|
- lib/strict/dsl/validatable.rb
|
164
169
|
- lib/strict/error.rb
|
170
|
+
- lib/strict/implementation_does_not_conform_error.rb
|
165
171
|
- lib/strict/initialization_error.rb
|
172
|
+
- lib/strict/interface.rb
|
173
|
+
- lib/strict/interfaces/instance.rb
|
166
174
|
- lib/strict/method.rb
|
167
175
|
- lib/strict/method_call_error.rb
|
168
176
|
- lib/strict/method_definition_error.rb
|
@@ -204,14 +212,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
204
212
|
requirements:
|
205
213
|
- - ">="
|
206
214
|
- !ruby/object:Gem::Version
|
207
|
-
version: 3.
|
215
|
+
version: 3.0.0
|
208
216
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
209
217
|
requirements:
|
210
218
|
- - ">="
|
211
219
|
- !ruby/object:Gem::Version
|
212
220
|
version: '0'
|
213
221
|
requirements: []
|
214
|
-
rubygems_version: 3.3
|
222
|
+
rubygems_version: 3.2.3
|
215
223
|
signing_key:
|
216
224
|
specification_version: 4
|
217
225
|
summary: Strictly define a contract for your objects and methods
|