heroics 0.0.7 → 0.0.8
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.
- checksums.yaml +4 -4
- data/README.md +43 -145
- data/bin/heroics-generate +26 -9
- data/heroics.gemspec +3 -3
- data/lib/heroics/client.rb +24 -0
- data/lib/heroics/version.rb +1 -1
- data/lib/heroics/views/client.erb +27 -5
- data/test/client_test.rb +48 -0
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16b5b5b9ec40a80d7ac06af58ebf2be1e0c773fe
|
4
|
+
data.tar.gz: a695f11ee1b3eb7841def942d6239c3a589c0fba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3a4e120811c3b4cef5d0cd919173c9b89e8e2b39834bbc103681056fa3e4355b371ff9a7cf27c1b22b3114e1c962f9c6fe45cf11a9a4d5317e9d6ce9d64e5e97
|
7
|
+
data.tar.gz: 5ad761a53a1baeb2cbbc6d33aa0239c4742eb61406194a0e68b551ea6283372c84f320b631d255bdbd8e7eba524bf86e284eded0d4576d654390f444f73bb983
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
[](https://travis-ci.org/interagent/heroics)
|
2
2
|
# Heroics
|
3
3
|
|
4
|
-
Ruby HTTP client for APIs represented with JSON schema.
|
4
|
+
Ruby HTTP client generator for APIs represented with JSON schema.
|
5
5
|
|
6
6
|
## Installation
|
7
7
|
|
@@ -19,180 +19,78 @@ Or install it yourself as:
|
|
19
19
|
|
20
20
|
## Usage
|
21
21
|
|
22
|
-
###
|
22
|
+
### Generating a client
|
23
23
|
|
24
|
-
Heroics
|
25
|
-
|
26
|
-
|
24
|
+
Heroics generates an HTTP client from a JSON schema that describes your API.
|
25
|
+
Look at [prmd](https://github.com/interagent/prmd) for tooling to help write a
|
26
|
+
JSON schema. When you have a JSON schema prepared you can generate a client
|
27
|
+
for your API:
|
27
28
|
|
28
|
-
```ruby
|
29
|
-
require 'cgi'
|
30
|
-
require 'json'
|
31
|
-
require 'heroics'
|
32
|
-
|
33
|
-
username = CGI.escape('username')
|
34
|
-
token = 'token'
|
35
|
-
url = "https://#{username}:#{token}@api.heroku.com"
|
36
|
-
options = {default_headers: {'Accept' => 'application/vnd.heroku+json; version=3'}}
|
37
|
-
data = JSON.parse(File.read('schema.json'))
|
38
|
-
schema = Heroics::Schema.new(data)
|
39
|
-
client = Heroics.client_from_schema(schema, url, options)
|
40
29
|
```
|
41
|
-
|
42
|
-
### Instantiate a client from a JSON schema with an OAuth token
|
43
|
-
|
44
|
-
The client will make requests to the API using an OAuth token when one is
|
45
|
-
provided.
|
46
|
-
|
47
|
-
```ruby
|
48
|
-
oauth_token = 'token'
|
49
|
-
url = "https://api.heroku.com"
|
50
|
-
options = {default_headers: {'Accept' => 'application/vnd.heroku+json; version=3'}}
|
51
|
-
data = JSON.parse(File.read('schema.json'))
|
52
|
-
schema = Heroics::Schema.new(data)
|
53
|
-
client = Heroics.oauth_client_from_schema(oauth_token, schema, url, options)
|
30
|
+
bin/heroics-generator MyApp schema.json https://api.myapp.com > client.rb
|
54
31
|
```
|
55
32
|
|
56
|
-
###
|
33
|
+
### Passing custom headers
|
57
34
|
|
58
|
-
|
59
|
-
|
35
|
+
If your client needs to pass custom headers with each request these can be
|
36
|
+
specified using `-H`:
|
60
37
|
|
61
|
-
```ruby
|
62
|
-
username = CGI.escape('username')
|
63
|
-
token = 'token'
|
64
|
-
url = "https://#{username}:#{token}@api.heroku.com/schema"
|
65
|
-
options = {default_headers: {'Accept' => 'application/vnd.heroku+json; version=3'},
|
66
|
-
cache: Moneta.new(:File, dir: "#{Dir.home}/.heroics/heroku-api")}
|
67
|
-
data = JSON.parse(File.read('schema.json'))
|
68
|
-
schema = Heroics::Schema.new(data)
|
69
|
-
client = Heroics.client_from_schema(schema, url, options)
|
70
38
|
```
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
those top-level resources. For example, you can [list the apps](https://devcenter.heroku.com/articles/platform-api-reference#app-list)
|
77
|
-
in your Heroku account:
|
78
|
-
|
79
|
-
```ruby
|
80
|
-
apps = client.app.list
|
39
|
+
bin/heroics-generator \
|
40
|
+
-H "Accept: application/vnd.myapp+json; version=3" \
|
41
|
+
MyApp \
|
42
|
+
schema.json \
|
43
|
+
https://api.myapp.com > client.rb
|
81
44
|
```
|
82
45
|
|
83
|
-
|
84
|
-
modifications. Response content with type `application/json` is
|
85
|
-
automatically decoded into a Ruby object.
|
86
|
-
|
87
|
-
### Handling content ranges
|
46
|
+
Pass multiple `-H` options if you need more than one custom header.
|
88
47
|
|
89
|
-
|
90
|
-
will return an `Enumerator` that can be used to access the data. It
|
91
|
-
only makes requests to the server to fetch additional data when the
|
92
|
-
current batch has been exhausted.
|
93
|
-
|
94
|
-
### Command-line interface
|
95
|
-
|
96
|
-
Heroics includes a builtin CLI that, like the client, is generated
|
97
|
-
from a JSON schema.
|
98
|
-
|
99
|
-
```ruby
|
100
|
-
username = 'username'
|
101
|
-
token = 'token'
|
102
|
-
url = "https://#{username}:#{token}@api.heroku.com/schema"
|
103
|
-
options = {
|
104
|
-
default_headers: {'Accept' => 'application/vnd.heroku+json; version=3'},
|
105
|
-
cache: Moneta.new(:File, dir: "#{Dir.home}/.heroics/heroku-api")}
|
106
|
-
data = JSON.parse(File.read('schema.json'))
|
107
|
-
schema = Heroics::Schema.new(data)
|
108
|
-
cli = Heroics.cli_from_schema('heroku-api', STDOUT, schema, url, options)
|
109
|
-
cli.run(*ARGV)
|
110
|
-
```
|
48
|
+
### Client-side caching
|
111
49
|
|
112
|
-
|
50
|
+
The generated client sends and caches ETags received from the server. By
|
51
|
+
default, this data is cached in memory and is only used during the lifetime of
|
52
|
+
a single instance. You can specify a directory for cache data:
|
113
53
|
|
114
54
|
```
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
account-feature:info Info for an existing account feature.
|
121
|
-
account-feature:list List existing account features.
|
122
|
-
account-feature:update Update an existing account feature.
|
123
|
-
account:change-email Change Email for account.
|
124
|
-
account:change-password Change Password for account.
|
125
|
-
account:info Info for account.
|
126
|
-
account:update Update account.
|
127
|
-
addon-service:info Info for existing addon-service.
|
128
|
-
addon-service:list List existing addon-services.
|
129
|
-
addon:create Create a new add-on.
|
130
|
-
--- 8< --- snip --- 8< ---
|
55
|
+
bin/heroics-generator \
|
56
|
+
-c "~/.heroics/myapp" \
|
57
|
+
MyApp \
|
58
|
+
schema.json \
|
59
|
+
https://api.myapp.com > client.rb
|
131
60
|
```
|
132
61
|
|
133
|
-
|
62
|
+
`~` will automatically be expanded to the user's home directory. Be sure to
|
63
|
+
wrap such paths in quotes to avoid the shell expanding it to the directory you
|
64
|
+
built the client in.
|
134
65
|
|
135
|
-
|
136
|
-
$ bundle exec bin/heroku-api help app:create
|
137
|
-
Usage: heroku-api app:create <body>
|
138
|
-
|
139
|
-
Description:
|
140
|
-
Create a new app.
|
141
|
-
|
142
|
-
Body example:
|
143
|
-
{
|
144
|
-
"name": "example",
|
145
|
-
"region": "",
|
146
|
-
"stack": ""
|
147
|
-
}
|
148
|
-
```
|
66
|
+
### Generating API documentation
|
149
67
|
|
150
|
-
|
151
|
-
|
68
|
+
The generated client has [Yard](http://yardoc.org/)-compatible docstrings.
|
69
|
+
You can generate documentation using `yardoc`:
|
152
70
|
|
153
|
-
```ruby
|
154
|
-
client.app.create({'name' => 'example',
|
155
|
-
'region' => '',
|
156
|
-
'stack' => ''})
|
157
71
|
```
|
158
|
-
|
159
|
-
### Command arguments
|
160
|
-
|
161
|
-
Commands that take arguments will list them in help output from the
|
162
|
-
client.
|
163
|
-
|
72
|
+
yard doc -m markdown client.rb
|
164
73
|
```
|
165
|
-
$ bundle exec bin/heroku-api help app:info
|
166
|
-
Usage: heroku-api app:info <id|name>
|
167
74
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
This command needs an app's UUID or name:
|
75
|
+
This will generate HTML in the `docs` directory. Note that Yard creates an
|
76
|
+
`_index.html` page that doesn't appear to be compatible with GitHub Pages. If
|
77
|
+
you're hosting your content there you can change the links:
|
173
78
|
|
174
|
-
```ruby
|
175
|
-
info = client.app.info('sushi')
|
176
79
|
```
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
### Using the Heroku API
|
182
|
-
|
183
|
-
Heroics comes with a builtin `heroku-api` program that serves as an
|
184
|
-
example and makes it easy to play with the [Heroku Platform API](https://devcenter.heroku.com/articles/platform-api-reference).
|
80
|
+
cd docs
|
81
|
+
sed -e 's/_index\.html/index\.html/g' -i `grep _index.html * -rl`
|
82
|
+
```
|
185
83
|
|
186
84
|
### Handling failures
|
187
85
|
|
188
|
-
The client uses [Excon](https://github.com/geemus/excon) under the hood and
|
189
|
-
failures occur.
|
86
|
+
The client uses [Excon](https://github.com/geemus/excon) under the hood and
|
87
|
+
raises Excon errors when failures occur.
|
190
88
|
|
191
89
|
```ruby
|
192
90
|
begin
|
193
91
|
client.app.create({'name' => 'example'})
|
194
|
-
rescue Excon::Errors::Forbidden =>
|
195
|
-
puts
|
92
|
+
rescue Excon::Errors::Forbidden => error
|
93
|
+
puts error
|
196
94
|
end
|
197
95
|
```
|
198
96
|
|
data/bin/heroics-generate
CHANGED
@@ -3,20 +3,37 @@
|
|
3
3
|
require 'optparse'
|
4
4
|
require 'heroics'
|
5
5
|
|
6
|
-
options = {}
|
6
|
+
options = {headers: {}, cache_path: nil}
|
7
7
|
option_parser = OptionParser.new do |opts|
|
8
8
|
opts.banner = 'Usage: heroics-generate module_name schema_filename url'
|
9
|
-
|
9
|
+
|
10
|
+
opts.on('-h', '--help', 'Display this screen') do
|
10
11
|
puts opts
|
11
12
|
exit
|
12
13
|
end
|
14
|
+
|
15
|
+
opts.on('-H', '--header [HEADER]',
|
16
|
+
'Include header with all requests') do |header|
|
17
|
+
parts = header.split(':', 0)
|
18
|
+
options[:headers][parts[0]] = parts[1].strip
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.on('-c', '--cache-dir [PATH]',
|
22
|
+
'Content cache directory (~ is automatically expanded)') do |path|
|
23
|
+
options[:cache_path] = path.sub('~', '#{Dir.home}')
|
24
|
+
end
|
13
25
|
end
|
14
26
|
|
15
27
|
option_parser.parse!
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
28
|
+
if ARGV.length != 3
|
29
|
+
puts option_parser
|
30
|
+
else
|
31
|
+
module_name, schema_filename, url = ARGV
|
32
|
+
schema = Heroics::Schema.new(MultiJson.decode(File.read(schema_filename)))
|
33
|
+
cache = 'Moneta.new(:Memory)'
|
34
|
+
if options[:cache_path]
|
35
|
+
cache = "Moneta.new(:File, dir: \"#{options[:cache_path]}\")"
|
36
|
+
end
|
37
|
+
options = {default_headers: options[:headers], cache: cache}
|
38
|
+
puts Heroics.generate_client(module_name, schema, url, options)
|
39
|
+
end
|
data/heroics.gemspec
CHANGED
@@ -10,9 +10,9 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.version = Heroics::VERSION
|
11
11
|
spec.authors = ['geemus', 'jkakar']
|
12
12
|
spec.email = ['geemus@gmail.com', 'jkakar@kakar.ca']
|
13
|
-
spec.description = 'A Ruby client for HTTP APIs described
|
14
|
-
spec.summary = 'A Ruby client for HTTP APIs described
|
15
|
-
spec.homepage = ''
|
13
|
+
spec.description = 'A Ruby client generator for HTTP APIs described with a JSON schema'
|
14
|
+
spec.summary = 'A Ruby client generator for HTTP APIs described with a JSON schema'
|
15
|
+
spec.homepage = 'https://github.com/interagent/heroics'
|
16
16
|
spec.license = 'MIT'
|
17
17
|
|
18
18
|
spec.files = `git ls-files`.split($/)
|
data/lib/heroics/client.rb
CHANGED
@@ -79,4 +79,28 @@ module Heroics
|
|
79
79
|
options[:default_headers].merge!({"Authorization" => authorization})
|
80
80
|
client_from_schema(schema, url, options)
|
81
81
|
end
|
82
|
+
|
83
|
+
# Create an HTTP client with Token credentials from a JSON schema.
|
84
|
+
#
|
85
|
+
# @param oauth_token [String] The token to pass using the `Bearer`
|
86
|
+
# authorization mechanism.
|
87
|
+
# @param schema [Schema] The JSON schema to build an HTTP client for.
|
88
|
+
# @param url [String] The URL the generated client should use when making
|
89
|
+
# requests.
|
90
|
+
# @param options [Hash] Configuration for links. Possible keys include:
|
91
|
+
# - default_headers: Optionally, a set of headers to include in every
|
92
|
+
# request made by the client. Default is no custom headers.
|
93
|
+
# - cache: Optionally, a Moneta-compatible cache to store ETags. Default
|
94
|
+
# is no caching.
|
95
|
+
# @return [Client] A client with resources and links from the JSON schema.
|
96
|
+
def self.token_client_from_schema(token, schema, url, options={})
|
97
|
+
authorization = "Token token=#{token}"
|
98
|
+
# Don't mutate user-supplied data.
|
99
|
+
options = Marshal.load(Marshal.dump(options))
|
100
|
+
if !options.has_key?(:default_headers)
|
101
|
+
options[:default_headers] = {}
|
102
|
+
end
|
103
|
+
options[:default_headers].merge!({"Authorization" => authorization})
|
104
|
+
client_from_schema(schema, url, options)
|
105
|
+
end
|
82
106
|
end
|
data/lib/heroics/version.rb
CHANGED
@@ -27,8 +27,8 @@ module <%= @module_name %>
|
|
27
27
|
end
|
28
28
|
cache = <%= @cache %>
|
29
29
|
options = {
|
30
|
-
|
31
|
-
cache: cache
|
30
|
+
default_headers: default_headers,
|
31
|
+
cache: cache
|
32
32
|
}
|
33
33
|
client = Heroics.client_from_schema(SCHEMA, url.to_s, options)
|
34
34
|
Client.new(client)
|
@@ -49,10 +49,32 @@ module <%= @module_name %>
|
|
49
49
|
end
|
50
50
|
cache = <%= @cache %>
|
51
51
|
options = {
|
52
|
-
|
53
|
-
cache: cache
|
52
|
+
default_headers: default_headers,
|
53
|
+
cache: cache
|
54
54
|
}
|
55
|
-
client = Heroics.
|
55
|
+
client = Heroics.oauth_client_from_schema(oauth_token, SCHEMA, url, options)
|
56
|
+
Client.new(client)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Get a Client configured to use Token authentication.
|
60
|
+
#
|
61
|
+
# @param token [String] The token to use with the API.
|
62
|
+
# @param headers [Hash<String,String>] Optionally, custom to headers to
|
63
|
+
# include with every response.
|
64
|
+
# @return [Client] A client configured to use the API with OAuth
|
65
|
+
# authentication.
|
66
|
+
def self.connect_token(token, headers=nil)
|
67
|
+
url = "<%= @url %>"
|
68
|
+
default_headers = <%= @default_headers %>
|
69
|
+
unless headers.nil?
|
70
|
+
default_headers.merge!(headers)
|
71
|
+
end
|
72
|
+
cache = <%= @cache %>
|
73
|
+
options = {
|
74
|
+
default_headers: default_headers,
|
75
|
+
cache: cache
|
76
|
+
}
|
77
|
+
client = Heroics.token_client_from_schema(token, SCHEMA, url, options)
|
56
78
|
Client.new(client)
|
57
79
|
end
|
58
80
|
|
data/test/client_test.rb
CHANGED
@@ -185,3 +185,51 @@ class OAuthClientFromSchemaTest < MiniTest::Unit::TestCase
|
|
185
185
|
assert_equal(body, client.resource.list)
|
186
186
|
end
|
187
187
|
end
|
188
|
+
|
189
|
+
class TokenClientFromSchemaTest < MiniTest::Unit::TestCase
|
190
|
+
include ExconHelper
|
191
|
+
|
192
|
+
# token_client_from_schema injects an Authorization header, built from the
|
193
|
+
# specified token, into the default header options.
|
194
|
+
def test_token_client_from_schema
|
195
|
+
body = {'Hello' => 'World!'}
|
196
|
+
Excon.stub(method: :get) do |request|
|
197
|
+
assert_equal(
|
198
|
+
'Token token=c55ef0d8-40b6-4759-b1bf-4a6f94190a66',
|
199
|
+
request[:headers]['Authorization'])
|
200
|
+
Excon.stubs.pop
|
201
|
+
{status: 200, headers: {'Content-Type' => 'application/json'},
|
202
|
+
body: MultiJson.dump(body)}
|
203
|
+
end
|
204
|
+
|
205
|
+
token = 'c55ef0d8-40b6-4759-b1bf-4a6f94190a66'
|
206
|
+
schema = Heroics::Schema.new(SAMPLE_SCHEMA)
|
207
|
+
client = Heroics.token_client_from_schema(token, schema,
|
208
|
+
'https://example.com')
|
209
|
+
assert_equal(body, client.resource.list)
|
210
|
+
end
|
211
|
+
|
212
|
+
# token_client_from_schema doesn't mutate the options object, and in
|
213
|
+
# particular, it doesn't mutate the :default_headers Hash in that object.
|
214
|
+
def test_token_client_from_schema_with_options
|
215
|
+
body = {'Hello' => 'World!'}
|
216
|
+
Excon.stub(method: :get) do |request|
|
217
|
+
assert_equal('application/vnd.heroku+json; version=3',
|
218
|
+
request[:headers]['Accept'])
|
219
|
+
assert_equal(
|
220
|
+
'Token token=c55ef0d8-40b6-4759-b1bf-4a6f94190a66',
|
221
|
+
request[:headers]['Authorization'])
|
222
|
+
Excon.stubs.pop
|
223
|
+
{status: 200, headers: {'Content-Type' => 'application/json'},
|
224
|
+
body: MultiJson.dump(body)}
|
225
|
+
end
|
226
|
+
|
227
|
+
token = 'c55ef0d8-40b6-4759-b1bf-4a6f94190a66'
|
228
|
+
options = {
|
229
|
+
default_headers: {'Accept' => 'application/vnd.heroku+json; version=3'}}
|
230
|
+
schema = Heroics::Schema.new(SAMPLE_SCHEMA)
|
231
|
+
client = Heroics.token_client_from_schema(token, schema,
|
232
|
+
'https://example.com', options)
|
233
|
+
assert_equal(body, client.resource.list)
|
234
|
+
end
|
235
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: heroics
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- geemus
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-05-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -137,7 +137,7 @@ dependencies:
|
|
137
137
|
- - '>='
|
138
138
|
- !ruby/object:Gem::Version
|
139
139
|
version: '0'
|
140
|
-
description: A Ruby client for HTTP APIs described
|
140
|
+
description: A Ruby client generator for HTTP APIs described with a JSON schema
|
141
141
|
email:
|
142
142
|
- geemus@gmail.com
|
143
143
|
- jkakar@kakar.ca
|
@@ -180,7 +180,7 @@ files:
|
|
180
180
|
- test/resource_test.rb
|
181
181
|
- test/schema_test.rb
|
182
182
|
- test/version_test.rb
|
183
|
-
homepage:
|
183
|
+
homepage: https://github.com/interagent/heroics
|
184
184
|
licenses:
|
185
185
|
- MIT
|
186
186
|
metadata: {}
|
@@ -203,5 +203,5 @@ rubyforge_project:
|
|
203
203
|
rubygems_version: 2.0.14
|
204
204
|
signing_key:
|
205
205
|
specification_version: 4
|
206
|
-
summary: A Ruby client for HTTP APIs described
|
206
|
+
summary: A Ruby client generator for HTTP APIs described with a JSON schema
|
207
207
|
test_files: []
|