hyperion_http 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +4 -0
  4. data/.travis.yml +7 -0
  5. data/CHANGES.md +145 -0
  6. data/Gemfile +5 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +421 -0
  9. data/Rakefile +11 -0
  10. data/hyperion_http.gemspec +34 -0
  11. data/lib/hyperion/aux/bug_error.rb +2 -0
  12. data/lib/hyperion/aux/hash_ext.rb +5 -0
  13. data/lib/hyperion/aux/logger.rb +54 -0
  14. data/lib/hyperion/aux/typho.rb +9 -0
  15. data/lib/hyperion/aux/util.rb +18 -0
  16. data/lib/hyperion/aux/version.rb +3 -0
  17. data/lib/hyperion/formats.rb +69 -0
  18. data/lib/hyperion/headers.rb +43 -0
  19. data/lib/hyperion/hyperion.rb +79 -0
  20. data/lib/hyperion/requestor.rb +88 -0
  21. data/lib/hyperion/result_handling/dispatch_dsl.rb +67 -0
  22. data/lib/hyperion/result_handling/dispatching_hyperion_result.rb +10 -0
  23. data/lib/hyperion/result_handling/result_maker.rb +64 -0
  24. data/lib/hyperion/types/client_error_code.rb +9 -0
  25. data/lib/hyperion/types/client_error_detail.rb +46 -0
  26. data/lib/hyperion/types/client_error_response.rb +50 -0
  27. data/lib/hyperion/types/hyperion_error.rb +6 -0
  28. data/lib/hyperion/types/hyperion_result.rb +24 -0
  29. data/lib/hyperion/types/hyperion_status.rb +10 -0
  30. data/lib/hyperion/types/hyperion_uri.rb +97 -0
  31. data/lib/hyperion/types/payload_descriptor.rb +9 -0
  32. data/lib/hyperion/types/response_descriptor.rb +21 -0
  33. data/lib/hyperion/types/rest_route.rb +20 -0
  34. data/lib/hyperion.rb +15 -0
  35. data/lib/hyperion_test/fake.rb +64 -0
  36. data/lib/hyperion_test/fake_server/config.rb +36 -0
  37. data/lib/hyperion_test/fake_server/dispatcher.rb +74 -0
  38. data/lib/hyperion_test/fake_server/types.rb +7 -0
  39. data/lib/hyperion_test/fake_server.rb +54 -0
  40. data/lib/hyperion_test/spec_helper.rb +19 -0
  41. data/lib/hyperion_test/test_framework_hooks.rb +34 -0
  42. data/lib/hyperion_test.rb +2 -0
  43. data/spec/lib/hyperion/aux/util_spec.rb +29 -0
  44. data/spec/lib/hyperion/formats_spec.rb +84 -0
  45. data/spec/lib/hyperion/headers_spec.rb +61 -0
  46. data/spec/lib/hyperion/logger_spec.rb +60 -0
  47. data/spec/lib/hyperion/test_spec.rb +222 -0
  48. data/spec/lib/hyperion/types/client_error_response_spec.rb +52 -0
  49. data/spec/lib/hyperion/types/hyperion_result_spec.rb +17 -0
  50. data/spec/lib/hyperion/types/hyperion_uri_spec.rb +113 -0
  51. data/spec/lib/hyperion_spec.rb +187 -0
  52. data/spec/lib/superion_spec.rb +151 -0
  53. data/spec/lib/types_spec.rb +46 -0
  54. data/spec/spec_helper.rb +3 -0
  55. data/spec/support/core_helpers.rb +5 -0
  56. metadata +280 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fe93d63e5710f95bd417f7281c7c3b96c267e625
