xenon 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. metadata +13 -145
  3. data/.codeclimate.yml +0 -18
  4. data/.gitignore +0 -25
  5. data/.rspec +0 -3
  6. data/.travis.yml +0 -6
  7. data/Gemfile +0 -13
  8. data/Guardfile +0 -16
  9. data/LICENSE +0 -22
  10. data/README.md +0 -86
  11. data/Rakefile +0 -8
  12. data/examples/hello_world/config.ru +0 -3
  13. data/examples/hello_world/hello_world.rb +0 -17
  14. data/lib/xenon.rb +0 -253
  15. data/lib/xenon/auth.rb +0 -63
  16. data/lib/xenon/errors.rb +0 -5
  17. data/lib/xenon/etag.rb +0 -48
  18. data/lib/xenon/headers.rb +0 -112
  19. data/lib/xenon/headers/accept.rb +0 -34
  20. data/lib/xenon/headers/accept_charset.rb +0 -59
  21. data/lib/xenon/headers/accept_encoding.rb +0 -63
  22. data/lib/xenon/headers/accept_language.rb +0 -59
  23. data/lib/xenon/headers/authorization.rb +0 -50
  24. data/lib/xenon/headers/cache_control.rb +0 -56
  25. data/lib/xenon/headers/content_type.rb +0 -23
  26. data/lib/xenon/headers/if_match.rb +0 -53
  27. data/lib/xenon/headers/if_modified_since.rb +0 -22
  28. data/lib/xenon/headers/if_none_match.rb +0 -53
  29. data/lib/xenon/headers/if_range.rb +0 -45
  30. data/lib/xenon/headers/if_unmodified_since.rb +0 -22
  31. data/lib/xenon/headers/user_agent.rb +0 -65
  32. data/lib/xenon/headers/www_authenticate.rb +0 -70
  33. data/lib/xenon/media_type.rb +0 -162
  34. data/lib/xenon/parsers/basic_rules.rb +0 -86
  35. data/lib/xenon/parsers/header_rules.rb +0 -60
  36. data/lib/xenon/parsers/media_type.rb +0 -53
  37. data/lib/xenon/quoted_string.rb +0 -20
  38. data/lib/xenon/routing/directives.rb +0 -14
  39. data/lib/xenon/routing/header_directives.rb +0 -32
  40. data/lib/xenon/routing/method_directives.rb +0 -26
  41. data/lib/xenon/routing/param_directives.rb +0 -22
  42. data/lib/xenon/routing/path_directives.rb +0 -37
  43. data/lib/xenon/routing/route_directives.rb +0 -51
  44. data/lib/xenon/routing/security_directives.rb +0 -20
  45. data/lib/xenon/version.rb +0 -3
  46. data/spec/spec_helper.rb +0 -94
  47. data/spec/xenon/etag_spec.rb +0 -19
  48. data/spec/xenon/headers/accept_charset_spec.rb +0 -31
  49. data/spec/xenon/headers/accept_encoding_spec.rb +0 -40
  50. data/spec/xenon/headers/accept_language_spec.rb +0 -33
  51. data/spec/xenon/headers/accept_spec.rb +0 -54
  52. data/spec/xenon/headers/authorization_spec.rb +0 -47
  53. data/spec/xenon/headers/cache_control_spec.rb +0 -64
  54. data/spec/xenon/headers/if_match_spec.rb +0 -73
  55. data/spec/xenon/headers/if_modified_since_spec.rb +0 -19
  56. data/spec/xenon/headers/if_none_match_spec.rb +0 -79
  57. data/spec/xenon/headers/if_range_spec.rb +0 -45
  58. data/spec/xenon/headers/if_unmodified_since_spec.rb +0 -19
  59. data/spec/xenon/headers/user_agent_spec.rb +0 -67
  60. data/spec/xenon/headers/www_authenticate_spec.rb +0 -43
  61. data/spec/xenon/media_type_spec.rb +0 -267
  62. data/xenon.gemspec +0 -30
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c82b7b50f76ea05e462ab4969825863e27e1f51a
4
- data.tar.gz: 033692599c1325d96064ee10d72ff2302b51db5f
3
+ metadata.gz: 667df7004abbed158847a2aba0f15151db579d8f
4
+ data.tar.gz: d3088dfa5b57e422c80bd682bf77dba901e8e6ff
5
5
  SHA512:
6
- metadata.gz: 55964419254b71d4c37032fc6c51fb0bda9ac770b04353b0b0f6e79428b372ce723504a33b0331380440398586ba1f647171e142188d34557e144b8632409689
7
- data.tar.gz: 1ff9d197b45558a4dc20ad0f5dbd8ce037ea4301384149d66ebc29d80787c05f3b4be3b5dde9ea9e2eb110af18af0834423a9edc0f3f04cbd4d0dc3d89bae391
6
+ metadata.gz: 6ec395fab860a41762f82b91aac705618e8a487ce38466db91a94731405157a231ccbf55abf9861b7fd4efbd6222b5d1377bfaeb9f6bd562dc0eabec1f9dd134
7
+ data.tar.gz: addd9426fde2c16ed7fbd073f9da3dd409288bf61722e74743cf9c7703f26c65c08d2b016ed8b48e4451e1993908e78595b0c8fe986629ac1cf0a0513d25183f
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xenon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Greg Beech
@@ -11,156 +11,40 @@ cert_chain: []
11
11
  date: 2015-10-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: activesupport
14
+ name: xenon-http
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: '4.0'
19
+ version: 0.0.4
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: '4.0'
26
+ version: 0.0.4
27
27
  - !ruby/object:Gem::Dependency
28
- name: parslet
28
+ name: xenon-routing
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: '1.7'
33
+ version: 0.0.4
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: '1.7'
41
- - !ruby/object:Gem::Dependency
42
- name: rack
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '1.6'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '1.6'
55
- - !ruby/object:Gem::Dependency
56
- name: bundler
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '1.10'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '1.10'
69
- - !ruby/object:Gem::Dependency
70
- name: rake
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '10.4'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '10.4'
83
- - !ruby/object:Gem::Dependency
84
- name: rspec
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '3.3'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '3.3'
40
+ version: 0.0.4
97
41
  description: Provides a model for the HTTP protocol and a tree-based routing syntax.
98
42
  email:
99
43
  - greg@gregbeech.com
100
44
  executables: []
101
45
  extensions: []
102
46
  extra_rdoc_files: []
103
- files:
104
- - ".codeclimate.yml"
105
- - ".gitignore"
106
- - ".rspec"
107
- - ".travis.yml"
108
- - Gemfile
109
- - Guardfile
110
- - LICENSE
111
- - README.md
112
- - Rakefile
113
- - examples/hello_world/config.ru
114
- - examples/hello_world/hello_world.rb
115
- - lib/xenon.rb
116
- - lib/xenon/auth.rb
117
- - lib/xenon/errors.rb
118
- - lib/xenon/etag.rb
119
- - lib/xenon/headers.rb
120
- - lib/xenon/headers/accept.rb
121
- - lib/xenon/headers/accept_charset.rb
122
- - lib/xenon/headers/accept_encoding.rb
123
- - lib/xenon/headers/accept_language.rb
124
- - lib/xenon/headers/authorization.rb
125
- - lib/xenon/headers/cache_control.rb
126
- - lib/xenon/headers/content_type.rb
127
- - lib/xenon/headers/if_match.rb
128
- - lib/xenon/headers/if_modified_since.rb
129
- - lib/xenon/headers/if_none_match.rb
130
- - lib/xenon/headers/if_range.rb
131
- - lib/xenon/headers/if_unmodified_since.rb
132
- - lib/xenon/headers/user_agent.rb
133
- - lib/xenon/headers/www_authenticate.rb
134
- - lib/xenon/media_type.rb
135
- - lib/xenon/parsers/basic_rules.rb
136
- - lib/xenon/parsers/header_rules.rb
137
- - lib/xenon/parsers/media_type.rb
138
- - lib/xenon/quoted_string.rb
139
- - lib/xenon/routing/directives.rb
140
- - lib/xenon/routing/header_directives.rb
141
- - lib/xenon/routing/method_directives.rb
142
- - lib/xenon/routing/param_directives.rb
143
- - lib/xenon/routing/path_directives.rb
144
- - lib/xenon/routing/route_directives.rb
145
- - lib/xenon/routing/security_directives.rb
146
- - lib/xenon/version.rb
147
- - spec/spec_helper.rb
148
- - spec/xenon/etag_spec.rb
149
- - spec/xenon/headers/accept_charset_spec.rb
150
- - spec/xenon/headers/accept_encoding_spec.rb
151
- - spec/xenon/headers/accept_language_spec.rb
152
- - spec/xenon/headers/accept_spec.rb
153
- - spec/xenon/headers/authorization_spec.rb
154
- - spec/xenon/headers/cache_control_spec.rb
155
- - spec/xenon/headers/if_match_spec.rb
156
- - spec/xenon/headers/if_modified_since_spec.rb
157
- - spec/xenon/headers/if_none_match_spec.rb
158
- - spec/xenon/headers/if_range_spec.rb
159
- - spec/xenon/headers/if_unmodified_since_spec.rb
160
- - spec/xenon/headers/user_agent_spec.rb
161
- - spec/xenon/headers/www_authenticate_spec.rb
162
- - spec/xenon/media_type_spec.rb
163
- - xenon.gemspec
47
+ files: []
164
48
  homepage: https://github.com/gregbeech/xenon
