TwirpRails
TwirpRails helps to use twirp-ruby gem with rails.
- twirp code generation from
.proto
file - handler, rspec and swagger code generation from
.proto
file mount_twirp
route helper to mount handlersrpc
helper to dry your handlers specs- ability to log twirp calls by Rails logger
- DSL to translate handler exceptions to twirp errors and client twirp errors to ruby exceptions
Installation
Add this line to your application's Gemfile:
gem 'twirp_rails'
See the twirp-ruby code generation documentation to install required protoc and twirp-ruby plugin.
Usage
Generator
Create a proto file rpc/people.proto
:
syntax = "proto3";
service People {
rpc getName(GetNameRequest) returns (GetNameResponse);
}
message GetNameRequest {
string uid = 1;
}
message GetNameResponse {
string name = 1;
}
and run
rails g twirp people
rails g twirp:rspec # run only once, if you want to use rspec rpc helper
This command will add the route and generate lib/twirp/people_pb.rb
, lib/twirp/people_twirp.rb
,
public/swagger/people.swagger.json
, app/rpc/people_handler.rb
and spec/rpc/people_handler_sprc.rb
.
# app/rpc/people_handler.rb
class PeopleHandler
def get_name(req, _env)
GetNameResponse.new
end
end
Call RPC
Modify app/rpc/people_handler.rb
:
def get_name(req, _env)
{ name: "Name of #{req.uid}" }
end
Run rails server
rails s
And check it from rails console.
PeopleClient.new('http://localhost:3000/twirp').get_name(uid: 'starship').data.name
=> "Name of starship"
Test your service with rspec
If you use RSpec, twirp generator creates handler spec file with all service methods test templates.
describe TeamsHandler do
context '#get' do
let(:team) { create(:team) }
rpc { [:get, id: team.id] }
it { should match(team: team.to_twirp) }
end
end
To include required spec helpers add this code to rails_helper.rb
require 'twirp_rails/rspec/helper'
RSpec.configure do |config|
config.include RSpec::Rails::RequestExampleGroup, type: :request, file_path: %r{spec/api}
end
or run rails g twirp:rspec
to do it automatically.
Log twirp calls
By default, Rails logs only start of POST request. To get a more detailed log of twirp calls
use config.log_twirp_calls
configuration option (default true
).
You can customize log output by pass a proc.
# config/initializers/twirp_rails.rb
config.log_twirp_calls = -> (event) do
twirp_call_info = {
duration: event.duration,
method: event.payload[:env][:rpc_method],
params: event.payload[:env][:input].to_h
}
if (exception = event.payload[:env][:exception])
twirp_call_info[:exception] = exception
end
Rails.logger.info "method=%{method} duration=%{duration}" % twirp_call_info
end
Generate clients
Clients generator calls protoc
to generate code from all proto files from
rpc_clients
directory to lib/twirp_clients
(all files auto required on application startup).
Generator runs without options by executing
$ rails g twirp:clients
Configuration
All configuration options (e.g. paths) can be customized via initializer file generated by
$ rails g twirp:init
Exception handling
Translate handler exceptions to twirp errors and client twirp errors to ruby exceptions.
This feature allow to use ruby style exception handling flow in ruby code.
Create class to describe translate error rules
class ApplicationErrorTranslator < TwirpRails::ErrorHandling::Base
# rules to translate exception raised by handler to twirp
translate_exception ArgumentError, with: :invalid_argument
translate_exception ActiveRecord::NotFound do |exception, handler|
Twirp::Error.not_found
end
# rules to translate twirp errors returned from client to exception
translate_error :invalid_argument, with: ArgumentError
translate_error :not_found do |error, client|
ActiveRecord::NotFound.new(error.msg)
end
end
And configure TwirpRails to use it:
# config/initializers/twirp_rails.rb
# ...
config.twirp_exception_translator_class = 'ApplicationErrorTranslator'
# ...
Services mounted by mount_twirp
translates raised exception to twirp errors by this rules.
To raise errors by clients, you should create client by TwirpRails.client
client = TwirpRails.client(PeopleClient, 'http://localhost:3000/twirp')
client.get_name(uid: 'not found').error
# Twirp::Error code:not_found msg:"Couldn't find User with 'uid'='not found'"
client.get_name(uid: 'not found').data.name # NoMethodError: undefined method `name' for nil:NilClass
# use bang method to raise translated error and didn't check each twirp call result error
client.get_name!(uid: 'not found').data.name # ActiveRecord::NotFound: undefined method `name' for nil:NilClass
API acronym
Gem adds inflector acronym API
to correct using services with suffix API
.
API
suffix required by prototool
linter with uber2
rules. You can disable this
inflection by set to false add_api_acronym
configuration option.
Swagger generation
Service generator rails g twirp service
generates swagger file in public/swagger
by default. You can turn it off or customize path in configuration.
Smart service detection
You can use rails g twirp svc
if your Svc
or
SvcAPI
service described at company/service/subservice/version/etc/svc_api.proto
and you have no other files svc_api.proto
.
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake test
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
. 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 tags, and push the .gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/severgroup-tt/twirp_rails. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the TwirpRails project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.