class Interface
Represents an interface definition for gRPC methods. Can be used by both client stubs and server implementations.
Definitions
RPC
RPC method definition
Implementation
RPC = Struct.new(:name, :request_class, :response_class, :streaming, :method, keyword_init: true) do
def initialize(name:, request_class:, response_class:, streaming: :unary, method: nil)
super
end
# Check if this RPC is a streaming RPC (server, client, or bidirectional).
# Server-side handlers for streaming RPCs are expected to block until all messages are sent.
# @returns [Boolean] `true` if streaming, `false` if unary
def streaming?
streaming != :unary
end
end
def self.stream(message_class)
Helper method to mark a message type as streamed in RPC definitions. Can be called directly within Interface subclasses without the Protocol::GRPC prefix.
Example: Define streaming RPCs
class MyService < Protocol::GRPC::Interface
rpc :sum, stream(Num), Num # client streaming
rpc :fib, FibArgs, stream(Num) # server streaming
rpc :chat, stream(Msg), stream(Msg) # bidirectional streaming
end
Signature
-
parameter
message_classClass The message class to mark as streamed
-
returns
Streaming A wrapper indicating this type is streamed
Implementation
def self.stream(message_class)
Streaming.new(message_class)
end
def self.inherited(subclass)
Hook called when a subclass is created. Initializes the RPC hash for the subclass.
Signature
-
parameter
subclassClass The subclass being created
Implementation
def self.inherited(subclass)
super
subclass.instance_variable_set(:@rpcs, {})
end
def self.rpc(name, request_class = nil, response_class = nil, **options)
Define an RPC method.
Example: Using stream() decorator syntax
rpc :div, DivArgs, DivReply # unary
rpc :sum, stream(Num), Num # client streaming
rpc :fib, FibArgs, stream(Num) # server streaming
rpc :chat, stream(DivArgs), stream(DivReply) # bidirectional streaming
Signature
-
parameter
nameSymbol Method name in PascalCase (e.g., :SayHello, matching .proto file)
-
parameter
request_classClass | Streaming Request message class, optionally wrapped with stream()
-
parameter
response_classClass | Streaming Response message class, optionally wrapped with stream()
-
parameter
streamingSymbol Streaming type (:unary, :server_streaming, :client_streaming, :bidirectional) This is automatically inferred from stream() decorators if not explicitly provided
-
parameter
methodSymbol | Nil Optional explicit Ruby method name (snake_case). If not provided, automatically converts PascalCase to snake_case.
Implementation
def self.rpc(name, request_class = nil, response_class = nil, **options)
options[:name] = name
# Check if request or response are wrapped with stream()
request_streaming = request_class.is_a?(Streaming)
response_streaming = response_class.is_a?(Streaming)
# Unwrap StreamWrapper if present
options[:request_class] ||= request_streaming ? request_class.message_class : request_class
options[:response_class] ||= response_streaming ? response_class.message_class : response_class
# Auto-detect streaming type from stream() decorators if not explicitly set
if !options.key?(:streaming)
if request_streaming && response_streaming
options[:streaming] = :bidirectional
elsif request_streaming
options[:streaming] = :client_streaming
elsif response_streaming
options[:streaming] = :server_streaming
else
options[:streaming] = :unary
end
end
# Ensure snake_case method name is always available
options[:method] ||= pascal_case_to_snake_case(name.to_s).to_sym
@rpcs[name] = RPC.new(**options)
end
def self.lookup_rpc(name)
Look up RPC definition for a method. Looks up the inheritance chain to find the RPC definition.
Signature
-
parameter
nameSymbol Method name.
-
returns
Protocol::GRPC::RPC | Nil RPC definition or
Nilif not found.
Implementation
def self.lookup_rpc(name)
klass = self
while klass && klass != Interface
if klass.instance_variable_defined?(:@rpcs)
if rpc = klass.instance_variable_get(:@rpcs)[name]
return rpc
end
end
klass = klass.superclass
end
# Not found:
return nil
end
def self.rpcs
Get all RPC definitions from this class and all parent classes.
Signature
-
returns
Hash All RPC definitions merged from inheritance chain
Implementation
def self.rpcs
all_rpcs = {}
klass = self
# Walk up the inheritance chain:
while klass && klass != Interface
if klass.instance_variable_defined?(:@rpcs)
all_rpcs.merge!(klass.instance_variable_get(:@rpcs))
end
klass = klass.superclass
end
all_rpcs
end
def initialize(name)
Initialize a new interface instance.
Signature
-
parameter
nameString Service name
Implementation
def initialize(name)
@name = name
end
attr :name
Signature
-
attribute
String The service name (e.g., "hello.Greeter").
def path(method_name)
Build gRPC path for a method.
Signature
-
parameter
method_nameString | Symbol Method name in PascalCase (e.g., :SayHello)
-
returns
String gRPC path with PascalCase method name
Implementation
def path(method_name)
Methods.build_path(@name, method_name.to_s)
end
def self.pascal_case_to_snake_case(pascal_case)
Convert PascalCase to snake_case.
Signature
-
parameter
pascal_caseString PascalCase string (e.g., "SayHello")
-
returns
String snake_case string (e.g., "say_hello")
Implementation
def self.pascal_case_to_snake_case(pascal_case)
pascal_case
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') # Insert underscore before capital letters followed by lowercase
.gsub(/([a-z\d])([A-Z])/, '\1_\2') # Insert underscore between lowercase/digit and uppercase
.downcase
end