rails_twirp 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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