class Tml::Api::Client

Constants

API_PATH

Public Class Methods

error?(data) click to toggle source

checks if there are any API errors

# File lib/tml/api/client.rb, line 68
def self.error?(data)
  not data['error'].nil?
end

Public Instance Methods

access_token() click to toggle source

access token

# File lib/tml/api/client.rb, line 203
def access_token
  application.token
end
api(path, params = {}, opts = {}) click to toggle source

checks mode and cache, and fetches data

# File lib/tml/api/client.rb, line 226
def api(path, params = {}, opts = {})
  # inline mode should always use API calls
  if live_api_request?
    params = params.merge(:access_token => access_token, :app_id => application.key)
    return process_response(execute_request(path, params, opts), opts)
  end

  return unless cache_enabled?(opts)

  # ensure the cache version is not outdated
  verify_cache_version

  return if Tml.cache.version.invalid?

  # get request uses local cache, then CDN
  data = Tml.cache.fetch(opts[:cache_key]) do
    fetched_data = get_from_cdn(opts[:cache_key]) unless Tml.cache.read_only?
    fetched_data || {}
  end

  process_response(data, opts)
end
api_host() click to toggle source

API Host

# File lib/tml/api/client.rb, line 77
def api_host
  @api_host ||= begin
    uri = api_uri.dup
    uri.path  = ''
    uri.query = nil
    uri.to_s
  end
end
api_uri() click to toggle source
# File lib/tml/api/client.rb, line 72
def api_uri
  @api_uri ||= URI::parse(application.host)
end
cache_enabled?(opts) click to toggle source

checks if cache is enable

# File lib/tml/api/client.rb, line 217
def cache_enabled?(opts)
  # only gets ever get cached
  return false unless opts[:method] == :get
  return false if opts[:cache_key].nil?
  return false unless Tml.cache.enabled?
  true
end
cdn_connection() click to toggle source

cdn_connection

# File lib/tml/api/client.rb, line 147
def cdn_connection
  @cdn_connection ||= Faraday.new(:url => cdn_host) do |faraday|
    faraday.request(:url_encoded) # form-encode POST params
    faraday.adapter(Faraday.default_adapter) # make requests with Net::HTTP
  end
end
cdn_host() click to toggle source
# File lib/tml/api/client.rb, line 122
def cdn_host
  @cdn_host ||= URI.join(application.cdn_host, '/').to_s
end
cdn_uri() click to toggle source
# File lib/tml/api/client.rb, line 126
def cdn_uri
  @cdn_host ||= URI::parse(application.cdn_host)
end
connection() click to toggle source

API connection

# File lib/tml/api/client.rb, line 87
def connection
  @connection ||= Faraday.new(:url => api_host) do |faraday|
    faraday.request(:url_encoded) # form-encode POST params
    # faraday.response :logger                  # log requests to STDOUT
    faraday.adapter(Faraday.default_adapter) # make requests with Net::HTTP
  end
end
decompress_data(compressed_data) click to toggle source
# File lib/tml/api/client.rb, line 154
def decompress_data(compressed_data)
  data = Zlib::GzipReader.new(StringIO.new(compressed_data.to_s)).read
  Tml.logger.debug("Compressed: #{compressed_data.length} Uncompressed: #{data.length}")
  data
rescue => ex
  Tml.logger.error("Failed to decompress data: #{ex.message[0..255]}")
  compressed_data
end
delete(path, params = {}, opts = {}) click to toggle source

delete from API

# File lib/tml/api/client.rb, line 63
def delete(path, params = {}, opts = {})
  api(path, params, opts.merge(:method => :delete))
end
execute_request(path, params = {}, opts = {}) click to toggle source

execute API request

# File lib/tml/api/client.rb, line 305
def execute_request(path, params = {}, opts = {})
  response = nil
  error = nil

  path = prepare_api_path(path)

  opts[:method] ||= :get

  trace_api_call(path, params, opts.merge(:host => api_host)) do
    begin
      if opts[:method] == :post
        response = connection.post(path, params)
      elsif opts[:method] == :put
        response = connection.put(path, params)
      elsif opts[:method] == :delete
        response = connection.delete(path, params)
      else
        response = connection.get do |request|
          prepare_request(request, path, params, opts)
        end
      end
    rescue => ex
      Tml.logger.error("Failed to execute request: #{ex.message[0..255]}")
      error = ex
      nil
    end
  end

  if error
    raise Tml::Exception.new("Error: #{error}")
  end

  if response.status >= 500 && response.status < 600
    raise Tml::Exception.new("Error: #{response.body}")
  end

  data = response.body

  if opts[:method] == :get && !opts[:uncompressed]
    return if data.nil? or data == ''

    unless opts[:uncompressed]
      data = decompress_data(data)
    end
  end

  return data if opts[:raw]

  begin
    data = JSON.parse(data)
  rescue => ex
    raise Tml::Exception.new("Failed to parse response: #{ex.message[0..255]}")
  end

  if data.is_a?(Hash) and not data['error'].nil?
    raise Tml::Exception.new("Error: #{data['error']}")
  end

  data
end
get(path, params = {}, opts = {}) click to toggle source

get from API

# File lib/tml/api/client.rb, line 48
def get(path, params = {}, opts = {})
  api(path, params, opts.merge(:method => :get))
end
get_cache_version() click to toggle source

get cache version from CDN

# File lib/tml/api/client.rb, line 96
def get_cache_version
  data = get_from_cdn('version', {t: Time.now.to_i}, {public: true, uncompressed: true})

  unless data
    Tml.logger.debug('No releases have been published yet')
    return '0'
  end

  data['version']
end
get_cdn_path(key, opts = {}) click to toggle source
# File lib/tml/api/client.rb, line 130
def get_cdn_path(key, opts = {})
  base_path = URI(application.cdn_host).path
  # Remove usage of String#last for clients who do not use ActiveSupport
  base_path += '/' unless base_path.size > 0 && base_path[-1..-1] == '/'

  adjusted_path = "#{base_path}#{application.key}/"

  if key == 'version'
    adjusted_path += "#{key}.json"
  else
    adjusted_path += "#{Tml.cache.version.to_s}/#{key}.json#{opts[:uncompressed] ? '' : '.gz'}"
  end

  adjusted_path
end
get_from_cdn(key, params = {}, opts = {}) click to toggle source

get from the CDN

# File lib/tml/api/client.rb, line 164
def get_from_cdn(key, params = {}, opts = {})
  if Tml.cache.version.invalid? and key != 'version'
    return nil
  end

  response = nil
  cdn_path = get_cdn_path(key, opts)

  trace_api_call(cdn_path, params, opts.merge(:host => cdn_host)) do
    begin
      response = cdn_connection.get do |request|
        prepare_request(request, cdn_path, params, opts)
      end
    rescue => ex
      Tml.logger.error("Failed to execute request: #{ex.message[0..255]}")
      return nil
    end
  end
  return if response.status >= 500 and response.status < 600
  return if response.body.nil? or response.body == '' or response.body.match(/xml/)

  data = response.body
  return if data.nil? or data == ''

  unless opts[:uncompressed]
    data = decompress_data(data)
  end

  begin
    data = JSON.parse(data)
  rescue => ex
    Tml.logger.error("Failed to parse response: #{ex.message[0..255]}")
    return nil
  end

  data
end
join_uri(*string) click to toggle source
# File lib/tml/api/client.rb, line 270
def join_uri(*string)
  string.join('/').gsub(/\/+/, '/')
end
live_api_request?() click to toggle source

should the API go to live server

# File lib/tml/api/client.rb, line 208
def live_api_request?
  # if no access token, never use live mode
  return false if access_token.nil?

  # if block is specifically asking for it or inline mode is activated
  Tml.session.inline_mode? or Tml.session.block_option(:live)
end
object_class(opts) click to toggle source

get object class from options

# File lib/tml/api/client.rb, line 367
def object_class(opts)
  return unless opts[:class]
  opts[:class].is_a?(String) ? opts[:class].constantize : opts[:class]
end
paginate(path, params = {}, opts = {}) { |result| ... } click to toggle source

paginates through API results

# File lib/tml/api/client.rb, line 250
def paginate(path, params = {}, opts = {})
  data = get(path, params, opts.merge({'raw' => true}))

  while data
    if data['results'].is_a?(Array)
      data['results'].each do |result|
        yield(result)
      end
    else
      yield(data['results'])
    end

    if data['pagination'] and data['pagination']['links']['next']
      data = get(data['pagination']['links']['next'], {}, opts.merge({'raw' => true}))
    else
      data = nil
    end
  end
