haveapi-client 0.1.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.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/README.md +67 -0
- data/Rakefile +2 -0
- data/bin/haveapi-cli +5 -0
- data/haveapi-client.gemspec +30 -0
- data/lib/haveapi/cli.rb +2 -0
- data/lib/haveapi/cli/cli.rb +290 -0
- data/lib/haveapi/client.rb +2 -0
- data/lib/haveapi/client/action.rb +81 -0
- data/lib/haveapi/client/client.rb +37 -0
- data/lib/haveapi/client/communicator.rb +115 -0
- data/lib/haveapi/client/exceptions.rb +13 -0
- data/lib/haveapi/client/inflections.rb +3 -0
- data/lib/haveapi/client/resource.rb +78 -0
- data/lib/haveapi/client/response.rb +33 -0
- data/lib/haveapi/client/version.rb +5 -0
- data/lib/restclient_ext/request.rb +16 -0
- data/lib/restclient_ext/resource.rb +11 -0
- metadata +175 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 411ee5aebf1042dcb0532fcfde75bb90a71a6a99
|
4
|
+
data.tar.gz: 81323b4e42c302b2030b051308082dca268556a1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8fde9de9cdd152dca98d07b1f6fd74dbeb599c202a3031df0158b803e213047ee648b38077f8270c86d8b0bc0272a03e57c57a3a06354f5a0b44556c582ba613
|
7
|
+
data.tar.gz: 4265a74df48c07c9dc3ae1a706f833cc2be1a9eb7be40fa54abedeb00808e1e588a98a1543d8da318c73dfe007dbf1bdd6d71f5d9dc64e46c806a67702ef8d5d
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
HaveAPI-Client
|
2
|
+
--------------
|
3
|
+
HaveAPI-Client is a Ruby CLI and client library for APIs built with
|
4
|
+
[HaveAPI framework](https://github.com/vpsfreecz/haveapi).
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'haveapi-client'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install haveapi-client
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
### CLI
|
22
|
+
$ haveapi-cli -h
|
23
|
+
Usage: haveapi-cli [options] <resource> <action> [objects ids] [-- [parameters]]
|
24
|
+
-a, --api URL API URL
|
25
|
+
--list-versions List all available API versions
|
26
|
+
--list-resources VERSION List all resource in API version
|
27
|
+
--list-actions VERSION List all resources and actions in API version
|
28
|
+
-r, --raw Print raw response as is
|
29
|
+
-u, --username USER User name
|
30
|
+
-p, --password PASSWORD Password
|
31
|
+
-v, --[no-]verbose Run verbosely
|
32
|
+
-h, --help Show this message
|
33
|
+
|
34
|
+
Using the API example from
|
35
|
+
[HaveAPI README](https://github.com/vpsfreecz/haveapi/blob/master/README.md#example),
|
36
|
+
users would be listed with:
|
37
|
+
|
38
|
+
$ haveapi-cli -a https://your.api.tld user index -u yourname -p yourpassword
|
39
|
+
|
40
|
+
Nested resources and object IDs:
|
41
|
+
|
42
|
+
$ haveapi-cli -a https://your.api.tld user.invoice index 10 -u yourname -p yourpassword
|
43
|
+
|
44
|
+
where `10` is user ID.
|
45
|
+
|
46
|
+
### Client library
|
47
|
+
```ruby
|
48
|
+
require 'haveapi/client'
|
49
|
+
|
50
|
+
api = HaveAPI::Client::Client.new('https://your.api.tld')
|
51
|
+
api.login('yourname', 'yourpassword')
|
52
|
+
|
53
|
+
response = api.user.index
|
54
|
+
p response.ok?
|
55
|
+
p response.response
|
56
|
+
|
57
|
+
p api.user(10).invoice
|
58
|
+
p api.user(10).delete
|
59
|
+
p api.user(10).delete!
|
60
|
+
p api.user.delete(10)
|
61
|
+
|
62
|
+
p api.user.create({
|
63
|
+
login: 'mylogin',
|
64
|
+
full_name: 'Very Full Name',
|
65
|
+
role: 'user'
|
66
|
+
})
|
67
|
+
```
|
data/Rakefile
ADDED
data/bin/haveapi-cli
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'haveapi/client/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'haveapi-client'
|
8
|
+
spec.version = HaveAPI::Client::VERSION
|
9
|
+
spec.authors = ['Jakub Skokan']
|
10
|
+
spec.email = ['jakub.skokan@vpsfree.cz']
|
11
|
+
spec.summary =
|
12
|
+
spec.description = 'Ruby API and CLI for HaveAPI'
|
13
|
+
spec.homepage = ''
|
14
|
+
spec.license = 'GPL'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
22
|
+
spec.add_development_dependency 'rake'
|
23
|
+
|
24
|
+
spec.add_runtime_dependency 'activesupport', '~> 4.1.0'
|
25
|
+
spec.add_runtime_dependency 'require_all', '~> 1.3.0'
|
26
|
+
spec.add_runtime_dependency 'rest_client', '~> 1.7.3'
|
27
|
+
spec.add_runtime_dependency 'json', '~> 1.8.1'
|
28
|
+
spec.add_runtime_dependency 'highline', '~> 1.6.21'
|
29
|
+
spec.add_runtime_dependency 'table_print', '~> 1.5.1'
|
30
|
+
end
|
data/lib/haveapi/cli.rb
ADDED
@@ -0,0 +1,290 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'pp'
|
3
|
+
require 'highline/import'
|
4
|
+
require 'table_print'
|
5
|
+
|
6
|
+
module HaveAPI
|
7
|
+
module CLI
|
8
|
+
class Cli
|
9
|
+
def self.run
|
10
|
+
c = new
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
args, @opts = options
|
15
|
+
@api = HaveAPI::Client::Communicator.new(api_url)
|
16
|
+
|
17
|
+
if @action
|
18
|
+
method(@action.first).call( * @action[1..-1] )
|
19
|
+
exit
|
20
|
+
end
|
21
|
+
|
22
|
+
if @opts[:help] || args.empty?
|
23
|
+
puts @global_opt.help
|
24
|
+
exit(true)
|
25
|
+
end
|
26
|
+
|
27
|
+
resources = args[0].split('.')
|
28
|
+
action = translate_action(args[1].to_sym)
|
29
|
+
|
30
|
+
action = @api.get_action(resources, action, args[2..-1])
|
31
|
+
|
32
|
+
@input_params = parameters(action)
|
33
|
+
|
34
|
+
if action
|
35
|
+
unless params_valid?(action)
|
36
|
+
warn 'Missing required parameters'
|
37
|
+
end
|
38
|
+
|
39
|
+
ret = action.execute(@input_params, raw: @opts[:raw])
|
40
|
+
|
41
|
+
if ret[:status]
|
42
|
+
format_output(action, ret[:response])
|
43
|
+
else
|
44
|
+
warn "Action failed: #{ret[:message]}"
|
45
|
+
|
46
|
+
if ret[:errors].any?
|
47
|
+
puts 'Errors:'
|
48
|
+
ret[:errors].each do |param, e|
|
49
|
+
puts "\t#{param}: #{e.join('; ')}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
else
|
55
|
+
warn "Action #{ARGV[0]}##{ARGV[1]} not valid"
|
56
|
+
exit(false)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def api_url
|
61
|
+
@opts[:client]
|
62
|
+
end
|
63
|
+
|
64
|
+
def options
|
65
|
+
options = {
|
66
|
+
client: default_url,
|
67
|
+
verbose: false,
|
68
|
+
}
|
69
|
+
|
70
|
+
@global_opt = OptionParser.new do |opts|
|
71
|
+
opts.banner = "Usage: #{$0} [options] <resource> <action> [objects ids] [-- [parameters]]"
|
72
|
+
|
73
|
+
opts.on('-a', '--api URL', 'API URL') do |url|
|
74
|
+
options[:client] = url
|
75
|
+
end
|
76
|
+
|
77
|
+
opts.on('--list-versions', 'List all available API versions') do
|
78
|
+
@action = [:list_versions]
|
79
|
+
end
|
80
|
+
|
81
|
+
opts.on('--list-resources VERSION', 'List all resource in API version') do |v|
|
82
|
+
@action = [:list_resources, v.sub(/^v/, '')]
|
83
|
+
end
|
84
|
+
|
85
|
+
opts.on('--list-actions VERSION', 'List all resources and actions in API version') do |v|
|
86
|
+
@action = [:list_actions, v.sub(/^v/, '')]
|
87
|
+
end
|
88
|
+
|
89
|
+
opts.on('-r', '--raw', 'Print raw response as is') do
|
90
|
+
options[:raw] = true
|
91
|
+
end
|
92
|
+
|
93
|
+
opts.on('-u', '--username USER', 'User name') do |u|
|
94
|
+
options[:user] = u
|
95
|
+
end
|
96
|
+
|
97
|
+
opts.on('-p', '--password PASSWORD', 'Password') do |p|
|
98
|
+
options[:password] = p
|
99
|
+
end
|
100
|
+
|
101
|
+
opts.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
|
102
|
+
options[:verbose] = v
|
103
|
+
end
|
104
|
+
|
105
|
+
opts.on('-h', '--help', 'Show this message') do
|
106
|
+
options[:help] = true
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
args = []
|
111
|
+
|
112
|
+
ARGV.each do |arg|
|
113
|
+
if arg == '--'
|
114
|
+
break
|
115
|
+
else
|
116
|
+
args << arg
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
@global_opt.parse!(args)
|
121
|
+
|
122
|
+
# p options
|
123
|
+
#p ARGV
|
124
|
+
|
125
|
+
[args, options]
|
126
|
+
end
|
127
|
+
|
128
|
+
def parameters(action)
|
129
|
+
options = {}
|
130
|
+
sep = ARGV.index('--')
|
131
|
+
|
132
|
+
@action_opt = OptionParser.new do |opts|
|
133
|
+
opts.banner = ''
|
134
|
+
|
135
|
+
action.input[:parameters].each do |name, p|
|
136
|
+
opts.on(param_option(name, p), p[:description] || p[:label] || '') do |*args|
|
137
|
+
options[name] = args.first
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
opts.on('-h', '--help', 'Show this message') do
|
142
|
+
@opts[:help] = true
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
if @opts[:help]
|
147
|
+
puts @global_opt.help
|
148
|
+
puts ''
|
149
|
+
print 'Action parameters:'
|
150
|
+
puts @action_opt.help
|
151
|
+
exit
|
152
|
+
end
|
153
|
+
|
154
|
+
return {} unless sep
|
155
|
+
|
156
|
+
@action_opt.parse!(ARGV[sep+1..-1])
|
157
|
+
|
158
|
+
options
|
159
|
+
end
|
160
|
+
|
161
|
+
def param_option(name, p)
|
162
|
+
ret = '--'
|
163
|
+
name = name.to_s.dasherize
|
164
|
+
|
165
|
+
if p[:type] == 'Boolean'
|
166
|
+
ret += "[no-]#{name}"
|
167
|
+
|
168
|
+
else
|
169
|
+
ret += "#{name} #{name.underscore.upcase}"
|
170
|
+
end
|
171
|
+
|
172
|
+
ret
|
173
|
+
end
|
174
|
+
|
175
|
+
def translate_action(action)
|
176
|
+
tr = {
|
177
|
+
list: :index,
|
178
|
+
new: :create,
|
179
|
+
change: :update
|
180
|
+
}
|
181
|
+
|
182
|
+
if tr.has_key?(action)
|
183
|
+
return tr[action]
|
184
|
+
end
|
185
|
+
|
186
|
+
action
|
187
|
+
end
|
188
|
+
|
189
|
+
def list_versions
|
190
|
+
desc = @api.describe_api
|
191
|
+
|
192
|
+
desc[:versions].each do |v, _|
|
193
|
+
next if v == :default
|
194
|
+
|
195
|
+
v_int = v.to_s.to_i
|
196
|
+
|
197
|
+
puts "#{v_int == desc[:default_version] ? '*' : ' '} v#{v}"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def list_resources(v)
|
202
|
+
@api.describe_api(v)[:resources].each do |resource, children|
|
203
|
+
nested_resource(resource, children, false)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def list_actions(v)
|
208
|
+
@api.describe_api(v)[:resources].each do |resource, children|
|
209
|
+
nested_resource(resource, children, true)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def nested_resource(prefix, children, actions=false)
|
214
|
+
if actions
|
215
|
+
children[:actions].each do |action, _|
|
216
|
+
puts "#{prefix}##{action}"
|
217
|
+
end
|
218
|
+
else
|
219
|
+
puts prefix
|
220
|
+
end
|
221
|
+
|
222
|
+
children[:resources].each do |resource, children|
|
223
|
+
nested_resource("#{prefix}.#{resource}", children, actions)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def format_output(action, response)
|
228
|
+
if @opts[:raw]
|
229
|
+
puts response
|
230
|
+
return
|
231
|
+
end
|
232
|
+
|
233
|
+
return if response.empty?
|
234
|
+
|
235
|
+
s = action.structure
|
236
|
+
namespace = action.namespace(:output).to_sym
|
237
|
+
|
238
|
+
case action.layout.to_sym
|
239
|
+
when :list
|
240
|
+
tp response[namespace]
|
241
|
+
|
242
|
+
|
243
|
+
when :object
|
244
|
+
response[namespace].each do |k, v|
|
245
|
+
puts "#{k}: #{v}"
|
246
|
+
end
|
247
|
+
|
248
|
+
|
249
|
+
when :custom
|
250
|
+
pp response[namespace]
|
251
|
+
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def header_for(action, param)
|
256
|
+
params = action.params
|
257
|
+
|
258
|
+
if params.has_key?(param) && params[param][:label]
|
259
|
+
params[param][:label]
|
260
|
+
else
|
261
|
+
param.to_s.upcase
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def params_valid?(action)
|
266
|
+
if action.auth?
|
267
|
+
@opts[:user] ||= ask('User name: ') { |q| q.default = nil }
|
268
|
+
|
269
|
+
@opts[:password] ||= ask('Password: ') do |q|
|
270
|
+
q.default = nil
|
271
|
+
q.echo = false
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
if action.auth? && !(@opts[:user] || @opts[:password])
|
276
|
+
return false
|
277
|
+
end
|
278
|
+
|
279
|
+
@api.login(@opts[:user], @opts[:password])
|
280
|
+
|
281
|
+
true
|
282
|
+
end
|
283
|
+
|
284
|
+
protected
|
285
|
+
def default_url
|
286
|
+
'http://localhost:4567'
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module HaveAPI
|
2
|
+
module Client
|
3
|
+
class Action
|
4
|
+
def initialize(api, name, spec, args)
|
5
|
+
@api = api
|
6
|
+
@name = name
|
7
|
+
@spec = spec
|
8
|
+
|
9
|
+
apply_args(args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute(*args)
|
13
|
+
ret = @api.call(self, *args)
|
14
|
+
@prepared_url = nil
|
15
|
+
ret
|
16
|
+
end
|
17
|
+
|
18
|
+
def name
|
19
|
+
@name
|
20
|
+
end
|
21
|
+
|
22
|
+
def auth?
|
23
|
+
@spec[:auth]
|
24
|
+
end
|
25
|
+
|
26
|
+
def input
|
27
|
+
@spec[:input]
|
28
|
+
end
|
29
|
+
|
30
|
+
def output
|
31
|
+
@spec[:output]
|
32
|
+
end
|
33
|
+
|
34
|
+
def layout
|
35
|
+
@spec[:output][:layout]
|
36
|
+
end
|
37
|
+
|
38
|
+
def structure
|
39
|
+
@spec[:output][:format]
|
40
|
+
end
|
41
|
+
|
42
|
+
def namespace(src)
|
43
|
+
@spec[src][:namespace]
|
44
|
+
end
|
45
|
+
|
46
|
+
def params
|
47
|
+
@spec[:output][:parameters]
|
48
|
+
end
|
49
|
+
|
50
|
+
def url
|
51
|
+
@spec[:url]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Url with resolved parameters.
|
55
|
+
def prepared_url
|
56
|
+
@prepared_url || @spec[:url]
|
57
|
+
end
|
58
|
+
|
59
|
+
def http_method
|
60
|
+
@spec[:method]
|
61
|
+
end
|
62
|
+
|
63
|
+
def unresolved_args?
|
64
|
+
prepared_url =~ /:[a-zA-Z\-_]+/
|
65
|
+
end
|
66
|
+
|
67
|
+
def provide_args(*args)
|
68
|
+
apply_args(args)
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
def apply_args(args)
|
73
|
+
@prepared_url ||= @spec[:url].dup
|
74
|
+
|
75
|
+
args.each do |arg|
|
76
|
+
@prepared_url.sub!(/:[a-zA-Z\-_]+/, arg.to_s)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
3
|
+
class HaveAPI::Client::Client
|
4
|
+
attr_reader :resources
|
5
|
+
|
6
|
+
def initialize(url, v=nil)
|
7
|
+
@version = v
|
8
|
+
@api = VpsAdmin::API::Communicator.new(url)
|
9
|
+
|
10
|
+
setup_api(@api.describe_api)
|
11
|
+
end
|
12
|
+
|
13
|
+
def login(*credentials)
|
14
|
+
@api.login(*credentials)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def setup_api(description)
|
19
|
+
v = @version || description[:default_version]
|
20
|
+
|
21
|
+
@resources = {}
|
22
|
+
|
23
|
+
description[:versions][v.to_s.to_sym][:resources].each do |name, desc|
|
24
|
+
r = VpsAdmin::API::Resource.new(@api, name)
|
25
|
+
r.setup(desc)
|
26
|
+
|
27
|
+
define_singleton_method(name) do |*args|
|
28
|
+
tmp = r.dup
|
29
|
+
tmp.prepared_args = args
|
30
|
+
tmp.setup_from_clone(r)
|
31
|
+
tmp
|
32
|
+
end
|
33
|
+
|
34
|
+
@resources[name] = r
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
require 'json'
|
3
|
+
require 'active_support/inflections'
|
4
|
+
require 'active_support/inflector'
|
5
|
+
require_rel '../../restclient_ext'
|
6
|
+
|
7
|
+
module HaveAPI
|
8
|
+
module Client
|
9
|
+
class Communicator
|
10
|
+
def initialize(url)
|
11
|
+
@url = url
|
12
|
+
@rest = RestClient::Resource.new(@url)
|
13
|
+
@version = 1
|
14
|
+
end
|
15
|
+
|
16
|
+
def login(user, password)
|
17
|
+
@rest = RestClient::Resource.new(@url, user, password)
|
18
|
+
end
|
19
|
+
|
20
|
+
def describe_api(v=nil)
|
21
|
+
description_for(path_for(v))
|
22
|
+
end
|
23
|
+
|
24
|
+
def describe_action(v, r)
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_action(resources, action, args)
|
29
|
+
@spec ||= describe_api(@version)
|
30
|
+
|
31
|
+
tmp = @spec
|
32
|
+
|
33
|
+
resources.each do |r|
|
34
|
+
tmp = tmp[:resources][r.to_sym]
|
35
|
+
|
36
|
+
return false unless tmp
|
37
|
+
end
|
38
|
+
|
39
|
+
a = tmp[:actions][action]
|
40
|
+
|
41
|
+
if a
|
42
|
+
Action.new(self, action, a, args)
|
43
|
+
else
|
44
|
+
false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def call(action, params, raw: false)
|
49
|
+
args = []
|
50
|
+
input_namespace = action.namespace(:input)
|
51
|
+
|
52
|
+
if %w(POST PUT).include?(action.http_method)
|
53
|
+
args << {input_namespace => params}.to_json
|
54
|
+
args << {:content_type => :json, :accept => :json}
|
55
|
+
|
56
|
+
elsif %w(GET DELETE).include?(action.http_method)
|
57
|
+
get_params = {}
|
58
|
+
|
59
|
+
params.each do |k, v|
|
60
|
+
get_params["#{input_namespace}[#{k}]"] = v
|
61
|
+
end
|
62
|
+
|
63
|
+
args << {params: get_params, accept: :json}
|
64
|
+
end
|
65
|
+
|
66
|
+
begin
|
67
|
+
response = parse(@rest[action.prepared_url].method(action.http_method.downcase.to_sym).call(*args))
|
68
|
+
|
69
|
+
rescue RestClient::Forbidden
|
70
|
+
return error('Access forbidden. Bad user name or password? Not authorized?')
|
71
|
+
|
72
|
+
rescue => e
|
73
|
+
return error("Fatal API error: #{e.inspect}")
|
74
|
+
end
|
75
|
+
|
76
|
+
if response[:status]
|
77
|
+
if raw
|
78
|
+
ok(JSON.pretty_generate(response[:response]))
|
79
|
+
else
|
80
|
+
ok(response[:response])
|
81
|
+
end
|
82
|
+
|
83
|
+
else
|
84
|
+
error(response[:message], response[:errors])
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
def ok(response)
|
90
|
+
{status: true, response: response}
|
91
|
+
end
|
92
|
+
|
93
|
+
def error(msg, errors={})
|
94
|
+
{status: false, message: msg, errors: errors}
|
95
|
+
end
|
96
|
+
|
97
|
+
def path_for(v=nil, r=nil)
|
98
|
+
ret = '/'
|
99
|
+
|
100
|
+
ret += "v#{v}/" if v
|
101
|
+
ret += r if r
|
102
|
+
|
103
|
+
ret
|
104
|
+
end
|
105
|
+
|
106
|
+
def description_for(path)
|
107
|
+
parse(@rest[path].get_options)
|
108
|
+
end
|
109
|
+
|
110
|
+
def parse(str)
|
111
|
+
JSON.parse(str, symbolize_names: true)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
class HaveAPI::Client::Resource
|
2
|
+
attr_reader :actions, :resources, :name
|
3
|
+
attr_accessor :prepared_args
|
4
|
+
|
5
|
+
def initialize(api, name)
|
6
|
+
@api = api
|
7
|
+
@name = name
|
8
|
+
@prepared_args = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def setup(description)
|
12
|
+
@actions = {}
|
13
|
+
@resources = {}
|
14
|
+
|
15
|
+
description[:actions].each do |name, desc|
|
16
|
+
action = HaveAPI::Client::Action.new(@api, name, desc, [])
|
17
|
+
define_action(action)
|
18
|
+
@actions[name] = action
|
19
|
+
end
|
20
|
+
|
21
|
+
description[:resources].each do |name, desc|
|
22
|
+
r = HaveAPI::Client::Resource.new(@api, name)
|
23
|
+
r.setup(desc)
|
24
|
+
define_resource(r)
|
25
|
+
@resources[name] = r
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def setup_from_clone(original)
|
30
|
+
original.actions.each_value do |action|
|
31
|
+
define_action(action)
|
32
|
+
end
|
33
|
+
|
34
|
+
original.resources.each_value do |resource|
|
35
|
+
define_resource(resource)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def define_action(action)
|
41
|
+
define_singleton_method(action.name) do |*args|
|
42
|
+
all_args = @prepared_args + args
|
43
|
+
|
44
|
+
if action.unresolved_args?
|
45
|
+
all_args.delete_if do |arg|
|
46
|
+
break unless action.unresolved_args?
|
47
|
+
|
48
|
+
action.provide_args(arg)
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
if action.unresolved_args?
|
53
|
+
raise ArgumentError.new('One or more object ids missing')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
all_args << {} if all_args.empty?
|
58
|
+
|
59
|
+
HaveAPI::Client::Response.new(action, action.execute(*all_args))
|
60
|
+
end
|
61
|
+
|
62
|
+
define_singleton_method("#{action.name}!".to_sym) do |*args|
|
63
|
+
ret = method(action.name).call(*args)
|
64
|
+
raise HaveAPI::Client::ActionFailed.new(ret) unless ret.ok?
|
65
|
+
|
66
|
+
ret
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def define_resource(resource)
|
71
|
+
define_singleton_method(resource.name) do |*args|
|
72
|
+
tmp = resource.dup
|
73
|
+
tmp.prepared_args = @prepared_args + args
|
74
|
+
tmp.setup_from_clone(resource)
|
75
|
+
tmp
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class HaveAPI::Client::Response
|
2
|
+
attr_reader :action
|
3
|
+
|
4
|
+
def initialize(action, response)
|
5
|
+
@action = action
|
6
|
+
@response = response
|
7
|
+
end
|
8
|
+
|
9
|
+
def ok?
|
10
|
+
@response[:status]
|
11
|
+
end
|
12
|
+
|
13
|
+
def failed?
|
14
|
+
!ok?
|
15
|
+
end
|
16
|
+
|
17
|
+
def response
|
18
|
+
case @action.layout.to_sym
|
19
|
+
when :object, :list
|
20
|
+
@response[:response][@action.namespace(:output).to_sym]
|
21
|
+
else
|
22
|
+
@response[:response]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def message
|
27
|
+
@response[:message]
|
28
|
+
end
|
29
|
+
|
30
|
+
def errors
|
31
|
+
@response[:errors]
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module RestClient
|
2
|
+
class Request
|
3
|
+
def self.execute_options(args, &block)
|
4
|
+
new(args).execute_options(&block)
|
5
|
+
end
|
6
|
+
|
7
|
+
def execute_options(&block)
|
8
|
+
uri = parse_url_with_auth(url)
|
9
|
+
http_request = Net::HTTPGenericRequest.new(method.to_s.upcase, false, true, uri.request_uri, processed_headers)
|
10
|
+
|
11
|
+
transmit uri, http_request, payload, & block
|
12
|
+
ensure
|
13
|
+
payload.close if payload
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module RestClient
|
2
|
+
class Resource
|
3
|
+
def get_options(additional_headers={}, &block)
|
4
|
+
headers = (options[:headers] || {}).merge(additional_headers)
|
5
|
+
Request.execute_options(options.merge(
|
6
|
+
:method => :options,
|
7
|
+
:url => url,
|
8
|
+
:headers => headers), &(block || @block))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
metadata
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: haveapi-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jakub Skokan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activesupport
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 4.1.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 4.1.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: require_all
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.3.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.3.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rest_client
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.7.3
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.7.3
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: json
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 1.8.1
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 1.8.1
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: highline
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.6.21
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.6.21
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: table_print
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 1.5.1
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ~>
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 1.5.1
|
125
|
+
description: Ruby API and CLI for HaveAPI
|
126
|
+
email:
|
127
|
+
- jakub.skokan@vpsfree.cz
|
128
|
+
executables:
|
129
|
+
- haveapi-cli
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- Gemfile
|
134
|
+
- README.md
|
135
|
+
- Rakefile
|
136
|
+
- bin/haveapi-cli
|
137
|
+
- haveapi-client.gemspec
|
138
|
+
- lib/haveapi/cli.rb
|
139
|
+
- lib/haveapi/cli/cli.rb
|
140
|
+
- lib/haveapi/client.rb
|
141
|
+
- lib/haveapi/client/action.rb
|
142
|
+
- lib/haveapi/client/client.rb
|
143
|
+
- lib/haveapi/client/communicator.rb
|
144
|
+
- lib/haveapi/client/exceptions.rb
|
145
|
+
- lib/haveapi/client/inflections.rb
|
146
|
+
- lib/haveapi/client/resource.rb
|
147
|
+
- lib/haveapi/client/response.rb
|
148
|
+
- lib/haveapi/client/version.rb
|
149
|
+
- lib/restclient_ext/request.rb
|
150
|
+
- lib/restclient_ext/resource.rb
|
151
|
+
homepage: ''
|
152
|
+
licenses:
|
153
|
+
- GPL
|
154
|
+
metadata: {}
|
155
|
+
post_install_message:
|
156
|
+
rdoc_options: []
|
157
|
+
require_paths:
|
158
|
+
- lib
|
159
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
160
|
+
requirements:
|
161
|
+
- - '>='
|
162
|
+
- !ruby/object:Gem::Version
|
163
|
+
version: '0'
|
164
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - '>='
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
requirements: []
|
170
|
+
rubyforge_project:
|
171
|
+
rubygems_version: 2.1.11
|
172
|
+
signing_key:
|
173
|
+
specification_version: 4
|
174
|
+
summary: Ruby API and CLI for HaveAPI
|
175
|
+
test_files: []
|