accept_headers 0.0.7 → 0.0.8
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 +4 -0
- data/README.md +8 -17
- data/lib/accept_headers/encoding.rb +1 -3
- data/lib/accept_headers/encoding/negotiator.rb +24 -13
- data/lib/accept_headers/language.rb +1 -3
- data/lib/accept_headers/language/negotiator.rb +28 -21
- data/lib/accept_headers/media_type.rb +1 -3
- data/lib/accept_headers/media_type/negotiator.rb +30 -22
- data/lib/accept_headers/negotiatable.rb +27 -5
- data/lib/accept_headers/version.rb +1 -1
- data/spec/encoding/negotiator_spec.rb +1 -4
- data/spec/language/negotiator_spec.rb +1 -4
- data/spec/media_type/negotiator_spec.rb +37 -34
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b55af3993793d079e27b4b85fd02344b7dbc878b
|
4
|
+
data.tar.gz: 30264067d3cbd349e13169a8592df750c81d06ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b45c6294fdba63025d7a1fe4d9c860078427d3011b98aa99c8a8b84429fd4454ed18aef2eefdb22d841a32284df4df475449da2787d485b41349b7664b5ca7e1
|
7
|
+
data.tar.gz: 32cbb89fbe0f5b85824a8f8ae9cac9bfa095a5f02de33625511fe7e8c1b8cd5f55de8e6231787dd062f10cfe55e0b409226e2146f7861454ed07952eb8c4dc3f
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
## HEAD
|
2
2
|
|
3
|
+
## 0.0.8 / November 23, 2014
|
4
|
+
|
5
|
+
* Change `#negotiate` to return the type instead of a hash. In the case of `MediaType` it'll also populate `extensions` from the matching accept header media type.
|
6
|
+
|
3
7
|
## 0.0.7 / November 19, 2014
|
4
8
|
|
5
9
|
* Rename `MediaType` `params` to `extensions`, since params technically includes the `q` value.
|
data/README.md
CHANGED
@@ -65,7 +65,7 @@ media_types.list
|
|
65
65
|
]
|
66
66
|
```
|
67
67
|
|
68
|
-
`#negotiate` takes an array of media range strings supported (by your API or route/controller) and returns
|
68
|
+
`#negotiate` takes an array of media range strings supported (by your API or route/controller) and returns the best supported `MediaType` and the `extensions` params from the matching internal media type.
|
69
69
|
|
70
70
|
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`.
|
71
71
|
|
@@ -73,12 +73,9 @@ This will first check the available list for any matching media types with a `q`
|
|
73
73
|
# The same media_types variable as above
|
74
74
|
media_types.negotiate(['text/html', 'text/plain'])
|
75
75
|
|
76
|
-
# Returns:
|
76
|
+
# Returns this equivalent:
|
77
77
|
|
78
|
-
{
|
79
|
-
supported: 'text/html',
|
80
|
-
matched: AcceptHeaders::MediaType.new('text', 'html', q: 1, extensions: { 'level' => '1' })
|
81
|
-
}
|
78
|
+
AcceptHeader::MediaType.new('text', 'html', extensions: { 'level' => '1' })
|
82
79
|
```
|
83
80
|
|
84
81
|
It returns the matching `MediaType`, so you can see which one matched and also access the `extensions` params. For example, if you wanted to put your API version in the extensions, you could then retrieve the value.
|
@@ -88,7 +85,7 @@ versions_header = 'Accept: application/json;version=2,application/json;version=1
|
|
88
85
|
media_types = AcceptHeaders::MediaType::Negotiator.new(versions_header)
|
89
86
|
|
90
87
|
m = media_types.negotiate('application/json')
|
91
|
-
puts m
|
88
|
+
puts m.extensions['version'] # returns '2'
|
92
89
|
```
|
93
90
|
|
94
91
|
`#accept?`:
|
@@ -121,12 +118,9 @@ encodings.list
|
|
121
118
|
```ruby
|
122
119
|
encodings.negotiate(['gzip', 'compress'])
|
123
120
|
|
124
|
-
# Returns:
|
121
|
+
# Returns this equivalent:
|
125
122
|
|
126
|
-
|
127
|
-
supported: 'gzip',
|
128
|
-
matched: AcceptHeaders::Encoding.new('gzip'))
|
129
|
-
}
|
123
|
+
AcceptHeader::Encoding.new('gzip')
|
130
124
|
```
|
131
125
|
|
132
126
|
`#accept?`:
|
@@ -163,12 +157,9 @@ languages.list
|
|
163
157
|
```ruby
|
164
158
|
languages.negotiate(['en-us', 'zh-Hant'])
|
165
159
|
|
166
|
-
# Returns:
|
160
|
+
# Returns this equivalent:
|
167
161
|
|
168
|
-
|
169
|
-
supported: 'en-us',
|
170
|
-
matched: AcceptHeaders::Language.new('en', 'us'))
|
171
|
-
}
|
162
|
+
AcceptHeaders::Language.new('en', 'us')
|
172
163
|
```
|
173
164
|
|
174
165
|
`#accept?`:
|
@@ -7,8 +7,6 @@ module AcceptHeaders
|
|
7
7
|
|
8
8
|
attr_reader :encoding
|
9
9
|
|
10
|
-
ENCODING_PATTERN = /^\s*(?<encoding>[\w!#$%^&*\-\+{}\\|'.`~]+)\s*$/
|
11
|
-
|
12
10
|
def initialize(encoding = '*', q: 1.0)
|
13
11
|
self.encoding = encoding
|
14
12
|
self.q = q
|
@@ -35,7 +33,7 @@ module AcceptHeaders
|
|
35
33
|
end
|
36
34
|
|
37
35
|
def match(encoding_string)
|
38
|
-
match_data = ENCODING_PATTERN.match(encoding_string)
|
36
|
+
match_data = Negotiator::ENCODING_PATTERN.match(encoding_string)
|
39
37
|
if !match_data
|
40
38
|
false
|
41
39
|
elsif encoding == match_data[:encoding]
|
@@ -6,21 +6,32 @@ module AcceptHeaders
|
|
6
6
|
class Negotiator
|
7
7
|
include Negotiatable
|
8
8
|
|
9
|
+
ENCODING_PATTERN = /^\s*(?<encoding>[\w!#$%^&*\-\+{}\\|'.`~]+)\s*$/
|
10
|
+
HEADER_PREFIX = 'Accept-Encoding:'
|
11
|
+
|
12
|
+
def negotiate(supported)
|
13
|
+
support, match = super(supported)
|
14
|
+
return nil if support.nil? && match.nil?
|
15
|
+
begin
|
16
|
+
return parse(support).first
|
17
|
+
rescue Encoding::Error
|
18
|
+
return nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
9
22
|
private
|
10
|
-
def
|
11
|
-
|
12
|
-
|
23
|
+
def no_header
|
24
|
+
[Encoding.new]
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_item(header)
|
28
|
+
return nil if header.nil?
|
13
29
|
header.strip!
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
encoding = Encoding::ENCODING_PATTERN.match(encoding_arr[0])
|
20
|
-
next if encoding.nil?
|
21
|
-
encodings << Encoding.new(encoding[:encoding], q: parse_q(encoding_arr[1]))
|
22
|
-
end
|
23
|
-
encodings.sort! { |x,y| y <=> x }
|
30
|
+
encoding_string, q_string = header.split(';', 2)
|
31
|
+
raise Error if encoding_string.nil?
|
32
|
+
encoding = ENCODING_PATTERN.match(encoding_string)
|
33
|
+
raise Error if encoding.nil?
|
34
|
+
Encoding.new(encoding[:encoding], q: parse_q(q_string))
|
24
35
|
end
|
25
36
|
end
|
26
37
|
end
|
@@ -7,8 +7,6 @@ module AcceptHeaders
|
|
7
7
|
|
8
8
|
attr_reader :primary_tag, :subtag
|
9
9
|
|
10
|
-
LANGUAGE_TAG_PATTERN = /^\s*(?<primary_tag>[\w]{1,8}|\*)(?:\s*\-\s*(?<subtag>[\w]{1,8}|\*))?\s*$/
|
11
|
-
|
12
10
|
def initialize(primary_tag = '*', subtag = nil, q: 1.0)
|
13
11
|
self.primary_tag = primary_tag
|
14
12
|
self.subtag = subtag
|
@@ -63,7 +61,7 @@ module AcceptHeaders
|
|
63
61
|
end
|
64
62
|
|
65
63
|
def match(language_tag_string)
|
66
|
-
match_data = LANGUAGE_TAG_PATTERN.match(language_tag_string)
|
64
|
+
match_data = Negotiator::LANGUAGE_TAG_PATTERN.match(language_tag_string)
|
67
65
|
if !match_data
|
68
66
|
false
|
69
67
|
elsif primary_tag == match_data[:primary_tag] && subtag == match_data[:subtag]
|
@@ -6,29 +6,36 @@ module AcceptHeaders
|
|
6
6
|
class Negotiator
|
7
7
|
include Negotiatable
|
8
8
|
|
9
|
+
LANGUAGE_TAG_PATTERN = /^\s*(?<primary_tag>[\w]{1,8}|\*)(?:\s*\-\s*(?<subtag>[\w]{1,8}|\*))?\s*$/
|
10
|
+
HEADER_PREFIX = 'Accept-Language:'
|
11
|
+
|
12
|
+
def negotiate(supported)
|
13
|
+
support, match = super(supported)
|
14
|
+
return nil if support.nil? && match.nil?
|
15
|
+
begin
|
16
|
+
return parse(support).first
|
17
|
+
rescue Language::Error
|
18
|
+
return nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
9
22
|
private
|
10
|
-
def
|
11
|
-
|
12
|
-
|
23
|
+
def no_header
|
24
|
+
[Language.new]
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_item(header)
|
28
|
+
return nil if header.nil?
|
13
29
|
header.strip!
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
language_range
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
language_range[:primary_tag],
|
24
|
-
language_range[:subtag],
|
25
|
-
q: parse_q(language_arr[1])
|
26
|
-
)
|
27
|
-
rescue Error
|
28
|
-
next
|
29
|
-
end
|
30
|
-
end
|
31
|
-
languages.sort! { |x,y| y <=> x }
|
30
|
+
language_string, q_string = header.split(';', 2)
|
31
|
+
raise Error if language_string.nil?
|
32
|
+
language_range = LANGUAGE_TAG_PATTERN.match(language_string)
|
33
|
+
raise Error if language_range.nil?
|
34
|
+
Language.new(
|
35
|
+
language_range[:primary_tag],
|
36
|
+
language_range[:subtag],
|
37
|
+
q: parse_q(q_string)
|
38
|
+
)
|
32
39
|
end
|
33
40
|
end
|
34
41
|
end
|
@@ -7,8 +7,6 @@ module AcceptHeaders
|
|
7
7
|
|
8
8
|
attr_reader :type, :subtype, :extensions
|
9
9
|
|
10
|
-
MEDIA_TYPE_PATTERN = /^\s*(?<type>[\w!#$%^&*\-\+{}\\|'.`~]+)(?:\s*\/\s*(?<subtype>[\w!#$%^&*\-\+{}\\|'.`~]+))?\s*$/
|
11
|
-
|
12
10
|
def initialize(type = '*', subtype = '*', q: 1.0, extensions: {})
|
13
11
|
self.type = type
|
14
12
|
self.subtype = subtype
|
@@ -77,7 +75,7 @@ module AcceptHeaders
|
|
77
75
|
end
|
78
76
|
|
79
77
|
def match(media_range_string)
|
80
|
-
match_data = MEDIA_TYPE_PATTERN.match(media_range_string)
|
78
|
+
match_data = Negotiator::MEDIA_TYPE_PATTERN.match(media_range_string)
|
81
79
|
if !match_data
|
82
80
|
false
|
83
81
|
elsif type == match_data[:type] && subtype == match_data[:subtype]
|
@@ -6,32 +6,40 @@ module AcceptHeaders
|
|
6
6
|
class Negotiator
|
7
7
|
include Negotiatable
|
8
8
|
|
9
|
+
MEDIA_TYPE_PATTERN = /^\s*(?<type>[\w!#$%^&*\-\+{}\\|'.`~]+)(?:\s*\/\s*(?<subtype>[\w!#$%^&*\-\+{}\\|'.`~]+))?\s*$/
|
9
10
|
PARAMS_PATTERN = /(?<attribute>[\w!#$%^&*\-\+{}\\|'.`~]+)\s*\=\s*(?:\"(?<value>[^"]*)\"|\'(?<value>[^']*)\'|(?<value>[\w!#$%^&*\-\+{}\\|\'.`~]*))/
|
11
|
+
HEADER_PREFIX = 'Accept:'
|
12
|
+
|
13
|
+
def negotiate(supported)
|
14
|
+
support, match = super(supported)
|
15
|
+
return nil if support.nil? && match.nil?
|
16
|
+
begin
|
17
|
+
media_type = parse(support).first
|
18
|
+
media_type.extensions = match.extensions
|
19
|
+
return media_type
|
20
|
+
rescue MediaType::Error
|
21
|
+
return nil
|
22
|
+
end
|
23
|
+
end
|
10
24
|
|
11
25
|
private
|
12
|
-
def
|
13
|
-
|
14
|
-
|
26
|
+
def no_header
|
27
|
+
[MediaType.new]
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse_item(header)
|
31
|
+
return nil if header.nil?
|
15
32
|
header.strip!
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
media_range
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
media_range[:subtype],
|
27
|
-
q: parse_q(accept_extensions),
|
28
|
-
extensions: parse_extensions(accept_extensions)
|
29
|
-
)
|
30
|
-
rescue Error
|
31
|
-
next
|
32
|
-
end
|
33
|
-
end
|
34
|
-
media_types.sort! { |x,y| y <=> x }
|
33
|
+
accept_media_range, accept_extensions = header.split(';', 2)
|
34
|
+
raise Error if accept_media_range.nil?
|
35
|
+
media_range = MEDIA_TYPE_PATTERN.match(accept_media_range)
|
36
|
+
raise Error if media_range.nil?
|
37
|
+
MediaType.new(
|
38
|
+
media_range[:type],
|
39
|
+
media_range[:subtype],
|
40
|
+
q: parse_q(accept_extensions),
|
41
|
+
extensions: parse_extensions(accept_extensions)
|
42
|
+
)
|
35
43
|
end
|
36
44
|
|
37
45
|
def parse_extensions(extensions_string)
|
@@ -3,10 +3,10 @@ module AcceptHeaders
|
|
3
3
|
class Error < StandardError; end
|
4
4
|
class ParseError < Error; end
|
5
5
|
|
6
|
-
Q_PATTERN = /(?:\A|;)\s*(?<exists>qs*\=)\s*(?:(?<q>0\.\d{1,3}|[01])|(?:[^;]*))\s*(?:\z|;)/
|
7
|
-
|
8
6
|
attr_reader :list
|
9
7
|
|
8
|
+
Q_PATTERN = /(?:\A|;)\s*(?<exists>qs*\=)\s*(?:(?<q>0\.\d{1,3}|[01])|(?:[^;]*))\s*(?:\z|;)/
|
9
|
+
|
10
10
|
def initialize(header)
|
11
11
|
@list = parse(header)
|
12
12
|
end
|
@@ -22,7 +22,7 @@ module AcceptHeaders
|
|
22
22
|
if part.q == 0.0
|
23
23
|
next
|
24
24
|
else
|
25
|
-
return
|
25
|
+
return [support, part]
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
@@ -35,8 +35,30 @@ module AcceptHeaders
|
|
35
35
|
end
|
36
36
|
|
37
37
|
private
|
38
|
-
def
|
39
|
-
raise NotImplementedError.new("#
|
38
|
+
def no_header
|
39
|
+
raise NotImplementedError.new("#no_header is not implemented")
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse_item(entry)
|
43
|
+
raise NotImplementedError.new("#parse_item(entry) is not implemented")
|
44
|
+
end
|
45
|
+
|
46
|
+
def parse(header, &block)
|
47
|
+
header = header.dup
|
48
|
+
header.sub!(/\A#{self.class::HEADER_PREFIX}\s*/, '')
|
49
|
+
header.strip!
|
50
|
+
return no_header if header.empty?
|
51
|
+
list = []
|
52
|
+
header.split(',').each do |entry|
|
53
|
+
begin
|
54
|
+
item = parse_item(entry)
|
55
|
+
next if item.nil?
|
56
|
+
list << item
|
57
|
+
rescue Error
|
58
|
+
next
|
59
|
+
end
|
60
|
+
end
|
61
|
+
list.sort! { |x,y| y <=> x }
|
40
62
|
end
|
41
63
|
|
42
64
|
def parse_q(header)
|
@@ -80,10 +80,7 @@ describe AcceptHeaders::Encoding::Negotiator do
|
|
80
80
|
describe "negotiate supported encodings" do
|
81
81
|
it "returns a best matching encoding" do
|
82
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
|
-
})
|
83
|
+
n.negotiate(['identity', 'deflate']).must_equal encoding.new('identity')
|
87
84
|
end
|
88
85
|
end
|
89
86
|
end
|
@@ -73,10 +73,7 @@ describe AcceptHeaders::Language::Negotiator do
|
|
73
73
|
it "returns a best matching language" do
|
74
74
|
match = language.new('en', 'us')
|
75
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
|
+
n.negotiate('en-us').must_equal language.new('en', 'us')
|
80
77
|
end
|
81
78
|
end
|
82
79
|
end
|
@@ -111,54 +111,57 @@ describe AcceptHeaders::MediaType::Negotiator do
|
|
111
111
|
all_browsers.each do |browser|
|
112
112
|
browser = subject.new(browser[:accept])
|
113
113
|
|
114
|
-
browser.negotiate('text/html').must_equal(
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
browser.negotiate(['text/html', 'application/xhtml+xml']).must_equal(
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
browser.negotiate(['application/xhtml+xml', 'application/json']).must_equal(
|
125
|
-
|
126
|
-
|
127
|
-
|
114
|
+
browser.negotiate('text/html').must_equal media_type.new(
|
115
|
+
'text',
|
116
|
+
'html'
|
117
|
+
)
|
118
|
+
|
119
|
+
browser.negotiate(['text/html', 'application/xhtml+xml']).must_equal media_type.new(
|
120
|
+
'text',
|
121
|
+
'html'
|
122
|
+
)
|
123
|
+
|
124
|
+
browser.negotiate(['application/xhtml+xml', 'application/json']).must_equal media_type.new(
|
125
|
+
'application',
|
126
|
+
'xhtml+xml'
|
127
|
+
)
|
128
128
|
end
|
129
129
|
|
130
130
|
[chrome, firefox, safari].each do| browser|
|
131
131
|
browser = subject.new(browser[:accept])
|
132
132
|
|
133
|
-
browser.negotiate(['application/xml']).must_equal(
|
134
|
-
|
135
|
-
|
136
|
-
|
133
|
+
browser.negotiate(['application/xml']).must_equal media_type.new(
|
134
|
+
'application',
|
135
|
+
'xml'
|
136
|
+
)
|
137
137
|
|
138
|
-
browser.negotiate(['application/json']).must_equal(
|
139
|
-
|
140
|
-
|
141
|
-
|
138
|
+
browser.negotiate(['application/json']).must_equal media_type.new(
|
139
|
+
'application',
|
140
|
+
'json'
|
141
|
+
)
|
142
142
|
end
|
143
143
|
|
144
144
|
api = subject.new('application/json;q=1;version=2,application/json;q=0.9;version=1')
|
145
145
|
api.negotiate('text/html').must_be_nil
|
146
146
|
api.negotiate('application/xml').must_be_nil
|
147
|
-
api.negotiate(['application/xml', 'application/json']).must_equal(
|
148
|
-
|
149
|
-
|
150
|
-
|
147
|
+
api.negotiate(['application/xml', 'application/json']).must_equal media_type.new(
|
148
|
+
'application',
|
149
|
+
'json',
|
150
|
+
extensions: {
|
151
|
+
'version' => '1'
|
152
|
+
}
|
153
|
+
)
|
151
154
|
|
152
155
|
q0 = subject.new('application/json,application/xml;q=0')
|
153
|
-
q0.negotiate('application/json').must_equal(
|
154
|
-
|
155
|
-
|
156
|
-
|
156
|
+
q0.negotiate('application/json').must_equal media_type.new(
|
157
|
+
'application',
|
158
|
+
'json'
|
159
|
+
)
|
157
160
|
q0.negotiate('application/xml').must_be_nil
|
158
|
-
q0.negotiate(['application/json', 'application/xml']).must_equal(
|
159
|
-
|
160
|
-
|
161
|
-
|
161
|
+
q0.negotiate(['application/json', 'application/xml']).must_equal media_type.new(
|
162
|
+
'application',
|
163
|
+
'json'
|
164
|
+
)
|
162
165
|
end
|
163
166
|
|
164
167
|
it "rejects matching q=0 even if it matches media ranges where q > 0" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
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.8
|
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-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|