github_api 0.3.9 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +331 -0
- data/features/README.rdoc +26 -0
- data/features/cassettes/repos/branches.yml +79 -0
- data/features/cassettes/repos/tags.yml +167 -0
- data/features/cassettes/repos/teams.yml +38 -0
- data/features/repos.feature +28 -0
- data/features/step_definitions/common_steps.rb +11 -0
- data/features/step_definitions/github_api_steps.rb +23 -0
- data/features/support/vcr.rb +6 -0
- data/lib/github_api.rb +12 -2
- data/lib/github_api/api.rb +9 -5
- data/lib/github_api/connection.rb +4 -4
- data/lib/github_api/constants.rb +38 -0
- data/lib/github_api/error.rb +5 -2
- data/lib/github_api/page_iterator.rb +118 -0
- data/lib/github_api/page_links.rb +45 -0
- data/lib/github_api/paged_request.rb +34 -0
- data/lib/github_api/repos.rb +2 -2
- data/lib/github_api/response/helpers.rb +8 -1
- data/lib/github_api/response/raise_error.rb +3 -0
- data/lib/github_api/result.rb +86 -8
- data/lib/github_api/utils/url.rb +46 -0
- data/lib/github_api/version.rb +2 -2
- data/spec/README.rdoc +4 -0
- data/spec/github/page_iterator_spec.rb +189 -0
- data/spec/github/page_links_spec.rb +34 -0
- data/spec/github/paged_request_spec.rb +38 -0
- data/spec/github/response/helpers_spec.rb +18 -0
- data/spec/github/result_spec.rb +67 -4
- data/spec/github/utils/url_spec.rb +36 -0
- metadata +37 -10
- data/README.rdoc +0 -235
@@ -0,0 +1,38 @@
|
|
1
|
+
---
|
2
|
+
- !ruby/struct:VCR::HTTPInteraction
|
3
|
+
request: !ruby/struct:VCR::Request
|
4
|
+
method: :get
|
5
|
+
uri: https://api.github.com:443/repos/peter-murach/github/teams
|
6
|
+
body:
|
7
|
+
headers:
|
8
|
+
accept-encoding:
|
9
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
10
|
+
response: !ruby/struct:VCR::Response
|
11
|
+
status: !ruby/struct:VCR::ResponseStatus
|
12
|
+
code: 404
|
13
|
+
message: Not Found
|
14
|
+
headers:
|
15
|
+
server:
|
16
|
+
- nginx/1.0.4
|
17
|
+
date:
|
18
|
+
- Tue, 03 Jan 2012 16:15:19 GMT
|
19
|
+
content-type:
|
20
|
+
- application/json; charset=utf-8
|
21
|
+
transfer-encoding:
|
22
|
+
- chunked
|
23
|
+
connection:
|
24
|
+
- keep-alive
|
25
|
+
status:
|
26
|
+
- 404 Not Found
|
27
|
+
x-ratelimit-limit:
|
28
|
+
- "5000"
|
29
|
+
etag:
|
30
|
+
- "\"e66a7a6c91e2c26803f3f49feb7a883f\""
|
31
|
+
x-ratelimit-remaining:
|
32
|
+
- "4997"
|
33
|
+
content-encoding:
|
34
|
+
- gzip
|
35
|
+
body: !binary |
|
36
|
+
H4sIAAAAAAAAA6tWyk0tLk5MT1WyUvLLL1Fwyy/NS1GqBQB8+Ys1FwAAAA==
|
37
|
+
|
38
|
+
http_version: "1.1"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
Feature: Accessing Repos Main API
|
2
|
+
In order to interact with github repositories
|
3
|
+
GithubAPI gem
|
4
|
+
Should return the expected results depending on passed parameters
|
5
|
+
|
6
|
+
Scenario: Returning all repository branches
|
7
|
+
Given I have "Github::Repos" instance
|
8
|
+
And I am looking for "branches" with the following params:
|
9
|
+
| user | repo |
|
10
|
+
| peter-murach | github |
|
11
|
+
And I make request within a cassette named "repos/branches"
|
12
|
+
Then the response should be "200"
|
13
|
+
|
14
|
+
Scenario: Returning all repository tags
|
15
|
+
Given I have "Github::Repos" instance
|
16
|
+
And I am looking for "tags" with the following params:
|
17
|
+
| user | repo |
|
18
|
+
| peter-murach | github |
|
19
|
+
And I make request within a cassette named "repos/tags"
|
20
|
+
Then the response should be "200"
|
21
|
+
|
22
|
+
Scenario: Returning all repositories for the user
|
23
|
+
Given I have "Github::Repos" instance
|
24
|
+
And I am looking for "list_repos" with the following params:
|
25
|
+
| user |
|
26
|
+
| peter-murach |
|
27
|
+
And I make request with hash params within a cassette named "repos/tags"
|
28
|
+
Then the response should be "200"
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Before do
|
2
|
+
step "I have github instance"
|
3
|
+
end
|
4
|
+
|
5
|
+
When /^(.*) within a cassette named "([^"]*)"$/ do |step_to_call, cassette_name|
|
6
|
+
VCR.use_cassette(cassette_name) { step step_to_call }
|
7
|
+
end
|
8
|
+
|
9
|
+
Then /^the response should be "([^"]*)"$/ do |expected_response|
|
10
|
+
@response.status.should eql expected_response.to_i
|
11
|
+
end
|
@@ -2,6 +2,14 @@ Given /^I have github instance$/ do
|
|
2
2
|
@github = Github.new
|
3
3
|
end
|
4
4
|
|
5
|
+
Given /^I have "([^"]*)" instance$/ do |api_classes|
|
6
|
+
constant = Object
|
7
|
+
api_classes.split('::').each do |api_class|
|
8
|
+
constant = constant.const_get api_class
|
9
|
+
end
|
10
|
+
@instance = constant.new
|
11
|
+
end
|
12
|
+
|
5
13
|
When /^I fetch "([^"]*)"$/ do |method|
|
6
14
|
@response = @github.send(method.to_sym)
|
7
15
|
end
|
@@ -9,3 +17,18 @@ end
|
|
9
17
|
When /^I will have access to "([^"]*)" API$/ do |api|
|
10
18
|
@response.class.to_s.should match api
|
11
19
|
end
|
20
|
+
|
21
|
+
When /^I am looking for "([^"]*)" with the following params:$/ do |method, table|
|
22
|
+
table.hashes.each do |attributes|
|
23
|
+
@method = method.to_sym
|
24
|
+
@attributes = attributes
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
When /^I make request$/ do
|
29
|
+
@response = @instance.send @method, *@attributes.values
|
30
|
+
end
|
31
|
+
|
32
|
+
When /^I make request with hash params$/ do
|
33
|
+
@response = @instance.send @method, @attributes
|
34
|
+
end
|
data/features/support/vcr.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
|
+
require 'vcr'
|
2
|
+
|
1
3
|
VCR.config do |conf|
|
2
4
|
conf.stub_with :webmock
|
3
5
|
conf.cassette_library_dir = 'features/cassettes'
|
4
6
|
conf.default_cassette_options = { :record => :new_episodes }
|
5
7
|
conf.filter_sensitive_data('<***>') { ''}
|
6
8
|
end
|
9
|
+
|
10
|
+
VCR.cucumber_tags do |t|
|
11
|
+
t.tag '@live', :record => :all
|
12
|
+
end
|
data/lib/github_api.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'github_api/version'
|
4
4
|
require 'github_api/configuration'
|
5
|
+
require 'github_api/constants'
|
6
|
+
require 'github_api/utils/url'
|
5
7
|
require 'github_api/connection'
|
6
8
|
|
7
9
|
module Github
|
@@ -12,7 +14,12 @@ module Github
|
|
12
14
|
#
|
13
15
|
# @return [Github::Client]
|
14
16
|
def new(options = {}, &block)
|
15
|
-
Github::Client.new(options, &block)
|
17
|
+
@@api_client = Github::Client.new(options, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns handle for the client instance
|
21
|
+
def api_client
|
22
|
+
@@api_client
|
16
23
|
end
|
17
24
|
|
18
25
|
# Delegate to Github::Client
|
@@ -68,6 +75,9 @@ module Github
|
|
68
75
|
:CoreExt => 'core_ext',
|
69
76
|
:MimeType => 'mime_type',
|
70
77
|
:Authorization => 'authorization',
|
71
|
-
:Authorizations => 'authorizations'
|
78
|
+
:Authorizations => 'authorizations',
|
79
|
+
:PageLinks => 'page_links',
|
80
|
+
:PageIterator => 'page_iterator',
|
81
|
+
:PagedRequest => 'paged_request'
|
72
82
|
|
73
83
|
end # Github
|
data/lib/github_api/api.rb
CHANGED
@@ -18,8 +18,9 @@ module Github
|
|
18
18
|
include Request
|
19
19
|
|
20
20
|
VALID_API_KEYS = [
|
21
|
-
|
22
|
-
|
21
|
+
'page',
|
22
|
+
'per_page',
|
23
|
+
'jsonp_callback'
|
23
24
|
]
|
24
25
|
|
25
26
|
attr_reader *Configuration::VALID_OPTIONS_KEYS
|
@@ -61,10 +62,13 @@ module Github
|
|
61
62
|
end
|
62
63
|
end
|
63
64
|
|
64
|
-
# Responds to attribute query
|
65
|
+
# Responds to attribute query or attribute clear
|
65
66
|
def method_missing(method, *args, &block) # :nodoc:
|
66
|
-
|
67
|
+
case method.to_s
|
68
|
+
when /^(.*)\?$/
|
67
69
|
return !self.send($1.to_s).nil?
|
70
|
+
when /^clear_(.*)$/
|
71
|
+
self.send("#{$1.to_s}=", nil)
|
68
72
|
else
|
69
73
|
super
|
70
74
|
end
|
@@ -123,7 +127,7 @@ module Github
|
|
123
127
|
case params
|
124
128
|
when Hash
|
125
129
|
params.keys.each do |k, v|
|
126
|
-
unless keys.include? k
|
130
|
+
unless (keys.include?(k) or VALID_API_KEYS.include?(k))
|
127
131
|
params.delete(k)
|
128
132
|
else
|
129
133
|
_filter_params_keys(keys, params[k])
|
@@ -25,9 +25,9 @@ module Github
|
|
25
25
|
def default_options(options={}) # :nodoc:
|
26
26
|
{
|
27
27
|
:headers => {
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
:accept => '*/*', #accepts,
|
29
|
+
:user_agent => user_agent,
|
30
|
+
:content_type => 'application/x-www-form-urlencoded'
|
31
31
|
},
|
32
32
|
:ssl => { :verify => false },
|
33
33
|
:url => endpoint
|
@@ -49,7 +49,7 @@ module Github
|
|
49
49
|
|
50
50
|
@connection ||= begin
|
51
51
|
Faraday.new(merged_options.merge(connection_options)) do |builder|
|
52
|
-
puts options.inspect
|
52
|
+
puts options.inspect if ENV['DEBUG']
|
53
53
|
|
54
54
|
builder.use Faraday::Request::JSON
|
55
55
|
builder.use Faraday::Request::Multipart
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Github
|
2
|
+
module Constants
|
3
|
+
extend self
|
4
|
+
|
5
|
+
# Response headers
|
6
|
+
RATELIMIT_REMAINING = 'X-RateLimit-Remaining'.freeze
|
7
|
+
|
8
|
+
RATELIMIT_LIMIT = 'X-RateLimit-Limit'.freeze
|
9
|
+
|
10
|
+
CONTENT_TYPE = 'Content-Type'.freeze
|
11
|
+
|
12
|
+
CONTENT_LENGTH = 'content-length'.freeze
|
13
|
+
|
14
|
+
# Link headers
|
15
|
+
HEADER_LINK = "Link".freeze
|
16
|
+
|
17
|
+
HEADER_NEXT = "X-Next".freeze
|
18
|
+
|
19
|
+
HEADER_LAST = "X-Last".freeze
|
20
|
+
|
21
|
+
META_REL = "rel".freeze
|
22
|
+
|
23
|
+
META_LAST = "last".freeze
|
24
|
+
|
25
|
+
META_NEXT = "next".freeze
|
26
|
+
|
27
|
+
META_FIRST = "first".freeze
|
28
|
+
|
29
|
+
META_PREV = "prev".freeze
|
30
|
+
|
31
|
+
PARAM_PAGE = "page".freeze
|
32
|
+
|
33
|
+
PARAM_PER_PAGE = "per_page".freeze
|
34
|
+
|
35
|
+
PARAM_START_PAGE = "start_page".freeze
|
36
|
+
|
37
|
+
end # Constants
|
38
|
+
end # Github
|
data/lib/github_api/error.rb
CHANGED
@@ -5,7 +5,7 @@ module Github
|
|
5
5
|
attr_reader :response_message, :response_headers
|
6
6
|
|
7
7
|
def initialize(message, headers)
|
8
|
-
@response_message = message
|
8
|
+
@response_message = message
|
9
9
|
super message
|
10
10
|
end
|
11
11
|
|
@@ -21,11 +21,14 @@ module Github
|
|
21
21
|
class Unauthorised < Error; end
|
22
22
|
|
23
23
|
# Raised when Github returns the HTTP status code 403
|
24
|
-
class Forbidden < Error; end
|
24
|
+
class Forbidden < Error; end
|
25
25
|
|
26
26
|
# Raised when Github returns the HTTP status code 404
|
27
27
|
class ResourceNotFound < Error; end
|
28
28
|
|
29
|
+
# Raised when Github returns the HTTP status code 422
|
30
|
+
class UnprocessableEntity < Error; end
|
31
|
+
|
29
32
|
# Raised when Github returns the HTTP status code 500
|
30
33
|
class InternalServerError < Error; end
|
31
34
|
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'github_api/utils/url'
|
4
|
+
|
5
|
+
module Github
|
6
|
+
class PageIterator
|
7
|
+
include Github::Constants
|
8
|
+
include Github::Utils::Url
|
9
|
+
include Github::PagedRequest
|
10
|
+
|
11
|
+
# Query string separator
|
12
|
+
QUERY_STR_SEP = '?'.freeze
|
13
|
+
|
14
|
+
# Setup attribute accesor for all the link types
|
15
|
+
ATTRIBUTES = [ META_FIRST, META_NEXT, META_PREV, META_LAST ]
|
16
|
+
|
17
|
+
ATTRIBUTES.each do |attr|
|
18
|
+
attr_accessor :"#{attr}_page_uri", :"#{attr}_page"
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(env)
|
22
|
+
@links = Github::PageLinks.new(env[:response_headers])
|
23
|
+
update_page_links @links
|
24
|
+
end
|
25
|
+
|
26
|
+
def has_next?
|
27
|
+
next_page == 0 || !next_page_uri.nil?
|
28
|
+
end
|
29
|
+
|
30
|
+
def first
|
31
|
+
return nil unless first_page_uri
|
32
|
+
response = page_request first_page_uri.split(QUERY_STR_SEP)[0],
|
33
|
+
'per_page' => parse_per_page_number(first_page_uri)
|
34
|
+
update_page_links response.links
|
35
|
+
response
|
36
|
+
end
|
37
|
+
|
38
|
+
def next
|
39
|
+
return nil unless has_next?
|
40
|
+
|
41
|
+
response = page_request next_page_uri.split(QUERY_STR_SEP)[0],
|
42
|
+
'page' => next_page,
|
43
|
+
'per_page'=> parse_per_page_number(next_page_uri)
|
44
|
+
update_page_links response.links
|
45
|
+
response
|
46
|
+
end
|
47
|
+
|
48
|
+
def prev
|
49
|
+
return nil unless prev_page_uri
|
50
|
+
response = page_request prev_page_uri.split(QUERY_STR_SEP)[0],
|
51
|
+
'page' => prev_page,
|
52
|
+
'per_page'=> parse_per_page_number(prev_page_uri)
|
53
|
+
update_page_links response.links
|
54
|
+
response
|
55
|
+
end
|
56
|
+
|
57
|
+
def last
|
58
|
+
return nil unless last_page_uri
|
59
|
+
response = page_request last_page_uri.split(QUERY_STR_SEP)[0],
|
60
|
+
'page' => last_page,
|
61
|
+
'per_page' => parse_per_page_number(last_page_uri)
|
62
|
+
update_page_links response.links
|
63
|
+
response
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the result for a specific page.
|
67
|
+
def get_page(page_number)
|
68
|
+
# Find URI that we can work with, if we cannot get the first or the
|
69
|
+
# last page URI then there is only one page.
|
70
|
+
page_uri = first_page_uri || last_page_uri
|
71
|
+
return nil unless page_uri
|
72
|
+
|
73
|
+
response = page_request page_uri.split(QUERY_STR_SEP)[0],
|
74
|
+
'page' => page_number,
|
75
|
+
'per_page' => parse_per_page_number(page_uri)
|
76
|
+
update_page_links response.links
|
77
|
+
response
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def parse_per_page_number(uri) # :nodoc:
|
84
|
+
parse_page_params(uri, PARAM_PER_PAGE)
|
85
|
+
end
|
86
|
+
|
87
|
+
def parse_page_number(uri) # :nodoc:
|
88
|
+
parse_page_params(uri, PARAM_PAGE)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Extracts query string parameter for given name
|
92
|
+
def parse_page_params(uri, attr) # :nodoc:
|
93
|
+
return -1 if uri.nil? || uri.empty?
|
94
|
+
parsed = nil
|
95
|
+
begin
|
96
|
+
parsed = URI.parse(uri)
|
97
|
+
rescue URI::Error => e
|
98
|
+
return -1
|
99
|
+
end
|
100
|
+
param = parse_query_for_param(parsed.query, attr)
|
101
|
+
return -1 if param.nil? || param.empty?
|
102
|
+
begin
|
103
|
+
return param.to_i
|
104
|
+
rescue ArgumentError => err
|
105
|
+
return -1
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Wholesale update of all link attributes
|
110
|
+
def update_page_links(links) # :nodoc:
|
111
|
+
ATTRIBUTES.each do |attr|
|
112
|
+
self.send(:"#{attr}_page_uri=", links.send(:"#{attr}"))
|
113
|
+
self.send(:"#{attr}_page=", parse_page_number(links.send(:"#{attr}")))
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
end # PageIterator
|
118
|
+
end # Github
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Github
|
2
|
+
# Determines the links in the current response link header to be used
|
3
|
+
# to find the links to other pages of request responses. These will
|
4
|
+
# only be present if the result set size exceeds the per page limit.
|
5
|
+
class PageLinks
|
6
|
+
include Github::Constants
|
7
|
+
|
8
|
+
DELIM_LINKS = ",".freeze # :nodoc:
|
9
|
+
|
10
|
+
# Hold the extracted values for URI from the Link header
|
11
|
+
# for the first, last, next and previous page.
|
12
|
+
attr_accessor :first, :last, :next, :prev
|
13
|
+
|
14
|
+
# Parses links from executed request
|
15
|
+
#
|
16
|
+
def initialize(response_headers)
|
17
|
+
link_header = response_headers[HEADER_LINK]
|
18
|
+
if link_header
|
19
|
+
return unless link_header =~ /(next|first|last|prev)/
|
20
|
+
|
21
|
+
link_header.split(DELIM_LINKS).each do |link|
|
22
|
+
if link.strip =~ /<([^>]+)>; rel=\"([^\"]+)\"/
|
23
|
+
url_part, meta_part = $1, $2
|
24
|
+
next if !url_part || !meta_part
|
25
|
+
case meta_part
|
26
|
+
when META_FIRST
|
27
|
+
self.first = url_part
|
28
|
+
when META_LAST
|
29
|
+
self.last = url_part
|
30
|
+
when META_NEXT
|
31
|
+
self.next = url_part
|
32
|
+
when META_PREV
|
33
|
+
self.prev = url_part
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
else
|
38
|
+
# When on the first page
|
39
|
+
self.next = response_headers[HEADER_NEXT]
|
40
|
+
self.last = response_headers[HEADER_LAST]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end # PageLinks
|
45
|
+
end # Github
|