accept_headers 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4df1427bbf8cc170c8d3eb303c6978f4c03d4b3e
4
- data.tar.gz: 7efc9d1f82cfd666507f72fae4b09e7020516970
3
+ metadata.gz: f095a96c5f335db2b23047bac10e48fff764b819
4
+ data.tar.gz: da3121b83be60d9a41dd7bfcf98ca8925e0d80e8
5
5
  SHA512:
6
- metadata.gz: 339ce5ee8e97554e7d890179f6362e279d1933deec79f351c617ada281415d1f30e80f92080cb4eebe20e56a5436b2b5cd5ecc663940a65b77ee9697252dd165
7
- data.tar.gz: 899d66e7efd0d9479adbc800de51d6abc3f3cb8d4c7950bf0ce1de344f7218757b5936d0d7814bbbdeb640daaa4faaf068b3a7fa39b879e1a4a6c0770040563c
6
+ metadata.gz: 9655d4af6bcb6aab678a5bd5e50f4b2ae312a69e08e2ca7ea8e3a0ddaebd28adef3934f22fe4726f9e815dba0dce1400028fa67b9921ac32c2ecdc546b67e6de
7
+ data.tar.gz: 082959990b7b5d8bea5d0222fba0aa3e23e72b11e42c27812ff1f940178bded01171de8c8246ef6350b17320e19586b275e0b8061d3e625a76556b629ac2825b
@@ -1,5 +1,12 @@
1
1
  ## HEAD
2
2
 
3
+ ## 0.0.6 / November 17, 2014
4
+
5
+ * Support parsing params with quoted values.
6
+ * Fix bug in `#negotiate` returning nil on first q=0 match, it should skip this match and move on to the next one in the array input.
7
+ * Fix Charset typos in README.
8
+ * Add specs for ignoring accept header prefixes.
9
+
3
10
  ## 0.0.5 / November 16, 2014
4
11
 
5
12
  * Add `#accept?` and `#reject?` methods to all negotiators.
data/README.md CHANGED
@@ -15,7 +15,7 @@ Some features of the library are:
15
15
  * Parser tested against all IANA registered [media types][iana-media-types]
16
16
  * A comprehensive [spec suite][spec] that covers many edge cases
17
17
 
18
- This library is optimistic when parsing headers. If a specific media type, encoding, charset, or language can't be parsed, is in an invalid format, or contains invalid characters, it will skip that specific entry when constructing the sorted list. If a `q` value can't be read or is in the wrong format (more than 3 decimal places), it will default it to `0.001` so it still has a chance to match. Lack of an explicit `q` value of course defaults to 1.
18
+ This library is optimistic when parsing headers. If a specific media type, encoding, or language can't be parsed, is in an invalid format, or contains invalid characters, it will skip that specific entry when constructing the sorted list. If a `q` value can't be read or is in the wrong format (more than 3 decimal places), it will default it to `0.001` so it still has a chance to match. Lack of an explicit `q` value of course defaults to 1.
19
19
 
20
20
  [rfc]: http://www.w3.org/Protocols/rfc2616/rfc2616.html
21
21
  [rfc-sec14]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
@@ -63,14 +63,20 @@ media_types.list
63
63
  ]
64
64
  ```
65
65
 
66
- `#negotiate` takes a string of media types supported (by your API or route/controller) and returns the best match as a `MediaType`. This will first check the available list for any matching media types with a `q` of 0 and return `nil` if there is a match. Then it'll look to the highest `q` values and look for matches in descending `q` value order and return the first match (accounting for wildcards). Finally, if there are no matches, it returns `nil`.
66
+ `#negotiate` takes an array of media range strings supported (by your API or route/controller) and returns a hash where the `supported` key contains the array element matched and the `matched` key containing a `MediaType` that was matched in the `Negotiator`s internal list.
67
+
68
+ This will first check the available list for any matching media types with a `q` of 0 and skip any matches. It does this because the RFC specifies that if the `q` value is 0, then content with this parameter is `not acceptable`. Then it'll look to the highest `q` values and look for matches in descending `q` value order and return the first match (accounting for wildcards). Finally, if there are no matches, it returns `nil`.
67
69
 
68
70
  ```ruby
69
- media_types.negotiate('text/html')
71
+ # The same media_types variable as above
72
+ media_types.negotiate(['text/html', 'text/plain'])
70
73
 
71
74
  # Returns:
72
75
 
73
- AcceptHeaders::MediaType.new('text', 'html', params: { 'level' => '1' })
76
+ {
77
+ supported: 'text/html',
78
+ matched: AcceptHeaders::MediaType.new('text', 'html', q: 1, params: { 'level' => '1' })
79
+ }
74
80
  ```
