Class: Miasma::Types::Api

Inherits:
Object
  • Object
show all
Includes:
Utils::Lazy, Utils::Memoization
Defined in:
lib/miasma/types/api.rb

Overview

Remote API connection

Constant Summary collapse

VALID_REQUEST_RETRY_METHODS =

HTTP request methods that are allowed retry

[:get, :head]
MAX_REQUEST_RETRIES =

Maximum allowed HTTP request retries (for non-HTTP related errors)

5
REQUEST_RETRY_DELAY =

Seconds to pause between retries

0.5

Instance Method Summary collapse

Methods included from Utils::Memoization

#_memo, #clear_memoizations!, #memoize, #unmemoize

Methods included from Utils::Lazy

included

Constructor Details

#initialize(creds) ⇒ self

Create new API connection

Parameters:

  • creds (Smash)

    credentials



23
24
25
26
27
28
29
30
31
# File 'lib/miasma/types/api.rb', line 23

def initialize(creds)
  custom_setup(creds)
  if(creds.is_a?(Hash))
    load_data(creds)
  else
    raise TypeError.new "Expecting `credentials` to be of type `Hash`. Received: `#{creds.class}`"
  end
  connect
end

Instance Method Details

#api_for(type) ⇒ Api

Build new API for specified type using current provider / creds

Parameters:

  • type (Symbol)

    api type

Returns:



58
59
60
61
62
63
64
65
66
67
68
# File 'lib/miasma/types/api.rb', line 58

def api_for(type)
  memoize(type) do
    Miasma.api(
      Smash.new(
        :type => type,
        :provider => provider,
        :credentials => attributes
      )
    )
  end
end

#connectself

Connect to the remote API

Returns:

  • (self)


50
51
52
# File 'lib/miasma/types/api.rb', line 50

def connect
  self
end

#connectionHTTP

Returns:

  • (HTTP)


71
72
73
# File 'lib/miasma/types/api.rb', line 71

def connection
  HTTP.with_headers('User-Agent' => "miasma/v#{Miasma::VERSION}")
end

#custom_setup(creds) ⇒ TrueClass

Simple hook for concrete APIs to make adjustments prior to initialization and connection

Parameters:

Returns:

  • (TrueClass)


38
39
40
# File 'lib/miasma/types/api.rb', line 38

def custom_setup(creds)
  true
end

#endpointString

Returns url endpoint.

Returns:

  • (String)

    url endpoint



76
77
78
# File 'lib/miasma/types/api.rb', line 76

def endpoint
  'http://api.example.com'
end

#format_response(result, extract_body = true) ⇒ Smash

Makes best attempt at formatting response

Parameters:

  • result (HTTP::Response)
  • extract_body (TrueClass, FalseClass) (defaults to: true)

    automatically extract body

Returns:



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/miasma/types/api.rb', line 162

def format_response(result, extract_body=true)
  extracted_headers = Smash[result.headers.map{|k,v| [Utils.snake(k), v]}]
  if(extract_body)
    body_content = result.body.to_s
    body_content.encode!('UTF-8', 'binary',
      :invalid => :replace,
      :undef => :replace,
      :replace => ''
    )
    if(extracted_headers[:content_type].to_s.include?('json'))
      extracted_body = from_json(body_content) || body_content
    elsif(extracted_headers[:content_type].to_s.include?('xml'))
      extracted_body = from_xml(body_content) || body_content
    else
      extracted_body = from_json(body_content) ||
        from_xml(body_content) ||
        body_content
    end
  end
  unless(extracted_body)
    # @note if body is over 100KB, do not extract
    if(extracted_headers[:content_length].to_i < 102400)
      extracted_body = result.body.to_s
    else
      extracted_body = result.body
    end
  end
  Smash.new(
    :response => result,
    :headers => extracted_headers,
    :body => extracted_body
  )
end

#from_json(string) ⇒ Hash, ...

Convert from JSON

Parameters:

  • string (String)

Returns:

  • (Hash, Array, NilClass)


200
201
202
203
204
205
206
# File 'lib/miasma/types/api.rb', line 200

def from_json(string)
  begin
    MultiJson.load(string).to_smash
  rescue MultiJson::ParseError
    nil
  end
end

#from_xml(string) ⇒ Hash, ...

Convert from JSON

Parameters:

  • string (String)

Returns:

  • (Hash, Array, NilClass)


212
213
214
215
216
217
218
# File 'lib/miasma/types/api.rb', line 212

def from_xml(string)
  begin
    MultiXml.parse(string).to_smash
  rescue MultiXml::ParseError
    nil
  end
end

#make_request(connection, http_method, request_args) ⇒ HTTP::Response

Note:

this is mainly here for concrete APIs to override if things need to be done prior to the actual request (like signature generation)

Perform request

Parameters:

  • connection (HTTP)
  • http_method (Symbol)
  • request_args (Array)

Returns:

  • (HTTP::Response)


153
154
155
# File 'lib/miasma/types/api.rb', line 153

def make_request(connection, http_method, request_args)
  connection.send(http_method, *request_args)
end

#providerSymbol

Returns name of provider.

Returns:

  • (Symbol)

    name of provider



43
44
45
# File 'lib/miasma/types/api.rb', line 43

def provider
  Utils.snake(self.class.to_s.split('::').last).to_sym
end

#request(args) ⇒ Smash

Perform request to remote API

Parameters:

  • args (Hash)

    options

Options Hash (args):

  • :method (String, Symbol)

    HTTP request method

  • :path (String)

    request path

  • :expects (Integer, Array<Integer>)

    expected response status code

  • :disable_body_extraction (TrueClass, FalseClass)

    do not auto-parse response body

Returns:

  • (Smash)

    => HTTP::Response, :headers => Smash, :body => Object



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/miasma/types/api.rb', line 89

def request(args)
  args = args.to_smash
  http_method = args.fetch(:method, 'get').to_s.downcase.to_sym
  unless(HTTP::Request::METHODS.include?(http_method))
    raise ArgumentError.new 'Invalid request method provided!'
  end
  request_args = [].tap do |ary|
    _endpoint = args.delete(:endpoint) || endpoint
    ary.push(
      File.join(_endpoint, args[:path].to_s)
    )
    options = {}.tap do |opts|
      [:form, :params, :json, :body].each do |key|
        opts[key] = args[key] if args[key]
      end
    end
    ary.push(options) unless options.empty?
  end
  if(args[:headers])
    _connection = connection.with_headers(args[:headers])
    args.delete(:headers)
  else
    _connection = connection
  end
  result = retryable_request(http_method) do
    make_request(_connection, http_method, request_args)
  end
  unless([args.fetch(:expects, 200)].flatten.compact.map(&:to_i).include?(result.code))
    raise Error::ApiError::RequestError.new(result.reason, :response => result)
  end
  format_response(result, !args[:disable_body_extraction])
end

#retryable_request(http_method) { ... } ⇒ Object

If HTTP request method is allowed to be retried then retry request on non-response failures. Otherwise just re-raise immediately

Parameters:

  • http_method (Symbol)

    HTTP request method

Yields:

  • request to be retried if allowed

Returns:

  • (Object)

    result of block



129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/miasma/types/api.rb', line 129

def retryable_request(http_method)
  attempts = VALID_REQUEST_RETRY_METHODS.include?(http_method) ? 0 :nil
  begin
    yield
  rescue => e
    if(attempts && attempts < MAX_REQUEST_RETRIES)
      attempts += 1
      sleep REQUEST_RETRY_DELAY
      retry
    else
      raise
    end
  end
end