strict 1.0.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|