apipie-bindings 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f00c3378b7cf3f91734f1a97d241da813a71e53d
4
+ data.tar.gz: 9530ae0e0a9a00c5c73986bdde9f072064c0bcb8
5
+ SHA512:
6
+ metadata.gz: f54f73e6605fa985aa4fc17f68776184eae491ba1cdfc277756182106e21bc57e202402f13d522643a274e5ba4f268e4bb86861c141c0ea7a46fd79d4ad83cc4
7
+ data.tar.gz: acbb649416de9369c3905173bbc3477dac2a05883cc85b5713413bea0bd83cd6fc3741c2f65050086880fd0c02a0e3289808fa257dd3ec7c06a45eb5e89c8f32
data/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ This program and entire repository is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version.
2
+
3
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
4
+
5
+ You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.
data/README.md ADDED
@@ -0,0 +1,83 @@
1
+ Apipie Bindings
2
+ ===============
3
+
4
+ The Ruby bindings for Apipie documented APIs.
5
+
6
+ Features
7
+ --------
8
+
9
+ #### Caching
10
+ The bindings cache the apidoc from the server. It has separated caches for each server it connects to. If the server sends apidoc hash in the headers ```Apipie-Apidoc-Hash: <md5>``` , the bindings can expire the cache and reload updated version before next request. If the server does not send the hashes, the cache does not expire and has to be deleted manually when necessary.
11
+
12
+ Sample patch for the server can be found here https://github.com/mbacovsky/foreman/commit/d35d76b9032bb3d3de2da7fb1dc780600eb08bdd
13
+
14
+ #### API introspection
15
+ It is possible to list available resources, actions, params, routes and its attributes
16
+
17
+ ##### Getting started
18
+ ```
19
+ $ rake install
20
+ $ irb
21
+ irb(main):003:0> require 'apipie-bindings'
22
+
23
+ irb(main):001:0> api = ApipieBindings::API.new({:uri => 'http://localhost:3000/', :username => 'admin', :password => :changeme})
24
+ ```
25
+
26
+ ##### Listing resources
27
+
28
+ ```
29
+ irb(main):005:0> api.resources
30
+ => [<Resource :roles>, <Resource :images>, <Resource :reports>, <Resource :hosts>, .... <Resource :architectures>]
31
+ ```
32
+
33
+ ##### Listing actions
34
+
35
+ ```
36
+ irb(main):006:0> api.resource(:architectures).actions
37
+ => [<Action :index>, <Action :show>, <Action :create>, <Action :update>, <Action :destroy>]
38
+ ```
39
+
40
+ ##### Listing routes
41
+ ```
42
+ irb(main):008:0> api.resource(:architectures).action(:show).routes
43
+ => [<Route /api/architectures/:id>]
44
+ ```
45
+
46
+ ##### Listing params
47
+
48
+ ```
49
+ irb(main):007:0> api.resource(:architectures).action(:show).params
50
+ => [<Param *id (String)>]
51
+
52
+
53
+ irb(main):009:0> api.resource(:architectures).action(:show).params.first.required?
54
+ => true
55
+ ```
56
+
57
+ ##### Calling methods (all the calls bellow are equivalent)
58
+
59
+ ```
60
+ irb(main):012:0> api.resource(:architectures).call(:show, :id => 1)
61
+ => {"name"=>"x86_64", "id"=>1, "created_at"=>"2013-12-03T15:00:08Z", "updated_at"=>"2013-12-03T15:00:08Z"}
62
+
63
+ irb(main):013:0> api.call(:architectures, :show, :id => 1)
64
+ => {"name"=>"x86_64", "id"=>1, "created_at"=>"2013-12-03T15:00:08Z", "updated_at"=>"2013-12-03T15:00:08Z"}
65
+
66
+ irb(main):014:0> api.resource(:architectures).action(:show).call(:id => 1)
67
+ => {"name"=>"x86_64", "id"=>1, "created_at"=>"2013-12-03T15:00:08Z", "updated_at"=>"2013-12-03T15:00:08Z"}
68
+
69
+ ```
70
+
71
+ TODO
72
+ ----
73
+ * parameter validation
74
+ * better configurability
75
+ * error handling
76
+ * logging
77
+ * lots of other things
78
+
79
+
80
+ License
81
+ -------
82
+
83
+ This project is licensed under the GPLv3+.
@@ -0,0 +1 @@
1
+ require 'apipie_bindings'
@@ -0,0 +1,15 @@
1
+ module ApipieBindings
2
+
3
+ def self.log
4
+ Logging.logger['API']
5
+ end
6
+
7
+ end
8
+
9
+ require 'apipie_bindings/version'
10
+ require 'apipie_bindings/api'
11
+ require 'apipie_bindings/resource'
12
+ require 'apipie_bindings/action'
13
+ require 'apipie_bindings/route'
14
+ require 'apipie_bindings/param'
15
+ require 'apipie_bindings/example'
@@ -0,0 +1,76 @@
1
+ module ApipieBindings
2
+
3
+ class Action
4
+
5
+ attr_reader :name
6
+
7
+ def initialize(resource, name, api)
8
+ @resource = resource
9
+ @name = name.to_sym
10
+ @api = api
11
+ end
12
+
13
+ def call(params={}, headers={}, options={})
14
+ @api.call(@resource, @name, params, headers, options)
15
+ end
16
+
17
+ def apidoc
18
+ methods = @api.apidoc[:docs][:resources][@resource][:methods].select do |action|
19
+ action[:name].to_sym == @name
20
+ end
21
+ methods.first
22
+ end
23
+
24
+ def routes
25
+ apidoc[:apis].map do |api|
26
+ ApipieBindings::Route.new(
27
+ api[:api_url], api[:http_method], api[:short_description])
28
+ end
29
+ end
30
+
31
+ def params
32
+ apidoc[:params].map do |param|
33
+ ApipieBindings::Param.new(param)
34
+ end
35
+ end
36
+
37
+ def examples
38
+ apidoc[:examples].map do |example|
39
+ ApipieBindings::Example.parse(example)
40
+ end
41
+ end
42
+
43
+ def find_route(params={})
44
+ sorted_routes = routes.sort_by { |r| [-1 * r.params_in_path.count, r.path] }
45
+
46
+ suitable_route = sorted_routes.find do |route|
47
+ route.params_in_path.all? { |path_param| params.keys.map(&:to_s).include?(path_param) }
48
+ end
49
+
50
+ suitable_route ||= sorted_routes.last
51
+ return suitable_route
52
+ end
53
+
54
+ def validate!(params)
55
+ # return unless params.is_a?(Hash)
56
+
57
+ # invalid_keys = params.keys.map(&:to_s) - (rules.is_a?(Hash) ? rules.keys : rules)
58
+ # raise ArgumentError, "Invalid keys: #{invalid_keys.join(", ")}" unless invalid_keys.empty?
59
+
60
+ # if rules.is_a? Hash
61
+ # rules.each do |key, sub_keys|
62
+ # validate_params!(params[key], sub_keys) if params[key]
63
+ # end
64
+ # end
65
+ end
66
+
67
+ def to_s
68
+ "<Action :#{@name}>"
69
+ end
70
+
71
+ def inspect
72
+ to_s
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,178 @@
1
+ require 'json'
2
+ require 'rest_client'
3
+ require 'oauth'
4
+ require 'logging'
5
+ require 'awesome_print'
6
+ module ApipieBindings
7
+
8
+ class API
9
+
10
+ attr_reader :apidoc_cache_name, :fake_responses
11
+ attr_writer :dry_run
12
+
13
+ def initialize(config, options={})
14
+ @uri = config[:uri]
15
+ @api_version = config[:api_version] || 2
16
+ @apidoc_cache_dir = config[:apidoc_cache_dir] || File.join('/tmp/apipie_bindings', @uri.tr(':/', '_'))
17
+ @apidoc_cache_name = config[:apidoc_cache_name] || set_default_name
18
+ @dry_run = config[:dry_run] || false
19
+ @fake_responses = {}
20
+
21
+ config = config.dup
22
+
23
+ headers = {
24
+ :content_type => 'application/json',
25
+ :accept => "application/json;version=#{@api_version}"
26
+ }
27
+ headers.merge!(config[:headers]) unless config[:headers].nil?
28
+ headers.merge!(options.delete(:headers)) unless options[:headers].nil?
29
+
30
+ resource_config = {
31
+ :user => config[:username],
32
+ :password => config[:password],
33
+ :oauth => config[:oauth],
34
+ :headers => headers
35
+ }.merge(options)
36
+
37
+ @client = RestClient::Resource.new(config[:uri], resource_config)
38
+ @config = config
39
+ end
40
+
41
+ def set_default_name(default='default')
42
+ cache_file = Dir["#{@apidoc_cache_dir}/*.json"].first
43
+ if cache_file
44
+ File.basename(cache_file, ".json")
45
+ else
46
+ default
47
+ end
48
+ end
49
+
50
+ def apidoc_cache_file
51
+ File.join(@apidoc_cache_dir, "#{@apidoc_cache_name}.json")
52
+ end
53
+
54
+ def load_apidoc
55
+ if File.exist?(apidoc_cache_file)
56
+ JSON.parse(File.read(apidoc_cache_file), :symbolize_names => true)
57
+ end
58
+ end
59
+
60
+ def retrieve_apidoc
61
+ FileUtils.mkdir_p(@apidoc_cache_dir) unless File.exists?(@apidoc_cache_dir)
62
+ path = "/apidoc/v#{@api_version}.json"
63
+ begin
64
+ response = http_call('get', path, {},
65
+ {:accept => "application/json;version=1"}, {:response => :raw})
66
+ rescue
67
+ raise "Could not load data from #{@uri}#{path}"
68
+ end
69
+ File.open(apidoc_cache_file, "w") { |f| f.write(response.body) }
70
+ ApipieBindings.log.debug "New apidoc loaded from the server"
71
+ load_apidoc
72
+ end
73
+
74
+ def apidoc
75
+ @apidoc = @apidoc || load_apidoc || retrieve_apidoc
76
+ @apidoc
77
+ end
78
+
79
+ def dry_run?
80
+ @dry_run ? true : false
81
+ end
82
+
83
+ def has_resource?(name)
84
+ apidoc[:docs][:resources].has_key? name
85
+ end
86
+
87
+ def resource(name)
88
+ ApipieBindings::Resource.new(name, self)
89
+ end
90
+
91
+ def resources
92
+ apidoc[:docs][:resources].keys.map { |res| resource(res) }
93
+ end
94
+
95
+ def call(resource_name, action_name, params={}, headers={}, options={})
96
+ resource = resource(resource_name)
97
+ action = resource.action(action_name)
98
+ route = action.find_route(params)
99
+ #action.validate(params)
100
+ options[:fake_response] = find_match(fake_responses, resource_name, action_name, params) || action.examples.first if dry_run?
101
+ return http_call(
102
+ route.method,
103
+ route.path(params),
104
+ params.reject { |par, _| route.params_in_path.include? par.to_s },
105
+ headers, options)
106
+ end
107
+
108
+
109
+ def http_call(http_method, path, params={}, headers={}, options={})
110
+ headers ||= { }
111
+
112
+ args = [http_method]
113
+ if %w[post put].include?(http_method.to_s)
114
+ args << params.to_json
115
+ else
116
+ headers[:params] = params if params
117
+ end
118
+
119
+ ApipieBindings.log.info "#{http_method.to_s.upcase} #{path}"
120
+ ApipieBindings.log.debug "Params: #{params.ai}"
121
+ # logger.debug "Headers: #{headers.inspect}"
122
+
123
+ args << headers if headers
124
+
125
+ if dry_run?
126
+ empty_response = ApipieBindings::Example.new('', '', '', 200, '')
127
+ ex = options[:fake_response ] || empty_response
128
+ response = RestClient::Response.create(ex.response, ex.status, ex.args)
129
+ else
130
+ response = @client[path].send(*args)
131
+ update_cache(response.headers[:apipie_apidoc_hash])
132
+ end
133
+
134
+ result = options[:response] == :raw ? response : process_data(response)
135
+ ApipieBindings.log.debug "Response #{result.ai}"
136
+ result
137
+ end
138
+
139
+ def process_data(response)
140
+ data = begin
141
+ JSON.parse(response.body)
142
+ rescue JSON::ParserError
143
+ response.body
144
+ end
145
+ # logger.debug "Returned data: #{data.inspect}"
146
+ return data
147
+ end
148
+
149
+ def update_cache(cache_name)
150
+ if !cache_name.nil? && (cache_name != @apidoc_cache_name)
151
+ clean_cache
152
+ ApipieBindings.log.debug "Cache expired. (#{@apidoc_cache_name} -> #{cache_name})"
153
+ @apidoc_cache_name = cache_name
154
+ end
155
+ end
156
+
157
+ def clean_cache
158
+ @apidoc = nil
159
+ Dir["#{@apidoc_cache_dir}/*.json"].each { |f| File.delete(f) }
160
+ end
161
+
162
+ private
163
+
164
+ def find_match(fakes, resource, action, params)
165
+ resource = fakes[[resource, action]]
166
+ if resource
167
+ if resource.has_key?(params)
168
+ return resource[params]
169
+ elsif resource.has_key?(:default)
170
+ return resource[:default]
171
+ end
172
+ end
173
+ return nil
174
+ end
175
+
176
+ end
177
+
178
+ end
@@ -0,0 +1,21 @@
1
+ module ApipieBindings
2
+
3
+ class Example
4
+
5
+ attr_reader :http_method, :path, :args, :status, :response
6
+
7
+ def initialize(http_method, path, args, status, response)
8
+ @http_method = http_method
9
+ @path = path
10
+ @args = args
11
+ @status = status.to_i
12
+ @response = response
13
+ end
14
+
15
+ def self.parse(example)
16
+ prep = /(\w+)\ ([^\n]*)\n?(.*)?\n(\d+)\n(.*)/m.match(example)
17
+ new(*prep[1..5])
18
+ end
19
+ end
20
+
21
+ end
@@ -0,0 +1,34 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+
3
+ module ApipieBindings
4
+
5
+ class Param
6
+
7
+ attr_reader :name, :params, :expected_type, :description, :validator
8
+
9
+ def initialize(param)
10
+ param = param.with_indifferent_access
11
+ @name = param[:name]
12
+ params = param[:params] || []
13
+ @params = params.map { |p| ApipieBindings::Param.new(p) }
14
+ @expected_type = param[:expected_type].to_sym
15
+ @description = param[:description].gsub(/<\/?[^>]+?>/, "")
16
+ @required = param[:required]
17
+ @validator = param[:validator]
18
+ end
19
+
20
+ def required?
21
+ @required
22
+ end
23
+
24
+ def to_s
25
+ "<Param #{ required? ? '*' : '' }#{@name} (#{@expected_type.capitalize})>"
26
+ end
27
+
28
+ def inspect
29
+ to_s
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,49 @@
1
+ require 'active_support/core_ext/string'
2
+
3
+ module ApipieBindings
4
+
5
+ class Resource
6
+
7
+ attr_reader :name
8
+
9
+ def initialize(name, api)
10
+ raise NameError.new("Resource '#{name}' does not exist in the API") unless api.apidoc[:docs][:resources].key?(name)
11
+ @name = name
12
+ @api = api
13
+ end
14
+
15
+ def call(action, params={}, headers={}, options={})
16
+ @api.call(@name, action, params, headers, options)
17
+ end
18
+
19
+ def apidoc
20
+ @api.apidoc[:docs][:resources][@name]
21
+ end
22
+
23
+ def actions
24
+ apidoc[:methods].map { |a| action(a[:name].to_sym) }
25
+ end
26
+
27
+ def has_action?(name)
28
+ apidoc[:methods].any? { |action| action[:name].to_sym == name }
29
+ end
30
+
31
+ def action(name)
32
+ ApipieBindings::Action.new(@name, name, @api)
33
+ end
34
+
35
+ def singular_name
36
+ @name.to_s.singularize
37
+ end
38
+
39
+ def to_s
40
+ "<Resource :#{@name}>"
41
+ end
42
+
43
+ def inspect
44
+ to_s
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,37 @@
1
+ module ApipieBindings
2
+
3
+ class Route
4
+
5
+ attr_reader :method, :description
6
+
7
+ def initialize(path, method, description="")
8
+ @path = path
9
+ @method = method.downcase
10
+ @description = description
11
+ end
12
+
13
+ def params_in_path
14
+ @path.scan(/:([^\/]*)/).map { |m| m.first }
15
+ end
16
+
17
+ def path(params=nil)
18
+ return @path if params.nil?
19
+
20
+ path = params_in_path.inject(@path) do |p, param_name|
21
+ param_value = (params[param_name.to_sym] or params[param_name.to_s]) or
22
+ raise ArgumentError, "missing param '#{param_name}' in parameters"
23
+ p.sub(":#{param_name}", URI.escape(param_value.to_s))
24
+ end
25
+ end
26
+
27
+ def to_s
28
+ "<Route #{@path}>"
29
+ end
30
+
31
+ def inspect
32
+ to_s
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -0,0 +1,5 @@
1
+ module ApipieBindings
2
+ def self.version
3
+ @version ||= Gem::Version.new '0.0.1'
4
+ end
5
+ end
@@ -0,0 +1,61 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ describe ApipieBindings::Action do
4
+
5
+ let(:resource) { ApipieBindings::API.new({:apidoc_cache_dir => 'test/unit/data',
6
+ :apidoc_cache_name => 'architecture'}).resource(:architectures) }
7
+
8
+ it "should allow user to call the action" do
9
+ params = { :a => 1 }
10
+ headers = { :content_type => 'application/json' }
11
+ ApipieBindings::API.any_instance.expects(:call).with(:architectures, :index, params, headers, {})
12
+ resource.action(:index).call(params, headers)
13
+ end
14
+
15
+ it "should return routes" do
16
+ resource.action(:index).routes.first.must_be_kind_of ApipieBindings::Route
17
+ end
18
+
19
+ it "should find suitable route" do
20
+ resource.action(:index).find_route.path.must_equal "/api/architectures"
21
+ end
22
+
23
+ it "should return params" do
24
+ resource.action(:create).params.map(&:name).must_equal ['architecture']
25
+ end
26
+
27
+ # TODO add tests for more find_route cases
28
+ # @param possible_apis [Array] Array of hasahs in form of
29
+ # [{:api_url => '/path1', :http_method => 'GET'}, {...}]
30
+ # @param params [Hash] enterred params
31
+ # @return api that suits the enterred params mosts
32
+ #
33
+ # Given this paths:
34
+ # 1. +/comments+
35
+ # 2. +/users/:user_id/comments+
36
+ # 3. +/users/:user_id/posts/:post_id/comments+
37
+ #
38
+ # If +:user_id+ and +:post_id+ is pecified, the third path is
39
+ # used. If only +:user_id+ is specified, the second one is used.
40
+ # The selection defaults to the path with the least number of
41
+ # incuded params in alphanumeric order.
42
+
43
+
44
+ it "should validate the params" do
45
+ resource.action(:create).validate!({ :architecture => { :name => 'i386' } })
46
+ end
47
+
48
+ it "should have name visible in puts" do
49
+ out, err = capture_io { puts resource.action(:index) }
50
+ out.must_equal "<Action :index>\n"
51
+ end
52
+
53
+ it "should have name visible in inspect" do
54
+ resource.action(:index).inspect.must_equal "<Action :index>"
55
+ end
56
+
57
+ it "should have examples" do
58
+ resource.action(:index).examples.length.must_equal 1
59
+ end
60
+
61
+ end
@@ -0,0 +1,109 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ describe ApipieBindings::API do
4
+
5
+ let(:api) { ApipieBindings::API.new({:apidoc_cache_dir => 'test/unit/data', :apidoc_cache_name => 'architecture'}) }
6
+
7
+ it "should provide resource" do
8
+ result = api.resource(:architectures)
9
+ result.must_be_kind_of ApipieBindings::Resource
10
+ end
11
+
12
+ it "should test if resource is available" do
13
+ api.has_resource?(:architectures).must_equal true
14
+ end
15
+
16
+ it "should list resources" do
17
+ api.resources.map(&:name).must_equal [:architectures]
18
+ end
19
+
20
+ # it "should have apidoc_cache_file available" do
21
+ # end
22
+
23
+ it "should call the method" do
24
+ params = { :a => 1 }
25
+ headers = { :content_type => 'application/json' }
26
+ ApipieBindings::API.any_instance.expects(:http_call).with('get', '/api/architectures', params, headers, {})
27
+ api.call(:architectures, :index, params, headers)
28
+ end
29
+
30
+ it "should call the method and fill in the params" do
31
+ params = { :id => 1 }
32
+ headers = { :content_type => 'application/json' }
33
+ ApipieBindings::API.any_instance.expects(:http_call).with('get', '/api/architectures/1', {}, headers, {})
34
+ api.call(:architectures, :show, params, headers)
35
+ end
36
+
37
+ it "should return values from examples in dry_run mode" do
38
+ api.dry_run = true
39
+ result = api.call(:architectures, :index)
40
+ result.must_be_kind_of Array
41
+ end
42
+
43
+ it "should allow to set dry_run mode in config params" do
44
+ api = ApipieBindings::API.new({
45
+ :apidoc_cache_dir => 'test/unit/data',
46
+ :apidoc_cache_name => 'architecture',
47
+ :dry_run => true })
48
+ result = api.call(:architectures, :index)
49
+ result.must_be_kind_of Array
50
+ end
51
+
52
+ it "should allow to set fake response in config params" do
53
+ api = ApipieBindings::API.new({
54
+ :apidoc_cache_dir => 'test/unit/data',
55
+ :apidoc_cache_name => 'architecture',
56
+ :dry_run => true,
57
+ :fake_params => { [:architectures, :index] => {:default => [] } }} )
58
+ result = api.call(:architectures, :index)
59
+ result.must_be_kind_of Array
60
+ end
61
+
62
+ context "update_cache" do
63
+
64
+ before :each do
65
+ @dir = Dir.mktmpdir
66
+ @api = ApipieBindings::API.new({:apidoc_cache_dir => @dir, :apidoc_cache_name => 'cache'})
67
+ @api.stubs(:retrieve_apidoc).returns(nil)
68
+ File.open(@api.apidoc_cache_file, "w") { |f| f.write(['test'].to_json) }
69
+ end
70
+
71
+ after :each do
72
+ Dir["#{@dir}/*"].each { |f| File.delete(f) }
73
+ Dir.delete(@dir)
74
+ end
75
+
76
+ it "should clean the internal cache when the name has changed" do
77
+ @api.update_cache('new_name')
78
+ @api.apidoc.must_equal nil
79
+ end
80
+
81
+ it "should clean the cache dir when the name has changed" do
82
+ @api.update_cache('new_name')
83
+ Dir["#{@dir}/*"].must_be_empty
84
+ end
85
+
86
+ it "should set the new cache name when the name has changed" do
87
+ @api.update_cache('new_name')
88
+ @api.apidoc_cache_file.must_equal File.join(@dir, 'new_name.json')
89
+ end
90
+
91
+ it "should not touch enything if the name is same" do
92
+ @api.update_cache('cache')
93
+ @api.apidoc.must_equal ['test']
94
+ end
95
+
96
+ # caching is turned off on server
97
+ it "should not touch enything if the name is nil" do
98
+ @api.update_cache(nil)
99
+ @api.apidoc.must_equal ['test']
100
+ end
101
+
102
+ it "should load cache and its name from cache dir" do
103
+ FileUtils.cp('test/unit/data/architecture.json', File.join(@dir, 'api_cache.json'))
104
+ @api = ApipieBindings::API.new({:apidoc_cache_dir => @dir})
105
+ @api.apidoc_cache_name.must_equal 'api_cache'
106
+ end
107
+ end
108
+
109
+ end
@@ -0,0 +1,153 @@
1
+ {
2
+ "docs": {
3
+ "api_url": "/api",
4
+ "copyright": "",
5
+ "doc_url": "/apidoc/v2",
6
+ "info": "Another API description",
7
+ "name": "Foreman",
8
+ "resources": {
9
+ "architectures": {
10
+ "api_url": "/api",
11
+ "doc_url": "/apidoc/v2/architectures",
12
+ "formats": null,
13
+ "full_description": null,
14
+ "methods": [
15
+ {
16
+ "apis": [
17
+ {
18
+ "api_url": "/api/architectures",
19
+ "http_method": "GET",
20
+ "short_description": "List all architectures."
21
+ }
22
+ ],
23
+ "doc_url": "/apidoc/v2/architectures/index",
24
+ "errors": [],
25
+ "examples": [
26
+ "GET /api/architectures\n200\n[\n {\n \"architecture\": {\n \"name\": \"s390\",\n \"id\": 381564594,\n \"updated_at\": \"2012-12-18T15:24:42Z\",\n \"operatingsystem_ids\": [],\n \"created_at\": \"2012-12-18T15:24:42Z\"\n }\n },\n {\n \"architecture\": {\n \"name\": \"sparc\",\n \"id\": 331892513,\n \"updated_at\": \"2012-12-18T15:24:42Z\",\n \"operatingsystem_ids\": [\n 442321401\n ],\n \"created_at\": \"2012-12-18T15:24:42Z\"\n }\n },\n {\n \"architecture\": {\n \"name\": \"x86_64\",\n \"id\": 501905019,\n \"updated_at\": \"2012-12-18T15:24:42Z\",\n \"operatingsystem_ids\": [\n 331303656,\n 309172073,\n 1073012828\n ],\n \"created_at\": \"2012-12-18T15:24:42Z\"\n }\n }\n]"
27
+ ],
28
+ "formats": null,
29
+ "full_description": "",
30
+ "name": "index",
31
+ "params": [
32
+ {
33
+ "allow_nil": false,
34
+ "description": "\n<p>filter results</p>\n",
35
+ "expected_type": "string",
36
+ "full_name": "search",
37
+ "name": "search",
38
+ "required": false,
39
+ "validator": "Must be String"
40
+ },
41
+ {
42
+ "allow_nil": false,
43
+ "description": "\n<p>sort results</p>\n",
44
+ "expected_type": "string",
45
+ "full_name": "order",
46
+ "name": "order",
47
+ "required": false,
48
+ "validator": "Must be String"
49
+ },
50
+ {
51
+ "allow_nil": false,
52
+ "description": "\n<p>paginate results</p>\n",
53
+ "expected_type": "string",
54
+ "full_name": "page",
55
+ "name": "page",
56
+ "required": false,
57
+ "validator": "Must be String"
58
+ },
59
+ {
60
+ "allow_nil": false,
61
+ "description": "\n<p>number of entries per request</p>\n",
62
+ "expected_type": "string",
63
+ "full_name": "per_page",
64
+ "name": "per_page",
65
+ "required": false,
66
+ "validator": "Must be String"
67
+ }
68
+ ],
69
+ "see": []
70
+ },
71
+ {
72
+ "apis": [
73
+ {
74
+ "api_url": "/api/architectures/:id",
75
+ "http_method": "GET",
76
+ "short_description": "Show an architecture."
77
+ }
78
+ ],
79
+ "doc_url": "/apidoc/v2/architectures/show",
80
+ "errors": [],
81
+ "examples": [
82
+ "GET /api/architectures/x86_64\n200\n{\n \"architecture\": {\n \"name\": \"x86_64\",\n \"id\": 501905019,\n \"updated_at\": \"2012-12-18T15:24:42Z\",\n \"operatingsystem_ids\": [\n 309172073,\n 1073012828,\n 331303656\n ],\n \"created_at\": \"2012-12-18T15:24:42Z\"\n }\n}"
83
+ ],
84
+ "formats": null,
85
+ "full_description": "",
86
+ "name": "show",
87
+ "params": [
88
+ {
89
+ "allow_nil": false,
90
+ "description": "",
91
+ "expected_type": "string",
92
+ "full_name": "id",
93
+ "name": "id",
94
+ "required": true,
95
+ "validator": "Must be an identifier, string from 1 to 128 characters containing only alphanumeric characters, space, underscore(_), hypen(-) with no leading or trailing space."
96
+ }
97
+ ],
98
+ "see": []
99
+ },
100
+ {
101
+ "apis": [
102
+ {
103
+ "api_url": "/api/architectures",
104
+ "http_method": "POST",
105
+ "short_description": "Create an architecture."
106
+ }
107
+ ],
108
+ "doc_url": "/apidoc/v2/architectures/create",
109
+ "errors": [],
110
+ "examples": [
111
+ "POST /api/architectures\n{\n \"architecture\": {\n \"name\": \"i386\"\n }\n}\n200\n{\n \"architecture\": {\n \"name\": \"i386\",\n \"id\": 501905020,\n \"updated_at\": \"2012-12-18T15:24:43Z\",\n \"operatingsystem_ids\": [],\n \"created_at\": \"2012-12-18T15:24:43Z\"\n }\n}"
112
+ ],
113
+ "formats": null,
114
+ "full_description": "",
115
+ "name": "create",
116
+ "params": [
117
+ {
118
+ "allow_nil": false,
119
+ "description": "",
120
+ "expected_type": "hash",
121
+ "full_name": "architecture",
122
+ "name": "architecture",
123
+ "params": [
124
+ {
125
+ "allow_nil": false,
126
+ "description": "",
127
+ "expected_type": "string",
128
+ "full_name": "architecture[name]",
129
+ "name": "name",
130
+ "required": true,
131
+ "validator": "Must be String"
132
+ },
133
+ {
134
+ "allow_nil": false,
135
+ "description": "\n<p>Operatingsystem ID\u2019s</p>\n",
136
+ "expected_type": "array",
137
+ "full_name": "architecture[operatingsystem_ids]",
138
+ "name": "operatingsystem_ids",
139
+ "required": false,
140
+ "validator": "Must be Array"
141
+ }
142
+ ],
143
+ "required": true,
144
+ "validator": "Must be a Hash"
145
+ }
146
+ ],
147
+ "see": []
148
+ }
149
+ ]
150
+ }
151
+ }
152
+ }
153
+ }
@@ -0,0 +1,25 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ describe ApipieBindings::Example do
4
+
5
+ it "should parse the example format from apidoc POST" do
6
+ example_string = "POST /api/architectures\n{\n \"architecture\": {\n \"name\": \"i386\"\n }\n}\n200\n{\n \"architecture\": {\n \"name\": \"i386\",\n \"id\": 501905020,\n \"updated_at\": \"2012-12-18T15:24:43Z\",\n \"operatingsystem_ids\": [],\n \"created_at\": \"2012-12-18T15:24:43Z\"\n }\n}"
7
+ ex = ApipieBindings::Example.parse(example_string)
8
+ ex.http_method.must_equal 'POST'
9
+ ex.path.must_equal '/api/architectures'
10
+ ex.args.must_equal "{\n \"architecture\": {\n \"name\": \"i386\"\n }\n}"
11
+ ex.status.must_equal 200
12
+ ex.response.must_include "{\n \"architecture\": "
13
+ end
14
+
15
+ it "should parse the example format from apidoc GET" do
16
+ example_string = "GET /api/architectures/x86_64\n200\n{\n \"architecture\": {\n \"name\": \"x86_64\",\n \"id\": 501905019,\n \"updated_at\": \"2012-12-18T15:24:42Z\",\n \"operatingsystem_ids\": [\n 309172073,\n 1073012828,\n 331303656\n ],\n \"created_at\": \"2012-12-18T15:24:42Z\"\n }\n}"
17
+ ex = ApipieBindings::Example.parse(example_string)
18
+ ex.http_method.must_equal 'GET'
19
+ ex.path.must_equal '/api/architectures/x86_64'
20
+ ex.args.must_equal ""
21
+ ex.status.must_equal 200
22
+ ex.response.must_include "{\n \"architecture\": "
23
+ end
24
+
25
+ end
@@ -0,0 +1,5 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ describe ApipieBindings do
4
+
5
+ end
@@ -0,0 +1,57 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ describe ApipieBindings::Param do
4
+
5
+ let(:param) {ApipieBindings::Param.new({
6
+ "allow_nil" => false,
7
+ "description" => "<p>Architecture</p>",
8
+ "expected_type" => "hash",
9
+ "full_name" => "architecture",
10
+ "name" => "architecture",
11
+ "params" => [
12
+ {
13
+ "allow_nil" => false,
14
+ "description" => "",
15
+ "expected_type" => "string",
16
+ "full_name" => "architecture[name]",
17
+ "name" => "name",
18
+ "required" => false,
19
+ "validator" => "Must be String"
20
+ }
21
+
22
+ ],
23
+ "required" => true,
24
+ "validator" => "Must be a Hash"
25
+ }
26
+ )}
27
+ it "should create nested params" do
28
+ param.params.first.name.must_equal 'name'
29
+ end
30
+
31
+ it "should have expected_type" do
32
+ param.expected_type.must_equal :hash
33
+ end
34
+
35
+ it "should have description taht strip html tags" do
36
+ param.description.must_equal "Architecture"
37
+ end
38
+
39
+ it "should have required?" do
40
+ param.required?.must_equal true
41
+ param.params.first.required?.must_equal false
42
+ end
43
+
44
+ it "should have validator" do
45
+ param.validator.must_equal "Must be a Hash"
46
+ end
47
+
48
+ it "should have full name, type and required visible in puts" do
49
+ out, err = capture_io { puts param }
50
+ out.must_equal "<Param *architecture (Hash)>\n"
51
+ end
52
+
53
+ it "should have full name, type and required visible in inspect" do
54
+ param.inspect.must_equal "<Param *architecture (Hash)>"
55
+ end
56
+
57
+ end
@@ -0,0 +1,49 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ describe ApipieBindings::Resource do
4
+
5
+ let(:resource) { ApipieBindings::API.new({:apidoc_cache_dir => 'test/unit/data',
6
+ :apidoc_cache_name => 'architecture'}).resource(:architectures) }
7
+
8
+ it "should list actions" do
9
+ resource.actions.map(&:name).must_equal [:index, :show, :create]
10
+ end
11
+
12
+ it "should test action existence" do
13
+ resource.has_action?(:index).must_equal true
14
+ end
15
+
16
+ it "should return action" do
17
+ resource.action(:index).must_be_kind_of ApipieBindings::Action
18
+ end
19
+
20
+ it "should allow user to call the action" do
21
+ params = { :a => 1 }
22
+ headers = { :content_type => 'application/json' }
23
+ ApipieBindings::API.any_instance.expects(:call).with(:architectures, :index, params, headers, {})
24
+ resource.call(:index, params, headers)
25
+ end
26
+
27
+ it "should allow user to call the action with minimal params" do
28
+ ApipieBindings::API.any_instance.expects(:call).with(:architectures, :index, {}, {}, {})
29
+ resource.call(:index)
30
+ end
31
+
32
+ it "should print name in singular on demand" do
33
+ resource.singular_name.must_equal 'architecture'
34
+ end
35
+
36
+ it "should have name visible in puts" do
37
+ out, err = capture_io { puts resource }
38
+ out.must_equal "<Resource :architectures>\n"
39
+ end
40
+
41
+ it "should have name visible in inspect" do
42
+ resource.inspect.must_equal "<Resource :architectures>"
43
+ end
44
+
45
+ it "should rise error when the resource does no exist" do
46
+ assert_raises( NameError ){ ApipieBindings::API.new({:apidoc_cache_dir => 'test/unit/data',
47
+ :apidoc_cache_name => 'architecture'}).resource(:none) }
48
+ end
49
+ end
@@ -0,0 +1,36 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ describe ApipieBindings::Route do
4
+
5
+ let(:route) { ApipieBindings::Route.new("/api/architectures/:id", "GET") }
6
+ it "should list params in path" do
7
+ route.params_in_path.must_equal ['id']
8
+ end
9
+
10
+ it "should downcase the method" do
11
+ route.method.must_equal 'get'
12
+ end
13
+
14
+ it "should fill in the params" do
15
+ route.path({ "id" => 1 }).must_equal "/api/architectures/1"
16
+ end
17
+
18
+ it "should fill in the params as symbols" do
19
+ route.path({ :id => 1 }).must_equal "/api/architectures/1"
20
+ end
21
+
22
+ it "should return the path as is without the params" do
23
+ route.path.must_equal "/api/architectures/:id"
24
+ end
25
+
26
+ it "should have path visible in puts" do
27
+ out, err = capture_io { puts route }
28
+ out.must_equal "<Route /api/architectures/:id>\n"
29
+ end
30
+
31
+ it "should have path visible in inspect" do
32
+ route.inspect.must_equal "<Route /api/architectures/:id>"
33
+ end
34
+
35
+
36
+ end
@@ -0,0 +1,19 @@
1
+ require 'simplecov'
2
+ require 'pathname'
3
+
4
+ SimpleCov.use_merging true
5
+ SimpleCov.start do
6
+ command_name 'MiniTest'
7
+ add_filter 'test'
8
+ end
9
+ SimpleCov.root Pathname.new(File.dirname(__FILE__) + "../../../")
10
+
11
+
12
+ require 'minitest/autorun'
13
+ require 'minitest/spec'
14
+ require "minitest-spec-context"
15
+ require "mocha/setup"
16
+
17
+ require 'apipie_bindings'
18
+
19
+ Logging.logger.root.appenders = Logging::Appenders['__test__'] || Logging::Appenders::StringIo.new('__test__')
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: apipie-bindings
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Martin Bačovský
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rest-client
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 1.6.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.6.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: oauth
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: activesupport
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: i18n
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: |
84
+ Bindings for API calls that are documented with Apipie. Bindings are generated on the fly.
85
+ email: mbacovsk@redhat.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files:
89
+ - README.md
90
+ files:
91
+ - LICENSE
92
+ - README.md
93
+ - lib/apipie-bindings.rb
94
+ - lib/apipie_bindings.rb
95
+ - lib/apipie_bindings/action.rb
96
+ - lib/apipie_bindings/api.rb
97
+ - lib/apipie_bindings/example.rb
98
+ - lib/apipie_bindings/param.rb
99
+ - lib/apipie_bindings/resource.rb
100
+ - lib/apipie_bindings/route.rb
101
+ - lib/apipie_bindings/version.rb
102
+ - test/unit/action_test.rb
103
+ - test/unit/api_test.rb
104
+ - test/unit/data/architecture.json
105
+ - test/unit/example_test.rb
106
+ - test/unit/main_test.rb
107
+ - test/unit/param_test.rb
108
+ - test/unit/resource_test.rb
109
+ - test/unit/route_test.rb
110
+ - test/unit/test_helper.rb
111
+ homepage: http://github.com/Apipie/apipie-bindings
112
+ licenses:
113
+ - GPL-3
114
+ metadata: {}
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - '>='
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - '>='
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubyforge_project:
131
+ rubygems_version: 2.0.14
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: The Ruby bindings for Apipie documented APIs
135
+ test_files:
136
+ - test/unit/action_test.rb
137
+ - test/unit/api_test.rb
138
+ - test/unit/data/architecture.json
139
+ - test/unit/example_test.rb
140
+ - test/unit/main_test.rb
141
+ - test/unit/param_test.rb
142
+ - test/unit/resource_test.rb
143
+ - test/unit/route_test.rb
144
+ - test/unit/test_helper.rb
145
+ has_rdoc: