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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +14 -2
  3. data/Gemfile.lock +19 -2
  4. data/README.md +165 -6
  5. data/lib/strict/accessor/attributes.rb +15 -0
  6. data/lib/strict/accessor/module.rb +45 -0
  7. data/lib/strict/assignment_error.rb +25 -0
  8. data/lib/strict/attribute.rb +71 -0
  9. data/lib/strict/attributes/class.rb +17 -0
  10. data/lib/strict/attributes/coercer.rb +32 -0
  11. data/lib/strict/attributes/configuration.rb +46 -0
  12. data/lib/strict/attributes/dsl.rb +43 -0
  13. data/lib/strict/attributes/instance.rb +68 -0
  14. data/lib/strict/coercers/array.rb +22 -0
  15. data/lib/strict/coercers/hash.rb +34 -0
  16. data/lib/strict/dsl/coercible.rb +14 -0
  17. data/lib/strict/dsl/validatable.rb +28 -0
  18. data/lib/strict/implementation_does_not_conform_error.rb +88 -0
  19. data/lib/strict/initialization_error.rb +57 -0
  20. data/lib/strict/interface.rb +21 -0
  21. data/lib/strict/interfaces/instance.rb +54 -0
  22. data/lib/strict/method.rb +72 -0
  23. data/lib/strict/method_call_error.rb +72 -0
  24. data/lib/strict/method_definition_error.rb +46 -0
  25. data/lib/strict/method_return_error.rb +25 -0
  26. data/lib/strict/methods/configuration.rb +14 -0
  27. data/lib/strict/methods/dsl.rb +56 -0
  28. data/lib/strict/methods/module.rb +26 -0
  29. data/lib/strict/methods/verifiable_method.rb +158 -0
  30. data/lib/strict/object.rb +9 -0
  31. data/lib/strict/parameter.rb +63 -0
  32. data/lib/strict/reader/attributes.rb +15 -0
  33. data/lib/strict/reader/module.rb +27 -0
  34. data/lib/strict/return.rb +28 -0
  35. data/lib/strict/validators/all_of.rb +24 -0
  36. data/lib/strict/validators/any_of.rb +24 -0
  37. data/lib/strict/validators/anything.rb +20 -0
  38. data/lib/strict/validators/array_of.rb +24 -0
  39. data/lib/strict/validators/boolean.rb +20 -0
  40. data/lib/strict/validators/hash_of.rb +25 -0
  41. data/lib/strict/validators/range_of.rb +24 -0
  42. data/lib/strict/value.rb +22 -0
  43. data/lib/strict/version.rb +1 -1
  44. data/lib/strict.rb +1 -0
  45. data/strict.gemspec +41 -0
  46. 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,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strict
4
+ module Methods
5
+ class Configuration
6
+ attr_reader :parameters, :returns
7
+
8
+ def initialize(parameters:, returns:)
9
+ @parameters = parameters
10
+ @returns = returns
11
+ end
12
+ end
13
+ end
14
+ 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