rails_twirp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fe63b82d059ab115dd9fdb2ad342fa6e71264a519abac81ffb68b361ed908209
4
+ data.tar.gz: '049a00e7d8928a0df13e012613840d21ac722693125440070261a2ad07d7e1b0'
5
+ SHA512:
6
+ metadata.gz: 919cede079ec3bf55898d5014d22dc3324379d9b793a7cfc3ef35d769a79a2458abe56f9971496ff2ec3c0d624967f51c6c50b0f501009cf0f76a5c9167fecd8
7
+ data.tar.gz: a7845a591ced9a9afe3dca8f0cd02babb8ed80142be3ddc7a0d1afd152a230a9dc9b1a6e8c847fb0095fa1665ea5647c89232f9810b83aa06178ab29a453b24b
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /doc/
3
+ /log/*.log
4
+ /pkg/
5
+ /tmp/
6
+ /test/dummy/db/*.sqlite3
7
+ /test/dummy/db/*.sqlite3-*
8
+ /test/dummy/log/*.log
9
+ /test/dummy/storage/
10
+ /test/dummy/tmp/
11
+ .byebug_history
12
+ Gemfile.lock
13
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3
+
4
+ # Specify your gem's dependencies in rails_twirp.gemspec.
5
+ gemspec
6
+
7
+ # To use a debugger
8
+ # gem 'byebug', group: [:development, :test]
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2021 Bouke van der Bijl
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # RailsTwirp
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'rails_twirp'
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install rails_twirp
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/setup"
2
+ require "bundler/gem_tasks"
3
+ require "rake/testtask"
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.libs << "test"
7
+ t.pattern = "test/**/*_test.rb"
8
+ t.verbose = false
9
+ end
10
+
11
+ task default: :test
data/bin/test ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path("../test", __dir__)
3
+
4
+ require "bundler/setup"
5
+ require "rails/plugin/test"
@@ -0,0 +1,26 @@
1
+ module RailsTwirp
2
+ module Command
3
+ class RoutesCommand < Rails::Command::Base
4
+ namespace "twirp"
5
+
6
+ desc "routes", "Show Twirp routes"
7
+ def perform
8
+ require_application_and_environment!
9
+ lines = [["Method", "Controller#Action"]]
10
+
11
+ Rails.application.twirp.routes.services.each do |svc, route_set|
12
+ route_set.rpcs.each do |name, mapping|
13
+ lines << ["/#{svc.service_full_name}/#{name}", mapping.to_s]
14
+ end
15
+ end
16
+
17
+ first_width = lines.map { |line| line[0].length }.max
18
+ second_width = lines.map { |line| line[1].length }.max
19
+
20
+ lines.each do |(first, second)|
21
+ say "#{first.rjust(first_width)} #{second.ljust(second_width)}\n"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ require "rails_twirp/version"
2
+
3
+ require "rails_twirp/base"
4
+ require "rails_twirp/route_set"
5
+ require "rails_twirp/testing/integration_test"
6
+
7
+ module RailsTwirp
8
+ mattr_accessor :test_app
9
+ end
10
+
11
+ require "rails_twirp/engine" if defined?(Rails)
@@ -0,0 +1,9 @@
1
+ require "rails_twirp/route_set"
2
+
3
+ module RailsTwirp
4
+ class Application
5
+ def routes
6
+ @routes ||= RouteSet.new
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,50 @@
1
+ module RailsTwirp
2
+ class Base < AbstractController::Base
3
+ abstract!
4
+
5
+ include AbstractController::Logger
6
+ include AbstractController::AssetPaths
7
+ include AbstractController::Callbacks
8
+ include AbstractController::Caching
9
+ include AbstractController::Rendering
10
+ include ActionView::Rendering
11
+
12
+ attr_internal :request, :env, :response_class
13
+ def initialize
14
+ @_request = nil
15
+ @_env = nil
16
+ @_response_class = nil
17
+ super
18
+ end
19
+
20
+ def http_request
21
+ @_http_request ||= ActionDispatch::Request.new(env[:rack_env])
22
+ end
23
+
24
+ def dispatch(action, request, response_class, env = {})
25
+ self.request = request
26
+ self.env = env
27
+ self.response_class = response_class
28
+
29
+ http_request.controller_instance = self
30
+
31
+ process(action)
32
+
33
+ # Implicit render
34
+ self.response_body = render unless response_body
35
+ response_body
36
+ end
37
+
38
+ def render(*args)
39
+ options = {formats: :pb, handlers: :pbbuilder, locals: {response_class: response_class}}
40
+ options.deep_merge! args.extract_options!
41
+ super(*args, options)
42
+ end
43
+
44
+ def self.dispatch(action, request, response_class, env = {})
45
+ new.dispatch(action, request, response_class, env)
46
+ end
47
+
48
+ ActiveSupport.run_load_hooks(:rails_twirp, self)
49
+ end
50
+ end
@@ -0,0 +1,63 @@
1
+ require "rails/railtie"
2
+ require "rails_twirp/application"
3
+
4
+ module RailsTwirp
5
+ # Even though this is an engine, we don't inherit from Rails::Engine because we don't want anything it provides.
6
+ class Engine < ::Rails::Railtie
7
+ # Implement Rack API
8
+ delegate :call, to: :routes
9
+
10
+ module TwirpValue
11
+ def twirp
12
+ @twirp ||= Application.new
13
+ end
14
+ end
15
+
16
+ initializer "rails_twirp.logger" do
17
+ # This hook is called whenever a RailsTwirp::Base is initialized, and it sets the logger
18
+ ActiveSupport.on_load(:rails_twirp) { self.logger ||= Rails.logger }
19
+ end
20
+
21
+ initializer :add_paths, before: :bootstrap_hook do |app|
22
+ app.config.paths.add "config/twirp/routes.rb"
23
+ app.config.paths.add "config/twirp/routes", glob: "**/*.rb"
24
+ app.config.paths.add "app/twirp/controllers", eager_load: true
25
+ app.config.paths.add "app/twirp/proto", load_path: true
26
+ app.config.paths.add "app/twirp/views", load_path: true
27
+ end
28
+
29
+ initializer :set_controller_view_path do
30
+ ActiveSupport.on_load(:rails_twirp) { prepend_view_path "app/twirp/views" }
31
+ end
32
+
33
+ initializer :add_twirp do |app|
34
+ # Here we add the 'twirp' method to application, which is accessible at Rails.application.twirp
35
+ app.extend TwirpValue
36
+ end
37
+
38
+ initializer :load_twirp_routes do |app|
39
+ # Load route files
40
+ route_configs = [
41
+ *app.config.paths["config/twirp/routes.rb"].existent,
42
+ *app.config.paths["config/twirp/routes"].existent
43
+ ]
44
+ load(*route_configs)
45
+
46
+ # Create a router that knows how to route all the registered services
47
+ services = app.twirp.routes.to_services
48
+ routes.draw do
49
+ services.each do |service|
50
+ mount service => service.full_name
51
+ end
52
+ end
53
+ end
54
+
55
+ initializer :set_test_app do |app|
56
+ RailsTwirp.test_app = app
57
+ end
58
+
59
+ def routes
60
+ @routes ||= ActionDispatch::Routing::RouteSet.new
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,113 @@
1
+ # Most of this logic is stolen from Rails ActionDispatch::Routing::RouteSet
2
+
3
+ module RailsTwirp
4
+ class RouteSet
5
+ attr_reader :services
6
+
7
+ def initialize
8
+ # Make services a hash with a default_proc, so the same class gets reused if the service
9
+ # method is used multiple times with the same key.
10
+ # This makes it possible to split up the routes into multiple files.
11
+ @services = Hash.new { |hash, key| hash[key] = ServiceRouteSet.new(key) }
12
+ end
13
+
14
+ def draw(&block)
15
+ mapper = Mapper.new(self)
16
+ mapper.instance_exec(&block)
17
+ end
18
+
19
+ def to_services
20
+ services.each_value.map(&:to_service)
21
+ end
22
+
23
+ class ServiceRouteSet
24
+ attr_reader :rpcs
25
+
26
+ def initialize(service_class)
27
+ @service_class = service_class
28
+ @rpcs = {}
29
+ end
30
+
31
+ def add_route(name, mapping)
32
+ if @rpcs[name]
33
+ raise ArgumentError, "Invalid RPC, route already defined: #{name}"
34
+ end
35
+
36
+ @rpcs[name] = mapping
37
+ end
38
+
39
+ def to_service
40
+ # Synthesize a handler that will process the requests
41
+ #
42
+ handler = Class.new
43
+ @rpcs.each do |name, mapping|
44
+ rpc_info = @service_class.rpcs[name]
45
+ method_name = rpc_info[:ruby_method]
46
+
47
+ # Stolen from Rails in ActionDispatch::Request#controller_class_for
48
+ controller_name = mapping.controller.underscore
49
+ const_name = controller_name.camelize << "Controller"
50
+ action_name = mapping.action
51
+ response_class = rpc_info[:output_class]
52
+
53
+ handler.define_method(method_name) do |req, env|
54
+ controller_class = ::ActiveSupport::Dependencies.constantize(const_name)
55
+ controller_class.dispatch(action_name, req, response_class, env)
56
+ end
57
+ end
58
+
59
+ service = @service_class.new(handler.new)
60
+ service.before do |rack_env, env|
61
+ env[:rack_env] = rack_env
62
+ end
63
+ service
64
+ end
65
+ end
66
+
67
+ class Mapping
68
+ attr_reader :controller, :action
69
+
70
+ def initialize(to:)
71
+ @controller, @action = split_to(to)
72
+ end
73
+
74
+ def to_s
75
+ "#{controller}##{action}"
76
+ end
77
+
78
+ private
79
+
80
+ # copied from Rails
81
+ def split_to(to)
82
+ if /#/.match?(to)
83
+ to.split("#").map!(&:-@)
84
+ else
85
+ []
86
+ end
87
+ end
88
+ end
89
+
90
+ class ServiceMapper
91
+ def initialize(service_route_set)
92
+ @service_route_set = service_route_set
93
+ end
94
+
95
+ def rpc(name, to:)
96
+ mapping = Mapping.new(to: to)
97
+ @service_route_set.add_route(name, mapping)
98
+ end
99
+ end
100
+
101
+ class Mapper
102
+ def initialize(route_set)
103
+ @route_set = route_set
104
+ end
105
+
106
+ def service(service_definition, &block)
107
+ service_route_set = @route_set.services[service_definition]
108
+ service_mapper = ServiceMapper.new(service_route_set)
109
+ service_mapper.instance_exec(&block)
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,43 @@
1
+ module RailsTwirp
2
+ class IntegrationTest < ActiveSupport::TestCase
3
+ attr_reader :response, :request, :controller
4
+
5
+ def initialize(name)
6
+ super
7
+ reset!
8
+ @before_rpc = []
9
+ end
10
+
11
+ def reset!
12
+ @request = nil
13
+ @response = nil
14
+ end
15
+
16
+ def before_rpc(&block)
17
+ @before_rpc << block
18
+ end
19
+
20
+ def rpc(service, rpc, request, headers: nil)
21
+ @request = request
22
+ service = app.twirp.routes.services[service].to_service
23
+
24
+ rack_env = {}
25
+ http_request = ActionDispatch::Request.new(rack_env)
26
+ http_request.headers.merge! headers if headers.present?
27
+ env = {rack_env: rack_env}
28
+
29
+ @before_rpc.each do |hook|
30
+ hook.call(env)
31
+ end
32
+
33
+ response = service.call_rpc rpc, request, env
34
+ @response = response
35
+ @controller = http_request.controller_instance
36
+ response
37
+ end
38
+
39
+ def app
40
+ RailsTwirp.test_app
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ module RailsTwirp
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,17 @@
1
+ require_relative "lib/rails_twirp/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "rails_twirp"
5
+ spec.version = RailsTwirp::VERSION
6
+ spec.authors = ["Bouke van der Bijl"]
7
+ spec.email = ["bouke@cheddar.me"]
8
+ spec.homepage = "https://github.com/cheddar-me/rails-twirp"
9
+ spec.summary = "Integrate Twirp into Rails"
10
+ spec.license = "MIT"
11
+
12
+ spec.files = `git ls-files`.split("\n")
13
+ spec.test_files = `git ls-files -- test/*`.split("\n")
14
+
15
+ spec.add_dependency "rails", "~> 6.1.3"
16
+ spec.add_dependency "twirp", "~> 1.7.2"
17
+ end
@@ -0,0 +1,7 @@
1
+ require "test_helper"
2
+
3
+ class RailsTwirpTest < ActiveSupport::TestCase
4
+ test "it has a version number" do
5
+ assert RailsTwirp::VERSION
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # Configure Rails Environment
2
+ ENV["RAILS_ENV"] = "test"
3
+
4
+ require "rails"
5
+ require "rails/test_help"
6
+ require "rails/test_unit/reporter"
7
+ Rails::TestUnitReporter.executable = "bin/test"
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_twirp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bouke van der Bijl
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-03-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 6.1.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 6.1.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: twirp
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.7.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.7.2
41
+ description:
42
+ email:
43
+ - bouke@cheddar.me
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - Gemfile
50
+ - MIT-LICENSE
51
+ - README.md
52
+ - Rakefile
53
+ - bin/test
54
+ - lib/commands/twirp/routes_command.rb
55
+ - lib/rails_twirp.rb
56
+ - lib/rails_twirp/application.rb
57
+ - lib/rails_twirp/base.rb
58
+ - lib/rails_twirp/engine.rb
59
+ - lib/rails_twirp/route_set.rb
60
+ - lib/rails_twirp/testing/integration_test.rb
61
+ - lib/rails_twirp/version.rb
62
+ - rails_twirp.gemspec
63
+ - test/rails_twirp_test.rb
64
+ - test/test_helper.rb
65
+ homepage: https://github.com/cheddar-me/rails-twirp
66
+ licenses:
67
+ - MIT
68
+ metadata: {}
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubygems_version: 3.2.3
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: Integrate Twirp into Rails
88
+ test_files:
89
+ - test/rails_twirp_test.rb
90
+ - test/test_helper.rb