strict 0.0.0 → 1.1.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 +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
|