Skip to content

JSON-RPC Request Builder — Ruby source

Build valid JSON-RPC 2.0 requests, notifications, success responses, and error responses, plus batch arrays. Validate message structure.

This is the Ruby implementation — the same logic the interactive tool runs, in a shareable, citable form.

# json-rpc-builder — Ruby port: JSON-RPC 2.0 message builder + structural validation.

require 'json'

JSONRPC_VERSION = '2.0'
STANDARD_ERRORS = {
  -32700 => 'Parse error', -32600 => 'Invalid Request', -32601 => 'Method not found',
  -32602 => 'Invalid params', -32603 => 'Internal error', -32000 => 'Server error'
}.freeze

# :UNSET distinguishes "argument absent" from a legitimate JSON null.
Outcome = Struct.new(:ok, :json, :error, keyword_init: true)

def stringify(obj, indent = nil)
  indent ? JSON.pretty_generate(obj) : JSON.generate(obj)
end

def build_request(method, params: :UNSET, id: 1, indent: nil)
  unless method.is_a?(String) && !method.empty?
    return Outcome.new(ok: false, json: '', error: 'method must be a non-empty string')
  end
  obj = { 'jsonrpc' => JSONRPC_VERSION, 'method' => method }
  obj['params'] = params unless params.equal?(:UNSET)
  obj['id'] = id
  Outcome.new(ok: true, json: stringify(obj, indent))
end

# A notification is a request without an id: fire-and-forget, no reply.
def build_notification(method, params: :UNSET, indent: nil)
  unless method.is_a?(String) && !method.empty?
    return Outcome.new(ok: false, json: '', error: 'method must be a non-empty string')
  end

  obj = { 'jsonrpc' => JSONRPC_VERSION, 'method' => method }
  obj['params'] = params unless params.equal?(:UNSET)
  Outcome.new(ok: true, json: stringify(obj, indent))
end

def build_success_response(id, result, indent = nil)
  obj = { 'jsonrpc' => JSONRPC_VERSION, 'result' => result, 'id' => id }
  Outcome.new(ok: true, json: stringify(obj, indent))
end

def build_error_response(id, code, message = nil, data: :UNSET, indent: nil)
  error = { 'code' => code, 'message' => message || STANDARD_ERRORS.fetch(code, 'Error') }
  error['data'] = data unless data.equal?(:UNSET)
  obj = { 'jsonrpc' => JSONRPC_VERSION, 'error' => error, 'id' => id }
  Outcome.new(ok: true, json: stringify(obj, indent))
end

# Per spec §6 a batch is a non-empty array sent in one round-trip.
def build_batch(messages, indent = nil)
  unless messages.is_a?(Array) && !messages.empty?
    return Outcome.new(ok: false, json: '', error: 'batch must be a non-empty array')
  end

  Outcome.new(ok: true, json: stringify(messages, indent))
end

# Lightweight shape check — reports every problem, not just the first.
def validate_rpc(obj)
  return [false, ['Not an object.']] unless obj.is_a?(Hash)

  errors = []
  errors << 'jsonrpc must be "2.0".' if obj['jsonrpc'] != JSONRPC_VERSION
  errors << 'method must be a string.' if obj.key?('method') && !obj['method'].is_a?(String)
  errors << 'cannot have both result and error.' if obj.key?('result') && obj.key?('error')
  unless %w[method result error].any? { |k| obj.key?(k) }
    errors << 'must have method, result, or error.'
  end
  [errors.empty?, errors]
end

if $PROGRAM_NAME == __FILE__
  puts build_request('getBalance', params: ['0x1234']).json
  puts build_notification('blockHeader').json
  puts build_success_response(1, '0x1bc16d674ec80000').json
  puts build_error_response(1, -32601).json
  p validate_rpc('jsonrpc' => '1.0')
end

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →