jersey 0.0.3

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c73c2c478b36fbd1a0574323dcbb7f023264517d
4
+ data.tar.gz: d7f9a1f7e5cd742a9de16d62c12b8b9cce788137
5
+ SHA512:
6
+ metadata.gz: 81638bfb73758bf758ae0245c163f88c2bf01c9214736a036c74f00e4a8f3a6309888588e07be9878783835f4ebad5bf02c348be1ec5dbb2adcdac72637d095e
7
+ data.tar.gz: 49fc8716d35f5e8b8d1cffb85d912e391c5325b22e8e72a751b9aedebaee14a94f82d9e27727b92773808d660790bcd4f0f9f95914845ac1d002adb499efced3
@@ -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/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in jersey.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'minitest'
8
+ gem 'rack-test', require: 'rack/test'
9
+ gem 'logfmt'
10
+ gem 'puma'
11
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 csquared
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.
@@ -0,0 +1,233 @@
1
+ # Jersey
2
+
3
+ <img src="http://geology.com/state-map/maps/new-jersey-physical-map.gif" height="200px" />
4
+
5
+ Because [worse is better](http://en.wikipedia.org/wiki/Worse_is_better).
6
+
7
+ Jersey is a gem for people who want to write excellent APIs but have their
8
+ own opinions on how to structure their code and projects.
9
+
10
+ Jersey provides sensible defaults that are composed with sensible pieces, making
11
+ it easy to compose your own stack or use Jesery's compositions.
12
+
13
+ ## Features
14
+ - env-conf for easy ENV based configuration
15
+ - request context aware request logging
16
+ - structured data loggers - json and logfmt
17
+ - unified exception handling
18
+
19
+ ### `Jersey.setup`
20
+
21
+ Setup embodies a few opinions we have about apps:
22
+ - Having a notion of 'environment' where the configuration lives
23
+ - Using a dependency manager
24
+ - Always using UTC for the Time zone
25
+ - Always printing times in ISO8601 format
26
+
27
+ `Jersey.setup` allows any app that has Gemfiles and `.env` files to
28
+ "just work" in development *and* production with just:
29
+
30
+ ```ruby
31
+ require 'jersey'
32
+ Jersey.setup
33
+ ```
34
+
35
+ Which is usually included as the first line of code in a library consuming
36
+ Jersey. For tests, you will want to set `RACK_ENV` to `test` before
37
+ requiring your library that uses the above.
38
+
39
+ ### `Jersey::API::Base`
40
+
41
+ Combines all of the Jersey middleware with a few standard middleware
42
+ from Rack and sinatra-contrib.
43
+
44
+ ```ruby
45
+ class API < Jersey::API::Base
46
+ get '/hello' do
47
+ Jersey.log(at: "hello")
48
+ 'hello'
49
+ end
50
+
51
+ get '/not_found' do
52
+ raise NotFound, "y u no here?"
53
+ end
54
+ end
55
+ ```
56
+
57
+
58
+ ```
59
+ $ curl http://localhost:9292/hello
60
+ ```
61
+
62
+ Server logs:
63
+ ```
64
+ at=start method=GET path=/hello request_id=2c018557-d246-4b04-a7d7-fc74bae67ec0 now=2014-11-11T18:04:25+00:00
65
+ at=hello now=2014-11-11T18:04:25+00:00 request_id=2c018557-d246-4b04-a7d7-fc74bae67ec0
66
+ at=finish method=GET path=/hello status=200 size#bytes=5 route_signature=/hello elapsed=0.000 request_id=2c018557-d246-4b04-a7d7-fc74bae67ec0 now=2014-11-11T18:04:25+00:00
67
+ ```
68
+
69
+ Unified, structured logging with all the info you will wish you had:
70
+
71
+ ```
72
+ $ curl http://localhost:9292/not_found | jq '.'
73
+ ```
74
+
75
+ Server logs:
76
+ ```
77
+ at=start method=GET path=/not_found request_id=f3085630-05cf-4314-a7dd-5855e752594b now=2014-11-11T18:05:15+00:00
78
+ at=finish method=GET path=/not_found status=404 size#bytes=6212 route_signature=/not_found elapsed=0.001 request_id=f3085630-05cf-4314-a7dd-5855e752594b now=2014-11-11T18:05:15+00:00
79
+ ```
80
+
81
+ Response:
82
+ ```json
83
+ {
84
+ "error": {
85
+ "type": "NotFound",
86
+ "message": "y u no here?",
87
+ "backtrace": [
88
+ "/Users/csquared/projects/jersey/examples/readme.ru:11:in `block in <class:API>'",
89
+ "/Users/csquared/projects/jersey/.bundle/bundle/ruby/2.1.0/gems/sinatra-1.4.5/lib/sinatra/base.rb:1603:in `call'",
90
+ ....
91
+ ]
92
+ }
93
+ }
94
+ ```
95
+
96
+ Unified, strucutred error handling. Notice how all we needed to do was raise `NotFound`
97
+ and we get a 404 response code (in the server logs) and our error message as part of the JSON payload.
98
+
99
+ #### `Jersey::HTTP::Errors`
100
+
101
+ Includes Ruby `Error` objects named with camel case for all of the HTTP 4xx and 5xx
102
+ errors. This allows you to raise `NotFound` as an error that has the `STATUS_CODE`
103
+ defined.
104
+
105
+ Allows uniform HTTP error handling when combined with the `ErrorHandler` sinatra extension.
106
+
107
+ #### Usage
108
+ Mix-in to any class that wants to raise HTTP errors, usually an API class.
109
+
110
+ ```ruby
111
+ class API < Sinatra::Base
112
+ include Jersey::HTTP::Errors
113
+ end
114
+ ```
115
+
116
+ ### `Jersey::Extensions::ErrorHandler`
117
+
118
+ Unifies error responses. If the error object's class has a `STATUS_CODE` defined (such as the
119
+ errors in `Jersey::HTTP::Errors`), this will use that as the HTTP return status. The error
120
+ message and backtraces are included in responses assuming that this in for an internal API
121
+ over secured channels and therefore favors ease of debugging over the security risk of
122
+ including the backtrace. This is something I may want to configure.
123
+
124
+ #### Usage
125
+ Register as a Sinatra extension
126
+
127
+ ```ruby
128
+ class API < Sinatra::Base
129
+ register Jersey::Extensions::ErrorHandler
130
+ end
131
+ ```
132
+
133
+ #### `Jersey::Extensions::RouteSignature`
134
+ Adds a `ROUTE_SIGNATURE` to the `env` for each request, which is the name of an api endpoint
135
+ as it is *defined* versus the path that reaches your app.
136
+ For example, when you define a route such as ` get "/hello/:id"`, the `ROUTE_SIGNATURE` would
137
+ equal `"/hello/:id"`.
138
+ When combined with the `RequestLogger`,
139
+ it greatly simplifies creating aggregate statistics about the traffic hitting various api endpoints.
140
+
141
+ *Note:* this is considered a hack and something that sinatra should, but does not, handle.
142
+
143
+ #### Usage
144
+ Register as a Sinatra extension
145
+
146
+ ```ruby
147
+ class API < Sinatra::Base
148
+ register Jersey::Extensions::RouteSignature
149
+ end
150
+ ```
151
+
152
+ #### `Jersey::Middleware::RequestID`
153
+
154
+ Creates a random request id for every http request, stored both in thread local storage
155
+ via the `RequestStore` and in the Rack `env`.
156
+
157
+
158
+ Works with or without explicitly including `RequestStore::Middleware`.
159
+
160
+ #### Usage
161
+ Use as a Rack middleware
162
+
163
+ ```ruby
164
+ class API < Sinatra::Base
165
+ use Jersey::Middleware::RequestID
166
+ end
167
+ ```
168
+
169
+ #### `Jersey::Middleware::RequestLogger`
170
+
171
+ Logs http start and finish and errors in a structured logging format.
172
+
173
+ It defaults to using the `Jersey.logger` singleton which is `RequestStore`-aware.
174
+ Anything in `RequestStore[:log]` will get appended to the log data. (This is how request ids
175
+ are made available to logger calls outside of HTTP blocks but within HTTP request lifecycles).
176
+
177
+ Because I think request_ids are important, the logger will try to get them from either the
178
+ `RequestStore` or the `env`.
179
+
180
+ Logs at request start:
181
+
182
+ at: "start",
183
+ request_id: env['REQUEST_ID'],
184
+ method: request.request_method,
185
+ path: request.path_info,
186
+ content_type: request.content_type,
187
+ content_length: request.content_length
188
+
189
+ Logs at request finish:
190
+
191
+ at: "finish",
192
+ method: request.request_method,
193
+ path: request.path_info,
194
+ status: status,
195
+ content_length: headers['Content-Length'],
196
+ route_signature: env['ROUTE_SIGNATURE'],
197
+ elapsed: (Time.now - @request_start).to_f,
198
+ request_id: env['REQUEST_ID']
199
+
200
+
201
+ #### Usage
202
+ Use as a Rack middleware
203
+
204
+ ```ruby
205
+ class API < Sinatra::Base
206
+ use Jersey::Middleware::RequestLogger
207
+ end
208
+ ```
209
+
210
+ ## Installation
211
+
212
+ Add this line to your application's Gemfile:
213
+
214
+ ```ruby
215
+ gem 'jersey'
216
+ ```
217
+
218
+ And then execute:
219
+
220
+ $ bundle
221
+
222
+ Or install it yourself as:
223
+
224
+ $ gem install jersey
225
+
226
+
227
+ ## Contributing
228
+
229
+ 1. Fork it ( https://github.com/[my-github-username]/jersey/fork )
230
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
231
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
232
+ 4. Push to the branch (`git push origin my-new-feature`)
233
+ 5. Create a new Pull Request
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.test_files = FileList['test/**/*_test.rb']
7
+ t.verbose = true
8
+ end
9
+
10
+ task default: :test
@@ -0,0 +1,15 @@
1
+ require 'jersey'
2
+ Jersey.setup
3
+
4
+ class API < Jersey::API::Base
5
+ get '/hello' do
6
+ Jersey.log(at: "hello")
7
+ 'hello'
8
+ end
9
+
10
+ get '/not_found' do
11
+ raise NotFound, "y u no here?"
12
+ end
13
+ end
14
+
15
+ run API
@@ -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 'jersey/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "jersey"
8
+ spec.version = Jersey::VERSION
9
+ spec.authors = ["csquared"]
10
+ spec.email = ["christopher.continanza@gmail.com"]
11
+ spec.summary = %q{Write APIs in the New Jersey Style}
12
+ spec.description = %q{Set of composable middleware and helpers for production sinatra APIs}
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_dependency "sinatra", "~> 1.4"
22
+ spec.add_dependency 'sinatra-contrib', "~> 1.4"
23
+ spec.add_dependency 'env-conf'
24
+ spec.add_dependency 'request_store'
25
+ spec.add_development_dependency "bundler", "~> 1.7"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ end
@@ -0,0 +1,10 @@
1
+ require "jersey/version"
2
+ require 'jersey/log'
3
+
4
+ module Jersey
5
+ end
6
+
7
+ require 'jersey/setup'
8
+ require 'jersey/api'
9
+ require 'jersey/base'
10
+ require 'jersey/time'
@@ -0,0 +1,12 @@
1
+ module Jersey
2
+ module API
3
+ module Middleware
4
+ end
5
+
6
+ module Extensions
7
+ end
8
+
9
+ module Helpers
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,31 @@
1
+ require "sinatra/base"
2
+ require 'sinatra/json'
3
+ require 'json'
4
+ require 'request_store'
5
+
6
+ # jersey
7
+ require 'jersey/http_errors'
8
+ require 'jersey/middleware/request_id'
9
+ require 'jersey/middleware/request_logger'
10
+ require 'jersey/extensions/route_signature'
11
+ require 'jersey/extensions/error_handler'
12
+ require 'jersey/helpers/log'
13
+
14
+ module Jersey::API
15
+ class Base < Sinatra::Base
16
+ include Jersey::HTTP::Errors
17
+
18
+ register Jersey::Extensions::RouteSignature
19
+ register Jersey::Extensions::ErrorHandler
20
+
21
+ use Rack::Deflater
22
+ use Rack::ConditionalGet
23
+ use Rack::ETag
24
+
25
+ use Jersey::Middleware::RequestID
26
+ use Jersey::Middleware::RequestLogger
27
+
28
+ helpers Sinatra::JSON
29
+ helpers Jersey::Helpers::Log
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ module Jersey::Extensions
2
+ module ErrorHandler
3
+ def self.registered(app)
4
+ app.set :dump_errors, false
5
+ app.set :raise_errors, false
6
+ app.set :show_exceptions, false
7
+
8
+ # APIS should always return meaningful standardized errors
9
+ # with statuses that best match HTTP conventions
10
+ #
11
+ # Places nicely with Jersey::HTTP::Errors
12
+ app.error do
13
+ content_type(:json)
14
+ e = env['sinatra.error']
15
+ # get status code from Jersey Errors
16
+ if e.class.const_defined?(:STATUS_CODE)
17
+ status(e.class::STATUS_CODE)
18
+ else
19
+ status(500)
20
+ end
21
+
22
+ json(error: {
23
+ type: e.class.name.split('::').last,
24
+ message: e.message,
25
+ backtrace: e.backtrace
26
+ })
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ module Jersey::Extensions
2
+ module RouteSignature
3
+ def self.registered(app)
4
+ app.helpers do
5
+ def route_signature
6
+ env["ROUTE_SIGNATURE"]
7
+ end
8
+ end
9
+ end
10
+
11
+ def route(verb, path, *)
12
+ condition { env["ROUTE_SIGNATURE"] = path.to_s }
13
+ super
14
+ end
15
+ end
16
+ end