Add this line to your application's Gemfile:
gem 'salestation'And then execute:
$ bundle
First include Salestation::Web. This will provide a method called process to execute a request and Responses module for return codes.
class Webapp < Sinatra::Base
include Salestation::Web.new
endCreate Salestation application:
def app
@_app ||= Salestation::App.new(env: ENVIRONMENT)
endDefine a route
post '/hello/:name' do
process(
HelloUser.call(app.create_request(
name: params['name']
)).map(Responses.to_ok)
)
endDefine chain
class HelloUser
def self.call(request)
request >> upcase >> format
end
def self.upcase
-> (request) do
input.with_input(name: input.fetch(:name).upcase)
end
end
def self.format
-> (request) do
name = request.input.fetch(:name)
Deterministic::Result::Success(message: "Hello #{name}")
end
end
endSalestation allows and recommends you to define your own custom errors. This is useful when your app has error classes that are not general enough for the salestation library.
include Salestation::Web.new(errors: {
CustomError => -> (error) { CustomResponse.new(error) }
})If you need to specify additional error fields you can use from method.
from accepts base error on which the rest of the response is built.
Base error must be a hash or implement to_h method.
Example:
App::Errors::Conflict.from({details: 'details'}, message: 'message', debug_message: 'debug_message')
Response:
{
"details": "details",
"message": "message",
"debug_message": "debug_message"
}Salestation provides extractors to fetch parameters from the request and pass them to the chain.
Available extractors are BodyParamExtractor, QueryParamExtractor, ConstantInput, HeadersExtractor.
Multiple extractors can be merged together. If two or more extractors use the same key, the value will be from the last extractor in the merge chain.
coercions can optionally be provided to BodyParamExtractor and QueryParamExtractor. These can be used to transform the values of the extracted parameters.
ParamExtractor, which is also used by BodyParamExtractor and QueryParamExtractor,
extracts only the content of such root level keys that are specified when creating the
extractor instance. All other root level keys are discarded. Everything inside the
whitelisted root level keys is automatically whitelisted.
Define a route
include Salestation::Web.new
include Salestation::Web::Extractors
APP = Salestation::App.new(env: {})
def create_app_request
-> (input) { App.create_request(input) }
end
post '/hello/:name' do |name|
extractor = BodyParamExtractor[:age]
.merge(ConstantInput[name: name])
.merge(HeadersExtractor[
'accept' => :accept,
'content-type' => :content_type,
'x-my-value' => :my_value
])
.coerce(age: ->(age) { age.to_s })
validate_input = Salestation::Web::InputValidator[
accept: Salestation::Web::InputValidators::AcceptHeader['application/json', 'application/xml'],
content_type: Salestation::Web::InputValidators::ContentTypeHeader['application/json'],
my_value: lambda do |my_value|
if my_value == 'foo'
Deterministic::Result::Success(nil)
else
Deterministic::Result::Failure('invalid value')
end
end
]
response = extractor.call(request)
.map(validate_input)
.map(create_app_request)
.map(HelloUser)
.map(Responses.to_ok)
process(response)
endSalestation provides a rack logging middleware which can be used to log structured objects.
class Webapp < Sinatra::Base
# ...
use Salestation::Web::RequestLogger, my_logger
endYou can configure per-request fields by defining salestation.request_logger.fields in sinatra env:
def my_handler(env)
env['salestation.request_logger.fields'] = { foo: 'bar' }
# ...
endSalestation provides a StatsD middleware which can be used record request
execution time. A distribution call with elapsed seconds is made to the provided
StatsD instance with path, method, status and status_class tags.
class Webapp < Sinatra::Base
# ...
use Salestation::Web::StatsdMiddleware,
Statsd.new(host, port),
metric: 'my_service.response.time'
endYou can configure per-request tags by defining salestation.statsd.tags in sinatra env:
def my_handler(env)
env['salestation.statsd.tags'] = ['foo:bar']
# ...
endTo use Salestation RSpec matchers you first need to include them:
require 'salestation/rspec'
RSpec.configure do |config|
config.include Salestation::RSpec::Matchers
end
After that it's possible to match against error results like this:
expect(result).to be_failure
.with_conflict
.containing(message: 'Oh noes')
See lib/salestation/rspec.rb for more examples.
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.
To install this gem onto your local machine, run bundle exec rake install. A new version is created when a change is merged into the master branch that changes the version number in salestation.gemspec. A Github Action will create a tag for the version and push the .gem file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/salestation.
The gem is available as open source under the terms of the MIT License.