4
+ data.tar.gz: 13daa226ae477357aacb51fa413f85f9967fed25
5
+ SHA512:
6
+ metadata.gz: 453822633702779a4fd790ef9527643865b42c8efb6ec3292dffa7cdb5e86457737062d106dfc978b3360c11aad670353f54f42b823858e9e26f4a6fbe0e760c
7
+ data.tar.gz: c5c618b3a65d8090964c9f0d36aafb050c2f75c8758a8100c2e873bd9e619b54a37db34e35a31b962ce20e9f9597fb6304ad3944cdb4d6dda15af69aabc31188
data/.gitignore ADDED
@@ -0,0 +1,18 @@
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
15
+ .idea
16
+ \#*\#
17
+ .\#*
18
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --format progress
2
+ -I spec/lib
3
+ --require 'support/core_helpers.rb'
4
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0-p247
4
+ branches:
5
+ only:
6
+ - master
7
+ - /^[ftb]-.*$/
data/CHANGES.md ADDED
@@ -0,0 +1,145 @@
1
+ ### 0.0.16
2
+ - Hyperion::request can now take a block an easily dispatch on response status, code, or other stuff
3
+
4
+ ### 0.0.17
5
+ - If Hyperion.fake allow is passed a route, its block can now return an object instead of a rack-style response.
6
+ The object is serialized according to the route's response descriptor.
7
+ - Serialize the POST/PUT payload according to the route's payload descriptor.
8
+
9
+ ### 0.0.18
10
+ - Return 404 instead of crashing when headers are the only thing preventing a faked route from matching.
11
+
12
+ ### 0.0.19
13
+ - Log stubs and requests for debugging purposes.
14
+
15
+ ### 0.0.20
16
+ - Added contracts to some public methods to provide more helpful error messages when passed invalid arguments.
17
+
18
+ ### 0.0.25
19
+ - Allow payload descriptor to be nil (for things like DELETE).
20
+
21
+ ### 0.0.26
22
+ - Fixed bug with Hyperion fake not defaulting the port to 80
23
+
24
+ ### 0.0.27
25
+ - Use Rails.logger if present
26
+
27
+ ### 0.0.28
28
+ - Use Rails.logger if present and not nil
29
+
30
+ ### 0.0.29
31
+ - Pretty RestRoute.to_s
32
+ - Added the ability to match responses on an HTTP code range
33
+
34
+ ### 0.0.30
35
+ - locked 'contracts' gem to version 0.5 due to possible incompatibility with ascent-web
36
+
37
+ ### 0.0.31
38
+ - disabled method contracts due to apparent weirdness
39
+ - enable Oj's 'compat' mode to allow writing of both string- and symbol-keyed hashes
40
+ - write Time objects to JSON as ISO 8601 (Indigo convention)
41
+
42
+ ### 0.0.32
43
+ - Include requested route on response object
44
+
45
+ ### 0.0.33
46
+ - Pretty HyperionResult#to_s
47
+
48
+ ### 0.0.34
49
+ - HyperionUri (models query params as a hash)
50
+ - Fixed logging bug where the logger would capture $stdout the first time it saw it
51
+ and always use that object instead of always using the current value of $stdout.
52
+
53
+ ### 0.0.35
54
+ - Canonicalize HyperionUri#to_s output by sorting query param names to make route matching possible.
55
+
56
+ ### 0.0.36
57
+ - Pretty ResponseDescriptor#to_s
58
+ - When using Hyperion.fake and a rack result is returned, the body is serialized as JSON
59
+ if it is not already a string.
60
+ - If parsing JSON fails, just return the unparsed JSON. (For 400s).
61
+
62
+ ### 0.0.37
63
+ - Pass the HyperionResult to the `when` block, since it won't always be in lexical scope.
64
+
65
+ ### 0.0.38
66
+ - Added superion
67
+ - Catch raised errors in handler predicates
68
+ - Read 400-level bodies as ClientErrorResponse
69
+
70
+ ### 0.0.39
71
+ - Refactored to appease CodeClimate
72
+
73
+ ### 0.0.40
74
+ - Moved mimic back to being a runtime dependency
75
+
76
+ ### 0.0.41
77
+ - Fixed ClientErrorResponse#from_attrs
78
+
79
+ ### 0.0.42
80
+ - Made ClientErrorResponse constructor interface less error-prone
81
+ - Always read client error response as JSON
82
+
83
+ ### 0.0.43
84
+ - Raise an error if superion response falls through and no superion_fallthrough method is defined.
85
+
86
+ ### 0.0.44
87
+ - Fixed "require" problems
88
+
89
+ ### 0.0.45
90
+ - Support query params that are arrays
91
+
92
+ ### 0.0.46
93
+ - Allow query to be nil (bugfix)
94
+
95
+ ### 0.0.47
96
+ - Fixed slow POSTs > 1KB
97
+
98
+ ### 0.0.48
99
+ - Fixed bug where superion's interface was too loose
100
+ - Do not require dispatch predicates to take an argument
101
+ - Log when requests complete
102
+
103
+ ### 0.0.50
104
+ - Allow dispatching on client error code
105
+
106
+ ### 0.0.52
107
+ - `ErrorInfo` -> `ClientErrorDetail`
108
+ - `ErrorInfo::Code` -> `ClientErrorCode`
109
+ - `HyperionResult::Status` -> `HyperionStatus`
110
+ - `ClientErrorResponse.new` signature changed
111
+ - superion was absorbed into hyperion. instead, `require 'hyperion'`
112
+ and `include Hyperion::Requestor`
113
+ - `superion_handler` -> `hyperion_handler`
114
+ - removed `superion_fallthrough`. it's only hit on 100s and 300s.
115
+ Custom handlers can already handle those if they want. An error is
116
+ raised if a 100 or 300 falls through.
117
+ - removed `Superion.missing`
118
+
119
+ ### 0.0.53
120
+ - Fixed bug due to missing `require`
121
+
122
+ ### 0.0.54
123
+ - abstractivator 0.0.27
124
+
125
+ ### 0.0.55
126
+ - upgraded abstractivator for wrapped enum values
127
+
128
+ ### 0.0.56
129
+ - another enum tweak
130
+
131
+ ### 0.0.57
132
+ - json enum support from abstractivator
133
+
134
+ ### 0.0.58
135
+ - Renamed `ClientErrorResponse#body` -> `ClientErrorResponse#content`
136
+
137
+ ### 0.1.0
138
+ - Bumped version
139
+
140
+ ### 0.1.1
141
+ - Attempted to solve problem in tests where clients hit an old server while
142
+ it's shutting down, resulting in a timeout.
143
+
144
+ ### 0.1.2
145
+ - Fixed broken gemspec version for abstractivator
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in hyperion.gemspec
4
+ gemspec
5
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Peter Winton
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,421 @@
1
+ # Hyperion
2
+ [![Build Status](https://travis-ci.org/indigo-biosystems/hyperion.svg)](https://travis-ci.org/indigo-biosystems/hyperion)
3
+
4
+ Hyperion is a Ruby REST client that follows certain conventions
5
+ layered on top of HTTP. The conventions implement best practices
6
+ surrounding versioning, error reporting, and crafting an API for
7
+ consumption by third parties. Hyperion provides abstractions that
8
+ make it easy to follow these conventions consistently across
9
+ projects.
10
+
11
+ This document describes the conventions, then demonstrates how to
12
+ use the API, and finally shows how to test your client-side code.
13
+
14
+ ## Conventions
15
+
16
+ The conventions are modeled after [GitHub's Developer API](https://developer.github.com/v3/).
17
+
18
+ ### Versioning
19
+
20
+ According to best practices, hyperion uses both _resource_ versioning and
21
+ _message_ versioning.
22
+
23
+ #### Message versioning
24
+
25
+ A client sends a _request_ to the server, and the server returns a
26
+ _response_. The request and response each have a message type with a
27
+ well specified structure. When placing the request, the client
28
+ specifies the type of messages it expects back. For PUT, POST, and
29
+ PATCH requests, the client also specifies the message type of the
30
+ payload it is sending with the request.
31
+
32
+ A client might send a request with the header:
33
+
34
+ `Accept: application/vnd.indigobio-ascent.user-v1+json`
35
+
36
+ which indicates that the server must return an "ascent.user" message, version
37
+ 1, formatted as JSON. If the server does not support this, it must
38
+ return a 400-level error (discussed below).
39
+
40
+ A client POSTing a new user also includes the header:
41
+
42
+ `Content-Type: application/json`
43
+
44
+ indicating that it is sending JSON. The server takes the Accept header
45
+ message type to be the type of the request payload.
46
+ <!--- ^ seems fishy -->
47
+
48
+ The message version is incremented when the message structure changes.
49
+
50
+ _Note: Message types are currently established via documentation. In
51
+ the future, it would be desirable for them to be declared precisely in
52
+ a [protobuf](https://github.com/google/protobuf)-like form, which would
53
+ allow for generated documentation, simpler serialization code,
54
+ automatic validation, and enhanced logging and diagnostics._
55
+
56
+ #### Resource versioning
57
+
58
+ Less frequently, the semantics of a given resource change. There are
59
+ several ways a server can route a particular resource version,
60
+ including:
61
+
62
+ - `/v2/` - incorporating the version in the URI
63
+ - `?v=2` - accepting the version as a query parameter
64
+ - `v2.archiver.indigobio.com` - incorporating the version in the hostname
65
+ - creating a differently named resource altogether
66
+
67
+ Hyperion does not expressly support any of these conventions, although
68
+ it may in the future.
69
+
70
+
71
+ ### Client Errors
72
+
73
+ The server always returns 400-level errors as exactly 400; no
74
+ distinction is made in the HTTP response code. Instead, the server
75
+ returns a well-defined "client error response" structure that contains
76
+
77
+ - a human-oriented error message, and
78
+ - a machine-oriented list of "error detail" structures.
79
+
80
+ The _message_ describes the problem. The _error details_ provide
81
+ enough information to begin resolving the problem.
82
+
83
+ An error detail consists of:
84
+
85
+ - code (not to be confused with the HTTP status code, _e.g._, 200)
86
+ - reason
87
+ - resource
88
+ - field
89
+ - value
90
+
91
+ The _code_ is an enumeration value (_e.g._, "missing", "invalid",
92
+ "unsupported") which describes the type of problem with the request.
93
+ <!--- ^ consider renaming "code" to "problem" -->
94
+
95
+ The _reason_ explains why the problem occurred.
96
+
97
+ The problem is associated with a particular _resource_, and perhaps
98
+ more specifically with a particular _field_ and _value_ on that resource.
99
+
100
+ Each field is a string. Depending on the code, some fields may not
101
+ apply. Inapplicable fields are always present, with the empty string
102
+ as their value. This simplifies the code that deals with them.
103
+
104
+
105
+ ### Server Errors
106
+
107
+ Server errors are returned as normal 500 responses, which may or may
108
+ not have a body. There is no special treatment.
109
+
110
+
111
+ ## Using hyperion
112
+
113
+ Hyperion's API revolves around the idea of a _route_, which is a
114
+ combination of:
115
+
116
+ - HTTP method
117
+ - URI
118
+ - parameters that influence header generation and de/serialization
119
+ - `ResponseDescriptor` (what kind of data do we want back)
120
+ - influences the `Accept` header
121
+ - `PayloadDescriptor` (what kind of data is being PUT or POSTed)
122
+ - influences the `Content-Type` header
123
+
124
+ Hyperion automatically deserializes responses according to the
125
+ `ResponseDescriptor` and serializes the request payload according to
126
+ the `PayloadDescriptor`.
127
+
128
+ Hyperion provides a basic interface for requesting routes.
129
+
130
+ ```ruby
131
+ require 'hyperion'
132
+
133
+ message_type = 'user'
134
+ version = 1
135
+ format = :json
136
+ route = RestRoute.new(:get, 'http://somesite.org/users/0', ResponseDescriptor.new(message_type, version, format))
137
+ user = Hyperion.request(route)
138
+ ```
139
+
140
+ You can pass `request` a block, in which case the return value of the
141
+ block becomes the return value of `request`.
142
+
143
+ ```ruby
144
+ user = Hyperion.request(route) do |result|
145
+ if result.status == HyperionResult::Status::SUCCESS
146
+ User.new(result.body)
147
+ end
148
+ end
149
+ ```
150
+
151
+ Production-quality error handling becomes hairy quickly, so hyperion
152
+ provides a mini DSL to make it easier.
153
+
154
+ ```ruby
155
+ Hyperion.request(route) do |result|
156
+ result.when(HyperionResult::Status::SUCCESS) { User.new(result.body) }
157
+ result.when(400..499) { raise 'we screwed up' }
158
+ result.when(500..599) { raise 'they screwed up' }
159
+ result.when(evil) { exit(1) }
160
+ end
161
+
162
+ def evil
163
+ proc do |result|
164
+ result.body['things'].any?{|x| x['id'] == '666'}
165
+ end
166
+ end
167
+ ```
168
+
169
+ Conditions are tested in order. When hyperion encounters the first
170
+ true condition, it executes the associated block, the value of which
171
+ becomes the return value of `request`.
172
+
173
+ A condition may be:
174
+
175
+ - a `HyperionResult::Status` enumeration member,
176
+ - an `ErrorInfo::Code` enumeration member,
177
+ - an HTTP code,
178
+ - a range of HTTP codes, or
179
+ - an arbitrary [predicate](http://en.wikipedia.org/wiki/Predicate_(mathematical_logic)).
180
+
181
+ To obviate guard logic in predicates, a predicate that raises an
182
+ exception is treated as a non-match. In the example above, if the body
183
+ didn't have a `'things'` key, then `.any?` would raise a
184
+ `NoMethodError`, interpreted as a non-match, and hyperion would move
185
+ on to the next predicate.
186
+
187
+ ### Route classes
188
+
189
+ In practice, you don't want to litter your code with `RestRoute.new`
190
+ invocations. Here is a pattern for encapsulating the routes. It is the
191
+ client-side analog of `routes.rb` in Rails.
192
+
193
+ ```ruby
194
+ class CrudRoutes
195
+ def initialize(resource)
196
+ @resource = resource
197
+ end
198
+
199
+ def read(id)
200
+ build(:get, id, response(message_type_for(@resource), 1, :json))
201
+ end
202
+
203
+ def create
204
+ build(:post, '', response(message_type_for(@resource), 1, :json), payload(:json))
205
+ end
206
+
207
+ ...
208
+ end
209
+ ```
210
+
211
+ You can easily imagine the rest of the routes and what the helper
212
+ methods `build`, `message_type_for`, `response`, and `payload` look like.
213
+
214
+ Use it like this:
215
+
216
+ ```ruby
217
+ user_routes = CrudRoutes.new('users')
218
+ Hyperion.request(user_routes.create, body: {name: 'joe', email: 'joe@schmoe.com'})
219
+ # later...
220
+ joe = Hyperion.request(user_routes.read(joes_id))
221
+ ```
222
+
223
+ A few notes:
224
+
225
+ `CrudRoutes` functions as an API spec, albeit a stripped down one.
226
+ Therefore, there is no need to write specs for it; the specs would
227
+ likely be harder to understand than the code itself. `CrudRoutes`
228
+ could be DRYed up more, but that would reduce its understandability
229
+ and create the need for specs.
230
+
231
+ See `AssaymaticRoutes` in ascent-web for the most complete example to
232
+ date.
233
+
234
+
235
+ ### Configuration
236
+
237
+ ```ruby
238
+ # configure hyperion
239
+ Hyperion.configure do |config|
240
+ config.vendor_string = 'indigobio-ascent' # becomes part of the Accept header
241
+ end
242
+ ```
243
+
244
+
245
+ ## Superion
246
+
247
+ Superion layers more convenience onto Hyperion by helping dispatch the
248
+ response: internalizing the response and dealing with errors.
249
+
250
+ ### Render and project
251
+
252
+ ```ruby
253
+ require 'superion'
254
+
255
+ class UserGateway
256
+ include Superion
257
+
258
+ def find(id)
259
+ route = RestRoute.new(:get, "http://somesite.org/users/#{id}")
260
+ user = request(route, render: as_user)
261
+ end
262
+
263
+ def as_user
264
+ proc do |hash|
265
+ User.new(hash)
266
+ end
267
+ end
268
+ end
269
+ ```
270
+
271
+ On success, the "render" proc has a chance to transform the body
272
+ (usually a `Hash`) into an internal representation (often an entity).
273
+
274
+ After rendering, a "project" proc (the block) has a chance to project
275
+ the rendered entity; for example, by choosing a subdocument or field.
276
+
277
+ ```ruby
278
+ def user_names
279
+ route = RestRoute.new(:get, "http://somesite.org/users")
280
+ request(route, render: as_users) do |users|
281
+ users.map(&:name)
282
+ end
283
+ end
284
+ ```
285
+
286
+ ### Result dispatch
287
+
288
+ Superion has four levels of dispatching:
289
+
290
+ - _core_,
291
+ - _includer_, and
292
+ - _request_.
293
+
294
+ <!--- TODO: these terms could use improvement. -->
295
+
296
+ They are distinguished by their scope. The core handler is built into
297
+ superion. An includer handler affects all requests made by a
298
+ particular class. A request handler affects only a particular request.
299
+
300
+ When superion receives a response, it passes the result through the
301
+ request, includer, and core handlers, in that order. The first handler
302
+ to match wins, and no further handlers are tried. If no handler
303
+ matches, then superion raises a `HyperionError` error.
304
+
305
+ #### Core
306
+
307
+ The core handler handles the 200 success case and 400- and 500-level
308
+ errors. In the success case, the body is rendered and projected. In
309
+ the error cases, a `HyperionError` is raised. The message for 400s is
310
+ taken from the error response. The message for 500s contains the raw
311
+ string body. Specifically for 404, no body is available, so an error
312
+ indicating an unimplemented route is raised.
313
+
314
+
315
+ #### Includer
316
+
317
+ The includer handler is an optional method on the requesting class.
318
+
319
+ ```ruby
320
+ class UserGateway
321
+ include Superion
322
+
323
+ def find(id)
324
+ ...
325
+ end
326
+
327
+ def superion_handler(result)
328
+ result.when(ErrorInfo::MISSING) { raise "The resource was not found: #{result.route}" }
329
+ result.when(HyperionResult::Status::SERVER_ERROR) { ... }
330
+ end
331
+ end
332
+ ```
333
+
334
+ #### Request
335
+
336
+ The request handler provides a convenient way to specify a handler as
337
+ a `Hash` for an individual `request` call. If a `superion_handler`
338
+ looks like:
339
+
340
+ ```ruby
341
+ result.when(condition) { return_something }
342
+ ```
343
+
344
+ then the equivalent request handler is
345
+
346
+ ```ruby
347
+ { condition => proc { return_something } }
348
+ ```
349
+
350
+ Pass it as the `also_handle` option:
351
+
352
+ ```ruby
353
+ request(route, render: as_user, also_handle: { condition => proc { return_something } })
354
+ ```
355
+
356
+ ## Testing
357
+
358
+ Hyperion includes methods to help test your client-side code.
359
+
360
+ ```ruby
361
+ require 'hyperion_test'
362
+
363
+ list_route = RestRoute.new(:get, "http://somesite.org/users")
364
+ find_route = RestRoute.new(:get, "http://somesite.org/users/123")
365
+
366
+ # start a fake server
367
+ Hyperion.fake('http://somesite.org') do |svr|
368
+ svr.allow(list_route) { [User.new('123'), User.new('456')].as_json }
369
+ svr.allow(find_route) do |result|
370
+ User.new(result.body['id']).as_json
371
+ end
372
+ end
373
+
374
+ # then place requests against it
375
+ users = Hyperion.request(list_route)
376
+ expect(users[0]['id']).to eql 123
377
+ expect(users[1]['id']).to eql 456
378
+ ```
379
+
380
+ `Hyperion::fake` starts a real web server which responds to the
381
+ configured routes. It provides an easy way to exercise the entire
382
+ stack when running your tests.
383
+
384
+ For simpler cases:
385
+
386
+ ```ruby
387
+ list_route = RestRoute.new(:get, "http://somesite.org/users")
388
+ response = [User.new('123'), User.new('456')]
389
+ fake_route(list_route, response)
390
+ ```
391
+
392
+ See the specs for details.
393
+
394
+ ## Maintenance
395
+
396
+ When improving hyperion, increment the version in `version.rb` (Hyperion
397
+ uses [semantic versioning](http://semver.org)) and describe your
398
+ changes in `CHANGES.md`.
399
+
400
+ ## Design decisions
401
+
402
+ Hyperion is backed by Typhoeus, which in turn is backed by libcurl.
403
+ Both are fully featured, have widespread adoption, and are actively
404
+ maintained. One particularly nice feature of Typhoeus is that it
405
+ provides an easy way to issue multiple requests in parallel, which
406
+ is important when you have a microservices architecture.
407
+
408
+ Our original plan was to have a second gem containing `Hyperion.fake`
409
+ and its dependencies. The problem is that you need to keep the two
410
+ gems in sync, which reduces agility. As a compromise, `Hyperion.fake`
411
+ still lives in the hyperion gem but is only loaded when you `require
412
+ 'hyperion_test'`. Assuming no production code `require`s
413
+ `hyperion_test`, none of the test-related dependencies will be loaded
414
+ in a production system, although the gem dependencies will be part of
415
+ the production bundle.
416
+
417
+ # Parking Lot
418
+
419
+ - Consider making the set of supported formats/content-types extensible.
420
+ - Consider adding a configuration value to set the logger to use. If none
421
+ is provided, fall back on Rails.logger, and then a new `Logger`.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ begin
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
9
+ rescue LoadError
10
+ # no rspec available
11
+ end
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'hyperion/aux/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'hyperion_http'
8
+ spec.version = Hyperion::VERSION
9
+ spec.authors = ['Indigo BioAutomation, Inc.']
10
+ spec.email = ['pwinton@indigobio.com']
11
+ spec.summary = 'Ruby REST client'
12
+ spec.description = 'Ruby REST client for internal service architecture'
13
+ spec.homepage = ''
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 'yard'
24
+ spec.add_development_dependency 'rspec', '~> 3.0'
25
+ spec.add_development_dependency 'json_spec'
26
+ spec.add_development_dependency 'rspec_junit_formatter'
27
+
28
+ spec.add_runtime_dependency 'abstractivator', '~> 0.0'
29
+ spec.add_runtime_dependency 'activesupport'
30
+ spec.add_runtime_dependency 'immutable_struct', '~> 1.1'
31
+ spec.add_runtime_dependency 'oj', '~> 2.12'
32
+ spec.add_runtime_dependency 'typhoeus', '~> 0.7'
33
+ spec.add_runtime_dependency 'mimic', '~> 0.4.3'
34
+ end
@@ -0,0 +1,2 @@
1
+ class BugError < Exception
2
+ end
@@ -0,0 +1,5 @@
1
+ class Hash
2
+ def subhash?(hash)
3
+ each_pair.all?{|k, v| hash[k] == v}
4
+ end
5
+ end