hyperion_http 0.1.2

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.
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