module Metadata

Provides operations for building and extracting gRPC metadata.

Nested Classes and Modules

class << self

Definitions

def self.build(metadata: {}, timeout: nil, content_type: "application/grpc")

Build gRPC request headers containing the given metadata.

Signature

parameter metadata Hash

Custom metadata key-value pairs.

parameter timeout Numeric | Nil

Optional timeout in seconds.

parameter content_type String

The request content type.

returns Protocol::HTTP::Headers

The constructed request headers.

Implementation

def self.build(metadata: {}, timeout: nil, content_type: "application/grpc")
	headers = Protocol::HTTP::Headers.new(policy: Protocol::GRPC::HEADER_POLICY)
	headers["content-type"] = content_type
	headers["te"] = "trailers"
	
	if timeout
		# Coerced to proper format by header policy:
		headers["grpc-timeout"] = timeout
	end
	
	metadata.each do |key, value|
		# Binary headers end with -bin and are base64 encoded:
		headers[key] = if key.end_with?("-bin")
			Base64.strict_encode64(value)
		else
			value.to_s
		end
	end
	
	headers
end

def self.extract(headers)

Extract application metadata from gRPC headers.

Signature

parameter headers Protocol::HTTP::Headers

The headers to inspect.

returns Hash

The extracted metadata key-value pairs.

Implementation

def self.extract(headers)
	metadata = {}
	
	headers.to_h.each do |key, value|
		# Skip reserved headers:
		next if key.start_with?("grpc-") || key == "content-type" || key == "te"
		
		# Decode binary headers:
		if key.end_with?("-bin")
			if value.is_a?(String)
				value = decode_binary(value)
			elsif value.is_a?(Array)
				value = value.map{|item| decode_binary(item)}
			end
		end
		
		metadata[key] = value
	end
	
	metadata
end

def self.decode_binary(value)

Decode a padded or unpadded binary metadata value.

Signature

parameter value String

The base64 encoded value.

returns String

The decoded bytes.

raises ArgumentError

If the value has invalid Base64 characters or padding.

Implementation

def self.decode_binary(value)
	# Only supply omitted padding; validate existing padding unchanged:
	unless value.end_with?("=")
		case value.bytesize % 4
		when 2
			value += "=="
		when 3
			value += "="
		end
	end
	
	Base64.strict_decode64(value)
end

def self.extract_status(headers)

Extract gRPC status from headers. Returns Status::UNKNOWN if status is not present.

Note: In Protocol::HTTP::Headers, trailers are merged into the headers so users just access headers["grpc-status"] regardless of whether it was sent as an initial header or trailer.

Signature

parameter headers Protocol::HTTP::Headers
returns Integer

Status code (0-16)

Implementation

def self.extract_status(headers)
	# Ensure policy is set - setting policy clears the index (@indexed = nil)
	# The index will be rebuilt automatically on next access via to_h
	headers.policy = Protocol::GRPC::HEADER_POLICY unless headers.policy == Protocol::GRPC::HEADER_POLICY
	
	status = headers["grpc-status"]
	return Status::UNKNOWN unless status
	
	return status.to_i
end

def self.extract_message(headers)

Extract gRPC status message from headers. Returns Nil if message is not present.

Signature

parameter headers Protocol::HTTP::Headers
returns String | Nil

Status message

Implementation

def self.extract_message(headers)
	# Ensure policy is set - setting policy clears the index (@indexed = nil)
	# The index will be rebuilt automatically on next access via to_h
	headers.policy = Protocol::GRPC::HEADER_POLICY unless headers.policy == Protocol::GRPC::HEADER_POLICY
	
	message = headers["grpc-message"]
	return nil unless message
	
	return message.decode
end

def self.assign_status!(headers, status: Status::OK, message: nil, error: nil, backtrace: false)

Assign gRPC status, message, and optional backtrace to headers.

Whether these become headers or trailers is controlled by the protocol layer.

Signature

parameter headers Protocol::HTTP::Headers
parameter status Integer

gRPC status code

parameter message String | Nil

Optional status message

parameter error Exception | Nil

Optional error object used for the message.

parameter backtrace Boolean

Whether to include the error backtrace for debugging.

Implementation

def self.assign_status!(headers, status: Status::OK, message: nil, error: nil, backtrace: false)
	headers["grpc-status"] = status
	
	if error && message.nil?
		# If message is not provided but error is, use error message
		message = error.message
	end
	
	if message
		headers["grpc-message"] = message
	end
	
	# Add backtrace from error if available
	if backtrace && error && error.backtrace && !error.backtrace.empty?
		# Assign backtrace array directly - Split header will handle it
		headers["backtrace"] = error.backtrace
	end
	
	return headers
end