Skip to content

Commit

Permalink
feat(fallback): introduce fall_result that is called for both failure…
Browse files Browse the repository at this point in the history
… and error, but haas lower priority when specific method is defined
  • Loading branch information
marian13 committed May 18, 2024
1 parent 60f8023 commit e714060
Show file tree
Hide file tree
Showing 22 changed files with 868 additions and 168 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def result
success
end

def fallback_failure_result
def fallback_result
success
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def result
success
end

def fallback_failure_result
def fallback_result
success
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def result
success
end

def fallback_failure_result
def fallback_result
success
end
end
Expand Down
13 changes: 13 additions & 0 deletions lib/convenient_service/service/configs/standard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,19 @@ module Standard
use ConvenientService::Plugins::Service::CanHaveFallbacks::Middleware.with(status: :error)
end

##
# @internal
# NOTE: Check `Essential` docs to understand why `use ConvenientService::Plugins::Common::NormalizesEnv::Middleware` for `:fallback_error_result` is used in `Standard`, not in `Essential` config.
#
middlewares :fallback_result do
use ConvenientService::Plugins::Common::NormalizesEnv::Middleware
use ConvenientService::Plugins::Service::CollectsServicesInException::Middleware
use ConvenientService::Plugins::Common::CachesReturnValue::Middleware

use ConvenientService::Plugins::Service::RaisesOnNotResultReturnValue::Middleware
use ConvenientService::Plugins::Service::CanHaveFallbacks::Middleware.with(status: nil)
end

##
# @internal
# NOTE: Check `Essential` docs to understand why `use ConvenientService::Plugins::Common::NormalizesEnv::Middleware` for `:negated_result` is used in `Standard`, not in `Essential` config.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ def fallback_failure_result
def fallback_error_result
::ConvenientService.raise Exceptions::FallbackResultIsNotOverridden.new(service: self, status: :error)
end

##
# Returns `ConvenientService::Service::Plugins::HasJSendResult::Entities::Result` when overridden.
#
# @raise [ConvenientService::Service::Plugins::CanHaveFallbacks::Exceptions::FallbackResultIsNotOverridden]
#
def fallback_result
::ConvenientService.raise Exceptions::FallbackResultIsNotOverridden.new(service: self, status: nil)
end
end

class_methods do
Expand Down Expand Up @@ -97,6 +106,15 @@ def fallback_failure_result(...)
def fallback_error_result(...)
new(...).fallback_error_result
end

##
# Returns `ConvenientService::Service::Plugins::HasJSendResult::Entities::Result` when `#fallback_result` is overridden.
#
# @raise [ConvenientService::Service::Plugins::CanHaveFallbacks::Exceptions::FallbackResultIsNotOverridden]
#
def fallback_result(...)
new(...).fallback_result
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,44 @@ module Exceptions
class FallbackResultIsNotOverridden < ::ConvenientService::Exception
def initialize_with_kwargs(service:, status:)
message = <<~TEXT
Fallback #{status} result method (#fallback_#{status}_result) of `#{service.class}` is NOT overridden.
Fallback#{enclose(status, " ")}result method (#fallback#{enclose(status, "_")}result) of `#{service.class}` is NOT overridden.
NOTE: Make sure overridden `fallback_#{status}_result` returns `success` with reasonable "null" data.
NOTE: Make sure overridden `fallback#{enclose(status, "_")}result` returns `success` with reasonable "null" data.
TEXT

initialize(message)
end

private

##
# @return [String]
#
def enclose(...)
ConvenientService::Utils::String.enclose(...)
end
end

class ServiceFallbackReturnValueNotSuccess < ::ConvenientService::Exception
def initialize_with_kwargs(service:, result:, status:)
message = <<~TEXT
Return value of service `#{service.class}` `#{status}` fallback is NOT a `success`.
Return value of service `#{service.class}`#{enclose(status, " ")}fallback is NOT a `success`.
It is `#{result.status}`.
Did you accidentally call `failure` or `error` instead of `success` from the `fallback_#{status}_result` method?
Did you accidentally call `failure` or `error` instead of `success` from the `fallback#{enclose(status, "_")}result` method?
TEXT

initialize(message)
end

private

##
# @return [String]
#
def enclose(...)
ConvenientService::Utils::String.enclose(...)
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ module Service
module Plugins
module CanHaveFallbacks
class Middleware < MethodChainMiddleware
intended_for [:fallback_failure_result, :fallback_error_result], entity: :service
intended_for [:fallback_failure_result, :fallback_error_result, :fallback_result], entity: :service

##
# @return [ConvenientService::Service::Plugins::HasJSendResult::Entities::Result]
#
# @note Both `fallback_failure_result` and `fallback_error_result` are always successful, that is why their statuses are pre-checked.
# @note Both `fallback_failure_result`, `fallback_error_result` and `fallback_result` are always successful, that is why their statuses are pre-checked.
#
def next(...)
fallback_result = chain.next(...)
Expand All @@ -24,7 +24,7 @@ def next(...)
private

##
# @return [Symbol]
# @return [Symbol, nil]
#
def status
middleware_arguments.kwargs.fetch(:status)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ def call_method(method)
# - https://ruby-doc.org/core-2.7.1/UnboundMethod.html#method-i-bind_call
# - https://blog.saeloun.com/2019/10/17/ruby-2-7-adds-unboundmethod-bind_call-method.html
#
# TODO: Util.
#
def own_method
method = Utils::Module.get_own_instance_method(organizer.class, method_name, private: true)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# frozen_string_literal: true

