Class: ApipieBindings::API

Inherits:
Object
  • Object
show all
Defined in:
lib/apipie_bindings/api.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config, options = {}) ⇒ API

Creates new API bindings instance

Examples:

connect to a server

ApipieBindings::API.new({:uri => 'http://localhost:3000/',
  :username => 'admin', :password => 'changeme',
  :api_version => '2', :aggressive_cache_checking => true})

connect with a local API description

ApipieBindings::API.new({:apidoc_cache_dir => 'test/unit/data',
  :apidoc_cache_name => 'architecture'})

Parameters:

  • config (Hash)

    API bindings configuration

  • options (Hash) (defaults to: {})

    params that are passed to ResClient as-is

Options Hash (config):

  • :uri (String)

    base URL of the server

  • :username (String)

    username to access the API

  • :password (String)

    username to access the API

  • :oauth (Hash)

    options to access API using OAuth

    • :consumer_key (String) OAuth key

    • :consumer_secret (String) OAuth secret

    • :options (Hash) options passed to OAuth

  • :credentials (AbstractCredentials)

    object implementing ApipieBindings::AbstractCredentials interface e.g. HammerCLIForeman::BasicCredentials This is prefered way to pass credentials. Credentials acquired form :credentials object take precedence over explicite params

  • :headers (Hash)

    additional headers to send with the requests

  • :api_version (String) — default: '1'

    version of the API

  • :language (String)

    prefered locale for the API description

  • :apidoc_cache_base_dir (String) — default: '~/.cache/apipie_bindings'

    base directory for building apidoc_cache_dir

  • :apidoc_cache_dir (String) — default: apidoc_cache_base_dir+'/<URI>'

    where to cache the JSON description of the API

  • :apidoc_cache_name (String) — default: 'default.json'

    name of te cache file. If there is cache in the :apidoc_cache_dir, it is used.

  • :apidoc_authenticated (String) — default: true

    whether or not does the call to obtain API description use authentication. It is useful to avoid unnecessary prompts for credentials

  • :fake_responses (Hash) — default: {}

    responses to return if used in dry run mode

  • :dry_run (Bool) — default: false

    dry run mode allows to test your scripts and not touch the API. The results are taken form exemples in the API description or from the :fake_responses

  • :aggressive_cache_checking (Bool) — default: false

    check before every request if the local cache of API description (JSON) is up to date. By default it is checked after each API request

  • :logger (Object) — default: Logger.new(STDERR)

    custom logger class

  • :timeout (Number)

    API request timeout in seconds

Raises:



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/apipie_bindings/api.rb', line 58

def initialize(config, options={})
  if config[:uri].nil? && config[:apidoc_cache_dir].nil?
    raise ApipieBindings::ConfigurationError.new('Either :uri or :apidoc_cache_dir needs to be set')
  end
  @uri = config[:uri]
  @api_version = config[:api_version] || 1
  @language = config[:language]
  apidoc_cache_base_dir = config[:apidoc_cache_base_dir] || File.join(File.expand_path('~/.cache'), 'apipie_bindings')
  @apidoc_cache_dir = config[:apidoc_cache_dir] || File.join(apidoc_cache_base_dir, @uri.tr(':/', '_'), "v#{@api_version}")
  @apidoc_cache_name = config[:apidoc_cache_name] || set_default_name
  @apidoc_authenticated = (config[:apidoc_authenticated].nil? ? true : config[:apidoc_authenticated])
  @dry_run = config[:dry_run] || false
  @aggressive_cache_checking = config[:aggressive_cache_checking] || false
  @fake_responses = config[:fake_responses] || {}
  @logger = config[:logger]
  unless @logger
    @logger = Logger.new(STDERR)
    @logger.level = Logger::ERROR
  end

  config = config.dup

  headers = {
    :content_type => 'application/json',
    :accept       => "application/json;version=#{@api_version}"
  }
  headers.merge!({ "Accept-Language" => language }) if language
  headers.merge!(config[:headers]) unless config[:headers].nil?
  headers.merge!(options.delete(:headers)) unless options[:headers].nil?

  log.debug "Global headers: #{headers.ai}"

  @credentials = config[:credentials] if config[:credentials] && config[:credentials].respond_to?(:to_params)

  @resource_config = {
    :timeout  => config[:timeout],
    :headers  => headers,
    :verify_ssl => false  # keep rest_client >= 1.8.0 setup comaptible
  }.merge(options)

  @config = config
end

Instance Attribute Details

