heroics 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +53 -0
- data/Rakefile +11 -0
- data/TODO +3 -0
- data/bin/heroku-api +20 -0
- data/heroics.gemspec +32 -0
- data/lib/heroics.rb +18 -0
- data/lib/heroics/cli.rb +92 -0
- data/lib/heroics/client.rb +65 -0
- data/lib/heroics/command.rb +67 -0
- data/lib/heroics/errors.rb +6 -0
- data/lib/heroics/link.rb +100 -0
- data/lib/heroics/naming.rb +19 -0
- data/lib/heroics/resource.rb +30 -0
- data/lib/heroics/schema.rb +242 -0
- data/lib/heroics/version.rb +3 -0
- data/test.rb +42 -0
- data/test/cli_test.rb +282 -0
- data/test/client_test.rb +162 -0
- data/test/command_test.rb +214 -0
- data/test/helper.rb +175 -0
- data/test/link_test.rb +349 -0
- data/test/naming_test.rb +45 -0
- data/test/resource_test.rb +35 -0
- data/test/schema_test.rb +207 -0
- data/test/version_test.rb +9 -0
- metadata +212 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 geemus
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# Heroics
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'heroics'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install heroics
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
The interface is designed to match the workings of the (Heroku Platform API)[https://devcenter.heroku.com/articles/platform-api-reference].
|
24
|
+
|
25
|
+
```
|
26
|
+
heroics = Heroics.new(token: ENV['HEROKU_API_TOKEN'])
|
27
|
+
|
28
|
+
# apps
|
29
|
+
heroics.apps.create(name: 'example') # returns new app named 'example'
|
30
|
+
heroics.apps.list # returns list of all apps
|
31
|
+
heroics.apps.info('example') # returns app with id or name of 'example'
|
32
|
+
|
33
|
+
app = heroics.apps('example') # returns local reference to app with id or name 'example'
|
34
|
+
app.update(name: 'rename') # returns updated app
|
35
|
+
app.delete # returns deleted app
|
36
|
+
|
37
|
+
# addons
|
38
|
+
app = heroics.apps('example') # returns local reference to app with id or name 'example'
|
39
|
+
app.addons.create(plan: { name: 'heroku-postgresql:dev' }) # returns new add-on with plan:name 'heroku-postgresql:dev'
|
40
|
+
app.addons.list # returns list of all add-ons for app with id or name of 'example'
|
41
|
+
|
42
|
+
addon = app.addons.info('heroku-postgresql:dev') # returns add-on with id or name 'heroku-postgresql:dev'
|
43
|
+
addon.update(plan: { name: 'heroku-postgresql:basic' }) # returns updated add-on
|
44
|
+
addon.delete # returns deleted add-on
|
45
|
+
```
|
46
|
+
|
47
|
+
## Contributing
|
48
|
+
|
49
|
+
1. Fork it
|
50
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
51
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
52
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
53
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
|
+
|
4
|
+
Rake::TestTask.new do |task|
|
5
|
+
task.verbose = true
|
6
|
+
task.ruby_opts << '-r turn/autorun'
|
7
|
+
task.ruby_opts << '-I test'
|
8
|
+
task.test_files = FileList['test/**/*_test.rb', 'test/**/*_spec.rb']
|
9
|
+
end
|
10
|
+
|
11
|
+
task :default => :test
|
data/TODO
ADDED
data/bin/heroku-api
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'heroics'
|
4
|
+
require 'netrc'
|
5
|
+
|
6
|
+
netrc = Netrc.read
|
7
|
+
username, token = netrc['api.heroku.com']
|
8
|
+
if username && token
|
9
|
+
username = username.split('@').first
|
10
|
+
url = "https://#{username}:#{token}@api.heroku.com/schema"
|
11
|
+
options = {
|
12
|
+
default_headers: {'Accept' => 'application/vnd.heroku+json; version=3'},
|
13
|
+
cache: Moneta.new(:File, dir: "#{Dir.home}/.heroics/heroku-api")}
|
14
|
+
cli = Heroics.cli_from_schema_url('heroku-api', STDOUT, url, options)
|
15
|
+
cli.run(*ARGV)
|
16
|
+
else
|
17
|
+
puts "ERROR Couldn't find credentials for api.heroku.com in ~/.netrc."
|
18
|
+
puts " Login with the Heroku Toolbelt by running 'heroku login'."
|
19
|
+
1
|
20
|
+
end
|
data/heroics.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require 'heroics/version'
|
7
|
+
|
8
|
+
Gem::Specification.new do |spec|
|
9
|
+
spec.name = 'heroics'
|
10
|
+
spec.version = Heroics::VERSION
|
11
|
+
spec.authors = ['geemus', 'jkakar']
|
12
|
+
spec.email = ['geemus@gmail.com', 'jkakar@kakar.ca']
|
13
|
+
spec.description = 'A Ruby client for HTTP APIs described using a JSON schema'
|
14
|
+
spec.summary = 'A Ruby client for HTTP APIs described using a JSON schema'
|
15
|
+
spec.homepage = ''
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
spec.files = `git ls-files`.split($/)
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep('^(test|spec|features)/')
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
24
|
+
spec.add_development_dependency 'minitest', '4.7.4'
|
25
|
+
spec.add_development_dependency 'rake'
|
26
|
+
spec.add_development_dependency 'turn'
|
27
|
+
|
28
|
+
spec.add_dependency 'excon'
|
29
|
+
spec.add_dependency 'netrc'
|
30
|
+
spec.add_dependency 'moneta'
|
31
|
+
spec.add_dependency 'multi_json'
|
32
|
+
end
|
data/lib/heroics.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'excon'
|
2
|
+
require 'moneta'
|
3
|
+
require 'multi_json'
|
4
|
+
require 'uri'
|
5
|
+
require 'zlib'
|
6
|
+
|
7
|
+
# Heroics is an HTTP client for an API described by a JSON schema.
|
8
|
+
module Heroics
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'heroics/errors'
|
12
|
+
require 'heroics/naming'
|
13
|
+
require 'heroics/link'
|
14
|
+
require 'heroics/resource'
|
15
|
+
require 'heroics/client'
|
16
|
+
require 'heroics/schema'
|
17
|
+
require 'heroics/command'
|
18
|
+
require 'heroics/cli'
|
data/lib/heroics/cli.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
module Heroics
|
2
|
+
class CLI
|
3
|
+
# Instantiate a CLI for an API described by a JSON schema.
|
4
|
+
#
|
5
|
+
# @param name [String] The name of the CLI.
|
6
|
+
# @param schema [Schema] The JSON schema describing the API.
|
7
|
+
# @param client [Client] A client generated from the JSON schema.
|
8
|
+
# @param output [IO] The stream to write to.
|
9
|
+
def initialize(name, commands, output)
|
10
|
+
@name = name
|
11
|
+
@commands = commands
|
12
|
+
@output = output
|
13
|
+
end
|
14
|
+
|
15
|
+
# Run a command.
|
16
|
+
#
|
17
|
+
# @param parameters [Array] The parameters to use when running the
|
18
|
+
# command. The first parameters is the name of the command and the
|
19
|
+
# remaining parameters are passed to it.
|
20
|
+
def run(*parameters)
|
21
|
+
name = parameters.shift
|
22
|
+
if name.nil? || name == 'help'
|
23
|
+
if command_name = parameters.first
|
24
|
+
command = @commands[command_name]
|
25
|
+
command.usage
|
26
|
+
else
|
27
|
+
usage
|
28
|
+
end
|
29
|
+
else
|
30
|
+
command = @commands[name]
|
31
|
+
if command.nil?
|
32
|
+
@output.write("There is no command called '#{name}'.\n")
|
33
|
+
else
|
34
|
+
command.run(*parameters)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# Write usage information to the output stream.
|
42
|
+
def usage
|
43
|
+
if @commands.empty?
|
44
|
+
@output.write 'No commands are available.'
|
45
|
+
return
|
46
|
+
end
|
47
|
+
|
48
|
+
@output.write <<-USAGE
|
49
|
+
Usage: #{@name} <command> [<parameter> [...]] [<body>]
|
50
|
+
|
51
|
+
Help topics, type "#{@name} help <topic>" for more details:
|
52
|
+
|
53
|
+
USAGE
|
54
|
+
|
55
|
+
name_width = @commands.keys.max_by { |key| key.size }.size
|
56
|
+
@commands.sort.each do |name, command|
|
57
|
+
name = name.ljust(name_width)
|
58
|
+
description = command.description
|
59
|
+
@output.puts(" #{name} #{description}")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.cli_from_schema(name, output, schema, url, options={})
|
65
|
+
client = client_from_schema(schema, url, options)
|
66
|
+
commands = {}
|
67
|
+
schema.resources.each do |resource_schema|
|
68
|
+
resource_schema.links.each do |link_schema|
|
69
|
+
command = Command.new(name, link_schema, client, output)
|
70
|
+
commands[command.name] = command
|
71
|
+
end
|
72
|
+
end
|
73
|
+
CLI.new(name, commands, output)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Download a JSON schema and create a CLI with it.
|
77
|
+
#
|
78
|
+
# @param name [String] The name of the CLI.
|
79
|
+
# @param output [IO] The stream to write to.
|
80
|
+
# @param url [String] The URL for the schema. The URL will be used by the
|
81
|
+
# generated CLI when it makes requests.
|
82
|
+
# @param options [Hash] Configuration for links. Possible keys include:
|
83
|
+
# - default_headers: Optionally, a set of headers to include in every
|
84
|
+
# request made by the CLI. Default is no custom headers.
|
85
|
+
# - cache: Optionally, a Moneta-compatible cache to store ETags. Default
|
86
|
+
# is no caching.
|
87
|
+
# @return [CLI] A CLI with commands generated from the JSON schema.
|
88
|
+
def self.cli_from_schema_url(name, output, url, options={})
|
89
|
+
schema = download_schema(url, options)
|
90
|
+
cli_from_schema(name, output, schema, url, options)
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Heroics
|
2
|
+
# An HTTP client with methods mapped to API resources.
|
3
|
+
class Client
|
4
|
+
# Instantiate an HTTP client.
|
5
|
+
#
|
6
|
+
# @param resources [Hash<String,Resource>] A hash that maps method names
|
7
|
+
# to resources.
|
8
|
+
def initialize(resources)
|
9
|
+
@resources = resources
|
10
|
+
end
|
11
|
+
|
12
|
+
# Find a resource.
|
13
|
+
#
|
14
|
+
# @param name [String] The name of the resource to find.
|
15
|
+
# @raise [NoMethodError] Raised if the name doesn't match a known resource.
|
16
|
+
# @return [Resource] The resource matching the name.
|
17
|
+
def method_missing(name)
|
18
|
+
resource = @resources[name.to_s]
|
19
|
+
if resource.nil?
|
20
|
+
address = "<#{self.class.name}:0x00#{(self.object_id << 1).to_s(16)}>"
|
21
|
+
raise NoMethodError.new("undefined method `#{name}' for ##{address}")
|
22
|
+
end
|
23
|
+
resource
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Create an HTTP client from a JSON schema.
|
28
|
+
#
|
29
|
+
# @param schema [Schema] The JSON schema to build an HTTP client for.
|
30
|
+
# @param url [String] The URL the generated client should use when making
|
31
|
+
# requests. Include the username and password to use with HTTP basic
|
32
|
+
# auth.
|
33
|
+
# @param options [Hash] Configuration for links. Possible keys include:
|
34
|
+
# - default_headers: Optionally, a set of headers to include in every
|
35
|
+
# request made by the client. Default is no custom headers.
|
36
|
+
# - cache: Optionally, a Moneta-compatible cache to store ETags. Default
|
37
|
+
# is no caching.
|
38
|
+
# @return [Client] A client with resources and links from the JSON schema.
|
39
|
+
def self.client_from_schema(schema, url, options={})
|
40
|
+
resources = {}
|
41
|
+
schema.resources.each do |resource_schema|
|
42
|
+
links = {}
|
43
|
+
resource_schema.links.each do |link_schema|
|
44
|
+
links[link_schema.name] = Link.new(url, link_schema, options)
|
45
|
+
end
|
46
|
+
resources[resource_schema.name] = Resource.new(links)
|
47
|
+
end
|
48
|
+
Client.new(resources)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Download a JSON schema and create an HTTP client with it.
|
52
|
+
#
|
53
|
+
# @param url [String] The URL for the schema. The URL will be used by the
|
54
|
+
# generated client when it makes requests.
|
55
|
+
# @param options [Hash] Configuration for links. Possible keys include:
|
56
|
+
# - default_headers: Optionally, a set of headers to include in every
|
57
|
+
# request made by the client. Default is no custom headers.
|
58
|
+
# - cache: Optionally, a Moneta-compatible cache to store ETags. Default
|
59
|
+
# is no caching.
|
60
|
+
# @return [Client] A client with resources and links from the JSON schema.
|
61
|
+
def self.client_from_schema_url(url, options={})
|
62
|
+
schema = download_schema(url, options)
|
63
|
+
client_from_schema(schema, URI::join(url, '/').to_s, options)
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Heroics
|
2
|
+
class Command
|
3
|
+
# Instantiate a command.
|
4
|
+
#
|
5
|
+
# @param cli_name [String] The name of the CLI.
|
6
|
+
# @param link_schema [LinkSchema] The schema for the underlying link this
|
7
|
+
# command represents.
|
8
|
+
# @param client [Client] The client to use when making requests.
|
9
|
+
# @param output [IO] The stream to write output to.
|
10
|
+
def initialize(cli_name, link_schema, client, output)
|
11
|
+
@cli_name = cli_name
|
12
|
+
@link_schema = link_schema
|
13
|
+
@client = client
|
14
|
+
@output = output
|
15
|
+
end
|
16
|
+
|
17
|
+
# The command name.
|
18
|
+
def name
|
19
|
+
"#{@link_schema.pretty_resource_name}:#{@link_schema.pretty_name}"
|
20
|
+
end
|
21
|
+
|
22
|
+
# The command description.
|
23
|
+
def description
|
24
|
+
@link_schema.description
|
25
|
+
end
|
26
|
+
|
27
|
+
# Write usage information to the output stream.
|
28
|
+
def usage
|
29
|
+
parameters = @link_schema.parameters.map { |parameter| "<#{parameter}>" }
|
30
|
+
parameters = parameters.empty? ? '' : " #{parameters.join(' ')}"
|
31
|
+
example_body = @link_schema.example_body
|
32
|
+
body_parameter = example_body.nil? ? '' : ' <body>'
|
33
|
+
@output.write <<-USAGE
|
34
|
+
Usage: #{@cli_name} #{name}#{parameters}#{body_parameter}
|
35
|
+
|
36
|
+
Description:
|
37
|
+
#{description}
|
38
|
+
USAGE
|
39
|
+
if example_body
|
40
|
+
example_body = MultiJson.dump(example_body, pretty: true)
|
41
|
+
example_body = example_body.lines.map do |line|
|
42
|
+
" #{line}"
|
43
|
+
end.join
|
44
|
+
@output.write <<-USAGE
|
45
|
+
|
46
|
+
Body example:
|
47
|
+
#{example_body}
|
48
|
+
USAGE
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Run the command and write the results to the output stream.
|
53
|
+
#
|
54
|
+
# @param parameters [Array] The parameters to pass when making a request
|
55
|
+
# to run the command.
|
56
|
+
def run(*parameters)
|
57
|
+
resource_name = @link_schema.resource_name
|
58
|
+
name = @link_schema.name
|
59
|
+
result = @client.send(resource_name).send(name, *parameters)
|
60
|
+
result = result.to_a if result.instance_of?(Enumerator)
|
61
|
+
if result && !result.instance_of?(String)
|
62
|
+
result = MultiJson.dump(result, pretty: true)
|
63
|
+
end
|
64
|
+
@output.puts(result) unless result.nil?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/heroics/link.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
module Heroics
|
2
|
+
# A link invokes requests with an HTTP server.
|
3
|
+
class Link
|
4
|
+
# Instantiate a link.
|
5
|
+
#
|
6
|
+
# @param url [String] The URL to use when making requests. Include the
|
7
|
+
# username and password to use with HTTP basic auth.
|
8
|
+
# @param link_schema [LinkSchema] The schema for this link.
|
9
|
+
# @param options [Hash] Configuration for the link. Possible keys
|
10
|
+
# include:
|
11
|
+
# - default_headers: Optionally, a set of headers to include in every
|
12
|
+
# request made by the client. Default is no custom headers.
|
13
|
+
# - cache: Optionally, a Moneta-compatible cache to store ETags.
|
14
|
+
# Default is no caching.
|
15
|
+
def initialize(url, link_schema, options={})
|
16
|
+
@url = url
|
17
|
+
@link_schema = link_schema
|
18
|
+
@default_headers = options[:default_headers] || {}
|
19
|
+
@cache = options[:cache] || Moneta.new(:Null)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Make a request to the server.
|
23
|
+
#
|
24
|
+
# JSON content received with an ETag is cached. When the server returns a
|
25
|
+
# *304 Not Modified* status code content is loaded and returned from the
|
26
|
+
# cache. The cache considers headers, in addition to the URL path, when
|
27
|
+
# creating keys so that requests to the same path, such as for paginated
|
28
|
+
# results, don't cause cache collisions.
|
29
|
+
#
|
30
|
+
# When the server returns a *206 Partial Content* status code the result
|
31
|
+
# is assumed to be an array and an enumerator is returned. The enumerator
|
32
|
+
# yields results from the response until they've been consumed at which
|
33
|
+
# point, if additional content is available from the server, it blocks and
|
34
|
+
# makes a request to fetch the subsequent page of data. This behaviour
|
35
|
+
# continues until the client stops iterating the enumerator or the dataset
|
36
|
+
# from the server has been entirely consumed.
|
37
|
+
#
|
38
|
+
# @param parameters [Array] The list of parameters to inject into the
|
39
|
+
# path. A request body can be passed as the final parameter and will
|
40
|
+
# always be converted to JSON before being transmitted.
|
41
|
+
# @raise [ArgumentError] Raised if either too many or too few parameters
|
42
|
+
# were provided.
|
43
|
+
# @return [String,Object,Enumerator] A string for text responses, an
|
44
|
+
# object for JSON responses, or an enumerator for list responses.
|
45
|
+
def run(*parameters)
|
46
|
+
path, body = @link_schema.format_path(parameters)
|
47
|
+
headers = @default_headers
|
48
|
+
if body
|
49
|
+
headers = headers.merge({'Content-Type' => 'application/json'})
|
50
|
+
body = MultiJson.dump(body)
|
51
|
+
end
|
52
|
+
cache_key = "#{path}:#{headers.hash}"
|
53
|
+
if @link_schema.method == :get
|
54
|
+
etag = @cache["etag:#{cache_key}"]
|
55
|
+
headers = headers.merge({'If-None-Match' => etag}) if etag
|
56
|
+
end
|
57
|
+
|
58
|
+
connection = Excon.new(@url)
|
59
|
+
response = connection.request(method: @link_schema.method, path: path,
|
60
|
+
headers: headers, body: body,
|
61
|
+
expects: [200, 201, 202, 206, 304])
|
62
|
+
content_type = response.headers['Content-Type']
|
63
|
+
if response.status == 304
|
64
|
+
MultiJson.load(@cache["data:#{cache_key}"])
|
65
|
+
elsif content_type && content_type.include?('application/json')
|
66
|
+
etag = response.headers['ETag']
|
67
|
+
if etag
|
68
|
+
@cache["etag:#{cache_key}"] = etag
|
69
|
+
@cache["data:#{cache_key}"] = response.body
|
70
|
+
end
|
71
|
+
body = MultiJson.load(response.body)
|
72
|
+
if response.status == 206
|
73
|
+
next_range = response.headers['Next-Range']
|
74
|
+
Enumerator.new do |yielder|
|
75
|
+
while true do
|
76
|
+
# Yield the results we got in the body.
|
77
|
+
body.each do |item|
|
78
|
+
yielder << item
|
79
|
+
end
|
80
|
+
|
81
|
+
# Only make a request to get the next page if we have a valid
|
82
|
+
# next range.
|
83
|
+
break unless next_range
|
84
|
+
headers = headers.merge({'Range' => next_range})
|
85
|
+
response = connection.request(method: @link_schema.method,
|
86
|
+
path: path, headers: headers,
|
87
|
+
expects: [200, 201, 206])
|
88
|
+
body = MultiJson.load(response.body)
|
89
|
+
next_range = response.headers['Next-Range']
|
90
|
+
end
|
91
|
+
end
|
92
|
+
else
|
93
|
+
body
|
94
|
+
end
|
95
|
+
elsif !response.body.empty?
|
96
|
+
response.body
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|