rest_chain 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0b52975f97979dfa59a9c6ce08c761c5f04b8e85
4
+ data.tar.gz: 6bc0900a599d7c35f3d15939ed97a31bfed2d547
5
+ SHA512:
6
+ metadata.gz: db20f8c62207117469e4eec63e15ee561d378dcd16f1bff847174148b10c2ff9467ca2bff05fc1dd00ba09a71d4874d647c4a21f7831cd7761b6012d5b949f1d
7
+ data.tar.gz: 74252ca8c40db09432d786e511a228c1ad2f64b5279c4629de40c90302e2dcab859457eb6a9e90146ccc55969e1cfea907cd1db7713cace0134707f6f9f7c74c
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rest_chain.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2016 Peter Wood
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # RestChain
2
+
3
+ RestChain is a library to wrap REST API calls and make their use look and feel
4
+ like standard method calls. RestChain is based on the Rest Client library.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'rest_chain'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install rest_chain
21
+
22
+ ## Usage
23
+
24
+ First thing to do is to require in the library. Do this bad adding a line like
25
+ the following to the top of your script...
26
+
27
+ ```ruby
28
+ require "rest_chain"
29
+ ```
30
+
31
+ Next you need to create a ```RestChain::Chain``` object. The ```Chain``` class
32
+ constructor takes a Hash of setting options and recognises the following keys
33
+ in this Hash...
34
+
35
+ * ```:host``` A String containing the name of the host on which the REST API
36
+ service is running. You must specify this value.
37
+
38
+ * ```:logger``` A Logger class instance that the ```Chain``` will use for
39
+ logging purposes. This is optional.
40
+
41
+ * ```:parameters``` A Hash of common parameters (i.e. parameters that are to
42
+ be part of all requests). This is optional.
43
+
44
+ * ```:path``` An Array or String of the common parts of the path used in REST
45
+ API URLs. For example if the path for all REST API URLs started
46
+ with /api/v1/ you could specify these in this parameter to avoid
47
+ having to repeatedly specify them on chained REST calls. This is
48
+ optional.
49
+
50
+ * ```:port``` The port number the REST API service is listening on. Only needed
51
+ if the service is running on a non-standard port (i.e. 80 for HTTP
52
+ services or 443 for HTTPS services). This is optional.
53
+
54
+ * ```:scheme``` The protocol scheme to be used when making REST API requests.
55
+ Options are "http" or "https". This is optional with the default
56
+ being "https".
57
+
58
+ So creating a new ```Chain``` object might look as follows...
59
+
60
+ ```ruby
61
+ chain = RestChain::Chain.new(host: "myresthost.com", path: ["api", "v1"])
62
+ ```
63
+
64
+ Once you have a ```Chain``` object you can make requests against it. Lets say
65
+ you wished to fetch the details for a specific user from your REST service and
66
+ the URL to do that looked as follows
67
+
68
+ ```
69
+ https://myresthost.com/api/v1/users/<user id>
70
+ ```
71
+
72
+ Where the ```<user id>``` part will be replaced by the unique identifier of the
73
+ users whose details are to be retrieved. You could use the ```Chain``` object to
74
+ fetch a user with an id of 123 as follows...
75
+
76
+ ```ruby
77
+ response = chain.users(123).get
78
+ ```
79
+
80
+ Note that if there were parameters that you wanted to append to the request (e.g.
81
+ a session id) then you can specify them as part of the 'verb' method that is used
82
+ to initiate the request. So, using the previous example as a basis, you could add
83
+ the session id like this...
84
+
85
+ ```ruby
86
+ response = chain.users(123).get(session_id: session.id)
87
+ ```
88
+
89
+ Methods are provided for the ```delete```, ```get```, ```patch```, ```post``` and
90
+ ```put``` verbs that all work in a similar fashion.
91
+
92
+ This call would return a ```RestChain::RestResponse``` object. This object will
93
+ possess details for the response received for the request including the status
94
+ code of the response, the headers and the response body. If you knew your response
95
+ would contain JSON formatted content then you could convert that directly to a
96
+ Ruby equivalent using...
97
+
98
+ ```ruby
99
+ response.json
100
+ ```
101
+
102
+ ## Contributing
103
+
104
+ 1. Fork it ( https://github.com/[my-github-username]/rest_chain/fork )
105
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
106
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
107
+ 4. Push to the branch (`git push origin my-new-feature`)
108
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,56 @@
1
+ require "stringio"
2
+
3
+ module RestChain
4
+ class Chain < Link
5
+ DEFAULT_SCHEME = "https".freeze
6
+
7
+ # Constructor for the Chain class.
8
+ #
9
+ # ==== Parameters
10
+ # settings:: A Hash of the settings for the chain. Currently recognised
11
+ # settings are...
12
+ # * :host The REST API host to be interacted with, this
13
+ # must be specified.
14
+ # * :logger The Logger to be used in the Chain.
15
+ # * :parameters A Hash of default parameters that get sent
16
+ # with all requests.
17
+ # * :path Either a String or an Array. If its an array
18
+ # the elements will be concatentated together
19
+ # into a String. This specifies common elements
20
+ # of the path to avoid having to repeatedly
21
+ # specify them as part of the chain.
22
+ # * :port The port number to contact the REST API host
23
+ # on. Need not be specified if the default port
24
+ # (80 for HTTP and 443 for HTTPS) are to be used.
25
+ # * :scheme Should be given a value of either "http" or "https".
26
+ # This is the scheme that will be used when talking
27
+ # to the REST API. Defaults to "https" if not
28
+ # explicitly specified.
29
+ def initialize(settings={})
30
+ super("", nil, settings[:logger])
31
+ @host = settings.fetch(:host, "").strip
32
+ raise RestChainError.new("No REST API host specified when creating a chain.") if @host == ""
33
+
34
+ @port = settings[:port]
35
+ @scheme = settings.fetch(:scheme, DEFAULT_SCHEME).downcase
36
+ @path = settings.fetch(:path, [])
37
+ @path = @path.split("/").delete_if {|s| s == ""} if @path.kind_of?(String)
38
+ @parameters = {}.merge(settings.fetch(:parameters, {}))
39
+ end
40
+
41
+ attr_reader :host, :path, :port, :scheme
42
+
43
+ def full_parameters(extras={})
44
+ {}.merge(@parameters).merge(extras)
45
+ end
46
+
47
+ def url
48
+ text = StringIO.new
49
+ text << @scheme << "://" << @host
50
+ text << ":#{@port}" if @port
51
+ text << "/" if text.string[-1,1] != "/"
52
+ text << "#{@path.join('/')}"
53
+ text.string
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,9 @@
1
+ module RestChain
2
+ class RestChainError < StandardError
3
+ def initialize(message, cause=nil)
4
+ super(message)
5
+ @cause = cause
6
+ end
7
+ attr_reader :cause
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module RestChain
2
+ class HTTPDriverClassFactory
3
+ def self.manufacture(log)
4
+ RestClientDriver
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,62 @@
1
+ require "logger"
2
+
3
+ module RestChain
4
+ class Link
5
+ def initialize(name, predecessor=nil, logger=nil)
6
+ @name = name
7
+ @predecessor = predecessor
8
+ @logger = logger ? logger : log
9
+ end
10
+
11
+ attr_reader :name, :predecessor
12
+
13
+ def delete(parameters={})
14
+ dispatch(:delete, parameters)
15
+ end
16
+
17
+ def dispatch(verb, parameters={})
18
+ driver = HTTPDriverClassFactory.manufacture(log).new(log)
19
+ driver.send(verb, url, parameters)
20
+ end
21
+
22
+ def get(parameters={})
23
+ dispatch(:get, parameters)
24
+ end
25
+
26
+ def log
27
+ if !@logger
28
+ @logger = Logger.new(STDOUT)
29
+ @logger.level = Logger::UNKNOWN
30
+ end
31
+ @logger
32
+ end
33
+ alias :logger :log
34
+
35
+ def method_missing(name, *arguments, &block)
36
+ arguments.inject(Link.new(name.to_s, self, log)) do |parent, argument|
37
+ Link.new(argument.to_s, parent, log)
38
+ end
39
+ end
40
+
41
+ def patch(parameters={})
42
+ dispatch(:patch, parameters)
43
+ end
44
+
45
+ def post(parameters={})
46
+ dispatch(:post, parameters)
47
+ end
48
+
49
+ def put(parameters={})
50
+ dispatch(:put, parameters)
51
+ end
52
+
53
+ def respond_to?(name, include_private=false)
54
+ true
55
+ end
56
+
57
+ def url
58
+ base = (@predecessor ? @predecessor.url : "")
59
+ @predecessor ? "#{base}#{base[-1,1] == '/' ? '' : '/'}#{@name}" : "/#{@name}"
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,43 @@
1
+ require "rest_client"
2
+
3
+ module RestChain
4
+ class RestClientDriver
5
+ def initialize(logger)
6
+ @logger = logger
7
+ end
8
+ attr_reader :logger
9
+ alias :log :logger
10
+
11
+ def delete(url, parameters={})
12
+ dispatch(:delete, url, parameters)
13
+ end
14
+
15
+ def get(url, parameters={})
16
+ dispatch(:get, url, parameters)
17
+ end
18
+
19
+ def patch(url, parameters={})
20
+ dispatch(:patch, url, parameters)
21
+ end
22
+
23
+ def post(url, parameters={})
24
+ dispatch(:post, url, parameters)
25
+ end
26
+
27
+ def put(url, parameters={})
28
+ dispatch(:put, url, parameters)
29
+ end
30
+
31
+ def dispatch(verb, url, parameters={})
32
+ log.debug "Dispatching a #{verb.to_s.upcase} request to #{url} with parameters: #{parameters}"
33
+ response = RestClient::Request.execute(headers: {params: parameters},
34
+ method: verb,
35
+ url: url)
36
+ RestResponse.new(response.code, response.headers, response.body)
37
+ rescue RestClient::Exception => error
38
+ log.debug "#{verb.to_s.upcase} request to #{url} raised a #{error.class.name} exception."
39
+ response = error.response
40
+ RestResponse.new(response.code, response.headers, response.body)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,20 @@
1
+ require "json"
2
+
3
+ module RestChain
4
+ class RestResponse
5
+ def initialize(status, headers, body)
6
+ @status = status
7
+ @headers = headers
8
+ @body = body
9
+ end
10
+ attr_reader :status, :headers, :body
11
+
12
+ def success?
13
+ (200..299).include?(@status)
14
+ end
15
+
16
+ def json(options={})
17
+ JSON.parse(body, options)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module RestChain
2
+ VERSION = "0.0.1"
3
+ end
data/lib/rest_chain.rb ADDED
@@ -0,0 +1,11 @@
1
+ require "rest_client"
2
+ require "rest_chain/version"
3
+ require "rest_chain/exceptions"
4
+ require "rest_chain/link"
5
+ require "rest_chain/rest_client_driver"
6
+ require "rest_chain/http_driver_class_factory"
7
+ require "rest_chain/chain"
8
+ require "rest_chain/rest_response"
9
+
10
+ module RestChain
11
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rest_chain/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rest_chain"
8
+ spec.version = RestChain::VERSION
9
+ spec.authors = ["Peter Wood"]
10
+ spec.email = ["peter.wood@longboat.com"]
11
+ spec.summary = %q{A library that abstracts the use of a REST interface.}
12
+ spec.description = %q{REST chain is a library that abstracts the path elements of making a REST API call to appear as method calls in Ruby.}
13
+ spec.homepage = "https://github.com/free-beer/rest_chain"
14
+ spec.license = "MIT"
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.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec", "~> 3.4"
24
+ spec.add_development_dependency "webmock", "~> 1.24"
25
+
26
+ spec.add_dependency "rest-client", "~> 1.8"
27
+ end
@@ -0,0 +1,64 @@
1
+ describe RestChain::Chain do
2
+ let(:host) {
3
+ "localhost"
4
+ }
5
+
6
+ let(:path) {
7
+ ["one", "two", "three"]
8
+ }
9
+
10
+ let(:port) {
11
+ 3645
12
+ }
13
+
14
+ let(:logger) {
15
+ Logger.new(STDOUT)
16
+ }
17
+
18
+ describe "#initialize()" do
19
+ subject {
20
+ RestChain::Chain
21
+ }
22
+
23
+ it "assigns values from the settings passed in" do
24
+ chain = subject.new(host: host, logger: logger, path: "/one/two/three", port: port, scheme: "http")
25
+ expect(chain.host).to eq(host)
26
+ expect(chain.logger).to eq(logger)
27
+ expect(chain.path).to eq(path)
28
+ expect(chain.port).to eq(port)
29
+ expect(chain.scheme).to eq("http")
30
+ end
31
+
32
+ it "accepts an array for the path setting" do
33
+ chain = subject.new(host: host, path: path)
34
+ expect(chain.path).to eq(path)
35
+ end
36
+
37
+ it "defaults to the HTTPS scheme" do
38
+ chain = subject.new(host: host)
39
+ expect(chain.scheme).to eq(RestChain::Chain::DEFAULT_SCHEME)
40
+ end
41
+
42
+ it "creates a default logger if one is not specified" do
43
+ chain = subject.new(host: host)
44
+ expect(chain.logger).not_to be_nil
45
+ expect(chain.log).to eq(chain.logger)
46
+ end
47
+
48
+ it "raises an exception is a host is not specified" do
49
+ expect {
50
+ subject.new
51
+ }.to raise_exception(RestChain::RestChainError, "No REST API host specified when creating a chain.")
52
+ end
53
+ end
54
+
55
+ describe "#url()" do
56
+ subject {
57
+ RestChain::Chain.new(host: host, logger: logger, path: "/one/two/three", port: port, scheme: "http")
58
+ }
59
+
60
+ it "include scheme, host, port and path elements" do
61
+ expect(subject.url).to eq("http://#{host}:#{port}/#{path.join('/')}")
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,238 @@
1
+ describe RestChain::Link do
2
+ #-----------------------------------------------------------------------------
3
+ # initialize()
4
+ #-----------------------------------------------------------------------------
5
+ describe "#initialize()" do
6
+ let(:subject) {
7
+ RestChain::Link
8
+ }
9
+
10
+ it "accepts just a single name parameter" do
11
+ expect {
12
+ subject.new("test_link")
13
+ }.not_to raise_exception
14
+ end
15
+
16
+ it "accepts a name and precessor parameters" do
17
+ link = subject.new("root")
18
+ expect {
19
+ subject.new("first", link)
20
+ }.not_to raise_exception
21
+ end
22
+
23
+ it "accepts a name, precessor and logger parameters" do
24
+ link = subject.new("root")
25
+ expect {
26
+ subject.new("first", link, Logger.new(STDOUT))
27
+ }.not_to raise_exception
28
+ end
29
+ end
30
+
31
+ #-----------------------------------------------------------------------------
32
+ # name()
33
+ #-----------------------------------------------------------------------------
34
+ describe "#name()" do
35
+ let(:subject) {
36
+ RestChain::Link.new("test_link")
37
+ }
38
+
39
+ it "returns the name assigned to the link" do
40
+ expect(subject.name).to eq("test_link")
41
+ end
42
+ end
43
+
44
+ #-----------------------------------------------------------------------------
45
+ # predecessor()
46
+ #-----------------------------------------------------------------------------
47
+ describe "#predecessor()" do
48
+ let(:predecessor) {
49
+ RestChain::Link.new("Level1")
50
+ }
51
+
52
+ let(:subject) {
53
+ RestChain::Link.new("Level2", predecessor)
54
+ }
55
+
56
+ it "returns the name assigned to the link" do
57
+ expect(subject.predecessor).to eq(predecessor)
58
+ end
59
+ end
60
+
61
+ #-----------------------------------------------------------------------------
62
+ # log()
63
+ #-----------------------------------------------------------------------------
64
+ describe "#log()" do
65
+ let(:logger) {
66
+ Logger.new(STDOUT)
67
+ }
68
+
69
+ let(:subject) {
70
+ RestChain::Link.new("test_link", nil, logger)
71
+ }
72
+
73
+ it "returns the name assigned to the link" do
74
+ expect(subject.log).to eq(logger)
75
+ end
76
+ end
77
+
78
+ #-----------------------------------------------------------------------------
79
+ # delete()
80
+ #-----------------------------------------------------------------------------
81
+ describe "#delete()" do
82
+ let(:parameters) {
83
+ {one: 1, two: "Two"}
84
+ }
85
+
86
+ let(:subject) {
87
+ RestChain::Link.new("test_link")
88
+ }
89
+
90
+ it "invokes the #dispatch() method" do
91
+ expect(subject).to receive(:dispatch).with(:delete, {}).once
92
+ subject.delete
93
+ end
94
+
95
+ it "passes through any parameters specified to it" do
96
+ expect(subject).to receive(:dispatch).with(:delete, parameters).once
97
+ subject.delete(parameters)
98
+ end
99
+ end
100
+
101
+ #-----------------------------------------------------------------------------
102
+ # get()
103
+ #-----------------------------------------------------------------------------
104
+ describe "#get()" do
105
+ let(:parameters) {
106
+ {one: 1, two: "Two"}
107
+ }
108
+
109
+ let(:subject) {
110
+ RestChain::Link.new("test_link")
111
+ }
112
+
113
+ it "invokes the #dispatch() method" do
114
+ expect(subject).to receive(:dispatch).with(:get, {}).once
115
+ subject.get
116
+ end
117
+
118
+ it "passes through any parameters specified to it" do
119
+ expect(subject).to receive(:dispatch).with(:get, parameters).once
120
+ subject.get(parameters)
121
+ end
122
+ end
123
+
124
+ #-----------------------------------------------------------------------------
125
+ # patch()
126
+ #-----------------------------------------------------------------------------
127
+ describe "#patch()" do
128
+ let(:parameters) {
129
+ {one: 1, two: "Two"}
130
+ }
131
+
132
+ let(:subject) {
133
+ RestChain::Link.new("test_link")
134
+ }
135
+
136
+ it "invokes the #dispatch() method" do
137
+ expect(subject).to receive(:dispatch).with(:patch, {}).once
138
+ subject.patch
139
+ end
140
+
141
+ it "passes through any parameters specified to it" do
142
+ expect(subject).to receive(:dispatch).with(:patch, parameters).once
143
+ subject.patch(parameters)
144
+ end
145
+ end
146
+
147
+ #-----------------------------------------------------------------------------
148
+ # post()
149
+ #-----------------------------------------------------------------------------
150
+ describe "#post()" do
151
+ let(:parameters) {
152
+ {one: 1, two: "Two"}
153
+ }
154
+
155
+ let(:subject) {
156
+ RestChain::Link.new("test_link")
157
+ }
158
+
159
+ it "invokes the #dispatch() method" do
160
+ expect(subject).to receive(:dispatch).with(:post, {}).once
161
+ subject.post
162
+ end
163
+
164
+ it "passes through any parameters specified to it" do
165
+ expect(subject).to receive(:dispatch).with(:post, parameters).once
166
+ subject.post(parameters)
167
+ end
168
+ end
169
+
170
+ #-----------------------------------------------------------------------------
171
+ # put()
172
+ #-----------------------------------------------------------------------------
173
+ describe "#put()" do
174
+ let(:parameters) {
175
+ {one: 1, two: "Two"}
176
+ }
177
+
178
+ let(:subject) {
179
+ RestChain::Link.new("test_link")
180
+ }
181
+
182
+ it "invokes the #dispatch() method" do
183
+ expect(subject).to receive(:dispatch).with(:put, {}).once
184
+ subject.put
185
+ end
186
+
187
+ it "passes through any parameters specified to it" do
188
+ expect(subject).to receive(:dispatch).with(:put, parameters).once
189
+ subject.put(parameters)
190
+ end
191
+ end
192
+
193
+ #-----------------------------------------------------------------------------
194
+ # url()
195
+ #-----------------------------------------------------------------------------
196
+ describe "#url()" do
197
+ let(:predecessor) {
198
+ RestChain::Link.new("level1")
199
+ }
200
+
201
+ let(:subject) {
202
+ RestChain::Link.new("level2", predecessor)
203
+ }
204
+
205
+ it "returns a String based on the link structure" do
206
+ expect(subject.url).to eq("/level1/level2")
207
+ end
208
+ end
209
+
210
+ #-----------------------------------------------------------------------------
211
+ # dispatch()
212
+ #-----------------------------------------------------------------------------
213
+ describe "#dispatch()" do
214
+ let!(:driver) {
215
+ object = OpenStruct.new(get: nil)
216
+ object.define_singleton_method(:get) {|verb, parameters|}
217
+ object
218
+ }
219
+
220
+ let(:parameters) {
221
+ {one: 1, two: "Two"}
222
+ }
223
+
224
+ subject {
225
+ RestChain::Link.new("level2", RestChain::Link.new("level1"))
226
+ }
227
+
228
+ before do
229
+ allow(RestChain::HTTPDriverClassFactory).to receive(:manufacture).and_return(OpenStruct)
230
+ allow(OpenStruct).to receive(:new).and_return(driver)
231
+ end
232
+
233
+ it "invokes the appropriate driver method and specifies an URL and parameters" do
234
+ expect(driver).to receive(:get).with(subject.url, parameters).once
235
+ subject.dispatch(:get, parameters)
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,137 @@
1
+ require "rest_chain/rest_client_driver"
2
+ require "stringio"
3
+
4
+ describe RestChain::RestClientDriver do
5
+ let(:logger) {
6
+ Logger.new(StringIO.new)
7
+ }
8
+
9
+ let(:output) {
10
+ RestChain::RestResponse.new(200, [], "")
11
+ }
12
+
13
+ let(:parameters) {
14
+ {one: 1, two: "Two"}
15
+ }
16
+
17
+ let(:url) {
18
+ "http://www.unittest.com/api/v1/users"
19
+ }
20
+
21
+ subject {
22
+ RestChain::RestClientDriver.new(logger)
23
+ }
24
+
25
+ #-----------------------------------------------------------------------------
26
+ # logger()
27
+ #-----------------------------------------------------------------------------
28
+ describe "#logger()" do
29
+ it "retrieves the driver logger instance" do
30
+ expect(subject.logger).to eq(logger)
31
+ end
32
+ end
33
+
34
+ #-----------------------------------------------------------------------------
35
+ # delete()
36
+ #-----------------------------------------------------------------------------
37
+ describe "#delete()" do
38
+ it "makes a delete dispatch request" do
39
+ expect(subject).to receive(:dispatch).with(:delete, url, {}).and_return(output).once
40
+ subject.delete(url)
41
+ end
42
+
43
+ it "passes parameters it receives to the dispatch call" do
44
+ expect(subject).to receive(:dispatch).with(:delete, url, parameters).and_return(output).once
45
+ subject.delete(url, parameters)
46
+ end
47
+ end
48
+
49
+ #-----------------------------------------------------------------------------
50
+ # get()
51
+ #-----------------------------------------------------------------------------
52
+ describe "#get()" do
53
+ it "makes a get dispatch request" do
54
+ expect(subject).to receive(:dispatch).with(:get, url, {}).and_return(output).once
55
+ subject.get(url)
56
+ end
57
+
58
+ it "passes parameters it receives to the dispatch call" do
59
+ expect(subject).to receive(:dispatch).with(:get, url, parameters).and_return(output).once
60
+ subject.get(url, parameters)
61
+ end
62
+ end
63
+
64
+ #-----------------------------------------------------------------------------
65
+ # patch()
66
+ #-----------------------------------------------------------------------------
67
+ describe "#patch()" do
68
+ it "makes a patch dispatch request" do
69
+ expect(subject).to receive(:dispatch).with(:patch, url, {}).and_return(output).once
70
+ subject.patch(url)
71
+ end
72
+
73
+ it "passes parameters it receives to the dispatch call" do
74
+ expect(subject).to receive(:dispatch).with(:patch, url, parameters).and_return(output).once
75
+ subject.patch(url, parameters)
76
+ end
77
+ end
78
+
79
+ #-----------------------------------------------------------------------------
80
+ # post()
81
+ #-----------------------------------------------------------------------------
82
+ describe "#post()" do
83
+ it "makes a post dispatch request" do
84
+ expect(subject).to receive(:dispatch).with(:post, url, {}).and_return(output).once
85
+ subject.post(url)
86
+ end
87
+
88
+ it "passes parameters it receives to the dispatch call" do
89
+ expect(subject).to receive(:dispatch).with(:post, url, parameters).and_return(output).once
90
+ subject.post(url, parameters)
91
+ end
92
+ end
93
+
94
+ #-----------------------------------------------------------------------------
95
+ # put()
96
+ #-----------------------------------------------------------------------------
97
+ describe "#put()" do
98
+ it "makes a put dispatch request" do
99
+ expect(subject).to receive(:dispatch).with(:put, url, {}).and_return(output).once
100
+ subject.put(url)
101
+ end
102
+
103
+ it "passes parameters it receives to the dispatch call" do
104
+ expect(subject).to receive(:dispatch).with(:put, url, parameters).and_return(output).once
105
+ subject.put(url, parameters)
106
+ end
107
+ end
108
+
109
+ #-----------------------------------------------------------------------------
110
+ # dispatch()
111
+ #-----------------------------------------------------------------------------
112
+ describe "#dispatch()" do
113
+ let(:success_response) {
114
+ OpenStruct.new(body: "", code: 200, headers: [])
115
+ }
116
+
117
+ let(:exception) {
118
+ exception = RestClient::Exception.new("Something went wrong!")
119
+ exception.define_singleton_method(:response) do
120
+ OpenStruct.new(body: "", code: 500, headers: [])
121
+ end
122
+ exception
123
+ }
124
+
125
+ it "calls execute() using the parameters it is passed" do
126
+ expect(RestClient::Request).to receive(:execute).with({headers: {params: parameters}, method: :delete, url: url}).and_return(success_response).once
127
+ subject.dispatch(:delete, url, parameters)
128
+ end
129
+
130
+ it "doesn't raise an exception even if the request does" do
131
+ expect(RestClient::Request).to receive(:execute).and_raise(exception)
132
+ expect {
133
+ subject.dispatch(:put, url)
134
+ }.not_to raise_exception
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,30 @@
1
+ describe RestChain::RestResponse do
2
+ describe "#success?()" do
3
+ it "returns true for responses with a status code between 200 and 299" do
4
+ expect(RestChain::RestResponse.new(250, [], "").success?).to eq(true)
5
+ end
6
+
7
+ it "returns false for response with a non-200 status" do
8
+ expect(RestChain::RestResponse.new(500, [], "").success?).to eq(false)
9
+ end
10
+ end
11
+
12
+ describe "#json()" do
13
+ let(:body) {
14
+ {"one" => 1, "two" => "TWO"}
15
+ }
16
+ subject {
17
+ RestChain::RestResponse.new(200, [], body.to_json)
18
+ }
19
+
20
+ it "converts the response body from JSON to a Ruby object" do
21
+ expect(subject.json).to eq(body)
22
+ end
23
+
24
+ it "passes any specified options through to the JSON parse" do
25
+ output = subject.json(symbolize_names: true)
26
+ expect(output).to include(:one)
27
+ expect(output).to include(:two)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,101 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
4
+ # this file to always be loaded, without a need to explicitly require it in any
5
+ # files.
6
+ #
7
+ # Given that it is always loaded, you are encouraged to keep this file as
8
+ # light-weight as possible. Requiring heavyweight dependencies from this file
9
+ # will add to the boot time of your test suite on EVERY test run, even for an
10
+ # individual file that may not need all of that loaded. Instead, consider making
11
+ # a separate helper file that requires the additional dependencies and performs
12
+ # the additional setup, and require it from the spec files that actually need
13
+ # it.
14
+ #
15
+ # The `.rspec` file also contains a few flags that are not defaults but that
16
+ # users commonly want.
17
+ #
18
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
19
+
20
+ require "webmock/rspec"
21
+ require "ostruct"
22
+ require "rest_chain"
23
+
24
+ RSpec.configure do |config|
25
+ # rspec-expectations config goes here. You can use an alternate
26
+ # assertion/expectation library such as wrong or the stdlib/minitest
27
+ # assertions if you prefer.
28
+ config.expect_with :rspec do |expectations|
29
+ # This option will default to `true` in RSpec 4. It makes the `description`
30
+ # and `failure_message` of custom matchers include text for helper methods
31
+ # defined using `chain`, e.g.:
32
+ # be_bigger_than(2).and_smaller_than(4).description
33
+ # # => "be bigger than 2 and smaller than 4"
34
+ # ...rather than:
35
+ # # => "be bigger than 2"
36
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
37
+ end
38
+
39
+ # rspec-mocks config goes here. You can use an alternate test double
40
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
41
+ config.mock_with :rspec do |mocks|
42
+ # Prevents you from mocking or stubbing a method that does not exist on
43
+ # a real object. This is generally recommended, and will default to
44
+ # `true` in RSpec 4.
45
+ mocks.verify_partial_doubles = true
46
+ end
47
+
48
+ # The settings below are suggested to provide a good initial experience
49
+ # with RSpec, but feel free to customize to your heart's content.
50
+ =begin
51
+ # These two settings work together to allow you to limit a spec run
52
+ # to individual examples or groups you care about by tagging them with
53
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
54
+ # get run.
55
+ config.filter_run :focus
56
+ config.run_all_when_everything_filtered = true
57
+
58
+ # Allows RSpec to persist some state between runs in order to support
59
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
60
+ # you configure your source control system to ignore this file.
61
+ config.example_status_persistence_file_path = "spec/examples.txt"
62
+
63
+ # Limits the available syntax to the non-monkey patched syntax that is
64
+ # recommended. For more details, see:
65
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
66
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
67
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
68
+ config.disable_monkey_patching!
69
+
70
+ # This setting enables warnings. It's recommended, but in some cases may
71
+ # be too noisy due to issues in dependencies.
72
+ config.warnings = true
73
+
74
+ # Many RSpec users commonly either run the entire suite or an individual
75
+ # file, and it's useful to allow more verbose output when running an
76
+ # individual spec file.
77
+ if config.files_to_run.one?
78
+ # Use the documentation formatter for detailed output,
79
+ # unless a formatter has already been configured
80
+ # (e.g. via a command-line flag).
81
+ config.default_formatter = 'doc'
82
+ end
83
+
84
+ # Print the 10 slowest examples and example groups at the
85
+ # end of the spec run, to help surface which specs are running
86
+ # particularly slow.
87
+ config.profile_examples = 10
88
+
89
+ # Run specs in random order to surface order dependencies. If you find an
90
+ # order dependency and want to debug it, you can fix the order by providing
91
+ # the seed, which is printed after each run.
92
+ # --seed 1234
93
+ config.order = :random
94
+
95
+ # Seed global randomization in this process using the `--seed` CLI option.
96
+ # Setting this allows you to use `--seed` to deterministically reproduce
97
+ # test failures related to randomization by passing the same `--seed` value
98
+ # as the one that triggered the failure.
99
+ Kernel.srand config.seed
100
+ =end
101
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rest_chain
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Peter Wood
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-03-05 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.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.4'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.4'
55
+ - !ruby/object:Gem::Dependency
56
+ name: webmock
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.24'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.24'
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.8'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.8'
83
+ description: REST chain is a library that abstracts the path elements of making a
84
+ REST API call to appear as method calls in Ruby.
85
+ email:
86
+ - peter.wood@longboat.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - ".rspec"
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - lib/rest_chain.rb
98
+ - lib/rest_chain/chain.rb
99
+ - lib/rest_chain/exceptions.rb
100
+ - lib/rest_chain/http_driver_class_factory.rb
101
+ - lib/rest_chain/link.rb
102
+ - lib/rest_chain/rest_client_driver.rb
103
+ - lib/rest_chain/rest_response.rb
104
+ - lib/rest_chain/version.rb
105
+ - rest_chain.gemspec
106
+ - spec/lib/rest_chain/chain_spec.rb
107
+ - spec/lib/rest_chain/link_spec.rb
108
+ - spec/lib/rest_chain/rest_client_driver_spec.rb
109
+ - spec/lib/rest_chain/rest_response_spec.rb
110
+ - spec/spec_helper.rb
111
+ homepage: https://github.com/free-beer/rest_chain
112
+ licenses:
113
+ - MIT
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.4.6
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: A library that abstracts the use of a REST interface.
135
+ test_files:
136
+ - spec/lib/rest_chain/chain_spec.rb
137
+ - spec/lib/rest_chain/link_spec.rb
138
+ - spec/lib/rest_chain/rest_client_driver_spec.rb
139
+ - spec/lib/rest_chain/rest_response_spec.rb
140
+ - spec/spec_helper.rb