75
81
 
76
82
  `#accept?`:
@@ -81,7 +87,7 @@ media_types.accept?('text/html') # true
81
87
 
82
88
  ### Accept-Encoding
83
89
 
84
- `AcceptHeader::Charset::Encoding`:
90
+ `AcceptHeader::Encoding::Encoding`:
85
91
 
86
92
  ```ruby
87
93
  encodings = AcceptHeaders::Encoding::Negotiator.new("deflate; q=0.5, gzip, compress; q=0.8, identity")
@@ -100,11 +106,14 @@ encodings.list
100
106
  `#negotiate`:
101
107
 
102
108
  ```ruby
103
- encodings.negotiate('gzip')
109
+ encodings.negotiate(['gzip', 'compress'])
104
110
 
105
111
  # Returns:
106
112
 
107
- AcceptHeaders::Encoding.new('gzip')
113
+ {
114
+ supported: 'gzip',
115
+ matched: AcceptHeaders::Encoding.new('gzip'))
116
+ }
108
117
  ```
109
118
 
110
119
  `#accept?`:
@@ -138,11 +147,14 @@ languages.list
138
147
  `#negotiate`:
139
148
 
140
149
  ```ruby
141
- languages.negotiate('en-us')
150
+ languages.negotiate(['en-us', 'zh-Hant'])
142
151
 
143
152
  # Returns:
144
153
 
145
- AcceptHeaders::Language.new('en', 'us')
154
+ {
155
+ supported: 'en-us',
156
+ matched: AcceptHeaders::Language.new('en', 'us'))
157
+ }
146
158
  ```
147
159
 
148
160
  `#accept?`:
@@ -18,6 +18,8 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
+ spec.required_ruby_version = '>= 2.0.0'
22
+
21
23
  spec.add_development_dependency "bundler", "~> 1.7"
22
24
  spec.add_development_dependency "rake", "~> 10.0"
23
25
 
@@ -36,7 +36,6 @@ module AcceptHeaders
36
36
  @q = q_float
37
37
  end
38
38
 
39
- private
40
39
  def match(other)
41
40
  raise NotImplementedError.new("#match is not implemented")
42
41
  end
@@ -34,7 +34,6 @@ module AcceptHeaders
34
34
  "#{encoding};q=#{qvalue}"
35
35
  end
36
36
 
37
- private
38
37
  def match(encoding_string)
39
38
  match_data = ENCODING_PATTERN.match(encoding_string)
40
39
  if !match_data
@@ -62,7 +62,6 @@ module AcceptHeaders
62
62
  end
63
63
  end
64
64
 
65
- private
66
65
  def match(language_tag_string)
67
66
  match_data = LANGUAGE_TAG_PATTERN.match(language_tag_string)
68
67
  if !match_data
@@ -76,7 +76,6 @@ module AcceptHeaders
76
76
  "#{type}/#{subtype}"
77
77
  end
78
78
 
79
- private
80
79
  def match(media_range_string)
81
80
  match_data = MEDIA_TYPE_PATTERN.match(media_range_string)
82
81
  if !match_data
@@ -35,11 +35,15 @@ module AcceptHeaders
35
35
  end
36
36
 
37
37
  def parse_params(params_string)