165
49
  licenses:
166
50
  - MIT
@@ -185,21 +69,5 @@ rubygems_version: 2.4.8
185
69
  signing_key:
186
70
  specification_version: 4
187
71
  summary: An HTTP framework for building RESTful APIs.
188
- test_files:
189
- - spec/spec_helper.rb
190
- - spec/xenon/etag_spec.rb
191
- - spec/xenon/headers/accept_charset_spec.rb
192
- - spec/xenon/headers/accept_encoding_spec.rb
193
- - spec/xenon/headers/accept_language_spec.rb
194
- - spec/xenon/headers/accept_spec.rb
195
- - spec/xenon/headers/authorization_spec.rb
196
- - spec/xenon/headers/cache_control_spec.rb
197
- - spec/xenon/headers/if_match_spec.rb
198
- - spec/xenon/headers/if_modified_since_spec.rb
199
- - spec/xenon/headers/if_none_match_spec.rb
200
- - spec/xenon/headers/if_range_spec.rb
201
- - spec/xenon/headers/if_unmodified_since_spec.rb
202
- - spec/xenon/headers/user_agent_spec.rb
203
- - spec/xenon/headers/www_authenticate_spec.rb
204
- - spec/xenon/media_type_spec.rb
72
+ test_files: []
205
73
  has_rdoc:
@@ -1,18 +0,0 @@
1
- # For more details, see here:
2
- # http://docs.codeclimate.com/article/289-configuring-your-repository-via-codeclimate-yml#platform
3
-
4
- # For a list of all available engines, see here:
5
- # http://docs.codeclimate.com/article/296-engines-available-engines
6
-
7
- engines:
8
- rubocop:
9
- enabled: true
10
-
11
- ratings:
12
- paths:
13
- - lib/**/*
14
-
15
- exclude_paths:
16
- - examples/**/*
17
- - spec/**/*
18
- - vendor/**/*
data/.gitignore DELETED
@@ -1,25 +0,0 @@
1
- *.gem
2
- *.rbc
3
- .ruby-version
4
- .ruby-gemset
5
- Gemfile.lock
6
- /.config
7
- /coverage/
8
- /InstalledFiles
9
- /pkg/
10
- /spec/reports/
11
- /test/tmp/
12
- /test/version_tmp/
13
- /tmp/
14
-
15
- ## Documentation cache and generated files:
16
- /.yardoc/
17
- /_yardoc/
18
- /doc/
19
- /rdoc/
20
-
21
- ## Environment normalisation:
22
- /.bundle/
23
- /lib/bundler/man/
24
- .ruby-gemset
25
- .ruby-version
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --color
2
- --require spec_helper
3
- --format documentation
@@ -1,6 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.2.3
4
- before_install:
5
- - gem install bundler
6
- cache: bundler
data/Gemfile DELETED
@@ -1,13 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gemspec
4
-
5
- group :development, :test do
6
- gem 'guard', require: false
7
- gem 'guard-rspec', require: false
8
- gem 'yard', require: false
9
- end
10
-
11
- group :test do
12
- gem "codeclimate-test-reporter", require: false
13
- end
data/Guardfile DELETED
@@ -1,16 +0,0 @@
1
- clearing :on
2
-
3
- guard :rspec, cmd: "bundle exec rspec" do
4
- require "guard/rspec/dsl"
5
- dsl = Guard::RSpec::Dsl.new(self)
6
-
7
- # RSpec files
8
- rspec = dsl.rspec
9
- watch(rspec.spec_helper) { rspec.spec_dir }
10
- watch(rspec.spec_support) { rspec.spec_dir }
11
- watch(rspec.spec_files)
12
-
13
- # Ruby files
14
- ruby = dsl.ruby
15
- dsl.watch_spec_files_for(ruby.lib_files)
16
- end
data/LICENSE DELETED
@@ -1,22 +0,0 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2015 Greg Beech
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
22
-
data/README.md DELETED
@@ -1,86 +0,0 @@
1
- # Xenon
2
-
3
- [![Gem Version][fury-badge]][fury] [![Build Status][travis-badge]][travis] [![Code Climate][cc-badge]][cc] [![Test Coverage][ccc-badge]][ccc] [![YARD Docs][docs-badge]][docs]
4
-
5
- An HTTP framework for building RESTful APIs, inspired by [Spray][spray].
6
-
7
- At the moment I probably wouldn't use this gem for anything you actually depend on because it's _very_ early in its lifecycle. However, this is a flavour of what's in here.
8
-
9
- ## HTTP Model
10
-
11
- A set of model objects for the HTTP protocol which can parse and format the strings you typically get into proper objects you can work with. At the moment this covers key things like media types and the most common headers, but it will expand to cover the whole protocol. You can use the model by itself without any other parts of the library.
12
-
13
- This is how things tend to look:
14
-
15
- ```ruby
16
- accept = Xenon::Headers::Accept.parse('application/json, application/*; q=0.5')
17
- accept.media_ranges.first.media_type.json? #=> true
18
- accept.media_ranges.last.q #=> 0.5
19
- # etc.
20
- ```
21
-
22
- ## Routing
23
-
24
- A tree-based routing approach based on Spray, giving you great flexibility in building APIs and without the need to write extensions, helpers, etc. because everything is a directive and you extend it by simply writing directives! This is highly unstable and in flux at the moment.
25
-
26
- This is the kind of syntax I'm aiming for which _sort of_ works, but needs a load of changes to allow composition so what's there now is really just a proof of concept of the basic syntax rather than anything close to useful.
27
-
28
- ```ruby
29
- path_prefix 'users' do
30
- path_end do
31
- get do
32
- complete 200, User.all
33
- end
34
- post do
35
- body as: User do |user|
36
- user.save!
37
- respond_with_header 'Location' => "/users/#{user.id}" do
38
- complete 201, user
39
- end
40
- end
41
- end
42
- end
43
- path /[0-9]+/ do |user_id|
44
- get do
45
- complete 200, User.get_by_id(user_id)
46
- end
47
- end
48
- end
49
- ```
50
-
51
- Of course, it'll do all the things you'd expect like support content negotiation properly and return the correct status codes when paths or methods aren't found.
52
-
53
- ## Installation
54
-
55
- Add this line to your application's Gemfile:
56
-
57
- gem 'xenon'
58
-
59
- And then execute:
60
-
61
- $ bundle
62
-
63
- Or install it yourself as:
64
-
65
- $ gem install xenon
66
-
67
- ## Contributing
68
-
69
- 1. Fork it ( https://github.com/gregbeech/xenon/fork )
70
- 2. Create your feature branch (`git checkout -b my-new-feature`)
71
- 3. Commit your changes (`git commit -am 'Add some feature'`)
72
- 4. Push to the branch (`git push origin my-new-feature`)
73
- 5. Create a new Pull Request
74
-
75
-
76
- [fury]: http://badge.fury.io/rb/xenon "Xenon at Rubygems"
77
- [fury-badge]: https://badge.fury.io/rb/xenon.svg "Gem Version"
78
- [travis]: https://travis-ci.org/gregbeech/xenon "Xenon at Travis CI"
79
- [travis-badge]: https://travis-ci.org/gregbeech/xenon.svg "Build Status"
80
- [cc]: https://codeclimate.com/github/gregbeech/xenon "Xenon Quality at Code Climate"
81
- [cc-badge]: https://codeclimate.com/github/gregbeech/xenon/badges/gpa.svg "Code Quality"
82
- [ccc]: https://codeclimate.com/github/gregbeech/xenon/coverage "Xenon Coverage at Code Climate"
83
- [ccc-badge]: https://codeclimate.com/github/gregbeech/xenon/badges/coverage.svg "Code Coverage"
84
- [docs]: http://www.rubydoc.info/github/gregbeech/xenon "YARD Docs"
85
- [docs-badge]: http://img.shields.io/badge/yard-docs-blue.svg "YARD Docs"
86
- [spray]: http://spray.io/ "spray"
data/Rakefile DELETED
@@ -1,8 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
-
4
- RSpec::Core::RakeTask.new
5
-
6
- task :default => :spec
7
- task :build => :spec
8
- task :test => :spec
@@ -1,3 +0,0 @@
1
- require_relative 'hello_world'
2
-
3
- run HelloWorld.new
@@ -1,17 +0,0 @@
1
- require 'xenon'
2
-
3
- class HelloWorld < Xenon::API
4
- authenticator = Xenon::BasicAuth.new realm: 'hello world' do |credentials|
5
- credentials.username # should actually auth here!
6
- end
7
-
8
- path '/' do
9
- get do
10
- authenticate(authenticator) do |user|
11
- params :greeting do |greeting|
12
- complete :ok, { greeting => user }
13
- end
14
- end
15
- end
16
- end
17
- end
@@ -1,253 +0,0 @@
1
- require 'json'
2
- require 'rack'
3
- require 'active_support/core_ext/string'
4
- require 'xenon/headers'
5
- require 'xenon/routing/directives'
6
- require 'xenon/version'
7
-
8
- module Xenon
9
-
10
- class Rejection
11
- attr_reader :reason, :info
12
-
13
- def initialize(reason, info = {})
14
- @reason = reason
15
- @info = info
16
- end
17
-
18
- def [](name)
19
- @info[name]
20
- end
21
- end
22
-
23
- class Request
24
- attr_accessor :unmatched_path
25
-
26
- def initialize(rack_req)
27
- @rack_req = rack_req
28
- @unmatched_path = rack_req.path.freeze
29
- end
30
-
31
- def request_method
32
- @rack_req.request_method.downcase.to_sym
33
- end
34
-
35
- def form_hash
36
- @form_hash ||= @rack_req.POST.with_indifferent_access.freeze
37
- end
38
-
39
- def param_hash
40
- puts "GET = #{@rack_req.GET.inspect}"
41
- @param_hash ||= @rack_req.GET.with_indifferent_access.freeze
42
- end
43
-
44
- def header(name)
45
- snake_name = name.to_s.tr('-', '_')
46
- value = @rack_req.env['HTTP_' + snake_name.upcase]
47
- return nil if value.nil?
48
-
49
- klass = Xenon::Headers.header_class(name)
50
- klass ? klass.parse(value) : Xenon::Headers::Raw.new(name, value)
51
- end
52
-
53
- def copy(changes = {})
54
- r = dup
55
- changes.each { |k, v| r.instance_variable_set("@#{k}", v.freeze) }
56
- r
57
- end
58
- end
59
-
60
- class Response
61
- attr_reader :status, :headers, :body
62
-
63
- def initialize
64
- @headers = Headers.new
65
- @complete = false
66
- freeze
67
- end
68
-
69
- def complete?
70
- @complete
71
- end
72
-
73
- def copy(changes = {})
74
- r = dup
75
- changes.each { |k, v| r.instance_variable_set("@#{k}", v) }
76
- r.freeze
77
- end
78
-
79
- def freeze
80
- @headers.freeze
81
- @body.freeze
82
- super
83
- end
84
- end
85
-
86
- class Context
87
- attr_accessor :request, :response, :rejections
88
-
89
- def initialize(request, response)
90
- @request = request
91
- @response = response
92
- @rejections = []
93
- end
94
-
95
- def branch
96
- original_request = @request
97
- original_response = @response
98
- yield
99
- ensure
100
- @request = original_request
101
- @response = original_response unless @response.complete?
102
- end
103
- end
104
-
105
- class JsonMarshaller
106
- def media_type
107
- MediaType::JSON
108
- end
109
-
110
- def content_type
111
- media_type.with_charset(Encoding::UTF_8)
112
- end
113
-
114
- def marshal_to?(media_range)
115
- media_range =~ media_type
116
- end
117
-
118
- def marshal(obj)
119
- [obj.to_json]
120
- end
121
- end
122
-
123
- class XmlMarshaller
124
- def media_type
125
- MediaType::XML
126
- end
127
-
128
- def content_type
129
- media_type.with_charset(Encoding::UTF_8)
130
- end
131
-
132
- def marshal_to?(media_range)
133
- media_range =~ media_type
134
- end
135
-
136
- def marshal(obj)
137
- raise "#{obj.class} does not support #to_xml" unless obj.respond_to?(:to_xml)
138
- [obj.to_xml]
139
- end
140
- end
141
-
142
- class API
143
- include Xenon::Routing::Directives
144
-
145
- DEFAULT_MARSHALLERS = [JsonMarshaller.new]
146
-
147
- class << self
148
- def marshallers(*marshallers)
149
- @marshallers = marshallers unless marshallers.nil? || marshallers.empty?
150
- (@marshallers.nil? || @marshallers.empty?) ? DEFAULT_MARSHALLERS : @marshallers
151
- end
152
-
153
- def select_marshaller(media_ranges)
154
- weighted = marshallers.sort_by do |m|
155
- media_range = media_ranges.find { |mr| m.marshal_to?(mr) }
156
- media_range ? media_range.q : 0.0
157
- end
158
- weighted.last
159
- end
160
- end
161
-
162
- attr_reader :context
163
-
164
- class << self
165
- def routes
166
- @routes ||= []
167
- end
168
-
169
- def method_missing(name, *args, &block)
170
- if instance_methods.include?(name)
171
- routes << [name, args, block]
172
- else
173
- super
174
- end
175
- end
176
- end
177
-
178
- def call(env)
179
- dup.call!(env)
180
- end
181
-
182
- def call!(env)
183
- @context = Context.new(Request.new(Rack::Request.new(env)), Response.new)
184
-
185
- accept = @context.request.header('Accept')
186
- marshaller = accept ? self.class.select_marshaller(accept.media_ranges) : self.class.marshallers.first
187
-
188
- catch (:complete) do
189
- begin
190
- if marshaller.nil?
191
- @context.rejections << Rejection.new(:accept, { supported: self.class.marshallers.map(&:media_type) })
192
- else
193
- self.class.routes.each do |route|
194
- name, args, block = route
195
- route_block = proc { instance_eval(&block) }
196
- send(name, *args, &route_block)
197
- end
198
- end
199
- handle_rejections(@context.rejections)
200
- rescue => e
201
- handle_error(e)
202
- end
203
- end
204
-
205
- marshaller ||= self.class.marshallers.first
206
- resp = @context.response.copy(
207
- headers: @context.response.headers.set(Headers::ContentType.new(marshaller.content_type)),
208
- body: marshaller.marshal(@context.response.body))
209
- [resp.status, resp.headers.map { |h| [h.name, h.to_s] }.to_h, resp.body]
210
- end
211
-
212
- def handle_error(e)
213
- puts "handle_error: #{e.class}: #{e}\n #{e.backtrace.join("\n ")}"
214
- case e
215
- when ParseError
216
- fail 400, e.message
217
- else
218
- fail 500, e.message # TODO: Only if verbose errors configured
219
- end
220
- end
221
-
222
- def handle_rejections(rejections)
223
- puts "handle_rejections: #{rejections}"
224
- if rejections.empty?
225
- fail 404
226
- else
227
- rejection = rejections.first
228
- case rejection.reason
229
- when :accept
230
- fail 406, "Supported media types: #{rejection[:supported].join(", ")}"
231
- when :header
232
- fail 400, "Missing required header: #{rejection[:required]}"
233
- when :method
234
- supported = rejections.take_while { |r| r.reason == :method }.map { |r| r[:supported].upcase }
235
- fail 405, "Supported methods: #{supported.join(", ")}"
236
- when :unauthorized
237
- if rejection[:scheme]
238
- challenge = Headers::Challenge.new(rejection[:scheme], rejection.info.except(:scheme))
239
- respond_with_header Headers::WWWAuthenticate.new(challenge) do
240
- fail 401
241
- end
242
- else
243
- fail 401
244
- end
245
- else
246
- fail 500
247
- end
248
- end
249
- end
250
-
251
- end
252
-
253
- end