#apidoc_cache_nameObject (readonly)

Returns the value of attribute apidoc_cache_name.



12
13
14
# File 'lib/apipie_bindings/api.rb', line 12

def apidoc_cache_name
  @apidoc_cache_name
end

#dry_run=(value) ⇒ Object (writeonly)

Sets the attribute dry_run

Parameters:

  • value

    the value to set the attribute dry_run to.



13
14
15
# File 'lib/apipie_bindings/api.rb', line 13

def dry_run=(value)
  @dry_run = value
end

#fake_responsesObject (readonly)

Returns the value of attribute fake_responses.



12
13
14
# File 'lib/apipie_bindings/api.rb', line 12

def fake_responses
  @fake_responses
end

#languageObject (readonly)

Returns the value of attribute language.



12
13
14
# File 'lib/apipie_bindings/api.rb', line 12

def language
  @language
end

Instance Method Details

#apidocObject



114
115
116
117
# File 'lib/apipie_bindings/api.rb', line 114

def apidoc
  @apidoc = @apidoc || load_apidoc || retrieve_apidoc
  @apidoc
end

#apidoc_cache_fileObject



102
103
104
# File 'lib/apipie_bindings/api.rb', line 102

def apidoc_cache_file
   File.join(@apidoc_cache_dir, "#{@apidoc_cache_name}#{cache_extension}")
end

#call(resource_name, action_name, params = {}, headers = {}, options = {}) ⇒ Object

Call an action in the API. It finds most fitting route based on given parameters with other attributes neccessary to do an API call. If in dry_run mode #initialize it finds fake response data in examples or user provided data. At the end when the response format is JSON it is parsed and returned as ruby objects. If server supports checksum sending the internal cache with API description is checked and updated if needed

Examples:

show user data

call(:users, :show, :id => 1)

Parameters:

  • resource_name (Symbol)

    name of the resource

  • action_name (Symbol)

    name of the action

  • params (Hash) (defaults to: {})

    parameters to be send in the request

  • headers (Hash) (defaults to: {})

    extra headers to be sent with the request

  • options (Hash) (defaults to: {})

    options to influence the how the call is processed

    • :response (Symbol) :raw - skip parsing JSON in response

    • :with_authentication (Bool) true - use rest client with/without auth configuration

    • :skip_validation (Bool) false - skip validation of parameters



155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/apipie_bindings/api.rb', line 155

def call(resource_name, action_name, params={}, headers={}, options={})
  check_cache if @aggressive_cache_checking
  resource = resource(resource_name)
  action = resource.action(action_name)
  route = action.find_route(params)
  action.validate!(params) unless options[:skip_validation]
  options[:fake_response] = find_match(fake_responses, resource_name, action_name, params) || action.examples.first if dry_run?
  return http_call(
    route.method,
    route.path(params),
    params.reject { |par, _| route.params_in_path.include? par.to_s },
    headers, options)
end

#check_cacheObject



246
247
248
249
250
251
252
253
# File 'lib/apipie_bindings/api.rb', line 246

def check_cache
  begin
    response = http_call('get', "/apidoc/apipie_checksum", {}, { :accept => "application/json" })
    response['checksum']
  rescue
    nil
  end
end

#clean_cacheObject



241
242
243
244
# File 'lib/apipie_bindings/api.rb', line 241

def clean_cache
  @apidoc = nil
  Dir["#{@apidoc_cache_dir}/*#{cache_extension}"].each { |f| File.delete(f) }
end

#dry_run?Boolean

Returns:

  • (Boolean)


119
120
121
# File 'lib/apipie_bindings/api.rb', line 119

def dry_run?
  @dry_run ? true : false
end

#has_resource?(name) ⇒ Boolean

Returns:

  • (Boolean)


123
124
125
# File 'lib/apipie_bindings/api.rb', line 123

def has_resource?(name)
  apidoc[:docs][:resources].has_key? name
end

#http_call(http_method, path, params = {}, headers = {}, options = {}) ⇒ Object

Low level call to the API. Suitable for calling actions not covered by apipie documentation. For all other cases use #call

Examples:

show user data

http_call('get', '/api/users/1')

Parameters:

  • http_method (String)

    one of get, put, post, destroy, patch

  • path (String)

    URL path that should be called

  • params (Hash) (defaults to: {})

    parameters to be send in the request

  • headers (Hash) (defaults to: {})

    extra headers to be sent with the request

  • options (Hash) (defaults to: {})

    options to influence the how the call is processed

    • :response (Symbol) :raw - skip parsing JSON in response

    • :reduce_response_log (Bool) - do not show response content in the log.

    • :with_authentication (Bool) true - use rest client with/without auth configuration



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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/apipie_bindings/api.rb', line 181

