xenon-routing 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +18 -0
  3. data/.gitignore +25 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +6 -0
  6. data/Gemfile +20 -0
  7. data/Guardfile +16 -0
  8. data/LICENSE +22 -0
  9. data/README.md +116 -0
  10. data/Rakefile +40 -0
  11. data/VERSION +1 -0
  12. data/examples/hello_world/config.ru +3 -0
  13. data/examples/hello_world/hello_world.rb +27 -0
  14. data/xenon-http/lib/xenon/auth.rb +63 -0
  15. data/xenon-http/lib/xenon/errors.rb +5 -0
  16. data/xenon-http/lib/xenon/etag.rb +48 -0
  17. data/xenon-http/lib/xenon/headers.rb +112 -0
  18. data/xenon-http/lib/xenon/headers/accept.rb +34 -0
  19. data/xenon-http/lib/xenon/headers/accept_charset.rb +59 -0
  20. data/xenon-http/lib/xenon/headers/accept_encoding.rb +63 -0
  21. data/xenon-http/lib/xenon/headers/accept_language.rb +59 -0
  22. data/xenon-http/lib/xenon/headers/authorization.rb +50 -0
  23. data/xenon-http/lib/xenon/headers/cache_control.rb +56 -0
  24. data/xenon-http/lib/xenon/headers/content_type.rb +23 -0
  25. data/xenon-http/lib/xenon/headers/if_match.rb +53 -0
  26. data/xenon-http/lib/xenon/headers/if_modified_since.rb +22 -0
  27. data/xenon-http/lib/xenon/headers/if_none_match.rb +53 -0
  28. data/xenon-http/lib/xenon/headers/if_range.rb +45 -0
  29. data/xenon-http/lib/xenon/headers/if_unmodified_since.rb +22 -0
  30. data/xenon-http/lib/xenon/headers/user_agent.rb +65 -0
  31. data/xenon-http/lib/xenon/headers/www_authenticate.rb +71 -0
  32. data/xenon-http/lib/xenon/http.rb +7 -0
  33. data/xenon-http/lib/xenon/http_version.rb +3 -0
  34. data/xenon-http/lib/xenon/media_type.rb +162 -0
  35. data/xenon-http/lib/xenon/parsers/basic_rules.rb +86 -0
  36. data/xenon-http/lib/xenon/parsers/header_rules.rb +60 -0
  37. data/xenon-http/lib/xenon/parsers/media_type.rb +53 -0
  38. data/xenon-http/lib/xenon/quoted_string.rb +20 -0
  39. data/xenon-http/spec/spec_helper.rb +94 -0
  40. data/xenon-http/spec/xenon/etag_spec.rb +19 -0
  41. data/xenon-http/spec/xenon/headers/accept_charset_spec.rb +31 -0
  42. data/xenon-http/spec/xenon/headers/accept_encoding_spec.rb +40 -0
  43. data/xenon-http/spec/xenon/headers/accept_language_spec.rb +33 -0
  44. data/xenon-http/spec/xenon/headers/accept_spec.rb +54 -0
  45. data/xenon-http/spec/xenon/headers/authorization_spec.rb +47 -0
  46. data/xenon-http/spec/xenon/headers/cache_control_spec.rb +64 -0
  47. data/xenon-http/spec/xenon/headers/if_match_spec.rb +73 -0
  48. data/xenon-http/spec/xenon/headers/if_modified_since_spec.rb +19 -0
  49. data/xenon-http/spec/xenon/headers/if_none_match_spec.rb +79 -0
  50. data/xenon-http/spec/xenon/headers/if_range_spec.rb +45 -0
  51. data/xenon-http/spec/xenon/headers/if_unmodified_since_spec.rb +19 -0
  52. data/xenon-http/spec/xenon/headers/user_agent_spec.rb +67 -0
  53. data/xenon-http/spec/xenon/headers/www_authenticate_spec.rb +43 -0
  54. data/xenon-http/spec/xenon/media_type_spec.rb +267 -0
  55. data/xenon-http/xenon-http.gemspec +25 -0
  56. data/xenon-routing/lib/xenon/api.rb +118 -0
  57. data/xenon-routing/lib/xenon/marshallers.rb +48 -0
  58. data/xenon-routing/lib/xenon/request.rb +40 -0
  59. data/xenon-routing/lib/xenon/response.rb +29 -0
  60. data/xenon-routing/lib/xenon/routing.rb +6 -0
  61. data/xenon-routing/lib/xenon/routing/context.rb +35 -0
  62. data/xenon-routing/lib/xenon/routing/directives.rb +14 -0
  63. data/xenon-routing/lib/xenon/routing/header_directives.rb +32 -0
  64. data/xenon-routing/lib/xenon/routing/method_directives.rb +26 -0
  65. data/xenon-routing/lib/xenon/routing/param_directives.rb +22 -0
  66. data/xenon-routing/lib/xenon/routing/path_directives.rb +37 -0
  67. data/xenon-routing/lib/xenon/routing/route_directives.rb +51 -0
  68. data/xenon-routing/lib/xenon/routing/security_directives.rb +34 -0
  69. data/xenon-routing/lib/xenon/routing_version.rb +3 -0
  70. data/xenon-routing/spec/spec_helper.rb +94 -0
  71. data/xenon-routing/xenon-routing.gemspec +25 -0
  72. data/xenon.gemspec +26 -0
  73. metadata +145 -0