require_relative "can_have_fallbacks/concern"
require_relative "can_have_fallbacks/exceptions"
require_relative "can_have_fallbacks/middleware"
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

module ConvenientService
module Service
module Plugins
module CanHaveSteps
module Entities
class Step
module Plugins
module CanHaveFallbacks
module Exceptions
class FallbackResultIsNotOverridden < ::ConvenientService::Exception
def initialize_with_kwargs(step:, service:, status:)
message = <<~TEXT
Neither `fallback_#{status}_result` nor `fallback_result` methods of `#{service.class}` are overridden, but the step is marked to be fallbacked.
Either override one of those methods or remove the `fallback` option from the corresponding step definition in `#{step.container.klass}`.
TEXT

initialize(message)
end
end
end
end
end
end
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ module CanHaveFallbacks
class Middleware < MethodChainMiddleware
intended_for :result, entity: :step

##
# @return [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step]
#
alias_method :step, :entity

##
# @return [ConvenientService::Service::Plugins::HasJSendResult::Entities::Result]
#
Expand All @@ -33,28 +38,28 @@ def next(...)
# @return [Boolean]
#
def fallback_failure_step?
entity.fallback_failure_step? || fallback_true_step_as_failure_step?
step.fallback_failure_step? || fallback_true_step_as_failure_step?
end

##
# @return [Boolean]
#
def fallback_true_step_as_failure_step?
entity.fallback_true_step? && fallback_true_status == :failure
step.fallback_true_step? && fallback_true_status == :failure
end

##
# @return [Boolean]
#
def fallback_error_step?
entity.fallback_error_step? || fallback_true_step_as_error_step?
step.fallback_error_step? || fallback_true_step_as_error_step?
end

##
# @return [Boolean]
#
def fallback_true_step_as_error_step?
entity.fallback_true_step? && fallback_true_status == :error
step.fallback_true_step? && fallback_true_status == :error
end

##
Expand All @@ -66,26 +71,71 @@ def fallback_true_status

##
# @return [ConvenientService::Service::Plugins::HasJSendResult::Entities::Result]
# @raise [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step::Exceptions::StepHasNoOrganizer]
#
# @internal
# IMPORTANT: `service.klass.fallback_failure_result(**input_values)` is the reason, why services should have only kwargs as arguments.
# TODO: `entity.service.fallback_failure_result`.
# @raise [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step::Plugins::CanHaveFallbacks::Exceptions::FallbackResultIsNotOverridden]
#
def fallback_failure_result
entity.service.klass.fallback_failure_result(**entity.input_values)
fallback_failure_result_own_method&.call || fallback_result_own_method&.call || ::ConvenientService.raise(Exceptions::FallbackResultIsNotOverridden.new(step: step, service: service, status: :failure))
end

##
# @return [ConvenientService::Service::Plugins::HasJSendResult::Entities::Result]
# @raise [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step::Exceptions::StepHasNoOrganizer]
# @raise [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step::Plugins::CanHaveFallbacks::Exceptions::FallbackResultIsNotOverridden]
#
def fallback_error_result
fallback_error_result_own_method&.call || fallback_result_own_method&.call || ::ConvenientService.raise(Exceptions::FallbackResultIsNotOverridden.new(step: step, service: service, status: :error))
end

##
# @return [Method, nil]
#
# @internal
# IMPORTANT: `service.klass.fallback_error_result(**input_values)` is the reason, why services should have only kwargs as arguments.
# TODO: `entity.service.fallback_error_result`.
# TODO: Util.
# TODO: Comprehensive suite.
#
def fallback_error_result
entity.service.klass.fallback_error_result(**entity.input_values)
def fallback_failure_result_own_method
method = Utils::Module.get_own_instance_method(service.class, :fallback_failure_result, private: true)

return unless method

method.bind(service)
end

##
# @return [Method, nil]
#
# @internal
# TODO: Util.
#
def fallback_error_result_own_method
method = Utils::Module.get_own_instance_method(service.class, :fallback_error_result, private: true)

return unless method

method.bind(service)
end

##
# @return [Method, nil]
#
# @internal
# TODO: Util.
#
def fallback_result_own_method
method = Utils::Module.get_own_instance_method(service.class, :fallback_result, private: true)

return unless method

method.bind(service)
end

##
# @return [ConvenientService::Service]
#
# @internal
# IMPORTANT: `step.service.klass.new(**input_values)` is the reason, why services should have only kwargs as arguments.
#
def service
@service ||= step.service.klass.new(**step.input_values)
end
end
end
Expand Down
5 changes: 5 additions & 0 deletions lib/convenient_service/utils/string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require_relative "string/camelize"
require_relative "string/demodulize"
require_relative "string/enclose"
require_relative "string/split"
require_relative "string/truncate"

Expand All @@ -17,6 +18,10 @@ def demodulize(...)
Demodulize.call(...)
end

def enclose(...)
Enclose.call(...)
end

def split(...)
Split.call(...)
end
Expand Down
40 changes: 40 additions & 0 deletions lib/convenient_service/utils/string/enclose.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

module ConvenientService
module Utils
module String
class Enclose < Support::Command
##
# @!attribute [r] string
# @return [String]
#
attr_reader :string

##
# @!attribute [r] char
# @return [String]
#
attr_reader :char

##
# @param string [#to_s]
# @param char [String]
# @return [void]
#
def initialize(string, char)
@string = string
@char = char
end

##
# @return [String]
#
def call
return char unless string

"#{char}#{string}#{char}"
end
end
end
end
end
Loading

0 comments on commit e714060

Please sign in to comment.