Protocol::GRPCSourceProtocolGRPCInterface

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_class Class

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 subclass Class

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 name Symbol

Method name in PascalCase (e.g., :SayHello, matching .proto file)

parameter request_class Class | Streaming

Request message class, optionally wrapped with stream()

parameter response_class Class | Streaming

Response message class, optionally wrapped with stream()

parameter streaming Symbol

Streaming type (:unary, :server_streaming, :client_streaming, :bidirectional) This is automatically inferred from stream() decorators if not explicitly provided

parameter method Symbol | 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 name Symbol

Method name.

returns Protocol::GRPC::RPC | Nil

RPC definition or Nil if 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 name String

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_name String | 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_case String

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