@@ -0,0 +1,43 @@
1
+ require 'xenon/headers/www_authenticate'
2
+
3
+ describe Xenon::Headers::WWWAuthenticate do
4
+
5
+ context '::parse' do
6
+
7
+ it 'can parse a Basic challenge with a realm' do
8
+ header = Xenon::Headers::WWWAuthenticate.parse('Basic realm="simple"')
9
+ expect(header.challenges.size).to eq(1)
10
+ expect(header.challenges[0].auth_scheme).to eq('Basic')
11
+ expect(header.challenges[0].realm).to eq('simple')
12
+ end
13
+
14
+ it 'can parse a Digest challenge with a realm' do
15
+ header = Xenon::Headers::WWWAuthenticate.parse('Digest realm="testrealm@host.com", qop="auth,auth-int", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"')
16
+ expect(header.challenges.size).to eq(1)
17
+ expect(header.challenges[0].auth_scheme).to eq('Digest')
18
+ expect(header.challenges[0].realm).to eq('testrealm@host.com')
19
+ expect(header.challenges[0].qop).to eq('auth,auth-int')
20
+ expect(header.challenges[0].nonce).to eq('dcd98b7102dd2f0e8b11d0f600bfb0c093')
21
+ expect(header.challenges[0].opaque).to eq('5ccc069c403ebaf9f0171e9517f40e41')
22
+ end
23
+
24
+ it 'can parse a custom challenge' do
25
+ header = Xenon::Headers::WWWAuthenticate.parse('Newauth realm="apps", type=1, title="Login to \"apps\""')
26
+ expect(header.challenges.size).to eq(1)
27
+ expect(header.challenges[0].auth_scheme).to eq('Newauth')
28
+ expect(header.challenges[0].realm).to eq('apps')
29
+ expect(header.challenges[0].type).to eq('1')
30
+ expect(header.challenges[0].title).to eq('Login to "apps"')
31
+ end
32
+
33
+ it 'can parse multiple challenges' do
34
+ header = Xenon::Headers::WWWAuthenticate.parse('Digest realm="testrealm@host.com", qop="auth,auth-int", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41", Basic realm="simple", Newauth realm="apps", type=1, title="Login to \"apps\""')
35
+ expect(header.challenges.size).to eq(3)
36
+ expect(header.challenges[0].auth_scheme).to eq('Digest')
37
+ expect(header.challenges[1].auth_scheme).to eq('Basic')
38
+ expect(header.challenges[2].auth_scheme).to eq('Newauth')
39
+ end
40
+
41
+
42
+ end
43
+ end
@@ -0,0 +1,267 @@
1
+ require 'xenon/media_type'
2
+
3
+ describe Xenon::MediaType do
4
+
5
+ context '::parse' do
6
+ it 'can parse basic media types' do
7
+ mt = Xenon::MediaType.parse('application/json')
8
+ expect(mt.type).to eq('application')
9
+ expect(mt.subtype).to eq('json')
10
+ end
11
+ it 'can parse media types with a subtype suffix' do
12
+ mt = Xenon::MediaType.parse('application/rss+xml')
13
+ expect(mt.type).to eq('application')
14
+ expect(mt.subtype).to eq('rss+xml')
15
+ end
16
+
17
+ it 'can parse media types with parameters' do
18
+ mt = Xenon::MediaType.parse('text/plain; format=flowed; paged')
19
+ expect(mt.type).to eq('text')
20
+ expect(mt.subtype).to eq('plain')
21
+ expect(mt.params).to eq({ 'format' => 'flowed', 'paged' => nil })
22
+ end
23
+
24
+ it 'strips whitespace around separators' do
25
+ mt = Xenon::MediaType.parse('text/plain ; format = flowed ; paged')
26
+ expect(mt.type).to eq('text')
27
+ expect(mt.subtype).to eq('plain')
28
+ expect(mt.params).to eq({ 'format' => 'flowed', 'paged' => nil })
29
+ end
30
+
31
+ it 'raises an error when the media type contains wildcards' do
32
+ expect { Xenon::MediaType.parse('*/*') }.to raise_error(Xenon::ParseError)
33
+ expect { Xenon::MediaType.parse('application/*') }.to raise_error(Xenon::ParseError)
34
+ end
35
+
36
+ it 'raises an error when the media type is invalid' do
37
+ expect { Xenon::MediaType.parse('application') }.to raise_error(Xenon::ParseError)
38
+ expect { Xenon::MediaType.parse('application; foo=bar') }.to raise_error(Xenon::ParseError)
39
+ expect { Xenon::MediaType.parse('/json') }.to raise_error(Xenon::ParseError)
40
+ expect { Xenon::MediaType.parse('/json; foo=bar') }.to raise_error(Xenon::ParseError)
41
+ end
42
+ end
43
+
44
+ %w(application audio image message multipart text video).each do |type|
45
+ context "#{type}?" do
46
+ it "returns true when the root type is '#{type}'" do
47
+ mt = Xenon::MediaType.new(type, 'dummy')
48
+ expect(mt.send("#{type}?")).to eq(true)
49
+ end
50
+
51
+ it "returns false when the root type is not '#{type}'" do
52
+ mt = Xenon::MediaType.new('dummy', 'dummy')
53
+ expect(mt.send("#{type}?")).to eq(false)
54
+ end
55
+ end
56
+ end
57
+
58
+ { experimental?: 'x', personal?: 'prs', vendor?: 'vnd' }.each do |method, prefix|
59
+ context method do
60
+ it "returns true when the subtype starts with '#{prefix}.'" do
61
+ mt = Xenon::MediaType.new('application', "#{prefix}.dummy")
62
+ expect(mt.send(method)).to eq(true)
63
+ end
64
+
65
+ it "returns false when the subtype does not start with '#{prefix}.'" do
66
+ mt = Xenon::MediaType.new('application', "dummy.dummy")
67
+ expect(mt.send(method)).to eq(false)
68
+ end
69
+ end
70
+ end
71
+
72
+ %w(ber der fastinfoset json wbxml xml zip).each do |format|
73
+ context "#{format}?" do
74
+ it "returns true when the subtype is '#{format}'" do
75
+ mt = Xenon::MediaType.new('application', format)
76
+ expect(mt.send("#{format}?")).to eq(true)
77
+ end
78
+
79
+ it "returns true when the subtype ends with '+#{format}'" do
80
+ mt = Xenon::MediaType.new('application', "dummy+#{format}")
81
+ expect(mt.send("#{format}?")).to eq(true)
82
+ end
83
+
84
+ it "returns false when the subtype is not '#{format}' and does not end with '+#{format}'" do
85
+ mt = Xenon::MediaType.new('dummy', 'dummy+dummy')
86
+ expect(mt.send("#{format}?")).to eq(false)
87
+ end
88
+ end
89
+ end
90
+
91
+ context '#to_s' do
92
+ it 'returns the string representation of a media type' do
93
+ mt = Xenon::MediaType.new('text', 'plain', 'format' => 'flowed', 'paged' => nil)
94
+ expect(mt.to_s).to eq('text/plain; format=flowed; paged')
95
+ end
96
+ end
97
+
98
+ end
99
+
100
+ describe Xenon::MediaRange do
101
+
102
+ context '::parse' do
103
+ it 'can parse basic media ranges' do
104
+ mt = Xenon::MediaRange.parse('application/json')
105
+ expect(mt.type).to eq('application')
106
+ expect(mt.subtype).to eq('json')
107
+ end
108
+
109
+ it 'can parse media ranges with parameters' do
110
+ mt = Xenon::MediaRange.parse('text/plain; format=flowed; paged')
111
+ expect(mt.type).to eq('text')
112
+ expect(mt.subtype).to eq('plain')
113
+ expect(mt.params).to eq({ 'format' => 'flowed', 'paged' => nil })
114
+ end
115
+
116
+ it 'strips whitespace around separators' do
117
+ mt = Xenon::MediaRange.parse('text/plain ; format = flowed ; paged')
118
+ expect(mt.type).to eq('text')
119
+ expect(mt.subtype).to eq('plain')
120
+ expect(mt.params).to eq({ 'format' => 'flowed', 'paged' => nil })
121
+ end
122
+
123
+ it 'can parse media ranges with subtype wildcards' do
124
+ mt = Xenon::MediaRange.parse('application/*')
125
+ expect(mt.type).to eq('application')
126
+ expect(mt.subtype).to eq('*')
127
+ end
128
+
129
+ it 'can parse media ranges with type and subtype wildcards' do
130
+ mt = Xenon::MediaRange.parse('*/*')
131
+ expect(mt.type).to eq('*')
132
+ expect(mt.subtype).to eq('*')
133
+ end
134
+
135
+ it 'extracts q from the parameters' do
136
+ mt = Xenon::MediaRange.parse('text/plain; q=0.8; format=flowed; paged')
137
+ expect(mt.type).to eq('text')
138
+ expect(mt.subtype).to eq('plain')
139
+ expect(mt.q).to eq(0.8)
140
+ expect(mt.params).to eq({ 'format' => 'flowed', 'paged' => nil })
141
+ end
142
+
143
+ it 'uses the default value for q if the value is not numeric' do
144
+ mt = Xenon::MediaRange.parse('application/json; q=foo')
145
+ expect(mt.type).to eq('application')
146
+ expect(mt.subtype).to eq('json')
147
+ expect(mt.q).to eq(Xenon::MediaRange::DEFAULT_Q)
148
+ end
149
+
150
+ it 'raises an error when the media range is invalid' do
151
+ expect { Xenon::MediaRange.parse('application') }.to raise_error(Xenon::ParseError)
152
+ expect { Xenon::MediaRange.parse('application; foo=bar') }.to raise_error(Xenon::ParseError)
153
+ expect { Xenon::MediaRange.parse('*/json') }.to raise_error(Xenon::ParseError)
154
+ expect { Xenon::MediaRange.parse('/json') }.to raise_error(Xenon::ParseError)
155
+ expect { Xenon::MediaRange.parse('/json; foo=bar') }.to raise_error(Xenon::ParseError)
156
+ end
157
+ end
158
+
159
+ context '#<=>' do
160
+ it 'considers a wildcard type less than a regular type' do
161
+ mr1 = Xenon::MediaRange.new('*', '*')
162
+ mr2 = Xenon::MediaRange.new('text', '*')
163
+ expect(mr1 <=> mr2).to eq(-1)
164
+ end
165
+
166
+ it 'considers a wildcard subtype less than a regular subtype' do
167
+ mr1 = Xenon::MediaRange.new('application', '*')
168
+ mr2 = Xenon::MediaRange.new('text', 'plain')
169
+ expect(mr1 <=> mr2).to eq(-1)
170
+ end
171
+
172
+ it 'considers media ranges with type and subtype equal' do
173
+ mr1 = Xenon::MediaRange.new('application', 'json')
174
+ mr2 = Xenon::MediaRange.new('text', 'plain')
175
+ expect(mr1 <=> mr2).to eq(0)
176
+ end
177
+
178
+ it 'considers a media range with parameters greater than one without' do
179
+ mr1 = Xenon::MediaRange.new('text', 'plain', 'format' => 'flowed')
180
+ mr2 = Xenon::MediaRange.new('text', 'plain')
181
+ expect(mr1 <=> mr2).to eq(1)
182
+ end
183
+
184
+ it 'does not consider the quality when one media range is more specific' do
185
+ mr1 = Xenon::MediaRange.new('application', '*', 'q' => '0.3')
186
+ mr2 = Xenon::MediaRange.new('*', '*', 'q' => '0.5')
187
+ expect(mr1 <=> mr2).to eq(1)
188
+ end
189
+
190
+ it 'considers the quality when media ranges are equally specific' do
191
+ mr1 = Xenon::MediaRange.new('application', 'json', 'q' => '0.8')
192
+ mr2 = Xenon::MediaRange.new('application', 'xml')
193
+ expect(mr1 <=> mr2).to eq(-1)
194
+ end
195
+ end
196
+
197
+ %i(=~ ===).each do |name|
198
+ context "##{name}" do
199
+ it 'returns true when the type and subtype are wildcards' do
200
+ mr = Xenon::MediaRange.new('*', '*')
201
+ mt = Xenon::MediaType.new('application', 'json')
202
+ expect(mr.send(name, mt)).to eq(true)
203
+ end
204
+
205
+ it 'returns true when the type matches and subtype is a wildcard' do
206
+ mr = Xenon::MediaRange.new('application', '*')
207
+ mt = Xenon::MediaType.new('application', 'json')
208
+ expect(mr.send(name, mt)).to eq(true)
209
+ end
210
+
211
+ it 'returns true when the type and subtype match exactly' do
212
+ mr = Xenon::MediaRange.new('application', 'json')
213
+ mt = Xenon::MediaType.new('application', 'json')
214
+ expect(mr.send(name, mt)).to eq(true)
215
+ end
216
+
217
+ it 'returns true when the type, subtype and parameters match exactly' do
218
+ mr = Xenon::MediaRange.new('text', 'plain', 'format' => 'flowed')
219
+ mt = Xenon::MediaType.new('text', 'plain', 'format' => 'flowed', 'paged' => nil)
220
+ expect(mr.send(name, mt)).to eq(true)
221
+ end
222
+
223
+ it 'returns true when the the media type has more specific parameters' do
224
+ mr = Xenon::MediaRange.new('text', 'plain')
225
+ mt = Xenon::MediaType.new('text', 'plain', 'format' => 'flowed', 'paged' => nil)
226
+ expect(mr.send(name, mt)).to eq(true)
227
+ end
228
+
229
+ it 'returns false when the type is different' do
230
+ mr = Xenon::MediaRange.new('text', 'json')
231
+ mt = Xenon::MediaType.new('application', 'json')
232
+ expect(mr.send(name, mt)).to eq(false)
233
+ end
234
+
235
+ it 'returns false when the type matches but subtype is different' do
236
+ mr = Xenon::MediaRange.new('application', 'xml')
237
+ mt = Xenon::MediaType.new('application', 'json')
238
+ expect(mr.send(name, mt)).to eq(false)
239
+ end
240
+
241
+ it 'returns false when the media range has more specific parameters' do
242
+ mr = Xenon::MediaRange.new('text', 'plain', 'format' => 'flowed', 'paged' => nil)
243
+ mt = Xenon::MediaType.new('text', 'plain')
244
+ expect(mr.send(name, mt)).to eq(false)
245
+ end
246
+
247
+ it 'returns false when the media range has a different parameter value' do
248
+ mr = Xenon::MediaRange.new('text', 'plain', 'format' => 'flowed')
249
+ mt = Xenon::MediaType.new('text', 'plain', 'format' => 'linear')
250
+ expect(mr.send(name, mt)).to eq(false)
251
+ end
252
+ end
253
+ end
254
+
255
+ context '#to_s' do
256
+ it 'returns the string representation of a media range' do
257
+ mt = Xenon::MediaRange.new('text', 'plain', 'q' => 0.8, 'format' => 'flowed', 'paged' => nil)
258
+ expect(mt.to_s).to eq('text/plain; format=flowed; paged; q=0.8')
259
+ end
260
+
261
+ it 'omits the q parameter when it is 1.0' do
262
+ mt = Xenon::MediaRange.new('application', 'json', 'q' => 1.0)
263
+ expect(mt.to_s).to eq('application/json')
264
+ end
265
+ end
266
+
267
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'xenon/http_version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'xenon-http'
8
+ spec.version = Xenon::HTTP_VERSION
9
+ spec.authors = ['Greg Beech']
10
+ spec.email = ['greg@gregbeech.com']
11
+ spec.summary = %q{An HTTP framework for building RESTful APIs.}
12
+ spec.description = %q{Provides a model for the HTTP protocol and a tree-based routing syntax.}
13
+ spec.homepage = 'https://github.com/gregbeech/xenon'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^xenon-http/bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^xenon-http/(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.required_ruby_version = '>= 2.2.0'
22
+
23
+ spec.add_runtime_dependency 'activesupport', '~> 4.0'
24
+ spec.add_runtime_dependency 'parslet', '~> 1.7'
25
+ end
@@ -0,0 +1,118 @@
1
+ require 'json'
2
+ require 'rack'
3
+ require 'xenon/routing'
4
+
5
+ module Xenon
6
+ class API
7
+ include Xenon::Routing::Directives
8
+
9
+ DEFAULT_MARSHALLERS = [JsonMarshaller.new]
10
+
11
+ class << self
12
+ def marshallers(*marshallers)
13
+ @marshallers = marshallers unless marshallers.nil? || marshallers.empty?
14
+ (@marshallers.nil? || @marshallers.empty?) ? DEFAULT_MARSHALLERS : @marshallers
15
+ end
16
+
17
+ def select_marshaller(media_ranges)
18
+ weighted = marshallers.map do |marshaller|
19
+ media_range = media_ranges.find { |media_range| marshaller.marshal_to?(media_range) }
20
+ [marshaller, media_range ? media_range.q : 0]
21
+ end
22
+ weighted.select { |_, q| q > 0 }.sort_by { |_, q| q }.map { |m, _| m }.last
23
+ end
24
+ end
25
+
26
+ attr_reader :context
27
+
28
+ class << self
29
+ def routes
30
+ @routes ||= []
31
+ end
32
+
33
+ def method_missing(name, *args, &block)
34
+ if instance_methods.include?(name)
35
+ routes << [name, args, block]
36
+ else
37
+ super
38
+ end
39
+ end
40
+ end
41
+
42
+ def call(env)
43
+ dup.call!(env)
44
+ end
45
+
46
+ def call!(env)
47
+ @context = Routing::Context.new(Request.new(Rack::Request.new(env)), Response.new)
48
+
49
+ accept = @context.request.header('Accept')
50
+ marshaller = accept ? self.class.select_marshaller(accept.media_ranges) : self.class.marshallers.first
51
+
52
+ catch (:complete) do
53
+ begin
54
+ if marshaller
55
+ self.class.routes.each do |route|
56
+ name, args, block = route
57
+ route_block = proc { instance_eval(&block) }
58
+ send(name, *args, &route_block)
59
+ end
60
+ else
61
+ reject :accept, supported: self.class.marshallers.map(&:media_type)
62
+ end
63
+ handle_rejections(@context.rejections)
64
+ rescue => e
65
+ handle_error(e)
66
+ end
67
+ end
68
+
69
+ marshaller ||= self.class.marshallers.first
70
+ resp = @context.response.copy(
71
+ headers: @context.response.headers.set(Headers::ContentType.new(marshaller.content_type)),
72
+ body: marshaller.marshal(@context.response.body))
73
+ [resp.status, resp.headers.map { |h| [h.name, h.to_s] }.to_h, resp.body]
74
+ end
75
+
76
+ def handle_error(e)
77
+ puts "handle_error: #{e.class}: #{e}\n #{e.backtrace.join("\n ")}"
78
+ case e
79
+ when ParseError
80
+ fail 400, e.message
81
+ else
82
+ fail 500, e.message # TODO: Only if verbose errors configured
83
+ end
84
+ end
85
+
86
+ def handle_rejections(rejections)
87
+ puts "handle_rejections: #{rejections}"
88
+ if rejections.empty?
89
+ fail 404
90
+ else
91
+ rejection = rejections.first
92
+ case rejection.reason
93
+ when :accept
94
+ fail 406, "Supported media types: #{rejection[:supported].join(", ")}"
95
+ when :forbidden
96
+ fail 403
97
+ when :header
98
+ fail 400, "Missing required header: #{rejection[:required]}"
99
+ when :method
100
+ supported = rejections.take_while { |r| r.reason == :method }.map { |r| r[:supported].upcase }
101
+ fail 405, "Supported methods: #{supported.join(", ")}"
102
+ when :unauthorized
103
+ if rejection[:scheme]
104
+ challenge = Headers::Challenge.new(rejection[:scheme], rejection.info.except(:scheme))
105
+ respond_with_header Headers::WWWAuthenticate.new(challenge) do
106
+ fail 401
107
+ end
108
+ else
109
+ fail 401
110
+ end
111
+ else
112
+ fail 500
113
+ end
114
+ end
115
+ end
116
+
117
+ end
118
+ end