Aims to enhance light-service to enhance this powerful and flexible service skeleton framework with an emphasis on simplicity
run bin/console for an interactive prompt.
Add this line to your application's Gemfile:
gem 'light-service-ext'And then execute:
$ bundle install
Or install it yourself as:
$ gem install light-service-ext
Adds the following support
Provided by
.with_error_handler
- Records errors via
issue_error_report!into context as exemplified below:
{
errors: {
base: "some-exception-message",
internal_only: {
type: 'ArgumentError',
message: "`user_id` must be a number",
exception: "ArgumentError : `user_id` must be a number",
backtrace: [], # filtered backtrace via `[ActiveSupport::BacktraceCleaner](https://api.rubyonrails.org/classes/ActiveSupport/BacktraceCleaner.html)`
error: original_captured_exception
}
}
}- Captures
model validationexceptions and record the messages to the organizer's:errorscontext field- Supports the following exceptions by default
ActiveRecord::ErrorsActiveModel::Errors
- Supports the following exceptions by default
- Raises any non validation errors up the stack
- records api responses set by an action's
:api_responsecontext field - Stored inside of the organizer's
:api_responsesfield
Allows for a block to be defined on an organizer in order to retrieve the model record
- Prevents further action's been executed in the following scenarios:
- All actions complete determined by organizer's
:outcomecontext field set toLightServiceExt::Outcome::COMPLETE
- All actions complete determined by organizer's
class TaxCalculator < LightServiceExt::ApplicationOrganizer
self.retrieve_record = -> (ctx:) { User.find_by(email: ctx.params[:email]) }
def self.call(input)
user = record(ctx: input) # `.record` method executes proc provided to `retrieve_record`
input = { user: user }.merge(input)
super(input)
end
def self.steps
[TaxValidator, CalculateTaxAction]
end
endUseful if you want the current
Organizerto act as aOrchestratorand call another organizer
- ONLY modifies the orchestrator context from executing
organizer_stepsif manually applied viaeach_organizer_resultProc
organizer_steps~ must be a list of organizers to be called prior to orchestrator's actions
class TaxCalculatorReport < LightServiceExt::ApplicationOrchestrator
self.retrieve_record = -> (ctx:) { User.find_by(email: ctx.params[:email]) }
def self.call(input)
user = record(ctx: input) # `.record` method executes proc provided to `retrieve_record`
input = { user: user }.merge(user: user)
reduce_with({ input: input }, steps)
super(input.merge({ user: user })) do |current_organizer_ctx, orchestrator_ctx:|
orchestrator_ctx.add_params(current_organizer_ctx.params.slice(:user_id)) # manually add params from executed organizer(s)
end
end
def organizer_steps
[TaxCalculator]
end
def steps
[TaxReportAction]
end
end- TODO
- NOTE Action's
executedblock gets called by the underlyingLightService::Action- this means in order to call your action's methods you need to invoke it from
invoked_action:instead ofself
- this means in order to call your action's methods you need to invoke it from
invoked_action:added to current action's context before it gets executed- Consist of an instance of the current action that implements
LightServiceExt::ApplicationAction
- Consist of an instance of the current action that implements
- Enhances
Dry::Validation::Contractwith the following methods:#keys~> returns names of params defined#t~> returns translation messages in context with the current organizer- Arguments:
keye.g. :not_foundbase_path:e.g. :user**optsoptions passed into underlying Rails i18n translate call
- E.g.
t(:not_found, base_path: 'business_create', scope: 'user')would execute- =>
I18n.t('business_create.user.not_found', opts.except(:scope))
- =>
- Arguments:
Responsible for mapping, filtering and validating the context
input:field
executedblock does the following:- Appends
params:field to the current context with the mapped and filtered values - Appends errors returned from a
ApplicationContractdry-validation contract to the current context'serrors:field- NOTE fails current context if
errors:present
- NOTE fails current context if
- Appends
.contract_class~> sets the dry-validation contract to be applied by the current validator action.params_mapper_class~> sets the mapper class that must implement.map_from(context)and return mapped:inputvalues
Adds useful defaults to the organizer/orchestrator context
:input~> values originally provided to organizer get moved here for better isolation:params- stores values
filteredandmappedfrom originalinput - outcomes/return values provided by any action that implements
LightServiceExt::ApplicationAction
- stores values
:errors- validation errors processed by
LightServiceExt::ApplicationValidatorActiondry-validation contract - manually added by an action e.g.
{ errors: { email: 'not found' } }
- validation errors processed by
:successful_actions~> provides a list of actions processed mostly useful for debugging purposes e.g.['SomeActionClassName']invoked_action~> instance of action to being called.:current_api_response~> action issued api response:api_responses~> contains a list of external API interactions mostly for recording/debugging purposes (internal only):allow_raise_on_failure~> determines whether or not to throw aRaiseOnContextErrorerror up the stack in the case of validation errors and/or captured exceptions:statusdenotes the current status of the organizer with one of the following flags:LightServiceExt::Status::COMPLETELightServiceExt::Status::INCOMPLETE
:last_failed_context~ copy of context that failed e.g. witherrorsfield presentinternal_only~ includes the likes of raised error summary and should never be passed to endpoint responsesmeta~ used to store any additional information that could be helpful especially for debugging purposes. Example
input = { order: order }
overrides = {} # optionally override `params`, `errors` and `allow_raise_on_failure`
meta = { current_user_id: 12345, request_id: some-unique-request-id, impersonator_id: 54321 }
LightServiceExt::ApplicationContext.make_with_defaults(input, overrides, meta: meta)
# => { input: { order: order },
# errors: { email: ['not found'] },
# params: { user_id: 1 },
# status: Status::INCOMPLETE,
# invoked_action: SomeActionInstance,
# successful_actions: ['SomeActionClassName'],
# current_api_response: { user_id: 1, status: 'ACTIVE' },
# api_responses: [ { user_id: 1, status: 'ACTIVE' } ],
# last_failed_context: {input: { order: order }, params: {}, ...},
# allow_raise_on_failure: true,
# internal_only: { error_info: ErrorInfoInstance },
# meta: { current_user_id: 12345, request_id: some-unique-request-id, impersonator_id: 54321 }
# }-
.add_params(**params)- Adds given args to context's
paramsfield - e.g.
add_params(user_id: 1) # => { params: { user_id: 1 } }
- Adds given args to context's
-
add_errors!- Adds given args to to context's
errorsfield - Fails and returns from current action/organizer's context
- e.g.
add_to_errors!(email: 'not found') # => { errors: { email: 'not found' } }
- Adds given args to to context's
-
.add_errors(**errors)- Adds given args to to context's
errorsfield - DOES NOT fails current context
- e.g.
add_to_errors(email: 'not found') # => { errors: { email: 'not found' } }
- Adds given args to to context's
-
.add_status(status)- Should be one of Statuses e.g.
Status::COMPLETE - e.g.
add_status(Status::COMPLETE) # => { status: Status::COMPLETE }
- Should be one of Statuses e.g.
-
.add_internal_only(attrs)- e.g.
add_internal_only(request_id: 54) # => { internal_only: { error_info: nil, request_id: 54 } }
- e.g.
-
add_to_successful_actions(action_name_or_names)~> adds action names successfully executed
Provides all the information related to an exception/validation errors captured by the current organizer
#error_info~>ErrorInfoinstance#context~> state of context provided#error~> original exception#message~> summarizes which action failed etc.
- Summarize captured exception
non_fatal_errors~> takes a list of error class names considered to be non fatal exceptions
#error~> captured exception#type~> exception class name e.g.ArgumentError#message~> error messagetitle~> combined error class name and error message e.g.ArgumentError : email must be present#fatal_error?#error_summary~> summarizes exception with message and cleaned backtrace viaActiveSupport::BacktraceCleaner
.match?(type, value)e.g.LightServiceExt::Regex.match?(email:, 'email@domain.com')- supported
type:
- supported
After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.
This project uses Lefthook to run lint, security and test tools.
Install Lefthook with Homebrew or RubyGems and run lefthook install to set up the git hooks:
brew install lefthook # or: gem install lefthook
lefthook installYou can manually run all checks with:
lefthook run pre-commit
lefthook run pre-pushThe same commands are executed in the CI workflow.
Each tool can also be run directly:
bundle exec rubocop
bundle exec bundler-audit check --update
bundle exec rspecTo install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/light-service-ext. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
The gem is available as open source under the terms of the MIT License.
Everyone interacting in the LightServiceExt project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.
