strict 0.0.0 → 1.1.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 +14 -2
- data/Gemfile.lock +19 -2
- data/README.md +165 -6
- data/lib/strict/accessor/attributes.rb +15 -0
- data/lib/strict/accessor/module.rb +45 -0
- data/lib/strict/assignment_error.rb +25 -0
- data/lib/strict/attribute.rb +71 -0
- data/lib/strict/attributes/class.rb +17 -0
- data/lib/strict/attributes/coercer.rb +32 -0
- data/lib/strict/attributes/configuration.rb +46 -0
- data/lib/strict/attributes/dsl.rb +43 -0
- data/lib/strict/attributes/instance.rb +68 -0
- 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/dsl/validatable.rb +28 -0
- data/lib/strict/implementation_does_not_conform_error.rb +88 -0
- data/lib/strict/initialization_error.rb +57 -0
- data/lib/strict/interface.rb +21 -0
- data/lib/strict/interfaces/instance.rb +54 -0
- data/lib/strict/method.rb +72 -0
- data/lib/strict/method_call_error.rb +72 -0
- data/lib/strict/method_definition_error.rb +46 -0
- data/lib/strict/method_return_error.rb +25 -0
- data/lib/strict/methods/configuration.rb +14 -0
- data/lib/strict/methods/dsl.rb +56 -0
- data/lib/strict/methods/module.rb +26 -0
- data/lib/strict/methods/verifiable_method.rb +158 -0
- data/lib/strict/object.rb +9 -0
- data/lib/strict/parameter.rb +63 -0
- data/lib/strict/reader/attributes.rb +15 -0
- data/lib/strict/reader/module.rb +27 -0
- data/lib/strict/return.rb +28 -0
- data/lib/strict/validators/all_of.rb +24 -0
- data/lib/strict/validators/any_of.rb +24 -0
- data/lib/strict/validators/anything.rb +20 -0
- data/lib/strict/validators/array_of.rb +24 -0
- data/lib/strict/validators/boolean.rb +20 -0
- data/lib/strict/validators/hash_of.rb +25 -0
- data/lib/strict/validators/range_of.rb +24 -0
- data/lib/strict/value.rb +22 -0
- data/lib/strict/version.rb +1 -1
- data/lib/strict.rb +1 -0
- data/strict.gemspec +41 -0
- metadata +97 -2
@@ -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,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
module Dsl
|
5
|
+
module Validatable
|
6
|
+
# rubocop:disable Naming/MethodName
|
7
|
+
|
8
|
+
def AllOf(*subvalidators) = ::Strict::Validators::AllOf.new(*subvalidators)
|
9
|
+
def AnyOf(*subvalidators) = ::Strict::Validators::AnyOf.new(*subvalidators)
|
10
|
+
def Anything = ::Strict::Validators::Anything.instance
|
11
|
+
def ArrayOf(element_validator) = ::Strict::Validators::ArrayOf.new(element_validator)
|
12
|
+
def Boolean = ::Strict::Validators::Boolean.instance
|
13
|
+
|
14
|
+
def HashOf(key_validator_to_value_validator)
|
15
|
+
if key_validator_to_value_validator.size != 1
|
16
|
+
raise ArgumentError, "HashOf's usage is: HashOf(KeyValidator => ValueValidator)"
|
17
|
+
end
|
18
|
+
|
19
|
+
key_validator, value_validator = key_validator_to_value_validator.first
|
20
|
+
::Strict::Validators::HashOf.new(key_validator, value_validator)
|
21
|
+
end
|
22
|
+
|
23
|
+
def RangeOf(element_validator) = ::Strict::Validators::RangeOf.new(element_validator)
|
24
|
+
|
25
|
+
# rubocop:enable Naming/MethodName
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,88 @@
|
|
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:)
|
8
|
+
super(message_from(interface:, receiver:, missing_methods:, invalid_method_definitions:))
|
9
|
+
|
10
|
+
@interface = interface
|
11
|
+
@receiver = receiver
|
12
|
+
@missing_methods = missing_methods
|
13
|
+
@invalid_method_definitions = invalid_method_definitions
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def message_from(interface:, receiver:, missing_methods:, invalid_method_definitions:)
|
19
|
+
details = [
|
20
|
+
missing_methods_message_from(missing_methods),
|
21
|
+
invalid_method_definitions_message_from(invalid_method_definitions)
|
22
|
+
].compact.join("\n")
|
23
|
+
|
24
|
+
case receiver
|
25
|
+
when ::Class, ::Module
|
26
|
+
"#{receiver}'s implementation of #{interface} does not conform:\n#{details}"
|
27
|
+
else
|
28
|
+
"#{receiver.class}'s implementation of #{interface} does not conform:\n#{details}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def missing_methods_message_from(missing_methods)
|
33
|
+
return nil unless missing_methods
|
34
|
+
|
35
|
+
details = missing_methods.map do |method_name|
|
36
|
+
" - #{method_name}"
|
37
|
+
end.join("\n")
|
38
|
+
|
39
|
+
" Some methods exposed in the interface were not defined in the implementation:\n#{details}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def invalid_method_definitions_message_from(invalid_method_definitions)
|
43
|
+
return nil if invalid_method_definitions.empty?
|
44
|
+
|
45
|
+
methods_details = invalid_method_definitions.map do |name, invalid_method_definition|
|
46
|
+
method_details = [
|
47
|
+
missing_parameters_message_from(invalid_method_definition.fetch(:missing_parameters)),
|
48
|
+
additional_parameters_message_from(invalid_method_definition.fetch(:additional_parameters)),
|
49
|
+
non_keyword_parameters_message_from(invalid_method_definition.fetch(:non_keyword_parameters))
|
50
|
+
].compact.join("\n")
|
51
|
+
|
52
|
+
" #{name}:\n#{method_details}"
|
53
|
+
end.join("\n")
|
54
|
+
|
55
|
+
" Some methods defined in the implementation did not conform to their interface:\n#{methods_details}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def missing_parameters_message_from(missing_parameters)
|
59
|
+
return nil unless missing_parameters.any?
|
60
|
+
|
61
|
+
details = missing_parameters.map do |parameter_name|
|
62
|
+
" - #{parameter_name}"
|
63
|
+
end.join("\n")
|
64
|
+
|
65
|
+
" Some parameters were expected, but were not in the parameter list:\n#{details}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def additional_parameters_message_from(additional_parameters)
|
69
|
+
return nil unless additional_parameters.any?
|
70
|
+
|
71
|
+
details = additional_parameters.map do |parameter_name|
|
72
|
+
" - #{parameter_name}"
|
73
|
+
end.join("\n")
|
74
|
+
|
75
|
+
" Some parameters were not expected, but were in the parameter list:\n#{details}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def non_keyword_parameters_message_from(non_keyword_parameters)
|
79
|
+
return nil unless non_keyword_parameters.any?
|
80
|
+
|
81
|
+
details = non_keyword_parameters.map do |parameter_name|
|
82
|
+
" - #{parameter_name}"
|
83
|
+
end.join("\n")
|
84
|
+
|
85
|
+
" Some parameters were not keywords, but only keywords are supported:\n#{details}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
class InitializationError < Error
|
5
|
+
attr_reader :remaining_attributes, :invalid_attributes, :missing_attributes
|
6
|
+
|
7
|
+
def initialize(initializable_class:, remaining_attributes:, invalid_attributes:, missing_attributes:)
|
8
|
+
super(message_from(initializable_class:, remaining_attributes:, invalid_attributes:, missing_attributes:))
|
9
|
+
|
10
|
+
@remaining_attributes = remaining_attributes
|
11
|
+
@invalid_attributes = invalid_attributes
|
12
|
+
@missing_attributes = missing_attributes
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def message_from(initializable_class:, remaining_attributes:, invalid_attributes:, missing_attributes:)
|
18
|
+
details = [
|
19
|
+
invalid_attributes_message_from(invalid_attributes),
|
20
|
+
missing_attributes_message_from(missing_attributes),
|
21
|
+
remaining_attributes_message_from(remaining_attributes)
|
22
|
+
].compact.join("\n")
|
23
|
+
|
24
|
+
"Initialization of #{initializable_class} failed because:\n#{details}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def invalid_attributes_message_from(invalid_attributes)
|
28
|
+
return nil unless invalid_attributes
|
29
|
+
|
30
|
+
details = invalid_attributes.map do |attribute, value|
|
31
|
+
" - #{attribute.name}: got #{value.inspect}, expected #{attribute.validator.inspect}"
|
32
|
+
end.join("\n")
|
33
|
+
|
34
|
+
" Some attributes were invalid:\n#{details}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def missing_attributes_message_from(missing_attributes)
|
38
|
+
return nil unless missing_attributes
|
39
|
+
|
40
|
+
details = missing_attributes.map do |attribute_name|
|
41
|
+
" - #{attribute_name}"
|
42
|
+
end.join("\n")
|
43
|
+
|
44
|
+
" Some attributes were missing:\n#{details}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def remaining_attributes_message_from(remaining_attributes)
|
48
|
+
return nil if remaining_attributes.none?
|
49
|
+
|
50
|
+
details = remaining_attributes.map do |attribute_name|
|
51
|
+
" - #{attribute_name}"
|
52
|
+
end.join("\n")
|
53
|
+
|
54
|
+
" Some attributes were provided, but not defined:\n#{details}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,21 @@
|
|
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, &)
|
11
|
+
sig = sig(&)
|
12
|
+
parameter_list = sig.parameters.map { |parameter| "#{parameter.name}:" }.join(", ")
|
13
|
+
|
14
|
+
module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
15
|
+
def #{method_name}(#{parameter_list}, &block) # def method_name(one:, two:, three:, &block)
|
16
|
+
implementation.#{method_name}(#{parameter_list}, &block) # implementation.method_name(one:, two:, three:, &block)
|
17
|
+
end # end
|
18
|
+
RUBY
|
19
|
+
end
|
20
|
+
end
|
21
|
+
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:,
|
45
|
+
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
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
module Method
|
5
|
+
def self.extended(mod)
|
6
|
+
return if mod.singleton_class?
|
7
|
+
|
8
|
+
mod.singleton_class.extend(self)
|
9
|
+
end
|
10
|
+
|
11
|
+
def sig(&)
|
12
|
+
instance = singleton_class? ? self : singleton_class
|
13
|
+
instance.instance_variable_set(:@__strict_method_internal_last_sig_configuration, Methods::Dsl.run(&))
|
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
|
32
|
+
end
|
33
|
+
|
34
|
+
# rubocop:disable Metrics/MethodLength
|
35
|
+
def singleton_method_added(method_name)
|
36
|
+
super
|
37
|
+
|
38
|
+
sig = singleton_class.instance_variable_get(:@__strict_method_internal_last_sig_configuration)
|
39
|
+
singleton_class.instance_variable_set(:@__strict_method_internal_last_sig_configuration, nil)
|
40
|
+
return unless sig
|
41
|
+
|
42
|
+
verifiable_method = Methods::VerifiableMethod.new(
|
43
|
+
method: singleton_class.instance_method(method_name),
|
44
|
+
parameters: sig.parameters,
|
45
|
+
returns: sig.returns,
|
46
|
+
instance: false
|
47
|
+
)
|
48
|
+
verifiable_method.verify_definition!
|
49
|
+
strict_class_methods[method_name] = verifiable_method
|
50
|
+
singleton_class.prepend(Methods::Module.new(verifiable_method))
|
51
|
+
end
|
52
|
+
|
53
|
+
def method_added(method_name)
|
54
|
+
super
|
55
|
+
|
56
|
+
sig = singleton_class.instance_variable_get(:@__strict_method_internal_last_sig_configuration)
|
57
|
+
singleton_class.instance_variable_set(:@__strict_method_internal_last_sig_configuration, nil)
|
58
|
+
return unless sig
|
59
|
+
|
60
|
+
verifiable_method = Methods::VerifiableMethod.new(
|
61
|
+
method: instance_method(method_name),
|
62
|
+
parameters: sig.parameters,
|
63
|
+
returns: sig.returns,
|
64
|
+
instance: true
|
65
|
+
)
|
66
|
+
verifiable_method.verify_definition!
|
67
|
+
strict_instance_methods[method_name] = verifiable_method
|
68
|
+
prepend(Methods::Module.new(verifiable_method))
|
69
|
+
end
|
70
|
+
# rubocop:enable Metrics/MethodLength
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
class MethodCallError < Error
|
5
|
+
attr_reader :verifiable_method, :remaining_args, :remaining_kwargs, :invalid_parameters, :missing_parameters
|
6
|
+
|
7
|
+
def initialize(verifiable_method:, remaining_args:, remaining_kwargs:, invalid_parameters:, missing_parameters:)
|
8
|
+
super(
|
9
|
+
message_from(verifiable_method:, remaining_args:, remaining_kwargs:, invalid_parameters:, missing_parameters:)
|
10
|
+
)
|
11
|
+
|
12
|
+
@verifiable_method = verifiable_method
|
13
|
+
@remaining_args = remaining_args
|
14
|
+
@remaining_kwargs = remaining_kwargs
|
15
|
+
@invalid_parameters = invalid_parameters
|
16
|
+
@missing_parameters = missing_parameters
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def message_from(verifiable_method:, remaining_args:, remaining_kwargs:, invalid_parameters:, missing_parameters:)
|
22
|
+
details = [
|
23
|
+
invalid_parameters_message_from(invalid_parameters),
|
24
|
+
missing_parameters_message_from(missing_parameters),
|
25
|
+
remaining_args_message_from(remaining_args),
|
26
|
+
remaining_kwargs_message_from(remaining_kwargs)
|
27
|
+
].compact.join("\n")
|
28
|
+
|
29
|
+
"Calling #{verifiable_method} failed because:\n#{details}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def invalid_parameters_message_from(invalid_parameters)
|
33
|
+
return nil unless invalid_parameters
|
34
|
+
|
35
|
+
details = invalid_parameters.map do |parameter, value|
|
36
|
+
" - #{parameter.name}: got #{value.inspect}, expected #{parameter.validator.inspect}"
|
37
|
+
end.join("\n")
|
38
|
+
|
39
|
+
" Some arguments were invalid:\n#{details}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def missing_parameters_message_from(missing_parameters)
|
43
|
+
return nil unless missing_parameters
|
44
|
+
|
45
|
+
details = missing_parameters.map do |parameter_name|
|
46
|
+
" - #{parameter_name}"
|
47
|
+
end.join("\n")
|
48
|
+
|
49
|
+
" Some arguments were missing:\n#{details}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def remaining_args_message_from(remaining_args)
|
53
|
+
return nil if remaining_args.none?
|
54
|
+
|
55
|
+
details = remaining_args.map do |arg|
|
56
|
+
" - #{arg.inspect}"
|
57
|
+
end.join("\n")
|
58
|
+
|
59
|
+
" Additional positional arguments were provided, but not defined:\n#{details}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def remaining_kwargs_message_from(remaining_kwargs)
|
63
|
+
return nil if remaining_kwargs.none?
|
64
|
+
|
65
|
+
details = remaining_kwargs.map do |key, value|
|
66
|
+
" - #{key}: #{value.inspect}"
|
67
|
+
end.join("\n")
|
68
|
+
|
69
|
+
" Additional keyword arguments were provided, but not defined:\n#{details}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
class MethodDefinitionError < Error
|
5
|
+
attr_reader :verifiable_method, :missing_parameters, :additional_parameters
|
6
|
+
|
7
|
+
def initialize(verifiable_method:, missing_parameters:, additional_parameters:)
|
8
|
+
super(message_from(verifiable_method:, missing_parameters:, additional_parameters:))
|
9
|
+
|
10
|
+
@verifiable_method = verifiable_method
|
11
|
+
@missing_parameters = missing_parameters
|
12
|
+
@additional_parameters = additional_parameters
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def message_from(verifiable_method:, missing_parameters:, additional_parameters:)
|
18
|
+
details = [
|
19
|
+
missing_parameters_message_from(missing_parameters),
|
20
|
+
additional_parameters_message_from(additional_parameters)
|
21
|
+
].compact.join("\n")
|
22
|
+
|
23
|
+
"Defining #{verifiable_method} failed because:\n#{details}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def missing_parameters_message_from(missing_parameters)
|
27
|
+
return nil unless missing_parameters.any?
|
28
|
+
|
29
|
+
details = missing_parameters.map do |parameter_name|
|
30
|
+
" - #{parameter_name}"
|
31
|
+
end.join("\n")
|
32
|
+
|
33
|
+
" Some parameters were in the sig, but were not in the parameter list:\n#{details}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def additional_parameters_message_from(additional_parameters)
|
37
|
+
return nil unless additional_parameters.any?
|
38
|
+
|
39
|
+
details = additional_parameters.map do |parameter_name|
|
40
|
+
" - #{parameter_name}"
|
41
|
+
end.join("\n")
|
42
|
+
|
43
|
+
" Some parameters were not in the sig, but were in the parameter list:\n#{details}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
class MethodReturnError < Error
|
5
|
+
attr_reader :verifiable_method, :value
|
6
|
+
|
7
|
+
def initialize(verifiable_method:, value:)
|
8
|
+
super(message_from(verifiable_method:, value:))
|
9
|
+
|
10
|
+
@verifiable_method = verifiable_method
|
11
|
+
@value = value
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def message_from(verifiable_method:, value:)
|
17
|
+
details = invalid_returns_message_from(verifiable_method, value)
|
18
|
+
"#{verifiable_method}'s return value was invalid because:\n#{details}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def invalid_returns_message_from(verifiable_method, value)
|
22
|
+
" - got #{value.inspect}, expected #{verifiable_method.returns.validator.inspect}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
module Methods
|
5
|
+
class Dsl < BasicObject
|
6
|
+
class << self
|
7
|
+
def run(&)
|
8
|
+
dsl = new
|
9
|
+
dsl.instance_eval(&)
|
10
|
+
::Strict::Methods::Configuration.new(
|
11
|
+
parameters: dsl.__strict_dsl_internal_parameters.values,
|
12
|
+
returns: dsl.__strict_dsl_internal_returns
|
13
|
+
)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
include ::Strict::Dsl::Coercible
|
18
|
+
include ::Strict::Dsl::Validatable
|
19
|
+
|
20
|
+
attr_reader :__strict_dsl_internal_parameters, :__strict_dsl_internal_returns
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@__strict_dsl_internal_parameters = {}
|
24
|
+
@__strict_dsl_internal_returns = ::Strict::Return.make
|
25
|
+
end
|
26
|
+
|
27
|
+
def returns(*args, **kwargs)
|
28
|
+
self.__strict_dsl_internal_returns = ::Strict::Return.make(*args, **kwargs)
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def strict_parameter(*args, **kwargs)
|
33
|
+
parameter = ::Strict::Parameter.make(*args, **kwargs)
|
34
|
+
__strict_dsl_internal_parameters[parameter.name] = parameter
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def method_missing(name, *args, **kwargs)
|
39
|
+
if respond_to_missing?(name)
|
40
|
+
strict_parameter(name, *args, **kwargs)
|
41
|
+
else
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def respond_to_missing?(method_name, _include_private = nil)
|
47
|
+
first_letter = method_name.to_s.each_char.first
|
48
|
+
first_letter.eql?(first_letter.downcase)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
attr_writer :__strict_dsl_internal_returns
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
module Methods
|
5
|
+
class Module < ::Module
|
6
|
+
attr_reader :verifiable_method
|
7
|
+
|
8
|
+
def initialize(verifiable_method)
|
9
|
+
super()
|
10
|
+
|
11
|
+
@verifiable_method = verifiable_method
|
12
|
+
define_method verifiable_method.name do |*args, **kwargs, &block|
|
13
|
+
args, kwargs = verifiable_method.verify_parameters!(*args, **kwargs)
|
14
|
+
|
15
|
+
super(*args, **kwargs, &block).tap do |value|
|
16
|
+
verifiable_method.verify_returns!(value)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def inspect
|
22
|
+
"#<#{self.class} (#{verifiable_method.name})>"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|