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,86 @@
1
+ require 'parslet'
2
+
3
+ module Xenon
4
+ module Parsers
5
+
6
+ # Parslet doesn't match sequence of sequences (i.e. [['foo', 'bar']]) as a sequence(:v) in transform
7
+ # rules so this is a little wrapper class that allows smuggling an array through the matcher rules,
8
+ # for example above would be [Tuple.new('foo', 'bar')], when no 'proper' class is required.
9
+ class Tuple
10
+ def initialize(*values)
11
+ @values = values
12
+ end
13
+
14
+ def to_a
15
+ @values
16
+ end
17
+ end
18
+
19
+ module BasicRules
20
+ include Parslet
21
+
22
+ # http://tools.ietf.org/html/rfc5234#appendix-B.1
23
+ rule(:alpha) { match(/[a-z]/i) }
24
+ rule(:bit) { match(/[01]/) }
25
+ rule(:char) { match(/[\u0001-\u007f]/) }
26
+ rule(:digit) { match(/[0-9]/) }
27
+ rule(:hexdig) { match(/[a-f0-9]/i)}
28
+ rule(:vchar) { match(/[\u0021-\u007e]/) }
29
+ rule(:alphanum) { alpha | digit }
30
+
31
+ rule(:sp) { str(' ') }
32
+ rule(:sp?) { sp.repeat }
33
+ rule(:htab) { str("\t") }
34
+ rule(:wsp) { sp | htab }
35
+ rule(:lwsp) { (crlf.maybe >> wsp).repeat }
36
+
37
+ rule(:cr) { str("\r") }
38
+ rule(:lf) { str("\n") }
39
+ rule(:crlf) { cr >> lf }
40
+ rule(:dquote) { str('"') }
41
+
42
+ # http://tools.ietf.org/html/rfc7230#section-3.2.3
43
+ rule(:ows) { wsp.repeat }
44
+ rule(:rws) { wsp.repeat(1) }
45
+ rule(:bws) { wsp.repeat }
46
+
47
+ # http://tools.ietf.org/html/rfc7230#section-3.2.6
48
+ rule(:tchar) { alpha | digit | match(/[!#\$%&'\*\+\-\.\^_`\|~]/) }
49
+ rule(:token) { tchar.repeat(1) }
50
+
51
+ # http://tools.ietf.org/html/rfc7231#section-7.1.1.1
52
+ rule(:day_name) { str('Mon') | str('Tue') | str('Wed') | str('Thu') | str('Fri') | str('Sat') | str('Sun') }
53
+ rule(:day) { digit.repeat(2) }
54
+ rule(:month) { (str('Jan') | str('Feb') | str('Mar') | str('Apr') | str('May') | str('Jun') | str('Jul') | str('Aug') | str('Sep') | str('Oct') | str('Nov') | str('Dec')) }
55
+ rule(:year) { digit.repeat(4) }
56
+ rule(:date1) { day >> sp >> month >> sp >> year }
57
+ rule(:gmt) { str('GMT') }
58
+ rule(:hour) { digit.repeat(2) }
59
+ rule(:minute) { digit.repeat(2) }
60
+ rule(:second) { digit.repeat(2) }
61
+ rule(:time_of_day) { hour >> str(':') >> minute >> str(':') >> second }
62
+ rule(:imf_fixdate) { day_name >> str(',') >> sp >> date1 >> sp >> time_of_day >> sp >> gmt }
63
+ rule(:day_name_l) { str('Monday') | str('Tuesday') | str('Wednesday') | str('Thursday') | str('Friday') | str('Saturday') | str('Sunday') }
64
+ rule(:year2) { digit.repeat(2) }
65
+ rule(:date2) { day >> str('-') >> month >> str('-') >> year2 }
66
+ rule(:rfc850_date) { day_name_l >> str(',') >> sp >> date2 >> sp >> time_of_day >> sp >> gmt }
67
+ rule(:day1) { sp >> digit }
68
+ rule(:date3) { month >> sp >> (day | day1) }
69
+ rule(:asctime_date) { day_name >> sp >> date3 >> sp >> time_of_day >> sp >> year }
70
+ rule(:obs_date) { rfc850_date | asctime_date }
71
+ rule(:http_date) { (imf_fixdate | obs_date).as(:http_date) }
72
+
73
+ # extras -- TODO: move these into header rules?
74
+ rule(:comma) { str(',') >> sp? }
75
+ rule(:semicolon) { str(';') >> sp? }
76
+ end
77
+
78
+ class BasicTransform < Parslet::Transform
79
+ rule(simple(:v)) { v.respond_to?(:str) ? v.str : v }
80
+
81
+ rule(quoted_string: simple(:qs)) { qs[1..-2].gsub(/\\(.)/, '\1') }
82
+ rule(http_date: simple(:str)) { Time.httpdate(str) }
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,60 @@
1
+ require 'xenon/quoted_string'
2
+ require 'xenon/parsers/basic_rules'
3
+
4
+ module Xenon
5
+ module Parsers
6
+
7
+ module HeaderRules
8
+ include Parslet, BasicRules
9
+
10
+ # http://tools.ietf.org/html/rfc7230#section-3.2.6
11
+ rule(:list_sep) { str(',') >> sp? }
12
+ rule(:param_sep) { str(';') >> sp? }
13
+ rule(:obs_text) { match(/[\u0080-\u00ff]/)}
14
+ rule(:qdtext) { htab | sp | match(/[\u0021\u0023-\u005b\u005d-\u007e]/) | obs_text }
15
+ rule(:quoted_pair) { str('\\') >> (htab | sp | vchar | obs_text) }
16
+ rule(:quoted_string) { (dquote >> (qdtext | quoted_pair).repeat >> dquote).as(:quoted_string) }
17
+ rule(:ctext) { htab | sp | match(/[\u0021-\u0027\u002a-\u005b\u005d-\u007e]/) | obs_text }
18
+ rule(:comment) { (str('(') >> (ctext | quoted_pair | comment).repeat >> str(')')).as(:comment) }
19
+
20
+ # http://tools.ietf.org/html/rfc7231#section-5.3.1
21
+ rule(:weight_value) { (digit >> (str('.') >> digit.repeat(0, 3)).maybe).as(:q) }
22
+ rule(:weight) { param_sep >> str('q') >> sp? >> str('=') >> sp? >> weight_value >> sp? }
23
+ end
24
+
25
+ module AuthHeaderRules
26
+ include Parslet, HeaderRules
27
+
28
+ rule(:token68) { ((alpha | digit | match(/[\-\._~\+\/]/)) >> str('=').repeat).repeat(1).as(:token) }
29
+ rule(:auth_scheme) { token.as(:auth_scheme) }
30
+ rule(:name) { token.as(:name) }
31
+ rule(:value) { token.as(:value) }
32
+ rule(:auth_param) { (name >> bws >> str('=') >> bws >> (token | quoted_string).as(:value)).as(:auth_param) }
33
+ rule(:auth_params) { (auth_param.maybe >> (ows >> comma >> ows >> auth_param).repeat).as(:auth_params) }
34
+ end
35
+
36
+ module ETagHeaderRules
37
+ include Parslet, HeaderRules
38
+
39
+ # http://tools.ietf.org/html/rfc7232#section-2.3
40
+ rule(:wildcard) { str('*').as(:wildcard) }
41
+ rule(:weak) { str('W/').as(:weak) }
42
+ rule(:etagc) { str('!') | match(/[\u0023-\u007e#-~]/) | obs_text }
43
+ rule(:opaque_tag) { dquote >> etagc.repeat.as(:opaque_tag) >> dquote }
44
+ rule(:etag) { (weak.maybe >> opaque_tag).as(:etag) }
45
+ end
46
+
47
+ class HeaderTransform < BasicTransform
48
+ using QuotedString
49
+
50
+ rule(quoted_string: simple(:qs)) { qs.unquote }
51
+ rule(comment: simple(:c)) { c.uncomment }
52
+ end
53
+
54
+ class ETagHeaderTransform < HeaderTransform
55
+ rule(etag: { opaque_tag: simple(:t), weak: simple(:w) }) { Xenon::ETag.new(t, weak: true) }
56
+ rule(etag: { opaque_tag: simple(:t) }) { Xenon::ETag.new(t) }
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,53 @@
1
+ require 'xenon/parsers/basic_rules'
2
+
3
+ module Xenon
4
+ module Parsers
5
+
6
+ # note this uses the rules from https://tools.ietf.org/html/rfc6838 not http://tools.ietf.org/html/rfc7231
7
+ # because the latter is slightly ambiguous, e.g. a token can include * so */json would parse correctly
8
+ module MediaTypeRules
9
+ include Parslet, BasicRules
10
+
11
+ rule(:restricted_name_first) { match(/[a-zA-Z0-9]/) }
12
+ rule(:restricted_name_chars) { match(/[a-zA-Z0-9!#\$&\-\^_\.\+]/).repeat(0, 126) }
13
+ rule(:restricted_name) { restricted_name_first >> restricted_name_chars }
14
+
15
+ rule(:type) { restricted_name.as(:type) }
16
+ rule(:slash) { str('/') }
17
+ rule(:subtype) { restricted_name.as(:subtype) >> sp? }
18
+
19
+ rule(:param_sep) { str(';') >> sp? }
20
+ rule(:param_name) { restricted_name.as(:param_name) >> sp? }
21
+ rule(:equals) { str('=') >> sp? }
22
+ rule(:param_value) { token.as(:param_value) >> sp? } # not quite correct but probably correct enough
23
+ rule(:param) { param_sep >> param_name >> (equals >> param_value).maybe >> sp? }
24
+ rule(:params) { param.repeat.as(:params) }
25
+
26
+ rule(:media_type) { (type >> slash >> subtype >> params).as(:media_type) >> sp? }
27
+
28
+ rule(:wildcard) { str('*') }
29
+ rule(:wild_media_range) { wildcard.as(:type) >> slash >> wildcard.as(:subtype) >> params }
30
+ rule(:root_media_range) { type >> slash >> (wildcard.as(:subtype) | subtype) >> params }
31
+ rule(:media_range) { (wild_media_range | root_media_range).as(:media_range) >> sp? }
32
+ end
33
+
34
+ class MediaType < Parslet::Parser
35
+ include MediaTypeRules
36
+ root(:media_type)
37
+ end
38
+
39
+ class MediaRange < Parslet::Parser
40
+ include MediaTypeRules
41
+ root(:media_range)
42
+ end
43
+
44
+ class MediaTypeTransform < Parslet::Transform
45
+ rule(param_name: simple(:n), param_value: simple(:v)) { [n.str, v.str] }
46
+ rule(param_name: simple(:n)) { [n.str, nil] }
47
+ rule(type: simple(:t), subtype: simple(:s), params: subtree(:p)) { { type: t.str, subtype: s.str, params: Hash[p] } }
48
+ rule(media_type: subtree(:mt)) { Xenon::MediaType.new(mt[:type], mt[:subtype], mt[:params])}
49
+ rule(media_range: subtree(:mr)) { Xenon::MediaRange.new(mr[:type], mr[:subtype], mr[:params])}
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,20 @@
1
+ module Xenon
2
+ module QuotedString
3
+ refine String do
4
+ def quote
5
+ qs = gsub(/([\\"])/, '\\\\\1')
6
+ self == qs ? self : %{"#{qs}"}
7
+ end
8
+
9
+ def unquote
10
+ qs = start_with?('"') && end_with?('"') ? self[1..-2] : self
11
+ qs.gsub(/\\(.)/, '\1')
12
+ end
13
+
14
+ def uncomment
15
+ qs = start_with?('(') && end_with?(')') ? self[1..-2] : self
16
+ qs.gsub(/\\(.)/, '\1')
17
+ end
18
+ end
19
+ end
20
+ 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
@@ -0,0 +1,19 @@
1
+ require 'xenon/etag'
2
+
3
+ describe Xenon::ETag do
4
+
5
+ describe '::parse' do
6
+ it 'can parse a strong etag' do
7
+ etag = Xenon::ETag.parse('"xyzzy"')
8
+ expect(etag.opaque_tag).to eq 'xyzzy'
9
+ expect(etag).to be_strong
10
+ end
11
+
12
+ it 'can parse a weak etag' do
13
+ etag = Xenon::ETag.parse('W/"xyzzy"')
14
+ expect(etag.opaque_tag).to eq 'xyzzy'
15
+ expect(etag).to be_weak
16
+ end
17
+ end
18
+
19
+ end
@@ -0,0 +1,31 @@
1
+ require 'xenon/headers/accept_charset'
2
+
3
+ describe Xenon::Headers::AcceptCharset do
4
+
5
+ context '::parse' do
6
+ it 'can parse a basic charset range' do
7
+ header = Xenon::Headers::AcceptCharset.parse('unicode-1-1;q=0.8')
8
+ expect(header.charset_ranges.size).to eq(1)
9
+ expect(header.charset_ranges[0].to_s).to eq('unicode-1-1; q=0.8')
10
+ end
11
+
12
+ it 'can parse the example from RFC 7231 § 5.3.3 with the right precedence' do
13
+ header = Xenon::Headers::AcceptCharset.parse('iso-8859-5, unicode-1-1;q=0.8')
14
+ expect(header.charset_ranges.size).to eq(2)
15
+ expect(header.charset_ranges[0].to_s).to eq('iso-8859-5')
16
+ expect(header.charset_ranges[1].to_s).to eq('unicode-1-1; q=0.8')
17
+ end
18
+ end
19
+
20
+ context '#merge' do
21
+ it 'can merge two headers with the right precedence' do
22
+ h1 = Xenon::Headers::AcceptCharset.parse('unicode-1-1;q=0.8')
23
+ h2 = Xenon::Headers::AcceptCharset.parse('iso-8859-5')
24
+ header = h1.merge(h2)
25
+ expect(header.charset_ranges.size).to eq(2)
26
+ expect(header.charset_ranges[0].to_s).to eq('iso-8859-5')
27
+ expect(header.charset_ranges[1].to_s).to eq('unicode-1-1; q=0.8')
28
+ end
29
+ end
30
+
31
+ end
@@ -0,0 +1,40 @@
1
+ require 'xenon/headers/accept_encoding'
2
+
3
+ describe Xenon::Headers::AcceptEncoding do
4
+
5
+ context '::parse' do
6
+ %w(identity compress x-compress deflate gzip x-gzip *).each do |cc|
7
+ it "can parse the #{cc} content coding" do
8
+ header = Xenon::Headers::AcceptEncoding.parse(cc)
9
+ expect(header.coding_ranges.size).to eq(1)
10
+ expect(header.coding_ranges[0].to_s).to eq(cc)
11
+ end
12
+ end
13
+
14
+ it 'can parse the fifth example from RFC 7231 § 5.3.4 with the right precedence' do
15
+ header = Xenon::Headers::AcceptEncoding.parse('gzip;q=1.0, identity; q=0.5, *;q=0')
16
+ expect(header.coding_ranges.size).to eq(3)
17
+ expect(header.coding_ranges[0].to_s).to eq('gzip')
18
+ expect(header.coding_ranges[1].to_s).to eq('identity; q=0.5')
19
+ expect(header.coding_ranges[2].to_s).to eq('*; q=0.0')
20
+ end
21
+
22
+ it 'parses an empty header as containing no codings' do
23
+ header = Xenon::Headers::AcceptEncoding.parse('')
24
+ expect(header.coding_ranges.size).to eq(0)
25
+ end
26
+ end
27
+
28
+ context '#merge' do
29
+ it 'can merge two headers with the right precedence' do
30
+ h1 = Xenon::Headers::AcceptEncoding.parse('identity; q=0.5')
31
+ h2 = Xenon::Headers::AcceptEncoding.parse('gzip;q=1.0, *;q=0')
32
+ header = h1.merge(h2)
33
+ expect(header.coding_ranges.size).to eq(3)
34
+ expect(header.coding_ranges[0].to_s).to eq('gzip')
35
+ expect(header.coding_ranges[1].to_s).to eq('identity; q=0.5')
36
+ expect(header.coding_ranges[2].to_s).to eq('*; q=0.0')
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,33 @@
1
+ require 'xenon/headers/accept_language'
2
+
3
+ describe Xenon::Headers::AcceptLanguage do
4
+
5
+ context '::parse' do
6
+ it 'can parse a basic language range' do
7
+ header = Xenon::Headers::AcceptLanguage.parse('en-gb;q=0.8')
8
+ expect(header.language_ranges.size).to eq(1)
9
+ expect(header.language_ranges[0].to_s).to eq('en-gb; q=0.8')
10
+ end
11
+
12
+ it 'can parse the example from RFC 7231 § 5.3.5 with the right precedence' do
13
+ header = Xenon::Headers::AcceptLanguage.parse('da, en-gb;q=0.8, en;q=0.7')
14
+ expect(header.language_ranges.size).to eq(3)
15
+ expect(header.language_ranges[0].to_s).to eq('da')
16
+ expect(header.language_ranges[1].to_s).to eq('en-gb; q=0.8')
17
+ expect(header.language_ranges[2].to_s).to eq('en; q=0.7')
18
+ end
19
+ end
20
+
21
+ context '#merge' do
22
+ it 'can merge two headers with the right precedence' do
23
+ h1 = Xenon::Headers::AcceptLanguage.parse('da, en;q=0.7')
24
+ h2 = Xenon::Headers::AcceptLanguage.parse('en-gb;q=0.8')
25
+ header = h1.merge(h2)
26
+ expect(header.language_ranges.size).to eq(3)
27
+ expect(header.language_ranges[0].to_s).to eq('da')
28
+ expect(header.language_ranges[1].to_s).to eq('en-gb; q=0.8')
29
+ expect(header.language_ranges[2].to_s).to eq('en; q=0.7')
30
+ end
31
+ end
32
+
33
+ end