No release in over a year
You can implement twirp service with Rails controller
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 1.36.0

Runtime

>= 6.0.0
~> 1.9
 Project Readme

ActionController::Twirp

build License

Implement twirp service with Rails controller.

Installation

Add this line to your application's Gemfile:

gem "action_controller-twirp"

And then execute:

$ bundle

Or install it yourself as:

$ gem install action_controller-twirp

Usage

twirp-ruby with Ruby on Rails.

It provides routing method twirp that maps twirp service class and Rails controller class, and a module of ActionController::Twirp to help execute RPC methods and rendering Twirp::Error.

First, you should generate twirp service class. see: example in twirp-ruby

Next, install initializer.

bin/rails g action_controller:twirp:install

Next, add twirp action in config/routes.rb.

Rails.application.routes.draw do
  scope :twirp do
    twirp Example::V1::UserApiService, controller: 'users'
  end
end

Result of bin/rails routes.

> bin/rails routes
 Prefix  Verb  URI Pattern                                    Controller#Action
         POST  /twirp/example.v1.UserApi/ListUsers(.:format)  users#list_users
         POST  /twirp/example.v1.UserApi/GetUser(.:format)    users#get_user

Finally, implement rpc method your controller.

class UsersController < ApplicationController
  include ActionController::Twirp

  USERS = [
    { id: 1, name: 'Anna' },
    { id: 2, name: 'Reina' }
  ].freeze

  def list_users(_req, _env)
    Example::V1::ListUsersResponse.new(users: USERS)
  end

  def get_user(req, _env)
    user = USERS.find { |u| u[:id] == req.id }

    raise ActiveRecord::RecordNotFound unless user

    Example::V1::User.new(user)
  end
end

Error handling

We must follow protocol when using Twirp. Specifically,

  • It accepts request parameters specified in proto file, and must return response body.
  • If an exception occurs, a response body of Twirp::Error's JSON must be returned.

This section describes when an exception occurs.

The following blocks are in the order of method calls when requested to the Rails app. (It's quite simplified)

 :
ActionController::Metal#dispatch
  AbstractController::Base#process
     :
    ActionController::Rescue#process_action
    ActionController::Callbacks#process_action
     :
    (Some module's #process_action is called)
     :
    AbstractController::Base#process_action
      AbstractController::Base#send_action

The gem is only override #send_action. It follows twirp protocal in this one.

For example, if an exception occurs in the callback, Rails app will return a response, so we must be implemented so that the twirp protocol can be followed.

This is easy to do, see below.

First, modify exception_codes config.

> cat config/initializers/action_controller_twirp.rb
# frozen_string_literal: true

ActionController::Twirp::Config.setup do |config|
  # Mapping your exception classes and Twirp::Error::ERROR_CODES
  # String => Symbol
  config.exception_codes = {
    'ActiveRecord::RecordInvalid' => :invalid_argument,
    'ActiveRecord::RecordNotFound' => :not_found,
    'MyApp::Unauthenticated' => :unauthenticated,
  }

  # Block to make Twirp::Error message when exception_codes exist
  # config.build_message = ->(exception) {}

  # Block to make Twirp::Error metadata. when exception_codes exist
  # It MUST return Hash value
  # config.build_metadata = ->(exception) {}

  # Block to run additional process. e.g. logging
  # config.on_exceptions = ->(exception) {}
end

Next, write a rescue_from block as usual and use the :twirp_error key on #render

class UsersController < ApplicationController
  include ActionController::Twirp

  before_action :authenticate!

  rescue_from MyApp::Unauthenticated do |e|
    render twirp_error: e
  end

  :
  :

  private

  def authenticate!
    return if your_logic

    raise MyApp::Unauthenticated
  end
end

See dummy app controller and integration test for details.

Contributing

Contribution directions go here.

License

The gem is available as open source under the terms of the MIT License.