Class: JSONRPC::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/jsonrpc/parser.rb,
sig/jsonrpc/parser.rbs

Overview

JSON-RPC 2.0 Parser for converting raw JSON into JSONRPC objects

The Parser handles converting raw JSON strings into appropriate JSONRPC objects based on the JSON-RPC 2.0 protocol specification.

Examples:

Parse a request

parser = JSONRPC::Parser.new
request = parser.parse('{"jsonrpc":"2.0","method":"subtract","params":[42,23],"id":1}')

JSONRPC::Parse a batch request

parser = JSONRPC::Parser.new
batch = parser.parse('[{"jsonrpc":"2.0","method":"sum","params":[1,2],"id":"1"},
                      {"jsonrpc":"2.0","method":"notify_hello","params":[7]}]')

Instance Method Summary collapse

Instance Method Details

#parse(json) ⇒ Request, ...

Parse a JSON-RPC 2.0 message

Examples:

Parse a single request

parser.parse('{"jsonrpc":"2.0","method":"subtract","params":[42,23],"id":1}')

Parse a batch request

parser.parse('[{"jsonrpc":"2.0","method":"sum","params":[1,2],"id":"1"}]')

Parameters:

  • json (String)

    the JSON-RPC 2.0 message

Returns:

Raises:



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/jsonrpc/parser.rb', line 38

def parse(json)
  data = MultiJson.load(json)

  if data.is_a?(Array)
    parse_batch(data)
  else
    parse_single(data)
  end
rescue MultiJson::ParseError => e
  raise ParseError.new(
    data: {
      details: e.message,
      adapter: MultiJson.adapter.name,
      input_preview: json[0..100]
    }
  )
end

#parse_batch(data) ⇒ BatchRequest

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parse a batch JSON-RPC 2.0 message

Parameters:

  • data (Array)

    the array of request data

Returns:

Raises:



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/jsonrpc/parser.rb', line 93

def parse_batch(data)
  raise InvalidRequestError.new(data: { details: 'Batch request cannot be empty' }) if data.empty?

  items = []

  data.each_with_index do |item, index|
    parsed_item = parse_single_for_batch(item)
    items << parsed_item
  rescue InvalidRequestError => e
    # For batch processing, we want to include the error in the results
    # rather than stopping the entire batch processing
    error_with_index = InvalidRequestError.new(
      data: { index: index, details: e.data&.fetch(:details, nil) },
      request_id: e.request_id
    )
    items << error_with_index
  end

  BatchRequest.new(items)
end

#parse_single(data) ⇒ Request, ...

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parse a single JSON-RPC 2.0 message

Parameters:

  • data (Hash)

    the parsed JSON data

Returns:



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/jsonrpc/parser.rb', line 66

def parse_single(data)
  validate_jsonrpc_version(data)
  validate_request_structure(data)

  method = data['method']
  params = data['params']

  if data.key?('id')
    Request.new(method: method, params: params, id: data['id'])
  else
    Notification.new(method: method, params: params)
  end
rescue ArgumentError => e
  request_id = data.is_a?(Hash) ? data['id'] : nil
  raise InvalidRequestError.new(data: { details: e.message }, request_id: request_id)
end

#parse_single_for_batch(data) ⇒ Request, Notification

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parse a single item within a batch, allowing errors to be captured

Parameters:

  • data (Hash)

    the parsed JSON data for a single item

Returns:

Raises:



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/jsonrpc/parser.rb', line 124

def parse_single_for_batch(data)
  validate_jsonrpc_version(data)
  validate_request_structure(data)

  method = data['method']
  params = data['params']

  if data.key?('id')
    Request.new(method: method, params: params, id: data['id'])
  else
    Notification.new(method: method, params: params)
  end
rescue ArgumentError => e
  request_id = data.is_a?(Hash) ? data['id'] : nil
  raise InvalidRequestError.new(data: { details: e.message }, request_id: request_id)
end

#validate_jsonrpc_version(data) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Validate the JSON-RPC 2.0 version

Parameters:

  • data (Hash)

    the request data

Raises:



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/jsonrpc/parser.rb', line 151

def validate_jsonrpc_version(data)
  raise InvalidRequestError.new(data: { details: 'Request must be an object' }) unless data.is_a?(Hash)

  jsonrpc = data['jsonrpc']

  # Get request ID from the data
  request_id = data['id']

  if jsonrpc.nil?
    raise InvalidRequestError.new(data: { details: "Missing 'jsonrpc' property" },
                                  request_id: request_id)
  end

  return if jsonrpc == '2.0'

  raise InvalidRequestError.new(data: { details: "Invalid JSON-RPC version, must be '2.0'" },
                                request_id: request_id)
end

#validate_request_structure(data) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Validate the request structure according to JSON-RPC 2.0 specification

Parameters:

  • data (Hash)

    the request data

Raises:



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/jsonrpc/parser.rb', line 180

def validate_request_structure(data)
  method = data['method']

  # Get ID for possible errors
  id = data['id']

  raise InvalidRequestError.new(data: { details: "Missing 'method' property" }, request_id: id) if method.nil?

  unless method.is_a?(String)
    raise InvalidRequestError.new(data: { details: 'Method must be a string' }, request_id: id)
  end

  params = data['params']

  unless params.nil? || params.is_a?(Array) || params.is_a?(Hash)
    raise InvalidRequestError.new(data: { details: 'Params must be an object, array, or omitted' }, request_id: id)
  end

  id = data['id']
  unless id.nil? || id.is_a?(String) || id.is_a?(Integer) || id.nil?
    raise InvalidRequestError.new(
      data: { details: 'ID must be a string, number, null, or omitted' },
      request_id: id
    )
  end

  nil unless id.is_a?(Integer)
end