github_api 0.3.9 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|