TwirpRails

Gem Version

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 handlers
  • rpc 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.