heroics 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Build Status](https://travis-ci.org/
|
1
|
+
[![Build Status](https://travis-ci.org/interagent/heroics.png?branch=master)](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: []
|