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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1c59b8c10eea7c406d76ca08d6daf1eb25a92f41
4
- data.tar.gz: 76b2194684f98c8812f71e32daf84af838a38faa
3
+ metadata.gz: b55af3993793d079e27b4b85fd02344b7dbc878b
4
+ data.tar.gz: 30264067d3cbd349e13169a8592df750c81d06ba
5
5
  SHA512:
6
- metadata.gz: a3da1d44c7090dcd155f4b6f03e6bb6f40b9292d7bdbd9fee8f77cf7d440a1e8b9e3497f7ee0c42ac229b4377fb0c0b1932afa085f3dc0ca23fa452a82c98361
7
- data.tar.gz: af9ead68fe19c681981c4a522b4c73cc166fd2e0e1709677232dd5d2f755b5c61d0bce036da997a218818b42297e2d899fdc1dc5324a30049f740ae4141b80e0
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 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.
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[:match].extensions['version'] # returns '2'
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 parse(original_header)
11
- header = original_header.dup
12
- header.sub!(/\AAccept-Encoding:\s*/, '')
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
- return [Encoding.new] if header.empty?
15
- encodings = []
16
- header.split(',').each do |entry|
17
- encoding_arr = entry.split(';', 2)
18
- next if encoding_arr[0].nil?
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 parse(original_header)
11
- header = original_header.dup
12
- header.sub!(/\AAccept-Language:\s*/, '')
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
- return [Language.new] if header.empty?
15
- languages = []
16
- header.split(',').each do |entry|
17
- language_arr = entry.split(';', 2)
18
- next if language_arr[0].nil?
19
- language_range = Language::LANGUAGE_TAG_PATTERN.match(language_arr[0])
20
- next if language_range.nil?
21
- begin
22
- languages << Language.new(
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 parse(original_header)
13
- header = original_header.dup
14
- header.sub!(/\AAccept:\s*/, '')
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
- return [MediaType.new] if header.empty?
17
- media_types = []
18
- header.split(',').each do |entry|
19
- accept_media_range, accept_extensions = entry.split(';', 2)
20
- next if accept_media_range.nil?
21
- media_range = MediaType::MEDIA_TYPE_PATTERN.match(accept_media_range)
22
- next if media_range.nil?
23
- begin
24
- media_types << MediaType.new(
25
- media_range[:type],
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 { supported: support, matched: part }
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 parse(header)
39
- raise NotImplementedError.new("#parse(header) is not implemented")
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)
@@ -1,3 +1,3 @@
1
1
  module AcceptHeaders
2
- VERSION = "0.0.7"
2
+ VERSION = "0.0.8"
3
3
  end
@@ -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
- 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
- })
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
- supported: 'application/xml',
135
- matched: media_type.new('application', 'xml', q: 0.9)
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
- supported: 'application/json',
140
- matched: media_type.new('*', '*', q: 0.8)
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
- supported: 'application/json',
149
- matched: media_type.new('application', 'json', extensions: { 'version' => '2' })
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
- supported: 'application/json',
155
- matched: media_type.new('application', 'json')
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
- supported: 'application/json',
160
- matched: media_type.new('application', 'json')
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.7
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-19 00:00:00.000000000 Z
11
+ date: 2014-11-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler