219 lines
6.6 KiB
Ruby
219 lines
6.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'sawyer'
|
|
require 'octokit/authentication'
|
|
module Octokit
|
|
# Network layer for API clients.
|
|
module Connection
|
|
include Octokit::Authentication
|
|
|
|
# Header keys that can be passed in options hash to {#get},{#head}
|
|
CONVENIENCE_HEADERS = Set.new(%i[accept content_type])
|
|
|
|
# Make a HTTP GET request
|
|
#
|
|
# @param url [String] The path, relative to {#api_endpoint}
|
|
# @param options [Hash] Query and header params for request
|
|
# @return [Sawyer::Resource]
|
|
def get(url, options = {})
|
|
request :get, url, parse_query_and_convenience_headers(options)
|
|
end
|
|
|
|
# Make a HTTP POST request
|
|
#
|
|
# @param url [String] The path, relative to {#api_endpoint}
|
|
# @param options [Hash] Body and header params for request
|
|
# @return [Sawyer::Resource]
|
|
def post(url, options = {})
|
|
request :post, url, options
|
|
end
|
|
|
|
# Make a HTTP PUT request
|
|
#
|
|
# @param url [String] The path, relative to {#api_endpoint}
|
|
# @param options [Hash] Body and header params for request
|
|
# @return [Sawyer::Resource]
|
|
def put(url, options = {})
|
|
request :put, url, options
|
|
end
|
|
|
|
# Make a HTTP PATCH request
|
|
#
|
|
# @param url [String] The path, relative to {#api_endpoint}
|
|
# @param options [Hash] Body and header params for request
|
|
# @return [Sawyer::Resource]
|
|
def patch(url, options = {})
|
|
request :patch, url, options
|
|
end
|
|
|
|
# Make a HTTP DELETE request
|
|
#
|
|
# @param url [String] The path, relative to {#api_endpoint}
|
|
# @param options [Hash] Query and header params for request
|
|
# @return [Sawyer::Resource]
|
|
def delete(url, options = {})
|
|
request :delete, url, options
|
|
end
|
|
|
|
# Make a HTTP HEAD request
|
|
#
|
|
# @param url [String] The path, relative to {#api_endpoint}
|
|
# @param options [Hash] Query and header params for request
|
|
# @return [Sawyer::Resource]
|
|
def head(url, options = {})
|
|
request :head, url, parse_query_and_convenience_headers(options)
|
|
end
|
|
|
|
# Make one or more HTTP GET requests, optionally fetching
|
|
# the next page of results from URL in Link response header based
|
|
# on value in {#auto_paginate}.
|
|
#
|
|
# @param url [String] The path, relative to {#api_endpoint}
|
|
# @param options [Hash] Query and header params for request
|
|
# @param block [Block] Block to perform the data concatination of the
|
|
# multiple requests. The block is called with two parameters, the first
|
|
# contains the contents of the requests so far and the second parameter
|
|
# contains the latest response.
|
|
# @return [Sawyer::Resource]
|
|
def paginate(url, options = {})
|
|
opts = parse_query_and_convenience_headers(options)
|
|
if @auto_paginate || @per_page
|
|
opts[:query][:per_page] ||= @per_page || (@auto_paginate ? 100 : nil)
|
|
end
|
|
|
|
data = request(:get, url, opts.dup)
|
|
|
|
if @auto_paginate
|
|
while @last_response.rels[:next] && rate_limit.remaining > 0
|
|
@last_response = @last_response.rels[:next].get(headers: opts[:headers])
|
|
if block_given?
|
|
yield(data, @last_response)
|
|
else
|
|
data.concat(@last_response.data) if @last_response.data.is_a?(Array)
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
data
|
|
end
|
|
|
|
# Hypermedia agent for the GitHub API
|
|
#
|
|
# @return [Sawyer::Agent]
|
|
def agent
|
|
@agent ||= Sawyer::Agent.new(endpoint, sawyer_options) do |http|
|
|
http.headers[:accept] = default_media_type
|
|
http.headers[:content_type] = 'application/json'
|
|
http.headers[:user_agent] = user_agent
|
|
if basic_authenticated?
|
|
http.request(*FARADAY_BASIC_AUTH_KEYS, @login, @password)
|
|
elsif token_authenticated?
|
|
http.request :authorization, 'token', @access_token
|
|
elsif bearer_authenticated?
|
|
http.request :authorization, 'Bearer', @bearer_token
|
|
elsif application_authenticated?
|
|
http.request(*FARADAY_BASIC_AUTH_KEYS, @client_id, @client_secret)
|
|
end
|
|
end
|
|
end
|
|
|
|
# Fetch the root resource for the API
|
|
#
|
|
# @return [Sawyer::Resource]
|
|
def root
|
|
get '/'
|
|
end
|
|
|
|
# Response for last HTTP request
|
|
#
|
|
# @return [Sawyer::Response]
|
|
def last_response
|
|
@last_response if defined? @last_response
|
|
end
|
|
|
|
protected
|
|
|
|
def endpoint
|
|
api_endpoint
|
|
end
|
|
|
|
private
|
|
|
|
def reset_agent
|
|
@agent = nil
|
|
end
|
|
|
|
def request(method, path, data, options = {})
|
|
if data.is_a?(Hash)
|
|
options[:query] = data.delete(:query) || {}
|
|
options[:headers] = data.delete(:headers) || {}
|
|
if accept = data.delete(:accept)
|
|
options[:headers][:accept] = accept
|
|
end
|
|
end
|
|
|
|
@last_response = response = agent.call(method, Addressable::URI.parse(path.to_s).normalize.to_s, data, options)
|
|
response_data_correctly_encoded(response)
|
|
rescue Octokit::Error => e
|
|
@last_response = nil
|
|
raise e
|
|
end
|
|
|
|
# Executes the request, checking if it was successful
|
|
#
|
|
# @return [Boolean] True on success, false otherwise
|
|
def boolean_from_response(method, path, options = {})
|
|
request(method, path, options)
|
|
[201, 202, 204].include? @last_response.status
|
|
rescue Octokit::NotFound
|
|
false
|
|
end
|
|
|
|
def sawyer_options
|
|
opts = {
|
|
links_parser: Sawyer::LinkParsers::Simple.new
|
|
}
|
|
conn_opts = @connection_options
|
|
conn_opts[:builder] = @middleware.dup if @middleware
|
|
conn_opts[:proxy] = @proxy if @proxy
|
|
if conn_opts[:ssl].nil?
|
|
conn_opts[:ssl] = { verify_mode: @ssl_verify_mode } if @ssl_verify_mode
|
|
else
|
|
verify = @connection_options[:ssl][:verify]
|
|
conn_opts[:ssl] = {
|
|
verify: verify,
|
|
verify_mode: verify == false ? 0 : @ssl_verify_mode
|
|
}
|
|
end
|
|
opts[:faraday] = Faraday.new(conn_opts)
|
|
|
|
opts
|
|
end
|
|
|
|
def parse_query_and_convenience_headers(options)
|
|
options = options.dup
|
|
headers = options.delete(:headers) { {} }
|
|
CONVENIENCE_HEADERS.each do |h|
|
|
if header = options.delete(h)
|
|
headers[h] = header
|
|
end
|
|
end
|
|
query = options.delete(:query)
|
|
opts = { query: options }
|
|
opts[:query].merge!(query) if query.is_a?(Hash)
|
|
opts[:headers] = headers unless headers.empty?
|
|
|
|
opts
|
|
end
|
|
|
|
def response_data_correctly_encoded(response)
|
|
content_type = response.headers.fetch('content-type', '')
|
|
return response.data unless content_type.include?('charset') && response.data.is_a?(String)
|
|
|
|
reported_encoding = content_type.match(/charset=([^ ]+)/)[1]
|
|
response.data.force_encoding(reported_encoding)
|
|
end
|
|
end
|
|
end
|