Module: Protocol::Jsonrpc
- Defined in:
- lib/protocol/jsonrpc.rb,
lib/protocol/jsonrpc/batch.rb,
lib/protocol/jsonrpc/error.rb,
lib/protocol/jsonrpc/frame.rb,
lib/protocol/jsonrpc/framer.rb,
lib/protocol/jsonrpc/message.rb,
lib/protocol/jsonrpc/request.rb,
lib/protocol/jsonrpc/version.rb,
lib/protocol/jsonrpc/response.rb,
lib/protocol/jsonrpc/connection.rb,
lib/protocol/jsonrpc/notification.rb,
lib/protocol/jsonrpc/error_response.rb,
lib/protocol/jsonrpc/invalid_message.rb
Defined Under Namespace
Modules: Message Classes: Connection, Error, Framer, InternalError, InvalidParamsError, InvalidRequestError, MethodNotFoundError, ParseError
Constant Summary collapse
- JSONRPC_VERSION =
"2.0"- Batch =
Data.define(:messages) do def self.load(data) return InvalidMessage.new(data: data.inspect) if data.empty? = data.map { || Message.load() } new() end def to_a = alias_method :to_ary, :to_a def as_json = to_a.map(&:as_json) def to_json(...) = JSON.generate(to_a.map(&:as_json), ...) alias_method :to_s, :to_json def reply(&block) to_a.filter_map do || .reply(&block) end end private def method_missing(method, *args, **kwargs, &block) if .respond_to?(method) .send(method, *args, **kwargs, &block) else super end end def respond_to_missing?(method, include_private = false) .respond_to?(method, include_private) || super end end
- Frame =
Frame represents the raw JSON data structure of a JSON-RPC message before it’s validated and converted into a proper Message object. Handles translation between JSON strings and Ruby Hashes and reading and writing to a stream.
Data.define(:raw_json) do class << self # Read a frame from the stream # @param stream [IO] An objects that responds to `gets` and returns a String # @return [Frame, nil] The parsed frame or nil if the stream is empty def read(stream) raw_json = stream.gets return nil if raw_json.nil? new(raw_json: raw_json.strip) end # Pack a message into a frame # @param message [Message, Array<Message>] The message to pack # @return [Frame] an instance that can be written to a stream # @raise [ArgumentError] if the message is not a Message or Array of Messages def pack() if .is_a?(Array) new(raw_json: .map { |msg| as_json(msg) }.to_json) else new(raw_json: as_json().to_json) end end private def as_json() return if .is_a?(Hash) return .as_json if .respond_to?(:as_json) raise ArgumentError, "Invalid message type: #{.class}. Must be a Hash or respond to :as_json." end end # Unpack the raw_json into a Hash representing the JSON object # Symbolizes the keys of the Hash. # @return [Hash] The parsed JSON object # @raise [ParseError] if the JSON is invalid def unpack JSON.parse(raw_json, symbolize_names: true) end def to_json(...) = raw_json def to_s = raw_json # Write the frame to a stream # @param stream [IO] The stream to write to # @return [void] def write(stream) stream.write("#{raw_json}\n") end end
- Request =
Data.define(:method, :params, :id, :jsonrpc) do include Message def initialize(method:, params: nil, id: SecureRandom.uuid, jsonrpc: JSONRPC_VERSION) unless method.is_a?(String) raise InvalidRequestError.new("Method must be a string", data: method.inspect) end unless params.nil? || params.is_a?(Array) || params.is_a?(Hash) raise InvalidRequestError.new("Params must be an array or object", data: params.inspect) end unless id.is_a?(String) || id.is_a?(Numeric) raise InvalidRequestError.new("ID must be a string or number", id:) end super end def to_h h = super h.delete(:params) if params.nil? h end def request? = true def reply(*args, &) if args.empty? && block_given? begin result_or_error = yield self rescue => error return ErrorResponse.new(id:, error:) end elsif args.length == 1 result_or_error = args.first else raise ArgumentError, "wrong number of arguments (given #{args.length}, expected 0 or 1)" end if result_or_error.is_a?(StandardError) ErrorResponse.new(id:, error: result_or_error) else Response.new(id:, result: result_or_error) end end end
- VERSION =
"0.2.1"- Response =
Data.define(:id, :result, :jsonrpc) do include Message def initialize(id:, result:, jsonrpc: JSONRPC_VERSION) unless id.nil? || id.is_a?(String) || id.is_a?(Numeric) raise InvalidRequestError.new("ID must be nil, string, or number", id:) end super end def response? = true end
- Notification =
Data.define(:method, :params, :jsonrpc) do include Message def initialize(method:, params: nil, jsonrpc: JSONRPC_VERSION) super unless method.is_a?(String) raise InvalidRequestError.new("Method must be a string", data: method.inspect) end unless params.nil? || params.is_a?(Array) || params.is_a?(Hash) raise InvalidRequestError.new("Params must be an array or object", data: params.inspect) end end def to_h h = super h.delete(:params) if params.nil? h end # Compatibility with the Message interface, Notifications have no ID def id = nil # Compatibility with the Request # Yields the notification for processing but ignores the result def reply(*, &) yield self if block_given? nil rescue nil # JSON-RPC 2.0 spec says notifications should never return, even on error. end def notification? = true end
- ErrorResponse =
Data.define(:id, :error, :jsonrpc) do include Message def initialize(id:, error:, jsonrpc: JSONRPC_VERSION) unless id.nil? || id.is_a?(String) || id.is_a?(Numeric) raise InvalidRequestError.new("ID must be nil, string or number", id: id) end error = Error.wrap(error) super end def to_h = super.merge(error: error.to_h) def error? = true def response? = true end
- InvalidMessage =
When the message received is not valid JSON or not a valid JSON-RPC message, this class is returned in place of a normal Message. The error that would have been raised is returned as the error. This simplifies batch processing because invalid messages in the batch can be processed as part of the batch rather than raising and interrupting the batch processing.
Data.define(:error, :id) do include Message def initialize(error: nil, data: nil, id: nil) error = Error.wrap(error, data:, id:) super(error:, id: error.id) end def invalid? = true def as_json = raise "InvalidMessage cannot be serialized" def reply(...) = ErrorResponse.new(id:, error:) def to_json(...) = raise "InvalidMessage cannot be serialized" def to_s = raise "InvalidMessage cannot be serialized" end