Module: Gienah::Protocol
- Defined in:
- lib/gienah/protocol.rb,
sig/gienah.rbs
Constant Summary collapse
- MAX_MESSAGE =
4 * 1024 * 1024
- MAX_HEADER =
16 * 1024
Class Method Summary collapse
- .frame(message, max_size: MAX_MESSAGE) ⇒ String
- .notification(method, params = {}) ⇒ Hash[String, untyped]
- .read(io, max_size: MAX_MESSAGE) ⇒ Hash[String, untyped]?
- .request(id, method, params = {}) ⇒ Hash[String, untyped]
- .response(id, result: nil, error: nil) ⇒ Hash[String, untyped]
- .validate_message(message) ⇒ Hash[String, untyped]
Class Method Details
.frame(message, max_size: MAX_MESSAGE) ⇒ String
9 10 11 12 13 14 15 16 17 |
# File 'lib/gienah/protocol.rb', line 9 def frame(, max_size: MAX_MESSAGE) () body = JSON.generate().b raise ProtocolError, "message exceeds #{max_size} bytes" unless body.bytesize.between?(1, max_size) "Content-Length: #{body.bytesize}\r\n\r\n".b + body rescue JSON::GeneratorError => error raise ProtocolError, "invalid JSON: #{error.}" end |
.notification(method, params = {}) ⇒ Hash[String, untyped]
82 83 84 |
# File 'lib/gienah/protocol.rb', line 82 def notification(method, params = {}) {"jsonrpc" => "2.0", "method" => method, "params" => params} end |
.read(io, max_size: MAX_MESSAGE) ⇒ Hash[String, untyped]?
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/gienah/protocol.rb', line 19 def read(io, max_size: MAX_MESSAGE) headers = {} bytes = 0 loop do line = io.gets("\r\n", MAX_HEADER + 1) return nil if line.nil? && headers.empty? raise ProtocolError, "truncated header" unless line&.end_with?("\r\n") raise ProtocolError, "oversized header" if line.bytesize > MAX_HEADER break if line == "\r\n" bytes += line.bytesize raise ProtocolError, "oversized headers" if bytes > MAX_HEADER raise ProtocolError, "non-ASCII header" unless line.ascii_only? key, value = line.delete_suffix("\r\n").split(":", 2) raise ProtocolError, "invalid header" unless key&.match?(/\A[A-Za-z][A-Za-z0-9-]*\z/) && value key = key.downcase raise ProtocolError, "duplicate header" if headers.key?(key) headers[key] = value.strip end raw_length = headers["content-length"] raise ProtocolError, "missing or invalid Content-Length" unless raw_length&.match?(/\A\d+\z/) length = Integer(raw_length, 10) raise ProtocolError, "message exceeds #{max_size} bytes" unless length.between?(1, max_size) body = read_exact(io, length) raise ProtocolError, "truncated body" unless body&.bytesize == length body.force_encoding(Encoding::UTF_8) raise ProtocolError, "invalid UTF-8 body" unless body.valid_encoding? (JSON.parse(body)) rescue JSON::ParserError => error raise ProtocolError, "invalid JSON: #{error.}" end |
.request(id, method, params = {}) ⇒ Hash[String, untyped]
78 79 80 |
# File 'lib/gienah/protocol.rb', line 78 def request(id, method, params = {}) {"jsonrpc" => "2.0", "id" => id, "method" => method, "params" => params} end |
.response(id, result: nil, error: nil) ⇒ Hash[String, untyped]
86 87 88 89 90 91 |
# File 'lib/gienah/protocol.rb', line 86 def response(id, result: nil, error: nil) raise ArgumentError, "result and error are exclusive" if !error.nil? && !result.nil? value = {"jsonrpc" => "2.0", "id" => id} error.nil? ? value["result"] = result : value["error"] = error value end |
.validate_message(message) ⇒ Hash[String, untyped]
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/gienah/protocol.rb', line 53 def () raise ProtocolError, "message must be an object" unless .is_a?(Hash) version = ["jsonrpc"] || [:jsonrpc] raise ProtocolError, "jsonrpc must be \"2.0\"" unless version == "2.0" method = ["method"] || [:method] id_present = .key?("id") || .key?(:id) has_result = .key?("result") || .key?(:result) has_error = .key?("error") || .key?(:error) raise ProtocolError, "method must be a nonempty String" if method && (!method.is_a?(String) || method.empty?) if method raise ProtocolError, "request cannot contain result or error" if has_result || has_error params = ["params"] || [:params] raise ProtocolError, "params must be an object or array" if params && !params.is_a?(Hash) && !params.is_a?(Array) elsif id_present id = ["id"] || [:id] raise ProtocolError, "invalid response id" unless id.is_a?(Integer) || id.is_a?(String) raise ProtocolError, "response must contain exactly one result or error" unless has_result ^ has_error error = ["error"] || [:error] raise ProtocolError, "invalid response error" if has_error && (!error.is_a?(Hash) || !error["message"].is_a?(String)) else raise ProtocolError, "message must contain method or id" end end |