connect_rpc_rails 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.
@@ -0,0 +1,101 @@
1
+ # Generated from lib/connect_rpc_rails/routing.rb with RBS::Inline
2
+
3
+ module ConnectRpcRails
4
+ # Routes DSL for one Connect service, installed on the Rails routes mapper. The service
5
+ # is named as the `.proto` names it and mapped to a controller the way Rails' own `to:`
6
+ # names one — as a string, so drawing the routes doesn't load the controller class:
7
+ #
8
+ # Rails.application.routes.draw do
9
+ # connect_service "greet.v1.GreetService" => :greet
10
+ # end
11
+ #
12
+ # A block maps the RPCs to a controller each, for a service whose methods are better off
13
+ # not sharing one class — an RPC then gets its own `before_action`s rather than callbacks
14
+ # the whole service runs with `only:`:
15
+ #
16
+ # connect_service "greet.v1.GreetService" do
17
+ # rpc "SayHello" => :greet_say_hello
18
+ # rpc "SayGoodbye" => :greet_say_goodbye
19
+ # end
20
+ #
21
+ # Every mapped name has to be one the descriptor declares, so a typo or a rename fails at
22
+ # boot rather than drawing a route nothing reaches. The mapping does not have to cover the
23
+ # service: an RPC left out is routed to the first mapped controller, which serves the
24
+ # service but not that method, so it is answered `unimplemented` the way a declared RPC
25
+ # nobody implements always is.
26
+ #
27
+ # Every RPC the descriptor declares becomes one `POST /<pkg.Service>/<Method>` route to
28
+ # the action implementing it (1 RPC = 1 action), so the protobuf definition is the only
29
+ # place the method list lives. A declared RPC the controller doesn't implement is
30
+ # answered Connect `unimplemented` by the controller, not by a route. After the RPC
31
+ # routes comes one catch-all over the service prefix, which is where a method the
32
+ # descriptor never declared becomes a 404.
33
+ module Routing
34
+ # Whether the routed controllers can be resolved right now. Only under eager loading:
35
+ # Rails eager loads before it draws the routes, so the classes are already in memory
36
+ # and constantizing one autoloads nothing. With lazy loading (dev, and a plain Rack
37
+ # host) the class is deliberately left untouched.
38
+ # : () -> bool
39
+ def self.verify_controllers?: () -> bool
40
+
41
+ # Checks that each routed service resolves to a controller that actually serves it, so
42
+ # a service wired to the wrong controller fails at boot instead of 404-ing in
43
+ # production. Called as the routes are drawn (see .verify_controllers?).
44
+ # : (String, String) -> void
45
+ def self.verify_controller!: (String, String) -> void
46
+
47
+ # : (Hash[String | Symbol, String | Symbol] | String | Symbol) ?{ (?MethodMapping) [self: MethodMapping] -> void } -> void
48
+ def connect_service: (Hash[String | Symbol, String | Symbol] | String | Symbol) ?{ (?MethodMapping) [self: MethodMapping] -> void } -> void
49
+
50
+ # Collects the per-RPC controller mapping a `connect_service` block declares. `rpc`
51
+ # takes the method name as the `.proto` spells it, so both ends of the mapping grep to
52
+ # the protobuf definition.
53
+ class MethodMapping
54
+ # : () { (?MethodMapping) [self: MethodMapping] -> void } -> Hash[String, String]
55
+ def self.collect: () { (?MethodMapping) [self: MethodMapping] -> void } -> Hash[String, String]
56
+
57
+ attr_reader mapping: Hash[String, String]
58
+
59
+ # : () -> void
60
+ def initialize: () -> void
61
+
62
+ # : (Hash[String | Symbol, String | Symbol]) -> void
63
+ def rpc: (Hash[String | Symbol, String | Symbol]) -> void
64
+ end
65
+
66
+ # Draws the routes for one service: every RPC the descriptor declares, then the
67
+ # catch-all. The RPCs go to one controller, or to the controller each is mapped to.
68
+ class Service
69
+ # : (untyped mapper, String service_name, String | Hash[String, String] controllers) -> void
70
+ def initialize: (untyped mapper, String service_name, String | Hash[String, String] controllers) -> void
71
+
72
+ # : () -> void
73
+ def draw: () -> void
74
+
75
+ # A mapping is checked against the descriptor as the routes are drawn: a controller
76
+ # mapped to a name the service does not declare is a typo or a rename, and it would
77
+ # otherwise draw a route nothing can reach. An RPC the block leaves out is *not* an
78
+ # error — it is routed too (see #controller_for), because a declared RPC nobody
79
+ # implements is what Connect answers `unimplemented`.
80
+ # : () -> void
81
+ private def verify_mapping!: () -> void
82
+
83
+ # An RPC the mapping leaves out still gets a route, at the first mapped controller:
84
+ # it serves the service but not that method, so #action_missing answers it
85
+ # `unimplemented` (501) exactly as the protocol wants — where no route at all would
86
+ # have made it a 404.
87
+ # : (String) -> String
88
+ private def controller_for: (String) -> String
89
+
90
+ # : () -> Array[String]
91
+ private def controller_paths: () -> Array[String]
92
+
93
+ # Routes match every verb (`via: :all`) so a wrong-verb request reaches the
94
+ # controller and becomes a Connect-correct 405 rather than a router 404.
95
+ # `format: false` keeps the dots in the service name from being parsed as a format
96
+ # suffix.
97
+ # : (String, String, String) -> void
98
+ private def route: (String, String, String) -> void
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,46 @@
1
+ # Generated from lib/connect_rpc_rails/service_registration.rb with RBS::Inline
2
+
3
+ module ConnectRpcRails
4
+ # Reads a protobuf ServiceDescriptor (generated by protoc/buf and present in the
5
+ # descriptor pool) into the RPC table the transport dispatches from. Dispatch is
6
+ # reflection-based: the action names and the input/output message classes come
7
+ # entirely from the descriptor, so no per-service code generation is required.
8
+ #
9
+ # The service is named the way the `.proto` names it — `"greet.v1.GreetService"` — and
10
+ # looked up in the pool, so the string in the code is greppable from the protobuf
11
+ # definition. A ServiceDescriptor is accepted too, for a host that already holds one.
12
+ #
13
+ # The service is implemented by the controller itself — the RPC methods are ordinary
14
+ # controller instance methods. There is deliberately no handler object: a handler held
15
+ # on the controller class would be one instance shared by every request in the process,
16
+ # so leftover instance state could leak between callers, whereas Rails builds a
17
+ # controller per request.
18
+ class ServiceRegistration
19
+ class Rpc < Struct[untyped]
20
+ attr_accessor name(): untyped
21
+
22
+ attr_accessor action(): untyped
23
+
24
+ attr_accessor input_class(): untyped
25
+
26
+ attr_accessor output_class(): untyped
27
+
28
+ def self.new: (?name: untyped, ?action: untyped, ?input_class: untyped, ?output_class: untyped) -> instance
29
+ | ({ ?name: untyped, ?action: untyped, ?input_class: untyped, ?output_class: untyped }) -> instance
30
+ end
31
+
32
+ attr_reader service_name: String
33
+
34
+ # : (String | untyped service) -> void
35
+ def initialize: (String | untyped service) -> void
36
+
37
+ # The generated `_pb` file has to have been required, since requiring it is what puts
38
+ # the service in the pool. Say so, rather than let a typo and a missing require look
39
+ # the same.
40
+ # : (String) -> untyped
41
+ def self.lookup!: (String) -> untyped
42
+
43
+ # : () -> Array[Rpc]
44
+ def rpcs: () -> Array[Rpc]
45
+ end
46
+ end
@@ -0,0 +1,5 @@
1
+ # Generated from lib/connect_rpc_rails/version.rb with RBS::Inline
2
+
3
+ module ConnectRpcRails
4
+ VERSION: ::String
5
+ end
@@ -0,0 +1,15 @@
1
+ # Generated from lib/connect_rpc_rails.rb with RBS::Inline
2
+
3
+ module ConnectRpcRails
4
+ # Installs what the gem adds to Action Dispatch: the routes DSL, and Connect's binary
5
+ # content-type so `request.format` (and thus the instrumentation payload / request log)
6
+ # reports :proto instead of defaulting to :html. In a Rails app the Railtie calls this
7
+ # during boot; a plain Rack host (or a spec) calls it itself.
8
+ # : () -> void
9
+ def self.install!: () -> void
10
+
11
+ # "SayHello" -> "say_hello". Maps a Connect method name to the controller action
12
+ # implementing it, so dispatch stays reflection-driven (no per-service codegen).
13
+ # : (String) -> String
14
+ def self.underscore: (String) -> String
15
+ end
@@ -0,0 +1,32 @@
1
+ # The class-level state ConnectRpcRails::Controller installs via `extend ClassMethods`.
2
+ # `self.class` on the including controller can't be typed from the module body alone,
3
+ # so the mix-in declares this as part of its self-type.
4
+ module ConnectRpcRails
5
+ interface _ConnectControllerClass
6
+ # class_attribute, so a base controller's declaration is readable from a subclass
7
+ # serving one RPC of the service.
8
+ def connect_registration: () -> ServiceRegistration
9
+
10
+ def connect_registration=: (ServiceRegistration) -> void
11
+
12
+ def connect_rpcs: () -> Hash[String, untyped]
13
+
14
+ def connect_rpcs=: (Hash[String, untyped]) -> void
15
+
16
+ def name: () -> String?
17
+
18
+ # ActiveSupport::Rescuable, and the class_attribute `map_connect_errors` stores its
19
+ # mapping in.
20
+ def rescue_from: (untyped, with: Symbol) -> void
21
+
22
+ def connect_error_mapping: () -> Hash[Class, Symbol]
23
+
24
+ def connect_error_mapping=: (Hash[Class, Symbol]) -> void
25
+ end
26
+
27
+ interface _ConnectControllerSelf
28
+ def class: () -> _ConnectControllerClass
29
+
30
+ def dispatch_connect_rpc: (ServiceRegistration::Rpc) -> void
31
+ end
32
+ end
@@ -0,0 +1,25 @@
1
+ # The Rails APIs the gem touches that gem_rbs_collection's actionpack/activesupport
2
+ # signatures don't declare. railties itself has no published signatures, so `Rails` and
3
+ # `Rails::Railtie` are only stubbed there (activesupport's patch.rbs) as empty.
4
+ module Rails
5
+ def self.application: () -> untyped
6
+
7
+ class Railtie
8
+ def self.initializer: (String, **untyped) ?{ (?untyped) -> void } -> void
9
+ end
10
+ end
11
+
12
+ module ActionDispatch
13
+ class ExceptionWrapper
14
+ # Keyed by exception class *name*, valued by an HTTP status (a Rack symbol or an
15
+ # integer). The Hash answers an unregistered name with :internal_server_error, so read
16
+ # it with `fetch` when a miss has to be distinguishable.
17
+ def self.rescue_responses: () -> Hash[String, Symbol | Integer]
18
+ end
19
+ end
20
+
21
+ module AbstractController
22
+ class ActionNotFound < StandardError
23
+ def initialize: (?String? message, ?untyped controller, ?String? action) -> void
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,210 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: connect_rpc_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - IVRy Inc.
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: actionpack
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: google-protobuf
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '4.26'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '4.26'
40
+ - !ruby/object:Gem::Dependency
41
+ name: puma
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '8.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '8.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rake
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '13.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '13.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rbs
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '4.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '4.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rbs-inline
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '0.14'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0.14'
96
+ - !ruby/object:Gem::Dependency
97
+ name: rspec
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '3.13'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '3.13'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rubocop
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '1.0'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '1.0'
124
+ - !ruby/object:Gem::Dependency
125
+ name: rubocop-shopify
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '2.0'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '2.0'
138
+ - !ruby/object:Gem::Dependency
139
+ name: steep
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '2.0'
145
+ type: :development
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '2.0'
152
+ description: Serves Connect unary RPCs as ordinary Rails controller actions, so every
153
+ call flows through the normal ActionController::API lifecycle and the Rails observability
154
+ stack works with no extra wiring.
155
+ email:
156
+ - arch@ivry.jp
157
+ executables: []
158
+ extensions: []
159
+ extra_rdoc_files: []
160
+ files:
161
+ - CHANGELOG.md
162
+ - LICENSE
163
+ - README.md
164
+ - lib/connect_rpc_rails.rb
165
+ - lib/connect_rpc_rails/codec.rb
166
+ - lib/connect_rpc_rails/controller.rb
167
+ - lib/connect_rpc_rails/errors.rb
168
+ - lib/connect_rpc_rails/exceptions_app.rb
169
+ - lib/connect_rpc_rails/railtie.rb
170
+ - lib/connect_rpc_rails/routing.rb
171
+ - lib/connect_rpc_rails/service_registration.rb
172
+ - lib/connect_rpc_rails/version.rb
173
+ - sig/generated/connect_rpc_rails.rbs
174
+ - sig/generated/connect_rpc_rails/codec.rbs
175
+ - sig/generated/connect_rpc_rails/controller.rbs
176
+ - sig/generated/connect_rpc_rails/errors.rbs
177
+ - sig/generated/connect_rpc_rails/exceptions_app.rbs
178
+ - sig/generated/connect_rpc_rails/railtie.rbs
179
+ - sig/generated/connect_rpc_rails/routing.rbs
180
+ - sig/generated/connect_rpc_rails/service_registration.rbs
181
+ - sig/generated/connect_rpc_rails/version.rbs
182
+ - sig/manual/controller_self.rbs
183
+ - sig/manual/rails.rbs
184
+ homepage: https://github.com/ivry-inc/connect_rpc_rails
185
+ licenses:
186
+ - Apache-2.0
187
+ metadata:
188
+ allowed_push_host: https://rubygems.org
189
+ homepage_uri: https://github.com/ivry-inc/connect_rpc_rails
190
+ source_code_uri: https://github.com/ivry-inc/connect_rpc_rails
191
+ changelog_uri: https://github.com/ivry-inc/connect_rpc_rails/blob/main/CHANGELOG.md
192
+ rubygems_mfa_required: 'true'
193
+ rdoc_options: []
194
+ require_paths:
195
+ - lib
196
+ required_ruby_version: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: '3.4'
201
+ required_rubygems_version: !ruby/object:Gem::Requirement
202
+ requirements:
203
+ - - ">="
204
+ - !ruby/object:Gem::Version
205
+ version: '0'
206
+ requirements: []
207
+ rubygems_version: 4.0.16
208
+ specification_version: 4
209
+ summary: Minimal Connect RPC (unary) server for Rails, built on ActionController::API.
210
+ test_files: []