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,48 @@
1
+ require 'xenon/media_type'
2
+
3
+ module Xenon
4
+ class JsonMarshaller
5
+ def media_type
6
+ MediaType::JSON
7
+ end
8
+
9
+ def content_type
10
+ media_type.with_charset(Encoding::UTF_8)
11
+ end
12
+
13
+ def marshal_to?(media_range)
14
+ media_range =~ media_type
15
+ end
16
+
17
+ def marshal(obj)
18
+ [obj.to_json]
19
+ end
20
+ end
21
+
22
+ class XmlMarshaller
23
+ def initialize
24
+ gem 'builder'
25
+ require 'active_support/core_ext/array/conversions'
26
+ require 'active_support/core_ext/hash/conversions'
27
+ rescue Gem::LoadError
28
+ raise 'Install the "builder" gem to enable XML.'
29
+ end
30
+
31
+ def media_type
32
+ MediaType::XML
33
+ end
34
+
35
+ def content_type
36
+ media_type.with_charset(Encoding::UTF_8)
37
+ end
38
+
39
+ def marshal_to?(media_range)
40
+ media_range =~ media_type
41
+ end
42
+
43
+ def marshal(obj)
44
+ raise "#{obj.class} does not support #to_xml" unless obj.respond_to?(:to_xml)
45
+ [obj.to_xml]
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,40 @@
1
+ require 'xenon/headers'
2
+
3
+ module Xenon
4
+ class Request
5
+ attr_accessor :unmatched_path
6
+
7
+ def initialize(rack_req)
8
+ @rack_req = rack_req
9
+ @unmatched_path = rack_req.path.freeze
10
+ end
11
+
12
+ def request_method
13
+ @rack_req.request_method.downcase.to_sym
14
+ end
15
+
16
+ def form_hash
17
+ @form_hash ||= @rack_req.POST.with_indifferent_access.freeze
18
+ end
19
+
20
+ def param_hash
21
+ puts "GET = #{@rack_req.GET.inspect}"
22
+ @param_hash ||= @rack_req.GET.with_indifferent_access.freeze
23
+ end
24
+
25
+ def header(name)
26
+ snake_name = name.to_s.tr('-', '_')
27
+ value = @rack_req.env['HTTP_' + snake_name.upcase]
28
+ return nil if value.nil?
29
+
30
+ klass = Xenon::Headers.header_class(name)
31
+ klass ? klass.parse(value) : Xenon::Headers::Raw.new(name, value)
32
+ end
33
+
34
+ def copy(changes = {})
35
+ r = dup
36
+ changes.each { |k, v| r.instance_variable_set("@#{k}", v.freeze) }
37
+ r
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,29 @@
1
+ require 'xenon/headers'
2
+
3
+ module Xenon
4
+ class Response
5
+ attr_reader :status, :headers, :body
6
+
7
+ def initialize
8
+ @headers = Headers.new
9
+ @complete = false
10
+ freeze
11
+ end
12
+
13
+ def complete?
14
+ @complete
15
+ end
16
+
17
+ def copy(changes = {})
18
+ r = dup
19
+ changes.each { |k, v| r.instance_variable_set("@#{k}", v) }
20
+ r.freeze
21
+ end
22
+
23
+ def freeze
24
+ @headers.freeze
25
+ @body.freeze
26
+ super
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,6 @@
1
+ require 'xenon/routing_version'
2
+ require 'xenon/marshallers'
3
+ require 'xenon/request'
4
+ require 'xenon/response'
5
+ require 'xenon/routing/context'
6
+ require 'xenon/routing/directives'
@@ -0,0 +1,35 @@
1
+ module Xenon
2
+ module Routing
3
+ class Rejection
4
+ attr_reader :reason, :info
5
+
6
+ def initialize(reason, info = {})
7
+ @reason = reason
8
+ @info = info
9
+ end
10
+
11
+ def [](name)
12
+ @info[name]
13
+ end
14
+ end
15
+
16
+ class Context
17
+ attr_accessor :request, :response, :rejections
18
+
19
+ def initialize(request, response)
20
+ @request = request
21
+ @response = response
22
+ @rejections = []
23
+ end
24
+
25
+ def branch
26
+ original_request = @request
27
+ original_response = @response
28
+ yield
29
+ ensure
30
+ @request = original_request
31
+ @response = original_response unless @response.complete?
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,14 @@
1
+ Dir[File.join(__dir__, '*_directives.rb')].each { |f| require f }
2
+
3
+ module Xenon
4
+ module Routing
5
+ module Directives
6
+ include RouteDirectives
7
+ include HeaderDirectives
8
+ include MethodDirectives
9
+ include ParamDirectives
10
+ include PathDirectives
11
+ include SecurityDirectives
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,32 @@
1
+ require 'xenon/routing/route_directives'
2
+
3
+ module Xenon
4
+ module Routing
5
+ module HeaderDirectives
6
+ include RouteDirectives
7
+
8
+ def optional_header(name)
9
+ extract_request do |request|
10
+ yield request.header(name)
11
+ end
12
+ end
13
+
14
+ def header(name)
15
+ optional_header(name) do |value|
16
+ if value
17
+ yield value
18
+ else
19
+ reject Rejection.new(:header, { required: name })
20
+ end
21
+ end
22
+ end
23
+
24
+ def respond_with_header(header)
25
+ map_response -> r { r.copy(headers: r.headers.add(header)) } do
26
+ yield
27
+ end
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,26 @@
1
+ require 'xenon/routing/route_directives'
2
+
3
+ module Xenon
4
+ module Routing
5
+ module MethodDirectives
6
+ include RouteDirectives
7
+
8
+ def request_method(method)
9
+ extract_request do |request|
10
+ if request.request_method == method
11
+ yield
12
+ else
13
+ reject Rejection.new(:method, { supported: method })
14
+ end
15
+ end
16
+ end
17
+
18
+ %i(delete get head options patch post put).each do |method|
19
+ define_method(method) do |&inner|
20
+ request_method(method, &inner)
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ require 'xenon/routing/route_directives'
2
+
3
+ module Xenon
4
+ module Routing
5
+ module ParamDirectives
6
+ include RouteDirectives
7
+
8
+ def param_hash
9
+ extract_request do |request|
10
+ yield request.param_hash
11
+ end
12
+ end
13
+
14
+ def params(*names)
15
+ param_hash do |hash|
16
+ yield *hash.slice(*names).values
17
+ end
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,37 @@
1
+ require 'xenon/routing/route_directives'
2
+
3
+ module Xenon
4
+ module Routing
5
+ module PathDirectives
6
+ include RouteDirectives
7
+
8
+ def path_prefix(pattern)
9
+ extract_request do |request|
10
+ match = request.unmatched_path.match(pattern)
11
+ if match && match.pre_match == ''
12
+ map_request unmatched_path: match.post_match do
13
+ yield *match.captures
14
+ end
15
+ else
16
+ reject nil # path rejections are nil to allow more specific rejections to be seen
17
+ end
18
+ end
19
+ end
20
+
21
+ def path_end
22
+ path_prefix(/\Z/) do
23
+ yield
24
+ end
25
+ end
26
+
27
+ def path(pattern)
28
+ path_prefix(pattern) do |*captures|
29
+ path_end do
30
+ yield *captures
31
+ end
32
+ end
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,51 @@
1
+ require 'rack/utils'
2
+
3
+ module Xenon
4
+ module Routing
5
+ module RouteDirectives
6
+
7
+ def map_request(map)
8
+ context.branch do
9
+ context.request = map.respond_to?(:call) ? map.call(context.request) : context.request.copy(map)
10
+ yield
11
+ end
12
+ end
13
+
14
+ def map_response(map)
15
+ context.branch do
16
+ context.response = map.respond_to?(:call) ? map.call(context.response) : context.response.copy(map)
17
+ yield
18
+ end
19
+ end
20
+
21
+ def complete(status, body)
22
+ map_response complete: true, status: Rack::Utils.status_code(status), body: body do
23
+ throw :complete
24
+ end
25
+ end
26
+
27
+ def reject(rejection, info = {})
28
+ return if rejection.nil?
29
+ rejection = Rejection.new(rejection, info) unless rejection.is_a?(Rejection)
30
+ context.rejections << rejection
31
+ end
32
+
33
+ def fail(status, developer_message = nil)
34
+ body = {
35
+ status: status,
36
+ developer_message: developer_message || Rack::Utils::HTTP_STATUS_CODES[status]
37
+ }
38
+ complete status, body
39
+ end
40
+
41
+ def extract(lambda)
42
+ yield lambda.call(context)
43
+ end
44
+
45
+ def extract_request(lambda = nil)
46
+ yield lambda ? lambda.call(context.request) : context.request
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,34 @@
1
+ require 'xenon/routing/route_directives'
2
+
3
+ module Xenon
4
+ module Routing
5
+ module SecurityDirectives
6
+ include RouteDirectives
7
+
8
+ def authenticate(authenticator)
9
+ extract_request(authenticator) do |user|
10
+ if user
11
+ yield user
12
+ else
13
+ reject :unauthorized, { scheme: authenticator.scheme }.merge(authenticator.auth_params)
14
+ end
15
+ end
16
+ end
17
+
18
+ def authorize(check)
19
+ if check.respond_to?(:call)
20
+ extract_request(check) do |authorized|
21
+ authorize(authorized) do
22
+ yield
23
+ end
24
+ end
25
+ elsif check
26
+ yield
27
+ else
28
+ reject :forbidden
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ module Xenon
2
+ ROUTING_VERSION = File.read(File.join(__dir__, '..', '..', '..', 'VERSION'))
3
+ end
@@ -0,0 +1,94 @@
1
+ require "codeclimate-test-reporter"
2
+ CodeClimate::TestReporter.start
3
+
4
+ # This file was generated by the `rspec --init` command. Conventionally, all
5
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
6
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
7
+ # this file to always be loaded, without a need to explicitly require it in any
8
+ # files.
9
+ #
10
+ # Given that it is always loaded, you are encouraged to keep this file as
11
+ # light-weight as possible. Requiring heavyweight dependencies from this file
12
+ # will add to the boot time of your test suite on EVERY test run, even for an
13
+ # individual file that may not need all of that loaded. Instead, consider making
14
+ # a separate helper file that requires the additional dependencies and performs
15
+ # the additional setup, and require it from the spec files that actually need
16
+ # it.
17
+ #
18
+ # The `.rspec` file also contains a few flags that are not defaults but that
19
+ # users commonly want.
20
+ #
21
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
22
+ RSpec.configure do |config|
23
+ # rspec-expectations config goes here. You can use an alternate
24
+ # assertion/expectation library such as wrong or the stdlib/minitest
25
+ # assertions if you prefer.
26
+ config.expect_with :rspec do |expectations|
27
+ # This option will default to `true` in RSpec 4. It makes the `description`
28
+ # and `failure_message` of custom matchers include text for helper methods
29
+ # defined using `chain`, e.g.:
30
+ # be_bigger_than(2).and_smaller_than(4).description
31
+ # # => "be bigger than 2 and smaller than 4"
32
+ # ...rather than:
33
+ # # => "be bigger than 2"
34
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
35
+ end
36
+
37
+ # rspec-mocks config goes here. You can use an alternate test double
38
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
39
+ config.mock_with :rspec do |mocks|
40
+ # Prevents you from mocking or stubbing a method that does not exist on
41
+ # a real object. This is generally recommended, and will default to
42
+ # `true` in RSpec 4.
43
+ mocks.verify_partial_doubles = true
44
+ end
45
+
46
+ # The settings below are suggested to provide a good initial experience
47
+ # with RSpec, but feel free to customize to your heart's content.
48
+ =begin
49
+ # These two settings work together to allow you to limit a spec run
50
+ # to individual examples or groups you care about by tagging them with
51
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
52
+ # get run.
53
+ config.filter_run :focus
54
+ config.run_all_when_everything_filtered = true
55
+
56
+ # Limits the available syntax to the non-monkey patched syntax that is
57
+ # recommended. For more details, see:
58
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
59
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
60
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
61
+ config.disable_monkey_patching!
62
+
63
+ # This setting enables warnings. It's recommended, but in some cases may
64
+ # be too noisy due to issues in dependencies.
65
+ config.warnings = true
66
+
67
+ # Many RSpec users commonly either run the entire suite or an individual
68
+ # file, and it's useful to allow more verbose output when running an
69
+ # individual spec file.
70
+ if config.files_to_run.one?
71
+ # Use the documentation formatter for detailed output,
72
+ # unless a formatter has already been configured
73
+ # (e.g. via a command-line flag).
74
+ config.default_formatter = 'doc'
75
+ end
76
+
77
+ # Print the 10 slowest examples and example groups at the
78
+ # end of the spec run, to help surface which specs are running
79
+ # particularly slow.
80
+ config.profile_examples = 10
81
+
82
+ # Run specs in random order to surface order dependencies. If you find an
83
+ # order dependency and want to debug it, you can fix the order by providing
84
+ # the seed, which is printed after each run.
85
+ # --seed 1234
86
+ config.order = :random
87
+
88
+ # Seed global randomization in this process using the `--seed` CLI option.
89
+ # Setting this allows you to use `--seed` to deterministically reproduce
90
+ # test failures related to randomization by passing the same `--seed` value
91
+ # as the one that triggered the failure.
92
+ Kernel.srand config.seed
93
+ =end
94
+ end