-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #12 from kylekthompson/kt-strict-method
Introduce Strict::Method for validating method calls
- Loading branch information
Showing
12 changed files
with
875 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# frozen_string_literal: true | ||
|
||
module Strict | ||
module Method | ||
def self.extended(mod) | ||
return if mod.singleton_class? | ||
|
||
mod.singleton_class.extend(self) | ||
end | ||
|
||
def sig(&) | ||
instance = singleton_class? ? self : singleton_class | ||
instance.instance_variable_set(:@__strict_method_internal_last_sig_configuration, Methods::Dsl.run(&)) | ||
end | ||
|
||
# rubocop:disable Metrics/MethodLength | ||
def singleton_method_added(method_name) | ||
super | ||
|
||
sig = singleton_class.instance_variable_get(:@__strict_method_internal_last_sig_configuration) | ||
singleton_class.instance_variable_set(:@__strict_method_internal_last_sig_configuration, nil) | ||
return unless sig | ||
|
||
verifiable_method = Methods::VerifiableMethod.new( | ||
method: singleton_class.instance_method(method_name), | ||
parameters: sig.parameters, | ||
returns: sig.returns, | ||
instance: false | ||
) | ||
verifiable_method.verify_definition! | ||
singleton_class.prepend(Methods::Module.new(verifiable_method)) | ||
end | ||
|
||
def method_added(method_name) | ||
super | ||
|
||
sig = singleton_class.instance_variable_get(:@__strict_method_internal_last_sig_configuration) | ||
singleton_class.instance_variable_set(:@__strict_method_internal_last_sig_configuration, nil) | ||
return unless sig | ||
|
||
verifiable_method = Methods::VerifiableMethod.new( | ||
method: instance_method(method_name), | ||
parameters: sig.parameters, | ||
returns: sig.returns, | ||
instance: true | ||
) | ||
verifiable_method.verify_definition! | ||
prepend(Methods::Module.new(verifiable_method)) | ||
end | ||
# rubocop:enable Metrics/MethodLength | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# frozen_string_literal: true | ||
|
||
module Strict | ||
class MethodCallError < Error | ||
attr_reader :verifiable_method, :remaining_args, :remaining_kwargs, :invalid_parameters, :missing_parameters | ||
|
||
def initialize(verifiable_method:, remaining_args:, remaining_kwargs:, invalid_parameters:, missing_parameters:) | ||
super( | ||
message_from(verifiable_method:, remaining_args:, remaining_kwargs:, invalid_parameters:, missing_parameters:) | ||
) | ||
|
||
@verifiable_method = verifiable_method | ||
@remaining_args = remaining_args | ||
@remaining_kwargs = remaining_kwargs | ||
@invalid_parameters = invalid_parameters | ||
@missing_parameters = missing_parameters | ||
end | ||
|
||
private | ||
|
||
def message_from(verifiable_method:, remaining_args:, remaining_kwargs:, invalid_parameters:, missing_parameters:) | ||
details = [ | ||
invalid_parameters_message_from(invalid_parameters), | ||
missing_parameters_message_from(missing_parameters), | ||
remaining_args_message_from(remaining_args), | ||
remaining_kwargs_message_from(remaining_kwargs) | ||
].compact.join("\n") | ||
|
||
"Calling #{verifiable_method} failed because:\n#{details}" | ||
end | ||
|
||
def invalid_parameters_message_from(invalid_parameters) | ||
return nil unless invalid_parameters | ||
|
||
details = invalid_parameters.map do |parameter, value| | ||
" - #{parameter.name}: got #{value.inspect}, expected #{parameter.validator.inspect}" | ||
end.join("\n") | ||
|
||
" Some arguments were invalid:\n#{details}" | ||
end | ||
|
||
def missing_parameters_message_from(missing_parameters) | ||
return nil unless missing_parameters | ||
|
||
details = missing_parameters.map do |parameter_name| | ||
" - #{parameter_name}" | ||
end.join("\n") | ||
|
||
" Some arguments were missing:\n#{details}" | ||
end | ||
|
||
def remaining_args_message_from(remaining_args) | ||
return nil if remaining_args.none? | ||
|
||
details = remaining_args.map do |arg| | ||
" - #{arg.inspect}" | ||
end.join("\n") | ||
|
||
" Additional positional arguments were provided, but not defined:\n#{details}" | ||
end | ||
|
||
def remaining_kwargs_message_from(remaining_kwargs) | ||
return nil if remaining_kwargs.none? | ||
|
||
details = remaining_kwargs.map do |key, value| | ||
" - #{key}: #{value.inspect}" | ||
end.join("\n") | ||
|
||
" Additional keyword arguments were provided, but not defined:\n#{details}" | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# frozen_string_literal: true | ||
|
||
module Strict | ||
class MethodDefinitionError < Error | ||
attr_reader :verifiable_method, :missing_parameters, :additional_parameters | ||
|
||
def initialize(verifiable_method:, missing_parameters:, additional_parameters:) | ||
super(message_from(verifiable_method:, missing_parameters:, additional_parameters:)) | ||
|
||
@verifiable_method = verifiable_method | ||
@missing_parameters = missing_parameters | ||
@additional_parameters = additional_parameters | ||
end | ||
|
||
private | ||
|
||
def message_from(verifiable_method:, missing_parameters:, additional_parameters:) | ||
details = [ | ||
missing_parameters_message_from(missing_parameters), | ||
additional_parameters_message_from(additional_parameters) | ||
].compact.join("\n") | ||
|
||
"Defining #{verifiable_method} failed because:\n#{details}" | ||
end | ||
|
||
def missing_parameters_message_from(missing_parameters) | ||
return nil unless missing_parameters.any? | ||
|
||
details = missing_parameters.map do |parameter_name| | ||
" - #{parameter_name}" | ||
end.join("\n") | ||
|
||
" Some parameters were in the `sig`, but were not in the parameter list:\n#{details}" | ||
end | ||
|
||
def additional_parameters_message_from(additional_parameters) | ||
return nil unless additional_parameters.any? | ||
|
||
details = additional_parameters.map do |parameter_name| | ||
" - #{parameter_name}" | ||
end.join("\n") | ||
|
||
" Some parameters were not in the `sig`, but were in the parameter list:\n#{details}" | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# frozen_string_literal: true | ||
|
||
module Strict | ||
class MethodReturnError < Error | ||
attr_reader :verifiable_method, :value | ||
|
||
def initialize(verifiable_method:, value:) | ||
super(message_from(verifiable_method:, value:)) | ||
|
||
@verifiable_method = verifiable_method | ||
@value = value | ||
end | ||
|
||
private | ||
|
||
def message_from(verifiable_method:, value:) | ||
details = invalid_returns_message_from(verifiable_method, value) | ||
"#{verifiable_method}'s return value was invalid because:\n#{details}" | ||
end | ||
|
||
def invalid_returns_message_from(verifiable_method, value) | ||
" - got #{value.inspect}, expected #{verifiable_method.returns.validator.inspect}" | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# frozen_string_literal: true | ||
|
||
module Strict | ||
module Methods | ||
class Module < ::Module | ||
attr_reader :verifiable_method | ||
|
||
def initialize(verifiable_method) | ||
super() | ||
|
||
@verifiable_method = verifiable_method | ||
define_method verifiable_method.name do |*args, **kwargs, &block| | ||
args, kwargs = verifiable_method.verify_parameters!(*args, **kwargs) | ||
|
||
super(*args, **kwargs, &block).tap do |value| | ||
verifiable_method.verify_returns!(value) | ||
end | ||
end | ||
end | ||
|
||
def inspect | ||
"#<#{self.class} (#{verifiable_method.name})>" | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.