lib/seahorse/client/http/response.rb
# frozen_string_literal: true module Seahorse module Client module Http class Response # @option options [Integer] :status_code (0) # @option options [Headers] :headers (Headers.new) # @option options [IO] :body (StringIO.new) def initialize(options = {}) @status_code = options[:status_code] || 0 @headers = options[:headers] || Headers.new @body = options[:body] || StringIO.new @listeners = Hash.new { |h,k| h[k] = [] } @complete = false @done = nil @error = nil end # @return [Integer] Returns `0` if the request failed to generate # any response. attr_accessor :status_code # @return [Headers] attr_accessor :headers # @return [StandardError, nil] attr_reader :error # @return [IO] def body @body end # @param [#read, #size, #rewind] io def body=(io) @body = case io when nil then StringIO.new('') when String then StringIO.new(io) else io end end # @return [String|Array] def body_contents if body.is_a?(Array) # an array of parsed events body else body.rewind contents = body.read body.rewind contents end end # @param [Integer] status_code # @param [Hash<String,String>] headers def signal_headers(status_code, headers) @status_code = status_code @headers = Headers.new(headers) emit(:headers, @status_code, @headers) end # @param [string] chunk def signal_data(chunk) unless chunk == '' @body.write(chunk) emit(:data, chunk) end end # Completes the http response. # # @example Completing the response in a single call # # http_response.signal_done( # status_code: 200, # headers: {}, # body: '' # ) # # @example Complete the response in parts # # # signal headers straight-way # http_response.signal_headers(200, {}) # # # signal data as it is received from the socket # http_response.signal_data("...") # http_response.signal_data("...") # http_response.signal_data("...") # # # signal done once the body data is all written # http_response.signal_done # # @overload signal_done() # # @overload signal_done(options = {}) # @option options [required, Integer] :status_code # @option options [required, Hash] :headers # @option options [required, String] :body # def signal_done(options = {}) if options.keys.sort == [:body, :headers, :status_code] signal_headers(options[:status_code], options[:headers]) signal_data(options[:body]) signal_done elsif options.empty? @body.rewind if @body.respond_to?(:rewind) @done = true emit(:done) else msg = 'options must be empty or must contain :status_code, :headers, '\ 'and :body' raise ArgumentError, msg end end # @param [StandardError] networking_error def signal_error(networking_error) @error = networking_error signal_done end def on_headers(status_code_range = nil, &block) @listeners[:headers] << listener(status_code_range, block) end def on_data(&callback) @listeners[:data] << callback end def on_done(status_code_range = nil, &callback) listener = listener(status_code_range, callback) if @done listener.call else @listeners[:done] << listener end end def on_success(status_code_range = 200..599, &callback) on_done(status_code_range) do unless @error yield end end end def on_error(&callback) on_done(0..599) do if @error yield(@error) end end end def reset @status_code = 0 @headers.clear @body.truncate(0) @error = nil end private def listener(range, callback) range = range..range if Integer === range if range lambda do |*args| if range.include?(@status_code) callback.call(*args) end end else callback end end def emit(event_name, *args) @listeners[event_name].each { |listener| listener.call(*args) } end end end end end