38
- params = {}
39
- return params if !params_string || params_string.empty?
40
- params_string.split(';').each do |part|
41
- param = PARAM_PATTERN.match(part)
42
- params[param[:attribute]] = param[:value] if param
38
+ return {} if !params_string || params_string.empty?
39
+ if params_string.match(/['"]/)
40
+ params = params_string.scan(PARAM_PATTERN).map(&:compact).to_h
41
+ else
42
+ params = {}
43
+ params_string.split(';').each do |part|
44
+ param = PARAM_PATTERN.match(part)
45
+ params[param[:attribute]] = param[:value] if param
46
+ end
43
47
  end
44
48
  params.delete('q')
45
49
  params
@@ -12,20 +12,18 @@ module AcceptHeaders
12
12
  end
13
13
 
14
14
  def negotiate(supported)
15
- return nil if @list.empty?
15
+ return nil if list.empty?
16
16
  supported = [*supported]
17
- rejects, acceptable = @list.partition { |m| m.q == 0.0 }
18
- rejects.each do |reject|
17
+ # TODO: Maybe q=0 should be first by default when sorting
18
+ rejects, acceptable = list.partition { |m| m.q == 0.0 }
19
+ (rejects + acceptable).each do |part|
19
20
  supported.each do |support|
20
- if reject.reject?(support)
21
- return nil
22
- end
23
- end
24
- end
25
- acceptable.sort { |x,y| y <=> x }.each do |accepted|
26
- supported.each do |support|
27
- if accepted.accept?(support)
28
- return accepted
21
+ if part.match(support)
22
+ if part.q == 0.0
23
+ next
24
+ else
25
+ return { supported: support, matched: part }
26
+ end
29
27
  end
30
28
  end
31
29
  end
@@ -1,3 +1,3 @@
1
1
  module AcceptHeaders
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
@@ -1,86 +1,89 @@
1
1
  require_relative "../spec_helper"
2
2
 
3
- module AcceptHeaders
4
- class Encoding
5
- describe Negotiator do
6
- subject do
7
- AcceptHeaders::Encoding::Negotiator
8
- end
3
+ describe AcceptHeaders::Encoding::Negotiator do
4
+ subject { AcceptHeaders::Encoding::Negotiator }
5
+ let(:encoding) { AcceptHeaders::Encoding }
9
6
 
10
- describe "parsing an accept header" do
11
- it "returns a sorted array of encodings" do
12
- subject.new("*; q=0.2, compress").list.must_equal [
13
- Encoding.new('compress'),
14
- Encoding.new('*', q: 0.2)
15
- ]
7
+ describe "parsing an accept header" do
8
+ it "returns a sorted array of encodings" do
9
+ subject.new("*; q=0.2, compress").list.must_equal [
10
+ encoding.new('compress'),
11
+ encoding.new('*', q: 0.2)
12
+ ]
16
13
 
17
- subject.new("deflate; q=0.5, gzip, compress; q=0.8, identity").list.must_equal [
18
- Encoding.new('gzip'),
19
- Encoding.new('identity'),
20
- Encoding.new('compress', q: 0.8),
21
- Encoding.new('deflate', q: 0.5)
22
- ]
23
- end
14
+ subject.new("deflate; q=0.5, gzip, compress; q=0.8, identity").list.must_equal [
15
+ encoding.new('gzip'),
16
+ encoding.new('identity'),
17
+ encoding.new('compress', q: 0.8),
18
+ encoding.new('deflate', q: 0.5)
19
+ ]
20
+ end
24
21
 
25
- it "supports all registered IANA encodings" do
26
- require 'csv'
27
- # https://www.iana.org/assignments/http-parameters/http-parameters.xml#content-coding
28
- CSV.foreach("spec/support/encodings/content-coding.csv", headers: true) do |row|
29
- encoding = row['Name']
22
+ it "ignores the 'Accept-Encoding:' prefix" do
23
+ subject.new('Accept-Encoding: gzip').list.must_equal [
24
+ encoding.new('gzip')
25
+ ]
26
+ end
30
27
 
31
- if encoding
32
- subject.new(encoding).list.size.must_equal 1
33
- subject.new(encoding).list.first.encoding.must_equal encoding.downcase
34
- end
35
- end
36
- end
28
+ it "supports all registered IANA encodings" do
29
+ require 'csv'
30
+ # https://www.iana.org/assignments/http-parameters/http-parameters.xml#content-coding
31
+ CSV.foreach("spec/support/encodings/content-coding.csv", headers: true) do |row|
32
+ encoding = row['Name']
37
33
 
38
- it "sets encoding to * when the accept-encoding header is empty" do
39
- subject.new('').list.must_equal [
40
- Encoding.new('*')
41
- ]
34
+ if encoding
35
+ subject.new(encoding).list.size.must_equal 1
36
+ subject.new(encoding).list.first.encoding.must_equal encoding.downcase
42
37
  end
38
+ end
39
+ end
43
40
 
44
- it "defaults q to 1 if it's not explicitly specified" do
45
- subject.new("gzip").list.must_equal [
46
- Encoding.new('gzip', q: 1.0)
47
- ]
48
- end
41
+ it "sets encoding to * when the accept-encoding header is empty" do
42
+ subject.new('').list.must_equal [
43
+ encoding.new('*')
44
+ ]
45
+ end
49
46
 
50
- it "strips whitespace from between encodings" do
51
- subject.new("\tcompress\r,\ngzip\s").list.must_equal [
52
- Encoding.new('compress'),
53
- Encoding.new('gzip')
54
- ]
55
- end
47
+ it "defaults q to 1 if it's not explicitly specified" do
48
+ subject.new("gzip").list.must_equal [
49
+ encoding.new('gzip', q: 1.0)
50
+ ]
51
+ end
56
52
 
57
- it "strips whitespace around q" do
58
- subject.new("gzip;\tq\r=\n1, compress;q=0.8\n").list.must_equal [
59
- Encoding.new('gzip'),
60
- Encoding.new('compress', q: 0.8)
61
- ]
62
- end
53
+ it "strips whitespace from between encodings" do
54
+ subject.new("\tcompress\r,\ngzip\s").list.must_equal [
55
+ encoding.new('compress'),
56
+ encoding.new('gzip')
57
+ ]
58
+ end
63
59
 
64
- it "has a q value of 0.001 when parsed q is invalid" do
65
- subject.new("gzip;q=x").list.must_equal [
66
- Encoding.new('gzip', q: 0.001)
67
- ]
68
- end
60
+ it "strips whitespace around q" do
61
+ subject.new("gzip;\tq\r=\n1, compress;q=0.8\n").list.must_equal [
62
+ encoding.new('gzip'),
63
+ encoding.new('compress', q: 0.8)
64
+ ]
65
+ end
69
66
 
70
- it "skips invalid encodings" do
71
- subject.new("gzip, @blah").list.must_equal [
72
- Encoding.new('gzip', q: 1.0)
73
- ]
74
- end
75
- end
67
+ it "has a q value of 0.001 when parsed q is invalid" do
68
+ subject.new("gzip;q=x").list.must_equal [
69
+ encoding.new('gzip', q: 0.001)
70
+ ]
71
+ end
76
72
 
77
- describe "negotiate supported encodings" do
78
- it "returns a best matching encoding" do
79
- match =
80
- n = subject.new("deflate; q=0.5, gzip, compress; q=0.8, identity")
81
- n.negotiate("identity").must_equal Encoding.new('identity')
82
- end
83
- end
73
+ it "skips invalid encodings" do
74
+ subject.new("gzip, @blah").list.must_equal [
75
+ encoding.new('gzip', q: 1.0)
76
+ ]
77
+ end
78
+ end
79
+
80
+ describe "negotiate supported encodings" do
81
+ it "returns a best matching encoding" do
82
+ n = subject.new('deflate; q=0.5, gzip, compress; q=0.8, identity')
83
+ n.negotiate(['identity', 'deflate']).must_equal({
84
+ supported: 'identity',
85
+ matched: encoding.new('identity')
86
+ })
84
87
  end
85
88
  end
86
89
  end
@@ -1,78 +1,82 @@
1
1
  require_relative "../spec_helper"
2
2
 
3
- module AcceptHeaders
4
- class Language
5
- describe Negotiator do
6
- subject do
7
- AcceptHeaders::Language::Negotiator
8
- end
3
+ describe AcceptHeaders::Language::Negotiator do
4
+ subject { AcceptHeaders::Language::Negotiator }
5
+ let(:language) { AcceptHeaders::Language }
9
6
 
10
- describe "parsing an accept header" do
11
- it "returns a sorted array of media primary tags" do
12
- subject.new("en-*;q=0.2, en-us").list.must_equal [
13
- Language.new('en', 'us'),
14
- Language.new('en', '*', q: 0.2)
15
- ]
7
+ describe "parsing an accept header" do
8
+ it "returns a sorted array of media primary tags" do
9
+ subject.new("en-*;q=0.2, en-us").list.must_equal [
10
+ language.new('en', 'us'),
11
+ language.new('en', '*', q: 0.2)
12
+ ]
16
13
 
17
- subject.new("en-*, en-us, *;q=0.8").list.must_equal [
18
- Language.new('en', 'us'),
19
- Language.new('en', '*'),
20
- Language.new('*', '*', q: 0.8)
21
- ]
22
- end
14
+ subject.new("en-*, en-us, *;q=0.8").list.must_equal [
15
+ language.new('en', 'us'),
16
+ language.new('en', '*'),
17
+ language.new('*', '*', q: 0.8)
18
+ ]
19
+ end
23
20
 
24
- it "sets media primary tag to */* when the accept header is empty" do
25
- subject.new('').list.must_equal [
26
- Language.new('*', '*')
27
- ]
28
- end
21
+ it "ignores the 'Accept-Language:' prefix" do
22
+ subject.new('Accept-Language: en-us').list.must_equal [
23
+ language.new('en', 'us')
24
+ ]
25
+ end
29
26
 
30
- it "sets media primary tag to */* when the primary tag is only *" do
31
- subject.new('*').list.must_equal [
32
- Language.new('*', '*')
33
- ]
34
- end
27
+ it "sets media primary tag to */* when the accept header is empty" do
28
+ subject.new('').list.must_equal [
29
+ language.new('*', '*')
30
+ ]
31
+ end
35
32
 
36
- it "defaults q to 1 if it's not explicitly specified" do
37
- subject.new("en-us").list.must_equal [
38
- Language.new('en', 'plain', q: 1.0)
39
- ]
40
- end
33
+ it "sets media primary tag to */* when the primary tag is only *" do
34
+ subject.new('*').list.must_equal [
35
+ language.new('*', '*')
36
+ ]
37
+ end
41
38
 
42
- it "strips whitespace from between media primary tags" do
43
- subject.new("\ten-us\r,\nen-gb\s").list.must_equal [
44
- Language.new('en', 'us'),
45
- Language.new('en', 'gb')
46
- ]
47
- end
39
+ it "defaults q to 1 if it's not explicitly specified" do
40
+ subject.new("en-us").list.must_equal [
41
+ language.new('en', 'plain', q: 1.0)
42
+ ]
43
+ end
48
44
 
49
- it "strips whitespace around q" do
50
- subject.new("en-us;\tq\r=\n1, en-gb").list.must_equal [
51
- Language.new('en', 'us'),
52
- Language.new('en', 'gb')
53
- ]
54
- end
45
+ it "strips whitespace from between media primary tags" do
46
+ subject.new("\ten-us\r,\nen-gb\s").list.must_equal [
47
+ language.new('en', 'us'),
48
+ language.new('en', 'gb')
49
+ ]
50
+ end
55
51
 
56
- it "has a q value of 0.001 when parsed q is invalid" do
57
- subject.new("en-us;q=x").list.must_equal [
58
- Language.new('en', 'plain', q: 0.001)
59
- ]
60
- end
52
+ it "strips whitespace around q" do
53
+ subject.new("en-us;\tq\r=\n1, en-gb").list.must_equal [
54
+ language.new('en', 'us'),
55
+ language.new('en', 'gb')
56
+ ]
57
+ end
58
+
59
+ it "has a q value of 0.001 when parsed q is invalid" do
60
+ subject.new("en-us;q=x").list.must_equal [
61
+ language.new('en', 'plain', q: 0.001)
62
+ ]
63
+ end
61
64
 
62
- it "skips invalid media primary tags" do
63
- subject.new("en-us, en-us-omg;q=0.9").list.must_equal [
64
- Language.new('en', 'us', q: 1)
65
- ]
66
- end
67
- end
65
+ it "skips invalid media primary tags" do
66
+ subject.new("en-us, en-us-omg;q=0.9").list.must_equal [
67
+ language.new('en', 'us', q: 1)
68
+ ]
69
+ end
70
+ end
68
71
 
69
- describe "negotiate supported languages" do
70
- it "returns a best matching language" do
71
- match = Language.new('en', 'us')
72
- n = subject.new('en-*, en-us, *;q=0.8')
73
- n.negotiate('en-us').must_equal match
74
- end
75
- end
72
+ describe "negotiate supported languages" do
73
+ it "returns a best matching language" do
74
+ match = language.new('en', 'us')
75
+ n = subject.new('en-*, en-us, *;q=0.8')
76
+ n.negotiate('en-us').must_equal({
77
+ supported: 'en-us',
78
+ matched: match
79
+ })
76
80
  end
77
81
  end
78
82
  end
@@ -1,122 +1,197 @@
1
1
  require_relative "../spec_helper"
2
2
 
3
- module AcceptHeaders
4
- class MediaType
5
- describe Negotiator do
6
- subject do
7
- AcceptHeaders::MediaType::Negotiator
8
- end
3
+ describe AcceptHeaders::MediaType::Negotiator do
4
+ subject { AcceptHeaders::MediaType::Negotiator }
5
+ let(:media_type) { AcceptHeaders::MediaType }
6
+
7
+ describe "parsing an accept header" do
8
+ it "returns a sorted array of media types" do
9
+ subject.new("audio/*; q=0.2, audio/basic").list.must_equal [
10
+ media_type.new('audio', 'basic'),
11
+ media_type.new('audio', '*', q: 0.2)
12
+ ]
13
+ subject.new("text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c").list.must_equal [
14
+ media_type.new('text', 'html'),
15
+ media_type.new('text', 'x-c'),
16
+ media_type.new('text', 'x-dvi', q: 0.8),
17
+ media_type.new('text', 'plain', q: 0.5)
18
+ ]
19
+
20
+ subject.new("text/*, text/html, text/html;level=1, */*").list.must_equal [
21
+ media_type.new('text', 'html', params: { 'level' => '1' }),
22
+ media_type.new('text', 'html'),
23
+ media_type.new('text', '*'),
24
+ media_type.new('*', '*')
25
+ ]
26
+
27
+ subject.new("text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5").list.must_equal [
28
+ media_type.new('text', 'html', params: { 'level' => '1' }),
29
+ media_type.new('text', 'html', q: 0.7),
30
+ media_type.new('*', '*', q: 0.5),
31
+ media_type.new('text', 'html', q: 0.4, params: { 'level' => '2' }),
32
+ media_type.new('text', '*', q: 0.3)
33
+ ]
34
+ end
9
35
 
10
- describe "parsing an accept header" do
11
- it "returns a sorted array of media types" do
12
- subject.new("audio/*; q=0.2, audio/basic").list.must_equal [
13
- MediaType.new('audio', 'basic'),
14
- MediaType.new('audio', '*', q: 0.2)
15
- ]
16
- subject.new("text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c").list.must_equal [
17
- MediaType.new('text', 'html'),
18
- MediaType.new('text', 'x-c'),
19
- MediaType.new('text', 'x-dvi', q: 0.8),
20
- MediaType.new('text', 'plain', q: 0.5)
21
- ]
22
-
23
- subject.new("text/*, text/html, text/html;level=1, */*").list.must_equal [
24
- MediaType.new('text', 'html', params: { 'level' => '1' }),
25
- MediaType.new('text', 'html'),
26
- MediaType.new('text', '*'),
27
- MediaType.new('*', '*')
28
- ]
29
-
30
- subject.new("text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5").list.must_equal [
31
- MediaType.new('text', 'html', params: { 'level' => '1' }),
32
- MediaType.new('text', 'html', q: 0.7),
33
- MediaType.new('*', '*', q: 0.5),
34
- MediaType.new('text', 'html', q: 0.4, params: { 'level' => '2' }),
35
- MediaType.new('text', '*', q: 0.3)
36
- ]
37
- end
36
+ it "ignores the 'Accept:' prefix" do
37
+ subject.new('Accept: text/html').list.must_equal [
38
+ media_type.new('text', 'html')
39
+ ]
40
+ end
41
+
42
+ it "supports all registered IANA media types" do
43
+ require 'csv'
44
+ # https://www.iana.org/assignments/media-types/media-types.xhtml
45
+ %w[application audio image message model multipart text video].each do |filename|
46
+ CSV.foreach("spec/support/media_types/#{filename}.csv", headers: true) do |row|
47
+ media_range = row['Template']
38
48
 
39
- it "supports all registered IANA media types" do
40
- require 'csv'
41
- # https://www.iana.org/assignments/media-types/media-types.xhtml
42
- %w[application audio image message model multipart text video].each do |filename|
43
- CSV.foreach("spec/support/media_types/#{filename}.csv", headers: true) do |row|
44
- media_type = row['Template']
45
-
46
- if media_type
47
- subject.new(media_type).list.size.must_equal 1
48
- subject.new(media_type).list.first.media_range.must_equal media_type.downcase
49
- end
50
- end
49
+ if media_range
50
+ subject.new(media_range).list.size.must_equal 1
51
+ subject.new(media_range).list.first.media_range.must_equal media_range.downcase
51
52
  end
52
53
  end
54
+ end
55
+ end
53
56
 
54
- it "sets media type to */* when the accept header is empty" do
55
- subject.new('').list.must_equal [
56
- MediaType.new('*', '*')
57
- ]
58
- end
57
+ it "sets media type to */* when the accept header is empty" do
58
+ subject.new('').list.must_equal [
59
+ media_type.new('*', '*')
60
+ ]
61
+ end
59
62
 
60
- it "sets media type to */* when the type is only *" do
61
- subject.new('*').list.must_equal [
62
- MediaType.new('*', '*')
63
- ]
64
- end
63
+ it "sets media type to */* when the type is only *" do
64
+ subject.new('*').list.must_equal [
65
+ media_type.new('*', '*')
66
+ ]
67
+ end
65
68
 
66
- it "defaults q to 1 if it's not explicitly specified" do
67
- subject.new("text/plain").list.must_equal [
68
- MediaType.new('text', 'plain', q: 1.0)
69
- ]
70
- end
69
+ it "defaults q to 1 if it's not explicitly specified" do
70
+ subject.new("text/plain").list.must_equal [
71
+ media_type.new('text', 'plain', q: 1.0)
72
+ ]
73
+ end
71
74
 
72
- it "strips whitespace from between media types" do
73
- subject.new("\ttext/plain\r,\napplication/json\s").list.must_equal [
74
- MediaType.new('text', 'plain'),
75
- MediaType.new('application', 'json')
76
- ]
77
- end
75
+ it "strips whitespace from between media types" do
76
+ subject.new("\ttext/plain\r,\napplication/json\s").list.must_equal [
77
+ media_type.new('text', 'plain'),
78
+ media_type.new('application', 'json')
79
+ ]
80
+ end
78
81
 
79
- it "strips whitespace around q and params" do
80
- subject.new("text/plain;\tq\r=\n1, application/json;q=0.8;\slevel\t\t=\r\n1\n").list.must_equal [
81
- MediaType.new('text', 'plain'),
82
- MediaType.new('application', 'json', q: 0.8, params: { "level" => "1" })
83
- ]
84
- end
82
+ it "strips whitespace around q and params" do
83
+ subject.new("text/plain;\tq\r=\n1, application/json;q=0.8;\slevel\t\t=\r\n1\n").list.must_equal [
84
+ media_type.new('text', 'plain'),
85
+ media_type.new('application', 'json', q: 0.8, params: { "level" => "1" })
86
+ ]
87
+ end
85
88
 
86
- it "has a q value of 0.001 when parsed q is invalid" do
87
- subject.new("text/plain;q=x").list.must_equal [
88
- MediaType.new('text', 'plain', q: 0.001)
89
- ]
90
- end
89
+ it "has a q value of 0.001 when parsed q is invalid" do
90
+ subject.new("text/plain;q=x").list.must_equal [
91
+ media_type.new('text', 'plain', q: 0.001)
92
+ ]
93
+ end
91
94
 
92
- it "skips invalid media types" do
93
- subject.new("text/html, text/plain/omg;q=0.9").list.must_equal [
94
- MediaType.new('text', 'html', q: 1)
95
- ]
96
- end
95
+ it "parses params with quoted values" do
96
+ subject.new('text/html;q=1;version="2";level="a;b;cc\'cd", text/html;version=\'1\';level=\'\blah;x;1;;\'').list.must_equal [
97
+ media_type.new('text', 'html', params: { 'version' => '2', 'level' => 'a;b;cc\'cd'}),
98
+ media_type.new('text', 'html', params: { 'version' => '1', 'level' => '\'\blah;x;1;;\''})
99
+ ]
100
+ end
101
+
102
+ it "skips invalid media types" do
103
+ subject.new("text/html, text/plain/omg;q=0.9").list.must_equal [
104
+ media_type.new('text', 'html', q: 1)
105
+ ]
106
+ end
107
+ end
108
+
109
+ describe "#negotiate" do
110
+ it "returns a best matching media type" do
111
+ all_browsers.each do |browser|
112
+ browser = subject.new(browser[:accept])
113
+
114
+ browser.negotiate('text/html').must_equal({
115
+ supported: 'text/html',
116
+ matched: media_type.new('text', 'html')
117
+ })
118
+
119
+ browser.negotiate(['text/html', 'application/xhtml+xml']).must_equal({
120
+ supported: 'text/html',
121
+ matched: media_type.new('text', 'html')
122
+ })
123
+
124
+ browser.negotiate(['application/xhtml+xml', 'application/json']).must_equal({
125
+ supported: 'application/xhtml+xml',
126
+ matched: media_type.new('application', 'xhtml+xml')
127
+ })
97
128
  end
98
129
 
99
- describe "#negotiate" do
100
- it "returns a best matching media type" do
101
- n = subject.new("text/*, text/html, text/html;level=1, */*")
102
- n.negotiate("text/html").must_equal MediaType.new('text', 'html', params: { 'level' => '1' })
103
- end
130
+ [chrome, firefox, safari].each do| browser|
131
+ browser = subject.new(browser[:accept])
132
+
133
+ browser.negotiate(['application/xml']).must_equal({
134
+ supported: 'application/xml',
135
+ matched: media_type.new('application', 'xml', q: 0.9)
136
+ })
137
+
138
+ browser.negotiate(['application/json']).must_equal({
139
+ supported: 'application/json',
140
+ matched: media_type.new('*', '*', q: 0.8)
141
+ })
104
142
  end
105
143
 
106
- describe "#accept?" do
107
- it "returns whether specific media type is accepted" do
108
- n = subject.new("video/*, text/html, text/html;level=1;q:0.8")
109
- n.accept?("text/html").must_equal true
110
- n.accept?("application/json").must_equal false
111
- n.accept?("video/ogg").must_equal true
112
- end
144
+ api = subject.new('application/json;q=1;version=2,application/json;q=0.9;version=1')
145
+ api.negotiate('text/html').must_be_nil
146
+ api.negotiate('application/xml').must_be_nil
147
+ api.negotiate(['application/xml', 'application/json']).must_equal({
148
+ supported: 'application/json',
149
+ matched: media_type.new('application', 'json', params: { 'version' => '2' })
150
+ })
151
+
152
+ q0 = subject.new('application/json,application/xml;q=0')
153
+ q0.negotiate('application/json').must_equal({
154
+ supported: 'application/json',
155
+ matched: media_type.new('application', 'json')
156
+ })
157
+ q0.negotiate('application/xml').must_be_nil
158
+ q0.negotiate(['application/json', 'application/xml']).must_equal({
159
+ supported: 'application/json',
160
+ matched: media_type.new('application', 'json')
161
+ })
162
+ end
113
163
 
114
- it "returns false if accepted but q=0" do
115
- n = subject.new("video/*, text/html;q=0")
116
- n.accept?("text/html").must_equal false
117
- n.accept?("video/ogg").must_equal true
118
- end
119
- end
164
+ it "rejects matching q=0 even if it matches media ranges where q > 0" do
165
+ n = subject.new('application/xml;q=0;*/*')
166
+ n.negotiate('application/xml').must_be_nil
167
+
168
+ n2 = subject.new('application/xml;q=0;application/xml;q=1')
169
+ n2.negotiate('application/xml').must_be_nil
170
+ end
171
+ end
172
+
173
+ describe "#accept?" do
174
+ it "returns whether specific media type is accepted" do
175
+ n = subject.new('video/*, text/html, text/html;level=1;q:0.8')
176
+ n.accept?('text/html').must_equal true
177
+ n.accept?('application/json').must_equal false
178
+ n.accept?('video/ogg').must_equal true
179
+ n.accept?(['text/html','application/json']).must_equal true
180
+ n.accept?(['application/xml','application/json']).must_equal false
181
+ end
182
+
183
+ it "returns false if accepted but q=0" do
184
+ n = subject.new('video/*, text/html;q=0')
185
+ n.accept?('text/html').must_equal false
186
+ n.accept?('video/ogg').must_equal true
187
+ end
188
+
189
+ it "returns false when q=0 even if it matches media ranges where q > 0" do
190
+ n = subject.new('application/xml;q=0;*/*')
191
+ n.accept?('application/xml').must_equal false
192
+
193
+ n2 = subject.new('application/xml;q=0;application/xml;q=1')
194
+ n2.accept?('application/xml').must_equal false
120
195
  end
121
196
  end
122
- end
197
+ end
@@ -19,3 +19,33 @@ require "awesome_print"
19
19
  require "pry"
20
20
 
21
21
  require_relative "../lib/accept_headers"
22
+
23
+ class Minitest::Spec
24
+ def chrome
25
+ {
26
+ accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
27
+ }
28
+ end
29
+
30
+ def firefox
31
+ {
32
+ accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
33
+ }
34
+ end
35
+
36
+ def safari
37
+ {
38
+ accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
39
+ }
40
+ end
41
+
42
+ def ie
43
+ {
44
+ accept: 'text/html, application/xhtml+xml, */*'
45
+ }
46
+ end
47
+
48
+ def all_browsers
49
+ [chrome, firefox, safari, ie]
50
+ end
51
+ end
metadata CHANGED
@@ -1,83 +1,83 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: accept_headers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jack Chu
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-16 00:00:00.000000000 Z
11
+ date: 2014-11-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.7'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.7'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '10.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: minitest
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ~>
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
47
  version: '5.4'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ~>
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '5.4'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: guard
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - '>='
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - '>='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: guard-minitest
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - '>='
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - '>='
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  description: A ruby library that does content negotiation and parses and sorts http
@@ -88,8 +88,8 @@ executables: []
88
88
  extensions: []
89
89
  extra_rdoc_files: []
90
90
  files:
91
- - .gitignore
92
- - .travis.yml
91
+ - ".gitignore"
92
+ - ".travis.yml"
93
93
  - CHANGELOG.md
94
94
  - Gemfile
95
95
  - Guardfile
@@ -135,17 +135,17 @@ require_paths:
135
135
  - lib
136
136
  required_ruby_version: !ruby/object:Gem::Requirement
137
137
  requirements:
138
- - - '>='
138
+ - - ">="
139
139
  - !ruby/object:Gem::Version
140
- version: '0'
140
+ version: 2.0.0
141
141
  required_rubygems_version: !ruby/object:Gem::Requirement
142
142
  requirements:
143
- - - '>='
143
+ - - ">="
144
144
  - !ruby/object:Gem::Version
145
145
  version: '0'
146
146
  requirements: []
147
147
  rubyforge_project:
148
- rubygems_version: 2.4.3
148
+ rubygems_version: 2.2.2
149
149
  signing_key:
150
150
  specification_version: 4
151
151
  summary: A ruby library that does content negotiation and parses and sorts http accept