end
post(path, params = {}, opts = {}) click to toggle source

post to API

# File lib/tml/api/client.rb, line 53
def post(path, params = {}, opts = {})
  api(path, params, opts.merge(:method => :post))
end
prepare_api_path(path) click to toggle source

prepares API path

# File lib/tml/api/client.rb, line 275
def prepare_api_path(path)
  return path if path.match(/^https?:\/\//)
  clean_path = trim_prepending_slash(path)

  if clean_path.index('v1') == 0 || clean_path.index('v2') == 0
    join_uri(api_uri.path, clean_path)
  else
    join_uri(api_uri.path, API_PATH, clean_path)
  end
end
prepare_request(request, path, params, opts = {}) click to toggle source

prepares request

# File lib/tml/api/client.rb, line 291
def prepare_request(request, path, params, opts = {})
  request.options.timeout = Tml.config.api_client[:timeout]
  request.options.open_timeout = Tml.config.api_client[:open_timeout]
  request.headers['User-Agent'] = "tml-ruby v#{Tml::VERSION} (Faraday v#{Faraday::VERSION})"
  request.headers['Accept'] = 'application/json'

  unless opts[:uncompressed]
    request.headers['Accept-Encoding'] = 'gzip, deflate'
  end

  request.url(path, params)
end
process_response(data, opts) click to toggle source

process API response

# File lib/tml/api/client.rb, line 373
def process_response(data, opts)
  return nil if data.nil?
  return data if opts[:raw] or opts[:raw_json]

  if data.is_a?(Hash) and data['results']
    #Tml.logger.debug("received #{data['results'].size} result(s)")
    return data['results'] unless object_class(opts)
    objects = []
    data['results'].each do |data|
      objects << object_class(opts).new(data.merge(opts[:attributes] || {}))
    end
    return objects
  end

  return data unless object_class(opts)
  object_class(opts).new(data.merge(opts[:attributes] || {}))
end
put(path, params = {}, opts = {}) click to toggle source

put to API

# File lib/tml/api/client.rb, line 58
def put(path, params = {}, opts = {})
  api(path, params, opts.merge(:method => :put))
end
results(path, params = {}, opts = {}) click to toggle source

get results from API

# File lib/tml/api/client.rb, line 43
def results(path, params = {}, opts = {})
  get(path, params, opts)['results']
end
to_query(hash) click to toggle source

convert params to query

# File lib/tml/api/client.rb, line 392
def to_query(hash)
  query = []
  hash.each do |key, value|
    query << "#{key.to_s}=#{value.to_s}"
  end
  query.join('&')
end
trace_api_call(path, params, opts = {}) { || ... } click to toggle source

trace api call for logging

# File lib/tml/api/client.rb, line 401
def trace_api_call(path, params, opts = {})
  if Tml.config.logger[:secure]
    [:access_token].each do |param|
      params = params.merge(param => "##filtered##") if params[param]
    end
  end

  path = "#{path[0] == '/' ? '' : '/'}#{path}"

  if opts[:method] == :post
    Tml.logger.debug("post: #{opts[:host]}#{path}")
  else
    if params.any?
      Tml.logger.debug("get: #{opts[:host]}#{path}?#{to_query(params)}")
    else
      Tml.logger.debug("get: #{opts[:host]}#{path}")
    end
  end

  t0 = Time.now
  if block_given?
    ret = yield
  end
  t1 = Time.now

  Tml.logger.debug("call took #{t1 - t0} seconds")
  ret
end
trim_prepending_slash(str) click to toggle source
# File lib/tml/api/client.rb, line 286
def trim_prepending_slash(str)
  str.index('/') == 0 ? str[1..-1] : str
end
verify_cache_version() click to toggle source

verify cache version

# File lib/tml/api/client.rb, line 108
def verify_cache_version
  return if Tml.cache.version.defined?

  current_version = Tml.cache.version.fetch

  if current_version == 'undefined'
    Tml.cache.version.store(get_cache_version)
  else
    Tml.cache.version.set(current_version)
  end

  Tml.logger.info("Cache Version: #{Tml.cache.version}")
end