accept_headers 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +21 -9
- data/accept_headers.gemspec +2 -0
- data/lib/accept_headers/acceptable.rb +0 -1
- data/lib/accept_headers/encoding.rb +0 -1
- data/lib/accept_headers/language.rb +0 -1
- data/lib/accept_headers/media_type.rb +0 -1
- data/lib/accept_headers/media_type/negotiator.rb +9 -5
- data/lib/accept_headers/negotiatable.rb +10 -12
- data/lib/accept_headers/version.rb +1 -1
- data/spec/encoding/negotiator_spec.rb +72 -69
- data/spec/language/negotiator_spec.rb +67 -63
- data/spec/media_type/negotiator_spec.rb +177 -102
- data/spec/spec_helper.rb +30 -0
- metadata +18 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f095a96c5f335db2b23047bac10e48fff764b819
|
4
|
+
data.tar.gz: da3121b83be60d9a41dd7bfcf98ca8925e0d80e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9655d4af6bcb6aab678a5bd5e50f4b2ae312a69e08e2ca7ea8e3a0ddaebd28adef3934f22fe4726f9e815dba0dce1400028fa67b9921ac32c2ecdc546b67e6de
|
7
|
+
data.tar.gz: 082959990b7b5d8bea5d0222fba0aa3e23e72b11e42c27812ff1f940178bded01171de8c8246ef6350b17320e19586b275e0b8061d3e625a76556b629ac2825b
|
data/CHANGELOG.md
CHANGED
@@ -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,
|
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
|
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
|
71
|
+
# The same media_types variable as above
|
72
|
+
media_types.negotiate(['text/html', 'text/plain'])
|
70
73
|
|
71
74
|
# Returns:
|
72
75
|
|
73
|
-
|
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::
|
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
|
-
|
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
|
-
|
154
|
+
{
|
155
|
+
supported: 'en-us',
|
156
|
+
matched: AcceptHeaders::Language.new('en', 'us'))
|
157
|
+
}
|
146
158
|
```
|
147
159
|
|
148
160
|
`#accept?`:
|
data/accept_headers.gemspec
CHANGED
@@ -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
|
|
@@ -35,11 +35,15 @@ module AcceptHeaders
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def parse_params(params_string)
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
params
|
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
|
15
|
+
return nil if list.empty?
|
16
16
|
supported = [*supported]
|
17
|
-
|
18
|
-
rejects.
|
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
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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,86 +1,89 @@
|
|
1
1
|
require_relative "../spec_helper"
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
39
|
-
subject.new(
|
40
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
-
|
4
|
-
|
5
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
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
|
data/spec/spec_helper.rb
CHANGED
@@ -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.
|
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-
|
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:
|
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.
|
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
|