def http_call(http_method, path, params={}, headers={}, options={})
  headers ||= { }

  args = [http_method]
  if %w[post put].include?(http_method.to_s)
    #If using multi-part forms, the paramaters should not be json
    if ((headers.include?(:content_type)) and (headers[:content_type] == "multipart/form-data"))
      args << params
    else
      args << params.to_json
    end
  else
    headers[:params] = params if params
  end

  log.info "#{http_method.to_s.upcase} #{path}"
  log.debug "Params: #{params.ai}"
  log.debug "Headers: #{headers.ai}"

  args << headers if headers

  if dry_run?
    empty_response = ApipieBindings::Example.new('', '', '', 200, '')
    ex = options[:fake_response ] || empty_response
    net_http_resp = Net::HTTPResponse.new(1.0, ex.status, "")
    if RestClient::Response.method(:create).arity == 4 # RestClient > 1.8.0
      response = RestClient::Response.create(ex.response, net_http_resp, args,
        RestClient::Request.new(:method=>http_method, :url=>path))
    else
      response = RestClient::Response.create(ex.response, net_http_resp, args)
    end
  else
    begin
      apidoc_without_auth = (path =~ /\/apidoc\//) && !@apidoc_authenticated
      authenticate = options[:with_authentication].nil? ? !apidoc_without_auth : options[:with_authentication]
      client = authenticate ? authenticated_client : unauthenticated_client
      response = call_client(client, path, args)
      update_cache(response.headers[:apipie_checksum])
    rescue => e
      log.debug e.message + "\n" +
        (e.respond_to?(:response) ? process_data(e.response).ai : e.ai)
      raise
    end
  end

  result = options[:response] == :raw ? response : process_data(response)
  log.debug "Response: %s" % (options[:reduce_response_log] ? "Received OK" : result.ai)
  log.debug "Response headers: #{response.headers.ai}" if response.respond_to?(:headers)
  result
end

#load_apidocObject



106
107
108
109
110
111
# File 'lib/apipie_bindings/api.rb', line 106

def load_apidoc
  check_cache if @aggressive_cache_checking
  if File.exist?(apidoc_cache_file)
    JSON.parse(File.read(apidoc_cache_file), :symbolize_names => true)
  end
end

#logObject



279
280
281
# File 'lib/apipie_bindings/api.rb', line 279

def log
  @logger
end

#resource(name) ⇒ Object



127
128
129
# File 'lib/apipie_bindings/api.rb', line 127

def resource(name)
  ApipieBindings::Resource.new(name, self)
end

#resourcesArray<ApipieBindings::Resource>

List available resources

Returns:



133
134
135
# File 'lib/apipie_bindings/api.rb', line 133

def resources
  apidoc[:docs][:resources].keys.map { |res| resource(res) }
end

#retrieve_apidocObject



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/apipie_bindings/api.rb', line 255

def retrieve_apidoc
  FileUtils.mkdir_p(@apidoc_cache_dir) unless File.exists?(@apidoc_cache_dir)
  if language
    response = retrieve_apidoc_call("/apidoc/v#{@api_version}.#{language}.json", :safe => true)
    language_family = language.split('_').first
    if !response && language_family != language
      response = retrieve_apidoc_call("/apidoc/v#{@api_version}.#{language_family}.json", :safe => true)
    end
  end
  unless response
    begin
      response = retrieve_apidoc_call("/apidoc/v#{@api_version}.json")
    rescue
      raise ApipieBindings::DocLoadingError.new(
        "Could not load data from #{@uri}\n"\
        " - is your server down?\n"\
        " - was rake apipie:cache run when using apipie cache? (typical production settings)")
    end
  end
  File.open(apidoc_cache_file, "w") { |f| f.write(response.body) }
  log.debug "New apidoc loaded from the server"
  load_apidoc
end

#update_cache(cache_name) ⇒ Object



233
234
235
236
237
238
239
# File 'lib/apipie_bindings/api.rb', line 233

def update_cache(cache_name)
  if !cache_name.nil? && (cache_name != @apidoc_cache_name)
    clean_cache
    log.debug "Cache expired. (#{@apidoc_cache_name} -> #{cache_name})"
    @apidoc_cache_name = cache_name
  end
end