This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user